diff --git a/e2e/journeys/upgrade-free-tier.spec.ts b/e2e/journeys/upgrade-free-tier.spec.ts index f13adb4fa..76d5fafcd 100644 --- a/e2e/journeys/upgrade-free-tier.spec.ts +++ b/e2e/journeys/upgrade-free-tier.spec.ts @@ -14,6 +14,6 @@ test('upgrade - free tier', async ({ page }) => { await enterCreditCard(page); // skip members await page.getByRole('button', { name: 'change plan' }).click(); - await page.waitForURL(/\/console\/project-(?:[a-z0-9]+-)?([^/]+)\/overview\/platforms/); + await page.waitForURL(/\/console\/project-(?:[a-z0-9]+-)?([^/]+)\/get-started/); }); }); diff --git a/e2e/steps/pro-project.ts b/e2e/steps/pro-project.ts index fd4446362..05c95895f 100644 --- a/e2e/steps/pro-project.ts +++ b/e2e/steps/pro-project.ts @@ -19,7 +19,7 @@ export async function enterCreditCard(page: Page) { await stripe.locator('id=Field-expiryInput').fill('1250'); await stripe.locator('id=Field-cvcInput').fill('123'); await stripe.locator('id=Field-countryInput').selectOption('DE'); - await page.getByRole('button', { name: 'Add', exact: true }).click(); + await dialog.getByRole('button', { name: 'Add', exact: true }).click(); await dialog.waitFor({ state: 'hidden' }); @@ -29,8 +29,9 @@ export async function createProProject(page: Page): Promise { const organizationId = await test.step('create organization', async () => { await page.goto('./create-organization'); await page.locator('id=name').fill('test org'); - await page.getByLabel('pro').check(); - await page.getByRole('button', { name: 'get started' }).click(); + await page.getByRole('radio', { name: /^Pro\b/ }).check(); + // `create organization` because there's already free created on start! + await page.getByRole('button', { name: 'create organization' }).click(); await page.getByRole('button', { name: 'add' }).first().click(); await enterCreditCard(page); // skip members diff --git a/package.json b/package.json index e9c68e6fc..5992460a1 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,12 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "^1.8.0", + "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19", "@appwrite.io/pink": "0.25.0", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@510c1a7", + "@appwrite.io/pink-svelte": "https://try-module.cloud/module/@appwrite/@appwrite.io/pink-svelte@0cc439f", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", "@stripe/stripe-js": "^3.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fbc84b2c..20316f694 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: ^1.8.0 - version: 1.8.0 + specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19 + version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19 '@appwrite.io/pink': specifier: 0.25.0 version: 0.25.0 @@ -27,8 +27,8 @@ importers: specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@510c1a7 - version: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@510c1a7(svelte@5.25.3) + specifier: https://try-module.cloud/module/@appwrite/@appwrite.io/pink-svelte@0cc439f + version: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f(svelte@5.25.3) '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 @@ -257,8 +257,9 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@1.8.0': - resolution: {integrity: sha512-9M2zfg95P5c5CTYseMaE7ULoYJgzLGy624pV2g4DLdWmrX6ltMwP4khulIOsTd3tJpDDwuiInt7odVeoP9iXZg==} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19': + resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19} + version: 1.8.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==} @@ -280,8 +281,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@510c1a7': - resolution: {tarball: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@510c1a7} + '@appwrite.io/pink-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f': + resolution: {tarball: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3624,7 +3625,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@1.8.0': {} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: @@ -3643,7 +3644,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@510c1a7(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f(svelte@5.25.3)': dependencies: '@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3) '@floating-ui/dom': 1.6.13 diff --git a/src/lib/components/support.svelte b/src/lib/components/support.svelte index 2520ef032..d05d2639c 100644 --- a/src/lib/components/support.svelte +++ b/src/lib/components/support.svelte @@ -28,7 +28,7 @@ (team) => !$plansInfo.get((team as Organization).billingPlan)?.premiumSupport ); - $: upgradeURL = `${base}/organization-${freeOrganization.$id}/change-plan`; + $: upgradeURL = `${base}/organization-${freeOrganization?.$id}/change-plan`; $: supportTimings = `${utcHourToLocaleHour('16:00')} - ${utcHourToLocaleHour('00:00')} ${localeShortTimezoneName()}`; diff --git a/src/lib/helpers/waitlist.ts b/src/lib/helpers/waitlist.ts new file mode 100644 index 000000000..773b04a24 --- /dev/null +++ b/src/lib/helpers/waitlist.ts @@ -0,0 +1,30 @@ +import { get } from 'svelte/store'; +import { user } from '$lib/stores/user'; +import { sdk } from '$lib/stores/sdk'; +const userPreferences = () => get(user)?.prefs; + +export const joinWaitlistSites = () => { + const prefs = userPreferences(); + const newPrefs = { + ...prefs, + joinWaitlistSites: true + }; + + sdk.forConsole.account.updatePrefs(newPrefs); + + if (sessionStorage) { + sessionStorage.setItem('joinWaitlistSites', 'true'); + } +}; + +export const isOnWaitlistSites = (): boolean => { + const prefs = userPreferences(); + const joinedInPrefs = 'joinWaitlistSites' in prefs; + + let joinedInSession = false; + if (sessionStorage) { + joinedInSession = sessionStorage.getItem('joinWaitlistSites') === 'true'; + } + + return joinedInSession || joinedInPrefs; +}; diff --git a/src/lib/images/promos/init3-flutter.png b/src/lib/images/promos/init3-flutter.png deleted file mode 100644 index 18470db09..000000000 Binary files a/src/lib/images/promos/init3-flutter.png and /dev/null differ diff --git a/src/lib/images/promos/init3-images.png b/src/lib/images/promos/init3-images.png deleted file mode 100644 index 26b0b5db7..000000000 Binary files a/src/lib/images/promos/init3-images.png and /dev/null differ diff --git a/src/lib/images/promos/init3-keys.png b/src/lib/images/promos/init3-keys.png deleted file mode 100644 index c7a8f3907..000000000 Binary files a/src/lib/images/promos/init3-keys.png and /dev/null differ diff --git a/src/lib/images/promos/init3-sites.png b/src/lib/images/promos/init3-sites.png deleted file mode 100644 index ae32a73c2..000000000 Binary files a/src/lib/images/promos/init3-sites.png and /dev/null differ diff --git a/src/lib/images/promos/init3-tokens.png b/src/lib/images/promos/init3-tokens.png deleted file mode 100644 index 1efd158ba..000000000 Binary files a/src/lib/images/promos/init3-tokens.png and /dev/null differ diff --git a/src/lib/layout/createProject.svelte b/src/lib/layout/createProject.svelte index 945a4e5c3..5c206297e 100644 --- a/src/lib/layout/createProject.svelte +++ b/src/lib/layout/createProject.svelte @@ -28,7 +28,7 @@ label: region.name, value: region.$id, leadingHtml: `Region flag`, - disabled: region.disabled + disabled: region.disabled || !region.available }; }); } @@ -39,6 +39,7 @@ {/each} +
{#if showTitle} diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index 9b46263cf..00acfef72 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -43,7 +43,7 @@ export function getApiEndpoint(region?: string): string { VARS.APPWRITE_ENDPOINT ? VARS.APPWRITE_ENDPOINT : globalThis?.location?.toString() ); const protocol = url.protocol; - const hostname = url.hostname; + const hostname = url.host; // "hostname:port" (or just "hostname" if no port) // If instance supports multi-region, add the region subdomain. const subdomain = isMultiRegionSupported(url) ? getSubdomain(region) : ''; diff --git a/src/routes/(console)/account/sessions/+page.svelte b/src/routes/(console)/account/sessions/+page.svelte index f017fe0ea..ed52cef08 100644 --- a/src/routes/(console)/account/sessions/+page.svelte +++ b/src/routes/(console)/account/sessions/+page.svelte @@ -71,11 +71,11 @@ const sessionId = url.searchParams.get('sessionId'); if (sessionId) { logoutSessionId(sessionId); - } - // Clear the sessionId param from the URL - url.searchParams.delete('sessionId'); - goto(url.pathname + url.search, { replaceState: true }); + // Clear the sessionId param from the URL + url.searchParams.delete('sessionId'); + goto(url.pathname + url.search, { replaceState: true }); + } }); }); @@ -90,9 +90,9 @@ let:root columns={[ { id: 'client' }, - { id: 'location' }, - { id: 'ip' }, - { id: 'actions', width: 40 } + { id: 'location', width: 120 }, + { id: 'ip', width: 120 }, + { id: 'actions', width: 70 } ]}> Client diff --git a/src/routes/(console)/bottomAlerts.ts b/src/routes/(console)/bottomAlerts.ts index f95d11d51..5016b9c07 100644 --- a/src/routes/(console)/bottomAlerts.ts +++ b/src/routes/(console)/bottomAlerts.ts @@ -1,124 +1,19 @@ -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, - setMobileSingleAlertLayout, - showBottomModalAlert -} from '$lib/stores/bottom-alerts'; import { isCloud } from '$lib/system'; import { isSameDay } from '$lib/helpers/date'; +import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts'; const listOfPromotions: BottomModalAlertItem[] = []; -if (!isCloud) { - const getSrc = (image: string) => { - return { - dark: image, - light: image - }; - }; - - const sharedCTA = (url: string) => ({ - external: true, - link: () => url, - hideOnClick: true, - text: 'Read the announcement' - }); - - 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 - }); - } +if (isCloud) { + /* add promos here */ } export function addBottomModalAlerts() { listOfPromotions.forEach((promotion) => showBottomModalAlert(promotion)); } +// use this for time based promo handling +// noinspection JSUnusedGlobalSymbols export function isPromoLive( date: string, time: string, diff --git a/src/routes/(console)/onboarding/create-project/+page.svelte b/src/routes/(console)/onboarding/create-project/+page.svelte index 001adcdf4..60a95351f 100644 --- a/src/routes/(console)/onboarding/create-project/+page.svelte +++ b/src/routes/(console)/onboarding/create-project/+page.svelte @@ -10,6 +10,8 @@ import { base } from '$app/paths'; import { addNotification } from '$lib/stores/notifications'; import CreateProject from '$lib/layout/createProject.svelte'; + import { loadAvailableRegions } from '$routes/(console)/regions'; + import { regions as regionsStore } from '$lib/stores/organization'; let isLoading = false; let id: string; @@ -49,6 +51,9 @@ }); } } + + // safe side! + loadAvailableRegions(data.organization.$id); @@ -74,7 +79,7 @@ alt="Appwrite Logo" /> { const { organizations } = await parent(); + try { if (!organizations?.total) { try { @@ -29,8 +31,7 @@ export const load: PageLoad = async ({ parent }) => { if (isOrganization(org)) { return { - organization: org, - regions: await sdk.forConsole.billing.listRegions(org.$id) + organization: org }; } else { const e = new Error(org.message, { @@ -43,8 +44,7 @@ export const load: PageLoad = async ({ parent }) => { organization: await sdk.forConsole.teams.create( ID.unique(), 'Personal projects' - ), - regions: null + ) }; } } catch (e) { @@ -57,10 +57,8 @@ export const load: PageLoad = async ({ parent }) => { Query.limit(1) ]); if (!projects.total) { - const regions = isCloud ? await sdk.forConsole.billing.listRegions(org.$id) : null; return { - organization: org, - regions + organization: org }; } else { redirect(303, `${base}/console/organization-${org.$id}`); diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index b7af2ad1a..089351e32 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -17,7 +17,6 @@ } from '$lib/components'; import { goto } from '$app/navigation'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { services } from '$lib/stores/project-services'; import { sdk } from '$lib/stores/sdk'; import { loading } from '$routes/store'; import type { Models } from '@appwrite.io/console'; @@ -47,23 +46,6 @@ let showCreateProjectCloud = false; let addOrganization = false; - async function allServiceDisabled(project: Models.Project) { - let disabled = true; - try { - if (!project.$id) return false; - if (!project.platforms.length) return false; - services.load(project); - $services.list.forEach((service) => { - if (service.value) { - disabled = false; - } - }); - return disabled; - } catch (e) { - return true; - } - } - function filterPlatforms(platforms: { name: string; icon: string }[]) { return platforms.filter( (value, index, self) => index === self.findIndex((t) => t.name === value.name) @@ -138,9 +120,7 @@ }); function findRegion(project: Models.Project) { - return $regionsStore?.regions?.find( - (region) => region.$id === (project as Models.Project & { region: string }).region - ); + return $regionsStore?.regions?.find((region) => region.$id === project.region); } @@ -186,15 +166,6 @@ {project.name} - {#await allServiceDisabled(project) then isDisabled} - {#if isDisabled} -

- All services are - disabled. -

- {/if} - {/await} - {#each platforms as platform, i} {#if i < 3} {@const icon = getIconForPlatform(platform.icon)} diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index 2f63c5532..ae83f8c26 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -241,9 +241,9 @@ } } - $: isUpgrade = $plansInfo.get(selectedPlan).order > $currentPlan.order; - $: isDowngrade = $plansInfo.get(selectedPlan).order < $currentPlan.order; - $: isButtonDisabled = $organization.billingPlan === selectedPlan; + $: isUpgrade = $plansInfo.get(selectedPlan).order > $currentPlan?.order; + $: isDowngrade = $plansInfo.get(selectedPlan).order < $currentPlan?.order; + $: isButtonDisabled = $organization?.billingPlan === selectedPlan; diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/domainMetrics.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/domainMetrics.svelte index a83471dad..2189de9a1 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/domainMetrics.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/domainMetrics.svelte @@ -4,11 +4,11 @@ import { toLocaleDate } from '$lib/helpers/date'; import { Layout, Status } from '@appwrite.io/pink-svelte'; - export let domain: Domain; + let { domain }: { domain: Domain } = $props(); - $: isDomainVerified = domain.nameservers.toLocaleLowerCase() === 'appwrite'; + const isDomainVerified = domain.nameservers.toLocaleLowerCase() === 'appwrite'; - let metrics = [ + const metrics = [ { value: isDomainVerified ? 'Verified' : 'Not verified', description: 'Status' @@ -18,11 +18,11 @@ description: 'Registrar' }, { - value: domain?.nameservers ? domain?.nameservers : '-', + value: domain?.nameservers || '-', description: 'Nameservers' }, { - value: domain?.expiry ? toLocaleDate(domain?.expiry) : '-', + value: domain?.expiry ? toLocaleDate(domain.expiry) : '-', description: 'Expiry date' }, { diff --git a/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte b/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte index 738cb89ef..68d02d3fb 100644 --- a/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte +++ b/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte @@ -16,13 +16,18 @@ Input, InteractiveText } from '@appwrite.io/pink-svelte'; + import { base } from '$app/paths'; + import { page } from '$app/state'; + import { goto } from '$app/navigation'; export let domain: Domain; let verified = undefined; + const routeBase = `${base}/organization-${page.params.organization}/domains/domain-${domain.$id}`; + // TODO: split _APP_DOMAINS_NAMESERVERS? - let nameservers = $consoleVariables?._APP_DOMAINS_NAMESERVERS.split(',') ?? [ + const nameservers = $consoleVariables?._APP_DOMAINS_NAMESERVERS.split(',') ?? [ 'ns1.appwrite.io', 'ns2.appwrite.io' ]; @@ -31,6 +36,19 @@ try { domain = await sdk.forConsole.domains.updateNameservers(domain.$id); verified = domain.nameservers === 'Appwrite'; + if (verified) { + addNotification({ + type: 'success', + message: 'Domain verified' + }); + + await goto(routeBase); + } else { + addNotification({ + type: 'error', + message: 'Domain not verified' + }); + } } catch (error) { addNotification({ type: 'error', diff --git a/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte b/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte index 08bbaf855..9435dca89 100644 --- a/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte @@ -13,7 +13,7 @@ export let show = false; export let selectedDomain: Domain; - let nameservers = $consoleVariables?._APP_DOMAINS_NAMESERVERS.split(',') ?? [ + const nameservers = $consoleVariables?._APP_DOMAINS_NAMESERVERS.split(',') ?? [ 'ns1.appwrite.io', 'ns2.appwrite.io' ]; diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte index 54167235a..5c1622d82 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte @@ -8,7 +8,7 @@ import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { Icon, Layout, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte'; - import { writable } from 'svelte/store'; + import { get, writable } from 'svelte/store'; import { ID, Runtime } from '@appwrite.io/console'; import type { Models } from '@appwrite.io/console'; import { consoleVariables } from '$routes/(console)/store'; @@ -20,15 +20,22 @@ import Configuration from './configuration.svelte'; import { getIconFromRuntime } from '$lib/stores/runtimes'; import { InvalidFileType, removeFile } from '$lib/helpers/files'; + import { isCloud } from '$lib/system'; + import { humanFileSize } from '$lib/helpers/sizeConvertion'; + import { currentPlan } from '$lib/stores/organization'; export let data; + const iconFor = get(iconPath); + const runtimeOptions = data.runtimesList.runtimes.map((runtime) => { - return { - value: runtime.$id, - label: `${runtime.name} - ${runtime.version}`, - leadingHtml: `` - }; + const { $id: value, name, version, key } = runtime; + const label = `${name} - ${version}`; + const iconName = getIconFromRuntime(key); + const iconSrc = iconFor(iconName, 'color'); + const leadingHtml = `${iconName}`; + + return { value, label, leadingHtml }; }); const specificationOptions = data.specificationsList.specifications.map((size) => ({ @@ -153,6 +160,13 @@ }; }) : []; + + $: maxSize = + isCloud && $currentPlan + ? $currentPlan.deploymentSize * 1000000 + : $consoleVariables._APP_COMPUTE_SIZE_LIMIT; // already in MB + + $: readableMaxSize = humanFileSize(maxSize); @@ -176,7 +190,7 @@ bind:files title="Upload function" extensions={['gz', 'tar']} - maxSize={10000000} + {maxSize} required on:invalid={handleInvalid}> @@ -201,12 +215,12 @@ Max file size 10MB + >Max file size: {readableMaxSize.value + + readableMaxSize.unit}
{#if files?.length} - (files = removeFile(e.detail, files))} /> diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte index 5c8056398..4d6ae6e70 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte @@ -14,6 +14,7 @@ import { consoleVariables } from '$routes/(console)/store'; import { currentPlan } from '$lib/stores/organization'; import { isCloud } from '$lib/system'; + import { humanFileSize } from '$lib/helpers/sizeConvertion'; export let show = false; @@ -22,11 +23,11 @@ $: maxSize = isCloud && $currentPlan - ? $currentPlan?.deploymentSize === 0 - ? 0 - : $currentPlan?.deploymentSize * 1000000 + ? $currentPlan.deploymentSize * 1000000 : $consoleVariables._APP_COMPUTE_SIZE_LIMIT; // already in MB + $: readableMaxSize = humanFileSize(maxSize); + async function create() { try { await sdk @@ -100,7 +101,9 @@ >Only .tar.gz files allowed - Max file size 10MB + Max file size: {readableMaxSize.value + + readableMaxSize.unit} diff --git a/src/routes/(console)/project-[region]-[project]/messaging/providers/settingsFormInput.svelte b/src/routes/(console)/project-[region]-[project]/messaging/providers/settingsFormInput.svelte index 4227724d2..35ba274ba 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/providers/settingsFormInput.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/providers/settingsFormInput.svelte @@ -124,6 +124,7 @@ on:invalid={handleInvalid} extensions={['json']} bind:files={files[input.name]} + maxSize={10000000} required={!input.optional}> diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte index b30bd56ae..3c127f1eb 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte @@ -55,7 +55,7 @@ - {#if !inCard} + {#if !inCard && selectedDeployment?.sourceSize !== 0} { diff --git a/src/routes/(console)/project-[region]-[project]/sites/(images)/empty-sites-dark-mobile.svg b/src/routes/(console)/project-[region]-[project]/sites/(images)/empty-sites-dark-mobile.svg new file mode 100644 index 000000000..39c7917cf --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/sites/(images)/empty-sites-dark-mobile.svg @@ -0,0 +1,119 @@ + + +
+ + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/sites/(images)/empty-sites-light-mobile.svg b/src/routes/(console)/project-[region]-[project]/sites/(images)/empty-sites-light-mobile.svg new file mode 100644 index 000000000..8bd4b239e --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/sites/(images)/empty-sites-light-mobile.svg @@ -0,0 +1,157 @@ + + +
+ + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/sites/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/+page.svelte index 855274fa4..a933b669f 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/+page.svelte @@ -11,12 +11,14 @@ import { isServiceLimited } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { canWriteSites } from '$lib/stores/roles.js'; - import { Icon, Layout } from '@appwrite.io/pink-svelte'; + import { Card, Icon, Layout, Typography } from '@appwrite.io/pink-svelte'; import { Button } from '$lib/elements/forms'; import { app } from '$lib/stores/app'; import CreateSiteModal from './createSiteModal.svelte'; import EmptyLight from './(images)/empty-sites-light.svg'; import EmptyDark from './(images)/empty-sites-dark.svg'; + import EmptyLightMobile from './(images)/empty-sites-light-mobile.svg'; + import EmptyDarkMobile from './(images)/empty-sites-dark-mobile.svg'; import Grid from './grid.svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import { columns } from './store'; @@ -26,8 +28,15 @@ import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; + import { APPWRITE_OFFICIALS_ORG, isCloud } from '$lib/system'; + import { addNotification } from '$lib/stores/notifications'; + import { isOnWaitlistSites, joinWaitlistSites } from '$lib/helpers/waitlist'; + import { isSmallViewport } from '$lib/stores/viewport'; + export let data; + let show = false; + let isOnWaitlist = isOnWaitlistSites(); $: $registerCommands([ { @@ -37,6 +46,7 @@ }, keys: ['c'], disabled: + !showSites || isServiceLimited('sites', $organization?.billingPlan, data.siteList?.total) || !$canWriteSites, icon: IconPlus, @@ -53,46 +63,123 @@ } }); }); + + /** + * Controls visibility of Sites feature: + * - Shown if running on self-hosted + * - Shown on cloud only if the organization is Appwrite's. + * - Hidden on cloud for any non-Appwrite organization. + */ + $: showSites = !isCloud || $organization.$id === APPWRITE_OFFICIALS_ORG; + + $: isDark = $app.themeInUse === 'dark'; + + $: imgSrc = isDark + ? $isSmallViewport + ? EmptyDarkMobile + : EmptyDark + : $isSmallViewport + ? EmptyLightMobile + : EmptyLight; + + $: imgClass = $isSmallViewport ? 'mobile' : 'desktop'; + + function addToWaitlist() { + joinWaitlistSites(); + addNotification({ + type: 'success', + title: 'Waitlist joined', + message: "We'll let you know as soon as Appwrite Sites is ready for you." + }); + + isOnWaitlist = true; + } - - - + {#if showSites} + + + + + + + {#if $canWriteSites} + + {/if} + - - - {#if $canWriteSites} - + {#if data.siteList.total} + {#if data.view === View.Grid} + + {:else} + {/if} - - - {#if data.siteList.total} - {#if data.view === View.Grid} - + + {:else if data.search} + {:else} -
+ (show = true)}> + {/if} - - {:else if data.search} - {:else} - (show = true)}> - + + + + + + {#if isOnWaitlist} + + You've successfully joined the Sites waitlist + + + + We can't wait for you to try out Sites on Cloud. You will get access + soon. + + {:else} + + + Appwrite Sites is in high demand + + +
+ + To ensure a smooth experience for everyone, we’re rolling out + access gradually. Join the waitlist and be one of the first to + deploy with Sites. + +
+ +
+ +
+
+ {/if} +
+
+
{/if} diff --git a/src/routes/(console)/project-[region]-[project]/sites/+page.ts b/src/routes/(console)/project-[region]-[project]/sites/+page.ts index 62eeb026a..afaf7b02d 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/sites/+page.ts @@ -2,8 +2,15 @@ import { Query } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { getLimit, getPage, getSearch, getView, pageToOffset, View } from '$lib/helpers/load'; import { CARD_LIMIT, Dependencies } from '$lib/constants'; +import { APPWRITE_OFFICIALS_ORG, isCloud } from '$lib/system'; + +export const load = async ({ url, depends, route, params, parent }) => { + // don't load anything on cloud unless org is appwrite atm! + const { organization } = await parent(); + if (isCloud && organization?.$id !== APPWRITE_OFFICIALS_ORG) { + return; + } -export const load = async ({ url, depends, route, params }) => { depends(Dependencies.SITES); const page = getPage(url); diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte index 201709a24..eaea81678 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte @@ -20,6 +20,9 @@ import { consoleVariables } from '$routes/(console)/store'; import { IconInfo } from '@appwrite.io/pink-icons-svelte'; import { InvalidFileType, removeFile } from '$lib/helpers/files'; + import { humanFileSize } from '$lib/helpers/sizeConvertion'; + import { isCloud } from '$lib/system'; + import { currentPlan } from '$lib/stores/organization'; export let data; let showExitModal = false; @@ -29,7 +32,7 @@ let name = 'My website'; let id = ID.unique(); - let domain = id; + let domain = `${id}.${$consoleVariables._APP_DOMAIN_SITES}`; let framework: Models.Framework = data.frameworks.frameworks?.find((f) => f.key === 'other') ?? data.frameworks.frameworks?.[0]; @@ -40,9 +43,22 @@ let variables: Partial[] = []; let files: FileList; + $: maxSize = + isCloud && $currentPlan + ? $currentPlan.deploymentSize * 1000000 + : $consoleVariables._APP_COMPUTE_SIZE_LIMIT; // already in MB + + $: readableMaxSize = humanFileSize(maxSize); + async function create() { try { - domain = await buildVerboseDomain(name, $organization.name, $project.name, id); + domain = await buildVerboseDomain( + $consoleVariables._APP_DOMAIN_SITES, + name, + $organization.name, + $project.name, + id + ); const fr = Object.values(Framework).find((f) => f === framework.key); const buildRuntime = Object.values(BuildRuntime).find( @@ -167,7 +183,7 @@ @@ -192,7 +208,8 @@ Max file size 10MB + >Max file size: {readableMaxSize.value + + readableMaxSize.unit} diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/store.ts b/src/routes/(console)/project-[region]-[project]/sites/create-site/store.ts index 35557ba75..9f866d869 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/store.ts +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/store.ts @@ -45,4 +45,6 @@ export async function buildVerboseDomain( if (await checkDomain(domain, apex)) { return domain; } + + return ID.unique(); } diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts index 491697969..5ac8edfa3 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts @@ -2,9 +2,17 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { Dependencies } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; -import { error } from '@sveltejs/kit'; +import { error, redirect } from '@sveltejs/kit'; +import { APPWRITE_OFFICIALS_ORG, isCloud } from '$lib/system'; +import { base } from '$app/paths'; + +export const load = async ({ depends, params, parent }) => { + // don't load anything on cloud unless org is appwrite atm! + const { organization } = await parent(); + if (isCloud && organization?.$id !== APPWRITE_OFFICIALS_ORG) { + redirect(307, `${base}/project-${params.region}-${params.project}/sites`); + } -export const load = async ({ depends, params }) => { depends(Dependencies.SITE); try { const [site] = await Promise.all([ diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createManualDeploymentModal.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createManualDeploymentModal.svelte index cd4c9f434..3930d56c7 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createManualDeploymentModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createManualDeploymentModal.svelte @@ -12,6 +12,7 @@ import { isCloud } from '$lib/system'; import { currentPlan } from '$lib/stores/organization'; import { consoleVariables } from '$routes/(console)/store'; + import { humanFileSize } from '$lib/helpers/sizeConvertion'; export let show = false; export let site: Models.Site; @@ -21,11 +22,11 @@ $: maxSize = isCloud && $currentPlan - ? $currentPlan?.deploymentSize === 0 - ? 0 - : $currentPlan?.deploymentSize * 1000000 + ? $currentPlan.deploymentSize * 1000000 : $consoleVariables._APP_COMPUTE_SIZE_LIMIT; // already in MB + $: readableMaxSize = humanFileSize(maxSize); + async function createDeployment() { try { uploader.uploadSiteDeployment(site.$id, files[0]); @@ -94,7 +95,9 @@ >Only .tar.gz files allowed - Max file size 10MB + Max file size: {readableMaxSize.value + + readableMaxSize.unit} diff --git a/src/routes/(public)/template-[template]/+page.svelte b/src/routes/(public)/template-[template]/+page.svelte index d7cbca40d..0ea3ff531 100644 --- a/src/routes/(public)/template-[template]/+page.svelte +++ b/src/routes/(public)/template-[template]/+page.svelte @@ -80,11 +80,11 @@ selectedProject = projects?.total ? projects.projects[0].$id : null; } - function generateUrl() { + function generateUrl(project: Models.Project) { if (isSiteTemplate(data.template, data.product)) { - return `${base}/project-${selectedProject}/sites/create-site/templates/template-${data.template.key}`; + return `${base}/project-${project.region}-${project.$id}/sites/create-site/templates/template-${data.template.key}`; } else { - return `${base}/project-${selectedProject}/functions/create-function/templates/template-${data.template.name}`; + return `${base}/project-${project.region}-${project.$id}/functions/create-function/templates/template-${data.template.name}`; } } @@ -105,7 +105,7 @@ selectedProject = p.$id; - window.location.href = generateUrl(); + window.location.href = generateUrl(p); } catch (e) { trackError(e, Submit.ProjectCreate); addNotification({ @@ -114,7 +114,8 @@ }); } } else { - window.location.href = generateUrl(); + const project = projects.projects.find((p) => p.$id === selectedProject); + window.location.href = generateUrl(project); } }