diff --git a/src/lib/components/bottomModalAlert.svelte b/src/lib/components/bottomModalAlert.svelte index e10c02f63..7af6fad1a 100644 --- a/src/lib/components/bottomModalAlert.svelte +++ b/src/lib/components/bottomModalAlert.svelte @@ -4,7 +4,7 @@ import { app } from '$lib/stores/app'; import { type BottomModalAlertItem, - bottomModalAlerts, + bottomModalAlertsConfig, dismissBottomModalAlert, hideAllModalAlerts } from '$lib/stores/bottom-alerts'; @@ -16,6 +16,7 @@ import { project } from '$routes/(console)/project-[region]-[project]/store'; import { page } from '$app/state'; import { Click, trackEvent } from '$lib/actions/analytics'; + import { goto } from '$app/navigation'; let currentIndex = 0; let openModalOnMobile = false; @@ -33,23 +34,27 @@ return alerts .sort((a, b) => b.importance - a.importance) .filter((alert) => { - return ( - alert.show && - shouldShowNotification(alert.id) && - // if no scope > show in projects & org pages. - ((!alert.scope && (isProjectPage || isOrganizationPage)) || - // project scope, show only in project pages - (isProjectPage && alert.scope === 'project') || - // organization scope, show only in organization pages - (isOrganizationPage && alert.scope === 'organization')) - ); + if (!alert.show || !shouldShowNotification(alert.id)) return false; + + switch (alert.scope) { + case 'everywhere': + return true; + case 'project': + return isProjectPage; + case 'organization': + return isOrganizationPage; + default: + return false; + } }); } - $: filteredModalAlerts = filterModalAlerts($bottomModalAlerts, page.route.id); + $: filteredModalAlerts = filterModalAlerts($bottomModalAlertsConfig.alerts, page.route.id); $: currentModalAlert = filteredModalAlerts[currentIndex] as BottomModalAlertItem; + $: hasOnlyPrimaryCta = typeof currentModalAlert?.learnMore === 'undefined'; + function handleClose() { filteredModalAlerts.forEach((alert) => { const modalAlert = alert; @@ -67,9 +72,48 @@ currentIndex = (currentIndex - 1 + filteredModalAlerts.length) % filteredModalAlerts.length; } + function getMobileWindowConfig(): { + html: boolean; + cta: boolean; + title: string; + message: string; + } { + const config = $bottomModalAlertsConfig?.mobileSingleLayout; + const visibleAlerts = $bottomModalAlertsConfig.alerts.filter((a) => a.show); + + const fallback = { + title: 'New features available', + message: 'Explore new features to enhance your projects and improve security.' + }; + + const shouldApplyConfig = config?.enabled === true && visibleAlerts.length === 1; + + return { + cta: !!(shouldApplyConfig && config.cta), + html: !!(shouldApplyConfig && config.isHtml), + title: shouldApplyConfig && config.title ? config.title : fallback.title, + message: shouldApplyConfig && config.message ? config.message : fallback.message + }; + } + + function triggerMobileWindowLink() { + handleClose(); + + const url = $bottomModalAlertsConfig.mobileSingleLayout.cta.link({ + organization: $organization, + project: $project + }); + + if ($bottomModalAlertsConfig.mobileSingleLayout.cta.external) { + window.open(url, '_blank'); + } else { + goto(url); + } + } + function showUpgrade() { const plan = currentModalAlert.plan; - const organizationPlan = $organization.billingPlan; + const organizationPlan = $organization?.billingPlan; switch (plan) { case 'free': return false; @@ -87,7 +131,7 @@ }); -{#if filteredModalAlerts.length > 0 && currentModalAlert} +{#if filteredModalAlerts.length > 0 && currentModalAlert && !$page.url.pathname.includes('console/onboarding')} {@const shouldShowUpgrade = showUpgrade()}
@@ -164,8 +208,8 @@
{:else} + {@const mobileConfig = getMobileWindowConfig()}
- Explore new features to enhance your projects and improve security. + {#if mobileConfig.html} + {@html mobileConfig.message} + {:else} + {mobileConfig.message} + {/if}
@@ -377,6 +442,22 @@ border: hsl(var(--color-neutral-80)) solid 1px; } + :global(.main-alert-wrapper .only-primary-cta) { + width: 100%; + text-align: center; + justify-content: center; + } + + .showcase-image.u-only-light { + border-radius: 8px; + border: 0.795px solid var(--border-neutral-strong, #d8d8db); + } + + .showcase-image.u-only-dark { + border-radius: 8px; + border: 0.795px solid var(--border-neutral-strong, #414146); + } + .u-gap-10 { gap: 0.625rem; } diff --git a/src/lib/images/backups/promo/backups-dark.png b/src/lib/images/backups/promo/backups-dark.png deleted file mode 100644 index 6b31eab88..000000000 Binary files a/src/lib/images/backups/promo/backups-dark.png and /dev/null differ diff --git a/src/lib/images/backups/promo/backups-light.png b/src/lib/images/backups/promo/backups-light.png deleted file mode 100644 index aa675d86b..000000000 Binary files a/src/lib/images/backups/promo/backups-light.png and /dev/null differ diff --git a/src/lib/images/promo-init3.png b/src/lib/images/promo-init3.png new file mode 100644 index 000000000..842fa51f4 Binary files /dev/null and b/src/lib/images/promo-init3.png differ diff --git a/src/lib/stores/bottom-alerts.ts b/src/lib/stores/bottom-alerts.ts index 1b681dcc8..aca0e067b 100644 --- a/src/lib/stores/bottom-alerts.ts +++ b/src/lib/stores/bottom-alerts.ts @@ -5,10 +5,28 @@ import type { Models } from '@appwrite.io/console'; type BottomModalAlertAction = { text: string; + hideOnClick?: boolean; link: (ctx: { organization: Organization; project: Models.Project }) => string; external?: boolean; }; +/** + * Special layout settings for mobile when exactly one alert is shown. + * + * Applied only if `enabled` is `true` and the number of visible alerts is exactly 1. + * + * Useful when you want to display the alert directly in the floating window, + * without opening a separate modal. + */ +export type MobileSingleAlertLayoutConfig = { + title: string; + message: string; + enabled: boolean; + isHtml?: boolean; + // because message is the text! + cta: Omit; +}; + export type BottomModalAlertItem = { id: string; title: string; @@ -23,18 +41,36 @@ export type BottomModalAlertItem = { importance?: number; closed?: () => void; - scope?: 'organization' | 'project'; + scope: 'organization' | 'project' | 'everywhere'; notificationHideOptions?: NotificationCoolOffOptions; }; -export const bottomModalAlerts = writable([]); +type BottomModalAlertState = { + alerts: BottomModalAlertItem[]; + mobileSingleLayout?: MobileSingleAlertLayoutConfig; +}; + +export const bottomModalAlertsConfig = writable({ alerts: [] }); export const hideAllModalAlerts = () => { - bottomModalAlerts.update((all) => all.map((t) => ({ ...t, show: false }))); + bottomModalAlertsConfig.update((state) => ({ + ...state, + alerts: state.alerts.map((t) => ({ ...t, show: false })) + })); +}; + +export const setMobileSingleAlertLayout = (config: MobileSingleAlertLayoutConfig) => { + bottomModalAlertsConfig.update((state) => ({ + ...state, + mobileSingleLayout: config + })); }; export const dismissBottomModalAlert = (id: string) => { - bottomModalAlerts.update((all) => all.filter((t) => t.id !== id)); + bottomModalAlertsConfig.update((state) => ({ + ...state, + alerts: state.alerts.filter((t) => t.id !== id) + })); }; export const showBottomModalAlert = (notification: BottomModalAlertItem) => { @@ -45,8 +81,11 @@ export const showBottomModalAlert = (notification: BottomModalAlertItem) => { ...notification }; - bottomModalAlerts.update((all) => { - if (all.some((t) => t.id === notification.id)) return all; - return [...all, defaults as BottomModalAlertItem]; + bottomModalAlertsConfig.update((state) => { + if (state.alerts.some((t) => t.id === notification.id)) return state; + return { + ...state, + alerts: [...state.alerts, defaults as BottomModalAlertItem] + }; }); }; diff --git a/src/routes/(console)/bottomAlerts.ts b/src/routes/(console)/bottomAlerts.ts index 255f920e7..156a0dff8 100644 --- a/src/routes/(console)/bottomAlerts.ts +++ b/src/routes/(console)/bottomAlerts.ts @@ -1,32 +1,46 @@ -import { base } from '$app/paths'; -import BackupsDark from '$lib/images/backups/promo/backups-dark.png'; -import BackupsLight from '$lib/images/backups/promo/backups-light.png'; +import Init3Promo from '$lib/images/promo-init3.png'; -import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts'; +import { + type BottomModalAlertItem, + setMobileSingleAlertLayout, + showBottomModalAlert +} from '$lib/stores/bottom-alerts'; 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' + }; + listOfPromotions.push({ - id: 'modal:databaseBackups', + id: 'modal:init3', src: { - dark: BackupsDark, - light: BackupsLight + dark: Init3Promo, + light: Init3Promo }, - title: 'Database Backups are available now', - message: 'Protect your data and ensure quick recovery with our new backups', - plan: 'pro', - scope: 'project', + title, + message, + plan: 'free', importance: 8, - cta: { - text: 'Try now', - link: ({ project }) => `${base}/project-${project.region}-${project.$id}/databases` - }, - learnMore: { - text: 'Learn more', - link: () => 'https://appwrite.io/docs/products/databases/backups' - } + scope: 'everywhere', + cta: callToAction + }); + + // there's only one item. + setMobileSingleAlertLayout({ + title, + message, + enabled: true, + cta: callToAction }); } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte index e423d0082..b4b2e9915 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte @@ -1,10 +1,9 @@ - Document - Appwrite + Data - Appwrite - - Metadata - -
-

Created: {toLocaleDateTime($doc.$createdAt)}

-

Last updated: {toLocaleDateTime($doc.$updatedAt)}

-
-
-
- - Permissions - A user requires appropriate permissions at either the collection level or - document level to access a document. If no permissions are configured, no user can - access the document - Learn more - - {#if $collection.documentSecurity} - {#if showPermissionAlert} - (showPermissionAlert = false)}> - Document security is enabled -

- Users will be able to access this document if they have been granted either document or collection permissions. - -

-
- {/if} - {#if permissions} - - {/if} - {:else} - - Document security is disabled -

- If you want to assign document permissions. Go to Collection settings and enable document - security. Otherwise, only collection permissions will be used. -

-
- {/if} -
+ {#if $collection?.attributes?.length} + {#each $collection.attributes as attribute} + {@const label = attribute.key} + + {label} - - - - - - - Delete document - The document will be permanently deleted, including all the data within it. This action is irreversible. - - - -
{$doc.$id}
+ + -

- Last updated: {toLocaleDateTime($doc.$updatedAt)} -

-
-
- - - -
+ + + +
+ {/each} + {/if}
- - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/data/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/data/+page.svelte deleted file mode 100644 index 19aba3566..000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/data/+page.svelte +++ /dev/null @@ -1,137 +0,0 @@ - - - - Data - Appwrite - - - - {#if $collection?.attributes?.length} - {#each $collection.attributes as attribute} - {@const label = attribute.key} - - {label} - - - - - - - - - {/each} - {/if} - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/header.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/header.svelte index 300d1961d..d97751b9f 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/header.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/header.svelte @@ -14,20 +14,20 @@ const tabs = [ { href: path, - title: 'Overview', - event: 'overview' - }, - { - href: `${path}/data`, title: 'Data', - event: 'data', - hasChildren: true + event: 'data' }, { href: `${path}/activity`, title: 'Activity', event: 'activity', hasChildren: true + }, + { + href: `${path}/settings`, + title: 'Settings', + event: 'settings', + hasChildren: true } ]; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/settings/+page.svelte new file mode 100644 index 000000000..abccfb94f --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/settings/+page.svelte @@ -0,0 +1,142 @@ + + + + Document - Appwrite + + + + + Metadata + +
+

Created: {toLocaleDateTime($doc.$createdAt)}

+

Last updated: {toLocaleDateTime($doc.$updatedAt)}

+
+
+
+ + Permissions +

+ A user requires appropriate permissions at either the collection level or + document level to access a document. If no permissions are configured, no user + can access the document + Learn more about database permissions. +

+ + + {#if $collection.documentSecurity} + {#if showPermissionAlert} + (showPermissionAlert = false)}> + Document security is enabled +

+ Users will be able to access this document if they have been granted either document or collection permissions. + +

+
+ {/if} + {#if permissions} + + {/if} + {:else} + + Document security is disabled +

+ If you want to assign document permissions. Go to Collection settings and + enable document security. Otherwise, only collection permissions will be + used. +

+
+ {/if} +
+ + + + +
+ + + Delete document +

+ The document will be permanently deleted, including all the data within it. This action + is irreversible. +

+ + + +
{$doc.$id}
+
+

+ Last updated: {toLocaleDateTime($doc.$updatedAt)} +

+
+
+ + + + +
+
+ +