update: setup per day promos.

This commit is contained in:
Darshan committed 2025-05-18 19:00:42 +05:30
1 parent 3a0939656c
commit 6938491cdc
10 files changed
+167 -68

No files matched your search

+39 -35
View File
@@ -3,6 +3,7 @@
import { hideNotification, shouldShowNotification } from '$lib/helpers/notifications';
import { app } from '$lib/stores/app';
import {
type BottomModalAlertAction,
type BottomModalAlertItem,
bottomModalAlertsConfig,
dismissBottomModalAlert,
@@ -111,6 +112,34 @@
}
}
// the button component cannot have both href and on:click!
function triggerWindowLink(alertAction: BottomModalAlertAction, event?: string) {
const shouldShowUpgrade = showUpgrade();
const url = shouldShowUpgrade
? $upgradeURL
: alertAction.link({
organization: $organization,
project: $project
});
if (!shouldShowUpgrade && alertAction.external) {
window.open(url, '_blank');
} else {
goto(url);
}
if (alertAction?.hideOnClick === true) {
// be careful of this one.
// once clicked, its gone!
handleClose();
}
trackEvent(Click.PromoClick, {
promo: currentModalAlert.id,
type: shouldShowUpgrade ? 'upgrade' : (event ?? `cta_click_${currentModalAlert.id}`)
});
}
function showUpgrade() {
const plan = currentModalAlert.plan;
const organizationPlan = $organization?.billingPlan;
@@ -208,40 +237,23 @@
<div
class="buttons u-flex u-flex-vertical-mobile u-gap-4 u-padding-inline-8 u-padding-block-8">
<Button
fullWidthMobile
secondary={!hasOnlyPrimaryCta}
class={`${hasOnlyPrimaryCta ? 'only-primary-cta' : ''}`}
href={shouldShowUpgrade
? $upgradeURL
: currentModalAlert.cta.link({
organization: $organization,
project: $project
})}
external={!!currentModalAlert.cta.external}
fullWidthMobile
on:click={() => {
if (currentModalAlert.cta?.hideOnClick === true) {
// be careful of this one.
// once clicked, its gone!
handleClose();
}
trackEvent(Click.PromoClick, {
promo: currentModalAlert.id,
type: shouldShowUpgrade ? 'upgrade' : 'try_now'
});
}}>
on:click={() => triggerWindowLink(currentModalAlert.cta)}>
{currentModalAlert.cta.text}
</Button>
{#if currentModalAlert.learnMore}
<!-- docs, learn-more, etc always external -->
<Button
text
class="button"
external
fullWidthMobile
href={currentModalAlert.learnMore.link({
organization: $organization,
project: $project
project: $project,
organization: $organization
})}>
{currentModalAlert.learnMore.text}
</Button>
@@ -332,20 +344,10 @@
<Button
secondary={!hasOnlyPrimaryCta}
class="button"
href={shouldShowUpgrade
? $upgradeURL
: currentModalAlert.cta.link({
organization: $organization,
project: $project
})}
external={!!currentModalAlert.cta.external}
fullWidthMobile
on:click={() => {
openModalOnMobile = false;
trackEvent(Click.PromoClick, {
promo: currentModalAlert.id,
type: shouldShowUpgrade ? 'upgrade' : 'try_now'
});
triggerWindowLink(currentModalAlert.cta);
}}>
{shouldShowUpgrade
? 'Upgrade plan'
@@ -353,6 +355,7 @@
</Button>
{#if currentModalAlert.learnMore}
<!-- docs, learn-more, etc always external -->
<Button
text
class="button"
@@ -360,8 +363,8 @@
fullWidthMobile
on:click={() => (openModalOnMobile = false)}
href={currentModalAlert.learnMore.link({
organization: $organization,
project: $project
project: $project,
organization: $organization
})}>
{currentModalAlert.learnMore.text}
</Button>
@@ -433,6 +436,7 @@
top: 1rem;
right: 1rem;
cursor: pointer;
background: #fff;
position: absolute;
display: inline-flex;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

+1 -1
View File
@@ -3,7 +3,7 @@ import type { NotificationCoolOffOptions } from '$lib/helpers/notifications';
import type { Organization } from '$lib/stores/organization';
import type { Models } from '@appwrite.io/console';
type BottomModalAlertAction = {
export type BottomModalAlertAction = {
text: string;
hideOnClick?: boolean;
link: (ctx: { organization: Organization; project: Models.Project }) => string;
+126 -30
View File
@@ -1,4 +1,8 @@
import Init3Promo from '$lib/images/promo-init3.png';
import Init3KeysPromo from '$lib/images/promos/init3-keys.png';
import Init3SitesPromo from '$lib/images/promos/init3-sites.png';
import Init3FlutterPromo from '$lib/images/promos/init3-flutter.png';
import Init3ImagesPromo from '$lib/images/promos/init3-images.png';
import Init3TokensPromo from '$lib/images/promos/init3-tokens.png';
import {
type BottomModalAlertItem,
@@ -9,41 +13,133 @@ import { isCloud } from '$lib/system';
const listOfPromotions: BottomModalAlertItem[] = [];
if (isCloud) {
const title = 'Join Init 19-23 May';
const message =
'This release will change the way you build with Appwrite forever. Register for Init and join the giveaway.';
const callToAction = {
external: true,
hideOnClick: true,
text: 'Claim your ticket',
link: () => 'https://apwr.dev/clcon'
if (!isCloud) {
const getSrc = (image: string) => {
return {
dark: image,
light: image
};
};
listOfPromotions.push({
id: 'modal:init3',
src: {
dark: Init3Promo,
light: Init3Promo
},
title,
message,
plan: 'free',
importance: 8,
scope: 'everywhere',
cta: callToAction
const sharedCTA = (url: string) => ({
external: true,
link: () => url,
hideOnClick: true,
text: 'Read the announcement'
});
// there's only one item.
setMobileSingleAlertLayout({
title,
message,
enabled: true,
cta: callToAction
});
const promos: BottomModalAlertItem[] = [
{
id: 'modal:init3_sites',
src: getSrc(Init3SitesPromo),
title: 'Announcing: Sites',
message:
'The open-source Vercel alternative. With Sites, you deploy and host your websites and web apps right inside Appwrite.',
plan: 'free',
importance: 8,
scope: 'everywhere',
cta: sharedCTA('https://apwr.dev/sites1'),
// Day 1 = Monday 9 AM CET
show: isPromoLive('2025-05-19', '09:00')
},
{
id: 'modal:init3_flutter',
src: getSrc(Init3FlutterPromo),
title: 'Announcing: Hosting for Flutter Web',
message: 'Deploy your Flutter web apps directly from Appwrite Sites.',
plan: 'free',
importance: 8,
scope: 'everywhere',
cta: sharedCTA('https://apwr.dev/sitesfl'),
// Day 2 = Tuesday 3 PM CET
show: isPromoLive('2025-05-20', '15:00')
},
{
id: 'modal:init3_keys',
src: getSrc(Init3KeysPromo),
title: 'Announcing: Dev Keys',
message:
'A new Appwrite feature that lets you bypass rate limits during local development.',
plan: 'free',
importance: 8,
scope: 'everywhere',
cta: sharedCTA('https://apwr.dev/devkeys'),
// Day 3 = Wednesday 3 PM CET
show: isPromoLive('2025-05-21', '15:00')
},
{
id: 'modal:init3_images',
src: getSrc(Init3ImagesPromo),
title: 'Announcing: New image formats',
message:
'We have added support for two new image formats in Appwrite Storage: HEIC and AVIF.',
plan: 'free',
importance: 8,
scope: 'everywhere',
cta: sharedCTA('https://apwr.dev/imagefo'),
// Day 4 = Thursday 3 PM CET
show: isPromoLive('2025-05-22', '15:00')
},
{
id: 'modal:init3_file_tokens',
src: getSrc(Init3TokensPromo),
title: 'Announcing: File Tokens',
message:
'File tokens let you share files easily and securely, without modifying permissions or changing project access.',
plan: 'free',
importance: 8,
scope: 'everywhere',
cta: sharedCTA('https://apwr.dev/filet'),
// Day 5 = Friday 3 PM CET
show: isPromoLive('2025-05-23', '15:00')
}
];
const currentPromo = promos.find((promo) => promo.show);
if (currentPromo) {
listOfPromotions.push(currentPromo);
setMobileSingleAlertLayout({
title: currentPromo.title,
message: currentPromo.message,
enabled: true,
cta: currentPromo.cta
});
}
}
export function addBottomModalAlerts() {
listOfPromotions.forEach((promotion) => showBottomModalAlert(promotion));
}
function isPromoLive(date: string, time: string, timeZone: string = 'Europe/Paris'): boolean {
const isoString = `${date}T${time}:00`;
const formatter = new Intl.DateTimeFormat('en', {
timeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23'
});
const parts = formatter.formatToParts(new Date(isoString));
const map = Object.fromEntries(
parts.filter((p) => p.type !== 'literal').map((p) => [p.type, p.value])
);
const target = new Date(`${map.year}-${map.month}-${map.day}T${map.hour}:${map.minute}:00`);
const now = new Date();
const isSameDay = now.toISOString().slice(0, 10) === target.toISOString().slice(0, 10);
return isSameDay && now >= target;
}
@@ -22,8 +22,7 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
Query.orderDesc('')
]);
// set `default` or something else,
// `undefined` in the url looks really off!
// set `default` if no region!
for (const project of projects.projects) {
project.region ??= 'default';
}