diff --git a/src/lib/components/avatar.svelte b/src/lib/components/avatar.svelte index 0ff36651a..e341375aa 100644 --- a/src/lib/components/avatar.svelte +++ b/src/lib/components/avatar.svelte @@ -1,6 +1,12 @@ -{#if filteredModalAlerts.length > 0 && currentModalAlert} +{#if filteredModalAlerts.length > 0 && currentModalAlert && !page.url.pathname.includes('console/onboarding')} {@const shouldShowUpgrade = showUpgrade()}
@@ -164,8 +208,8 @@
{:else} -
- Explore new features to enhance your projects and improve security. + {#if mobileConfig.html} + {@html mobileConfig.message} + {:else} + {mobileConfig.message} + {/if}
- + {/if} {/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/components/box.svelte b/src/lib/components/box.svelte index 448f93c85..d129900a0 100644 --- a/src/lib/components/box.svelte +++ b/src/lib/components/box.svelte @@ -1,10 +1,9 @@ diff --git a/src/lib/components/breadcrumbs.svelte b/src/lib/components/breadcrumbs.svelte index 6bc8adf40..7d9329f3c 100644 --- a/src/lib/components/breadcrumbs.svelte +++ b/src/lib/components/breadcrumbs.svelte @@ -19,6 +19,7 @@ name: string; $id: string; isSelected: boolean; + region: string; }; type Organization = { name: string; diff --git a/src/lib/components/card.svelte b/src/lib/components/card.svelte index 28af1975c..fd94436ab 100644 --- a/src/lib/components/card.svelte +++ b/src/lib/components/card.svelte @@ -1,7 +1,16 @@ + + diff --git a/src/lib/components/labelCard.svelte b/src/lib/components/labelCard.svelte index ffcd8474c..8f2cc80c4 100644 --- a/src/lib/components/labelCard.svelte +++ b/src/lib/components/labelCard.svelte @@ -1,9 +1,24 @@ {#if !hidePages} - + {:else} & { state?: 'closed' | 'open' | 'icons'; - project: { $id: string } | undefined; + project: NavbarProject | undefined; avatar: string; progressCard?: { title: string; diff --git a/src/lib/components/tab.svelte b/src/lib/components/tab.svelte index 48946ea47..8c8d2041e 100644 --- a/src/lib/components/tab.svelte +++ b/src/lib/components/tab.svelte @@ -5,13 +5,12 @@ import { getElementDir } from '$lib/helpers/style'; import { waitUntil } from '$lib/helpers/waitUntil'; import { Tabs } from '@appwrite.io/pink-svelte'; - import type { Variant } from '@appwrite.io/pink-svelte/dist/tabs/types'; export let selected = false; export let href: string = null; export let event: string = null; export let noscroll = false; - export let root: { variant: Variant; stretch: boolean } = { + export let root: { variant: 'primary' | 'secondary'; stretch: boolean } = { variant: 'primary', stretch: false }; diff --git a/src/lib/elements/forms/inputDateTime.svelte b/src/lib/elements/forms/inputDateTime.svelte index 75dcaa27f..360b0f4c7 100644 --- a/src/lib/elements/forms/inputDateTime.svelte +++ b/src/lib/elements/forms/inputDateTime.svelte @@ -3,7 +3,7 @@ import { Input, Layout, Selector } from '@appwrite.io/pink-svelte'; export let id: string; - export let label: string; + export let label: string = ''; export let value: string; export let required = false; export let nullable = false; @@ -48,7 +48,7 @@ {value} {step} helper={error} - on:change={(event) => (value = event.target.value)} + on:change={(event) => (value = (event.target as HTMLInputElement).value)} autocomplete={autocomplete ? 'on' : 'off'}> {#if nullable} >( ): value is T[keyof T] { return Object.values(enumType).includes(value); } + +export type TableRootProp = { + allowSelection: boolean; + selectedRows: string[]; + selectedAll: boolean; + selectedNone: boolean; + selectedSome: boolean; + columns: Record; + toggle: (id: string) => void; + toggleAll: () => void; + addAvailableId: (id: string) => void; + removeAvailableId: (id: string) => void; +}; 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/layout/activity.svelte b/src/lib/layout/activity.svelte index 792bea865..f78f3bbe1 100644 --- a/src/lib/layout/activity.svelte +++ b/src/lib/layout/activity.svelte @@ -5,13 +5,13 @@ import type { Models } from '@appwrite.io/console'; import { Layout, Table, Card, Empty } from '@appwrite.io/pink-svelte'; import Button from '$lib/elements/forms/button.svelte'; - import type { Column } from '@appwrite.io/pink-svelte/dist/table'; + import type { PinkColumn } from '$lib/helpers/types'; export let logs: Models.LogList; export let offset = 0; export let limit = 0; - const columns: Column[] = [ + const columns: PinkColumn[] = [ { id: 'user' }, { id: 'event' }, { id: 'location' }, 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)/+layout.svelte b/src/routes/(console)/+layout.svelte index 6890009c4..2a101bc25 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -67,6 +67,7 @@ return { name: project?.name, $id: project.$id, + region: project.region, isSelected: data.currentProjectId === project.$id, platformCount: project.platforms.length, pingCount: project.pingCount diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 447f41d90..f09c16ef3 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -53,6 +53,11 @@ export const load: LayoutLoad = async ({ params, fetch, depends, parent }) => { projects = orgProjects.projects.length > 0 ? orgProjects.projects : []; } + // set `default` if no region! + for (const project of projects) { + project.region ??= 'default'; + } + return { consoleVariables: variables, version: data?.version ?? null, 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)/onboarding/create-project/+page.svelte b/src/routes/(console)/onboarding/create-project/+page.svelte index 7584dcc3e..445a9bb6f 100644 --- a/src/routes/(console)/onboarding/create-project/+page.svelte +++ b/src/routes/(console)/onboarding/create-project/+page.svelte @@ -2,54 +2,27 @@ import { Card, Layout, Button } from '@appwrite.io/pink-svelte'; import { isCloud } from '$lib/system'; import { sdk } from '$lib/stores/sdk'; - import { ID, Region, type Models } from '@appwrite.io/console'; + import { ID, Region } from '@appwrite.io/console'; import Loading from './loading.svelte'; - import { BillingPlan, Dependencies } from '$lib/constants'; + import { Dependencies } from '$lib/constants'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { goto, invalidate } from '$app/navigation'; import { base } from '$app/paths'; import { addNotification } from '$lib/stores/notifications'; - import { tierToPlan } from '$lib/stores/billing'; import CreateProject from '$lib/layout/createProject.svelte'; let isLoading = false; let id: string; let startAnimation = false; let projectName = 'Appwrite project'; - let region = Region.Default; - export let data: { regions: Models.ConsoleRegionList | null }; + let region = Region.Fra; + export let data; async function createProject() { isLoading = true; - let org; - try { - org = isCloud - ? await sdk.forConsole.billing.createOrganization( - ID.unique(), - 'Personal projects', - BillingPlan.FREE, - null, - null - ) - : await sdk.forConsole.teams.create(ID.unique(), 'Personal projects'); - } catch (e) { - isLoading = false; - trackError(e, Submit.OrganizationCreate); - addNotification({ - type: 'error', - message: e.message - }); - } - - trackEvent(Submit.OrganizationCreate, { - plan: tierToPlan(BillingPlan.FREE)?.name, - budget_cap_enabled: false, - members_invited: 0 - }); - - if (org) { - const teamId = org.$id; + if (data.organization) { + const teamId = data.organization.$id; try { const project = await sdk.forConsole.projects.create( id ?? ID.unique(), diff --git a/src/routes/(console)/onboarding/create-project/+page.ts b/src/routes/(console)/onboarding/create-project/+page.ts index e432d1cea..38fc6f4b9 100644 --- a/src/routes/(console)/onboarding/create-project/+page.ts +++ b/src/routes/(console)/onboarding/create-project/+page.ts @@ -1,9 +1,55 @@ import type { PageLoad } from './$types'; import { isCloud } from '$lib/system'; import { sdk } from '$lib/stores/sdk'; +import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; +import { isOrganization, tierToPlan } from '$lib/stores/billing'; +import { ID } from '@appwrite.io/console'; +import { BillingPlan } from '$lib/constants'; +import { redirect } from '@sveltejs/kit'; +import { base } from '$service-worker'; -export const load: PageLoad = async () => { - return { - regions: isCloud ? await sdk.forConsole.billing.listRegions() : null - }; +export const load: PageLoad = async ({ parent }) => { + const { organizations } = await parent(); + if (!organizations?.total) { + try { + if (isCloud) { + const org = await sdk.forConsole.billing.createOrganization( + ID.unique(), + 'Personal projects', + BillingPlan.FREE, + null, + null + ); + trackEvent(Submit.OrganizationCreate, { + plan: tierToPlan(BillingPlan.FREE)?.name, + budget_cap_enabled: false, + members_invited: 0 + }); + + if (isOrganization(org)) { + return { + organization: org, + regions: await sdk.forConsole.billing.listRegions(org.$id) + }; + } else { + const e = new Error(org.message, { + cause: org + }); + trackError(e, Submit.OrganizationCreate); + } + } else { + return { + organization: await sdk.forConsole.teams.create( + ID.unique(), + 'Personal projects' + ), + regions: null + }; + } + } catch (e) { + trackError(e, Submit.OrganizationCreate); + } + } else { + redirect(303, `${base}/console/organization-${organizations.teams[0].$id}`); + } }; diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte index 615c0800d..d1300c351 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte @@ -53,6 +53,6 @@ - + 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..fd24eb648 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/document-[document]/settings/+page.svelte @@ -0,0 +1,144 @@ + + + + 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)} +

+
+
+ + + + +
+
+ + diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/create/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/create/+page.svelte index ca1aa0cf5..0c43000cd 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/create/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/create/+page.svelte @@ -111,6 +111,7 @@ { return { + ...f, size: f.size, name: f.name, removable: true, diff --git a/src/routes/(public)/template-[template]/+page.svelte b/src/routes/(public)/template-[template]/+page.svelte index 7462e943f..b00b007ad 100644 --- a/src/routes/(public)/template-[template]/+page.svelte +++ b/src/routes/(public)/template-[template]/+page.svelte @@ -95,7 +95,7 @@ id ?? ID.unique(), projectName, selectedOrg, - isCloud ? (region as Region) : Region.Default + isCloud ? (region as Region) : Region.Fra ); trackEvent(Submit.ProjectCreate, { customId: !!id,