diff --git a/docker/nginx.conf b/docker/nginx.conf index 38eb1d96a..e26105222 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -4,7 +4,6 @@ server { gzip_static on; - # Set root for all locations root /usr/share/nginx/html; # Security headers for all locations @@ -22,10 +21,20 @@ server { add_header Cache-Control "public"; } + # Cache, but revalidate, for images, css, fonts, and icons folders + location ~* ^/console/(images|css|fonts|icons)/ { + expires 1d; + add_header Cache-Control "public, must-revalidate"; + } + # All other /console requests (no cache) location /console { index index.html index.html; try_files $uri /console/index.html; + + expires 0; + add_header Cache-Control "no-cache, no-store"; + add_header Pragma "no-cache"; } location / { 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 14e3308bb..98b200768 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,11 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@1959", - "@appwrite.io/pink": "0.25.0", + "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19", "@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/-/@appwrite/@appwrite.io/pink-svelte@7b28443", "@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 ad29a5d49..9279a8137 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,11 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@1959 - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@1959 - '@appwrite.io/pink': - specifier: 0.25.0 - version: 0.25.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-icons': specifier: 0.25.0 version: 0.25.0 @@ -27,8 +24,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/-/@appwrite/@appwrite.io/pink-svelte@7b28443 + version: https://try-module.cloud/-/@appwrite/%40appwrite.io%2Fpink-svelte@7b28443(svelte@5.25.3) '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 @@ -257,9 +254,9 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@1959': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@1959} - version: 1.5.2 + '@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==} @@ -281,15 +278,12 @@ 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/-/@appwrite/%40appwrite.io%2Fpink-svelte@7b28443': + resolution: {tarball: https://try-module.cloud/-/@appwrite/%40appwrite.io%2Fpink-svelte@7b28443} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink@0.25.0': - resolution: {integrity: sha512-9z4G/iAhjvVpM7tkNqzGqrokst4hvNt3Fkf2jD4W6n8j//EJbQ3mJ0I+5dye5Sry0a33wUanq5DSztqFNYq5zw==} - '@asamuzakjp/css-color@3.1.1': resolution: {integrity: sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA==} @@ -2664,9 +2658,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize.css@8.0.1: - resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==} - nwsapi@2.2.20: resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} @@ -3625,7 +3616,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@1959': {} + '@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: @@ -3644,7 +3635,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/-/@appwrite/%40appwrite.io%2Fpink-svelte@7b28443(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 @@ -3659,12 +3650,6 @@ snapshots: svelte-motion: 0.12.2(svelte@5.25.3) svelte-sonner: 0.3.28(svelte@5.25.3) - '@appwrite.io/pink@0.25.0': - dependencies: - '@appwrite.io/pink-icons': 0.25.0 - normalize.css: 8.0.1 - the-new-css-reset: 1.11.3 - '@asamuzakjp/css-color@3.1.1': dependencies: '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) @@ -6254,8 +6239,6 @@ snapshots: normalize-path@3.0.0: {} - normalize.css@8.0.1: {} - nwsapi@2.2.20: {} object-inspect@1.13.4: {} diff --git a/src/lib/commandCenter/panels/template.svelte b/src/lib/commandCenter/panels/template.svelte index 78dc3fe74..4065c0421 100644 --- a/src/lib/commandCenter/panels/template.svelte +++ b/src/lib/commandCenter/panels/template.svelte @@ -11,6 +11,7 @@ import { IconArrowSmRight } from '@appwrite.io/pink-icons-svelte'; import { Icon, Keyboard, Layout } from '@appwrite.io/pink-svelte'; import { Submit, trackEvent } from '$lib/actions/analytics'; + import type { Action } from 'svelte/action'; /* eslint no-undef: "off" */ type Option = $$Generic & { group?: string }>; @@ -247,6 +248,10 @@ return isGroup(nextItem) || !nextItem?.nested; }; + + const autofocus: Action = (node) => { + if (node) node.focus(); + }; @@ -271,8 +276,11 @@ @@ -305,7 +313,7 @@ on:mouseleave={getOptionBlurHandler()} on:focus={getOptionFocusHandler(item)}> - + {#if item.icon} -
- Get $50 Cloud credits for Appwrite Pro. + + Get $50 Cloud credits for Appwrite Pro. + -
+
{/if} diff --git a/src/lib/components/billing/gradientBanner.svelte b/src/lib/components/billing/gradientBanner.svelte index 5cae07cd9..27c58ce62 100644 --- a/src/lib/components/billing/gradientBanner.svelte +++ b/src/lib/components/billing/gradientBanner.svelte @@ -1,11 +1,41 @@ -
+ + +
+
- + +
+ + diff --git a/src/lib/components/billing/selectPaymentMethod.svelte b/src/lib/components/billing/selectPaymentMethod.svelte index dac287731..7dfe14a2e 100644 --- a/src/lib/components/billing/selectPaymentMethod.svelte +++ b/src/lib/components/billing/selectPaymentMethod.svelte @@ -21,6 +21,7 @@ async function cardSaved(event: CustomEvent) { value = event.detail.$id; invalidate(Dependencies.UPGRADE_PLAN); + invalidate(Dependencies.CREATE_ORGANIZATION); } onMount(() => { diff --git a/src/lib/components/billing/usageRates.svelte b/src/lib/components/billing/usageRates.svelte index 653b9c154..b58df6f3a 100644 --- a/src/lib/components/billing/usageRates.svelte +++ b/src/lib/components/billing/usageRates.svelte @@ -17,31 +17,6 @@ ? new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toString() : org?.billingNextInvoiceDate; - const planData = [ - { - id: 'members', - resource: 'Organization members', - unit: 'member' - }, - { id: 'bandwidth', resource: 'Bandwidth', unit: 'GB' }, - { id: 'storage', resource: 'Storage', unit: 'GB' }, - { - id: 'executions', - resource: 'Function executions', - unit: ' executions' - }, - { - id: 'users', - resource: 'Users', - unit: ' Users' - }, - { - id: 'realtime', - resource: 'Concurrent connections', - unit: ' connections' - } - ]; - $: isFree = org.billingPlan === BillingPlan.FREE; // equal or above means unlimited! @@ -50,6 +25,10 @@ const exceedsSafeLimit = count >= Number.MAX_SAFE_INTEGER; return exceedsSafeLimit ? 'Unlimited' : count || 0; }; + + function getPlanLimit(key: string): number | false { + return plan[key] || false; + } @@ -77,29 +56,33 @@ Limit Rate - {#each planData as usage} - {#if usage['id'] === 'members'} - - {usage.resource} - - {getCorrectSeatsCountValue(plan.addons.seats.limit)} - + {#each Object.values(plan.addons) as addon} + + {addon.invoiceDesc} + + {getCorrectSeatsCountValue(addon.limit)} + + {#if !isFree} - {formatCurrency(plan.addons?.seats?.price)}/{usage?.unit} + {formatCurrency(addon.price)}/member - - {:else} - {@const addon = plan.addons[usage.id]} + {/if} + + {/each} + {#each Object.entries(plan.usage) as [key, usage]} + {@const limit = getPlanLimit(key)} + {@const show = limit !== false} + {#if show} - {usage.resource} + {usage.name} - {abbreviateNumber(plan[usage.id])}{usage?.unit} + {abbreviateNumber(limit)}{usage.unit} {#if !isFree} - {formatCurrency(addon?.price)}/{['MB', 'GB', 'TB'].includes(addon?.unit) - ? addon?.value - : abbreviateNumber(addon?.value, 0)}{usage?.unit} + {formatCurrency(usage.price)}/{abbreviateNumber( + usage.value + )}{usage.unit} {/if} diff --git a/src/lib/components/cardContainer.svelte b/src/lib/components/cardContainer.svelte index ac5223a6d..ccbd1075b 100644 --- a/src/lib/components/cardContainer.svelte +++ b/src/lib/components/cardContainer.svelte @@ -2,10 +2,11 @@ import { page } from '$app/state'; import { Empty } from '$lib/components'; import { CARD_LIMIT } from '$lib/constants'; - import { getServiceLimit, type PlanServices } from '$lib/stores/billing'; - import { preferences } from '$lib/stores/preferences'; import { isCloud } from '$lib/system'; import CardPlanLimit from './cardPlanLimit.svelte'; + import { preferences } from '$lib/stores/preferences'; + import { isSmallViewport } from '$lib/stores/viewport'; + import { getServiceLimit, type PlanServices } from '$lib/stores/billing'; export let disableEmpty = true; export let offset = 0; @@ -17,9 +18,13 @@ $: planLimit = getServiceLimit(serviceId) || Infinity; $: limit = preferences.get(page.route)?.limit ?? CARD_LIMIT; + + $: gridItemStyle = $isSmallViewport + ? undefined + : `--grid-item-size: ${total > 3 ? '22rem' : '25rem'}`; -
    3 ? '22rem' : '25rem'};`} data-private> +
      {#if total > 3 ? total < limit + offset : total % 2 !== 0} diff --git a/src/lib/components/logs/logsRequest.svelte b/src/lib/components/logs/logsRequest.svelte index 85bd2482a..dc04eb022 100644 --- a/src/lib/components/logs/logsRequest.svelte +++ b/src/lib/components/logs/logsRequest.svelte @@ -13,11 +13,22 @@ } from '@appwrite.io/pink-svelte'; import { onMount } from 'svelte'; - export let selectedLog: Models.Execution; + let { + selectedLog, + product + }: { + selectedLog: Models.Execution; + product: 'site' | 'function'; + } = $props(); - let requestTab: 'parameters' | 'headers' = 'parameters'; + let requestTab: 'parameters' | 'headers' = $state('parameters'); - let parameters = []; + let parameters = $state([]); + + const href = + product === 'site' + ? 'https://appwrite.io/docs/products/sites/logs#log-details' + : 'https://appwrite.io/docs/products/functions/develop#logging'; onMount(() => { try { @@ -121,8 +132,8 @@ - Missing headers? Check the docs to - see the supported data and how to log it. + Missing headers? Check the docs to see + the supported data and how to log it. {:else} diff --git a/src/lib/components/logs/logsResponse.svelte b/src/lib/components/logs/logsResponse.svelte index 227efc636..a1361af54 100644 --- a/src/lib/components/logs/logsResponse.svelte +++ b/src/lib/components/logs/logsResponse.svelte @@ -28,11 +28,10 @@ let responseTab: 'logs' | 'errors' | 'headers' | 'body' = $state('logs'); - let href = $derived( + const href = product === 'site' ? 'https://appwrite.io/docs/products/sites/logs#log-details' - : 'https://appwrite.io/docs/products/functions/develop#logging' - ); + : 'https://appwrite.io/docs/products/functions/develop#logging'; onMount(() => { if (selectedLog?.errors) { diff --git a/src/lib/components/mfaChallengeFormList.svelte b/src/lib/components/mfaChallengeFormList.svelte index 86e6c2dec..3e369b2ab 100644 --- a/src/lib/components/mfaChallengeFormList.svelte +++ b/src/lib/components/mfaChallengeFormList.svelte @@ -3,7 +3,7 @@ export async function verify(challenge: Models.MfaChallenge, code: string) { try { - if (challenge == null) { + if (challenge === null) { challenge = await sdk.forConsole.account.createMfaChallenge( AuthenticationFactor.Totp ); @@ -59,9 +59,9 @@ onMount(async () => { const enabledNonRecoveryFactors = enabledFactors.filter( - ([factor, _]) => factor != 'recoveryCode' + ([factor, _]) => factor !== 'recoveryCode' ); - if (enabledNonRecoveryFactors.length == 1) { + if (enabledNonRecoveryFactors.length === 1) { if (factors.phone) { createChallenge(AuthenticationFactor.Phone); } else if (factors.email) { @@ -77,7 +77,7 @@ {:else} - {#if factors.totp && (challengeType == AuthenticationFactor.Totp || challengeType == null)} + {#if factors.totp && (challengeType === AuthenticationFactor.Totp || challengeType === null)} Enter below a 6-digit one-time code generated by your authentication app. {:else if challengeType === AuthenticationFactor.Email} diff --git a/src/lib/components/sidebar.svelte b/src/lib/components/sidebar.svelte index 99bd5db85..31cba6724 100644 --- a/src/lib/components/sidebar.svelte +++ b/src/lib/components/sidebar.svelte @@ -39,11 +39,11 @@ import { Click, trackEvent } from '$lib/actions/analytics'; import type { HTMLAttributes } from 'svelte/elements'; - import type { NavbarProject } from '$lib/components/navbar.svelte'; + import type { Models } from '@appwrite.io/console'; type $$Props = HTMLAttributes & { state?: 'closed' | 'open' | 'icons'; - project: NavbarProject | undefined; + project: Models.Project | undefined; avatar: string; progressCard?: { title: string; @@ -84,7 +84,7 @@
      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/constants.ts b/src/lib/constants.ts index 58ae2e443..067b354dd 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -18,6 +18,7 @@ export enum Dependencies { INVOICES = 'dependency:invoices', ADDRESS = 'dependency:address', UPGRADE_PLAN = 'dependency:upgrade_plan', + CREATE_ORGANIZATION = 'dependency:create_organization', PAYMENT_METHODS = 'dependency:paymentMethods', ORGANIZATION = 'dependency:organization', MEMBERS = 'dependency:members', diff --git a/src/lib/elements/forms/button.svelte b/src/lib/elements/forms/button.svelte index 5f11e8478..a60d36a01 100644 --- a/src/lib/elements/forms/button.svelte +++ b/src/lib/elements/forms/button.svelte @@ -124,3 +124,18 @@ {/if} + + + diff --git a/src/lib/helpers/regions.ts b/src/lib/helpers/regions.ts new file mode 100644 index 000000000..9f04f3fda --- /dev/null +++ b/src/lib/helpers/regions.ts @@ -0,0 +1,27 @@ +import { getFlagUrl } from '$lib/helpers/flag'; +import type { Models } from '@appwrite.io/console'; + +type RegionOption = { + label: string; + value: string; + disabled: boolean; + leadingHtml: string; +}; + +export function filterRegions(regions: Models.ConsoleRegion[]): RegionOption[] { + if (!regions.length) return []; + + return regions + .filter((region) => region.$id !== 'default') + .sort((a, b) => { + if (a.disabled && !b.disabled) return 1; + if (!a.disabled && b.disabled) return -1; + return a.name.localeCompare(b.name); + }) + .map((region) => ({ + label: region.name, + value: region.$id, + disabled: region.disabled || !region.available, + leadingHtml: `Region flag` + })); +} 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/cover.svelte b/src/lib/layout/cover.svelte index fff0765ea..fba7a062f 100644 --- a/src/lib/layout/cover.svelte +++ b/src/lib/layout/cover.svelte @@ -11,7 +11,7 @@
      - + diff --git a/src/lib/layout/coverTitle.svelte b/src/lib/layout/coverTitle.svelte index eb3a43fa1..ce87e70e5 100644 --- a/src/lib/layout/coverTitle.svelte +++ b/src/lib/layout/coverTitle.svelte @@ -1,18 +1,24 @@ -{#if href} - - - - - -{/if} - - - + + {#if href} + + + + + + {/if} + + + + diff --git a/src/lib/layout/createProject.svelte b/src/lib/layout/createProject.svelte index 945a4e5c3..7d368dde0 100644 --- a/src/lib/layout/createProject.svelte +++ b/src/lib/layout/createProject.svelte @@ -5,6 +5,7 @@ import { getFlagUrl } from '$lib/helpers/flag'; import { isCloud } from '$lib/system.js'; import type { Models } from '@appwrite.io/console'; + import { filterRegions } from '$lib/helpers/regions'; export let projectName: string; export let id: string; @@ -13,25 +14,6 @@ export let showTitle = true; let showCustomId = false; - - function getRegions() { - return regions - .filter((region) => region.$id !== 'default') - .sort((regionA, regionB) => { - if (regionA.disabled && !regionB.disabled) { - return 1; - } - return regionA.name > regionB.name ? 1 : -1; - }) - .map((region) => { - return { - label: region.name, - value: region.$id, - leadingHtml: `Region flag`, - disabled: region.disabled - }; - }); - } @@ -39,6 +21,7 @@ {/each} +
      {#if showTitle} @@ -68,7 +51,7 @@ Region cannot be changed after creation diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 871f2d525..e5062eddc 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -11,21 +11,20 @@ import { sdk } from '$lib/stores/sdk'; import { user } from '$lib/stores/user'; import { tierToPlan } from '$lib/stores/billing'; - import { type Models } from '@appwrite.io/console'; import { isCloud } from '$lib/system'; import SideNavigation from '$lib/layout/navigation.svelte'; import { hasOnboardingDismissed } from '$lib/helpers/onboarding'; import { isSidebarOpen } from '$lib/stores/sidebar'; import { BillingPlan } from '$lib/constants'; import { page } from '$app/stores'; + import type { Models } from '@appwrite.io/console'; export let showSideNavigation = false; export let showHeader = true; export let showFooter = true; export let loadedProjects: Array = []; - export let projects: Array = []; + export let selectedProject: Models.Project | null = null; - $: selectedProject = loadedProjects.find((project) => project.isSelected); let yOnMenuOpen: number; let showContentTransition = false; let timeoutId: ReturnType; @@ -120,11 +119,9 @@ const progressCard = function getProgressCard() { if (selectedProject && !hasOnboardingDismissed(selectedProject.$id)) { - const currentProject = projects.find((project) => project.$id === selectedProject.$id); - return { title: 'Get started', - percentage: currentProject && currentProject.platforms.length ? 100 : 33 + percentage: selectedProject && selectedProject.platforms.length ? 100 : 33 }; } diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index aab3ffe59..9a4cdfe4f 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -278,6 +278,7 @@ export type AddressesList = { }; export type AdditionalResource = { + name: string; currency: string; invoiceDesc: string; price: number; diff --git a/src/lib/stores/domains.ts b/src/lib/stores/domains.ts index ea339f83d..348e10c9b 100644 --- a/src/lib/stores/domains.ts +++ b/src/lib/stores/domains.ts @@ -1,23 +1,21 @@ +import { StatusCode } from '@appwrite.io/console'; + export const statusCodeOptions = [ { label: '301 Moved permanently', - value: 301 + value: StatusCode.MovedPermanently301 }, { label: '302 Found', - value: 302 - }, - { - label: '303 See other', - value: 303 + value: StatusCode.Found302 }, { label: '307 Temporary redirect', - value: 307 + value: StatusCode.TemporaryRedirect307 }, { label: '308 Permanent redirect', - value: 308 + value: StatusCode.PermanentRedirect308 } ]; diff --git a/src/lib/stores/organization.ts b/src/lib/stores/organization.ts index 6f22cbc96..9ae54ea27 100644 --- a/src/lib/stores/organization.ts +++ b/src/lib/stores/organization.ts @@ -61,4 +61,4 @@ export const organization = derived(page, ($page) => $page.data?.organization as export const currentPlan = derived(page, ($page) => $page.data?.currentPlan as Plan); export const members = derived(page, ($page) => $page.data.members as Models.MembershipList); -export const regions = writable(undefined); +export const regions = writable({ total: 0, regions: [] }); 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)/(migration-wizard)/index.ts b/src/routes/(console)/(migration-wizard)/index.ts index 9c065b9bf..fcde1e141 100644 --- a/src/routes/(console)/(migration-wizard)/index.ts +++ b/src/routes/(console)/(migration-wizard)/index.ts @@ -3,6 +3,7 @@ import { wizard } from '$lib/stores/wizard'; import { requestedMigration } from '$routes/store'; import { get, writable } from 'svelte/store'; import Wizard from './wizard.svelte'; +import { Region } from '@appwrite.io/console'; export const formData = createMigrationFormStore(); @@ -18,5 +19,6 @@ export function openMigrationWizard() { } export const selectedProject = writable(null); +export const selectedRegion = writable(Region.Fra); export const provider = createMigrationProviderStore(); diff --git a/src/routes/(console)/(migration-wizard)/resource-form.svelte b/src/routes/(console)/(migration-wizard)/resource-form.svelte index c0b09b5a2..103d084b2 100644 --- a/src/routes/(console)/(migration-wizard)/resource-form.svelte +++ b/src/routes/(console)/(migration-wizard)/resource-form.svelte @@ -1,66 +1,27 @@ - -
      - Good to know -
      -
      - -
      -
      -

      Project settings are not imported

      -

      You will need to set service and project settings manually

      -
      -
      -
      -
      - -
      -
      -

      Keep your organization plan's limits in mind

      -

      - Make sure to have enough storage in your organization plan when importing files. -

      -
      -
      - {#if $provider.provider === 'firebase'} -
      -
      - -
      -
      -

      Possible charges by Firebase

      -

      - Appwrite does not impose charges for importing data, but please note that - Firebase may have its own pricing for this service -

      -
      -
      - {:else} -
      -
      - -
      -
      -

      Transfer is free of charge

      -

      You won't be charged for Appwrite bandwidth usage for importing data

      -
      -
      - {/if} -
      -
      - -{#if report && !isVersionAtLeast(version, '1.4.0') && $provider.provider === 'appwrite'} -
      - p - 1); - } - } - ]}> + + {#if report && !isVersionAtLeast(version, '1.4.0') && $provider.provider === 'appwrite'} + Functions not available for import To migrate your functions, update the version of the Appwrite instance you're importing from to a version newer than 1.4 - -
      -{/if} + + {/if} -{#if error} -
      - p - 1); - } - } - ]}> - Request failed - Please check if your credentials are filled in correctly in the previous step - -
      -{/if} - -
        -
      • - -
      • - -
      • - -
      • -
      - -
        - {#if resources?.includes('user')} -
      • - -
        - Users - - {#if $provider.provider !== 'firebase'} - {#if report?.user !== undefined} - {report.user} - {:else if !error} - - {/if} - {/if} -
        -
        - Import all users - - {#if resources?.includes('team')} -
          -
        • - -
          - Include teams - {#if $provider.provider === 'firebase'} - {#if report?.team !== undefined} - {report.team} - {:else if !error} - - {/if} - {/if} -
          -
          - Import all teams and the team memberships of your users -
        • -
        + {#if error} + + {#if migrationType === 'provider'} + Please double-check your credentials from the previous step and try again. + {:else} + The API key required for the migration may no longer be valid or has expired, please + verify and start migration again. {/if} -
      • - {/if} + + {:else} + + + - {#if resources?.includes('database')} -
      • - -
        - Databases - {#if $provider.provider !== 'firebase'} - {#if report?.database !== undefined} - {report.database} - {:else if !error} - - {/if} - {/if} -
        -
        - Import all databases, including collections, indexes and attributes + + + + - {#if resources?.includes('document')} -
          -
        • - -
          - Include documents - {#if $provider.provider !== 'firebase'} - {#if report?.document !== undefined} - {report.document} - {:else if !error} - - {/if} - {/if} -
          -
          - Import all of your documents -
        • -
        + {#each Object.entries($formData) as [groupKey, formGroup]} + {#if shouldRenderGroup(groupKey)} + {/if} -
      • + {/each} {/if} - - {#if resources?.includes('function') && isVersionAtLeast(version, '1.4.0')} -
      • - -
        - Functions - {#if $provider.provider !== 'firebase'} - {#if report?.function !== undefined} - {report.function} - {:else if !error} - - {/if} - {/if} -
        -
        - Import all functions and their active deployment -
          - {#if resources?.includes('environment-variable')} -
        • - -
          - Include environment variables -
          -
          - Import all environment variables -
        • - {/if} - {#if resources?.includes('deployment')} -
        • - -
          - Include inactive deployments -
          -
          - Import all deployments that are not currently active -
        • - {/if} -
        -
      • - {/if} - - {#if resources?.includes('bucket') && resources?.includes('file')} -
      • - -
        - Storage - {#if $provider.provider !== 'firebase'} - {#if report?.size !== undefined} - {`${report.size.toFixed(2)}MB`} - {:else if !error} - - {/if} - {/if} -
        -
        - -

        - Import all buckets - {#if $provider.provider !== 'firebase' && report?.bucket} - {report.bucket} - {/if} - and files - {#if $provider.provider !== 'firebase' && report?.file} - {report.file} - {/if} -

        -
      • - {/if} -
      - - - + diff --git a/src/routes/(console)/(migration-wizard)/wizard.svelte b/src/routes/(console)/(migration-wizard)/wizard.svelte index e0b296759..99c23b7d2 100644 --- a/src/routes/(console)/(migration-wizard)/wizard.svelte +++ b/src/routes/(console)/(migration-wizard)/wizard.svelte @@ -1,52 +1,388 @@ - + + + {#if !showResources} + + {#if organizations.length >= 1} +
      + ({ + label: project.name, + value: project.$id + }))} + disabled={loadingProjects} /> +
      + {/if} + +
      + {#if loadingProjects} + + + + + + + + {:else} + + + + Import data to an existing project + + + + Import data to a new project + + + + {#if projectType === 'existing'} + ({ + label: p.name, + value: p.$id + }))} /> + {:else} + + + + {#if isCloud && $regions.regions.length > 0} + + Region cannot be changed after creation + {/if} + + {/if} + + { + if (isExisting) { + showResources = true; + } else { + const project = await createNewProject(); + if (project !== null) { + projectSdkInstance = sdk.forProject( + project.region, + project.$id + ); + showResources = true; + } + } + }}> + {#if creatingProject} + + {/if} + + Next + + + + {/if} +
      +
      + {:else} + + + + + + + {capitalize(getProjectName())} + + + + (showResources = !showResources)}> + Update + + + + {/if} + + {#if showResources} +
      + + + +
      + {/if} +
      + + + + + Good to know + + + + + + + + Project settings are not imported + You will need to set service and project settings manually. + + + + + + + + + + Keep your organization plan's limits in mind + Make sure to have enough storage in your organization plan when importing files. + + + + + + {@const isFirebase = $provider.provider === 'firebase'} + + + + + + {isFirebase + ? 'Possible charges by Firebase' + : 'Transfer is free of charge'} + + + {#if isFirebase} + Appwrite does not impose charges for importing data, but please note + that Firebase may have its own pricing for this service + {:else} + You won't be charged for Appwrite bandwidth usage for importing data + {/if} + + + + + + + + (showExitModal = true)}> + Cancel + + + + {#if migrationStarted} + + {/if} + Create + + +
      diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 2a101bc25..cc4a74d96 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -52,6 +52,7 @@ IconSparkles, IconSwitchHorizontal } from '@appwrite.io/pink-icons-svelte'; + import type { LayoutData } from './$types'; function kebabToSentenceCase(str: string) { return str @@ -62,7 +63,8 @@ const isAssistantEnabled = $consoleVariables?._APP_ASSISTANT_ENABLED === true; - export let data; + export let data: LayoutData; + $: loadedProjects = data.projects.map((project) => { return { name: project?.name, @@ -338,10 +340,10 @@ !page.url.pathname.includes('/console/account') && !page.url.pathname.includes('/console/card') && !page.url.pathname.includes('/console/onboarding')} - showHeader={!page.url.pathname.includes('/console/onboarding')} - showFooter={!page.url.pathname.includes('/console/onboarding')} - bind:loadedProjects - bind:projects={data.projects}> + showHeader={!page.url.pathname.includes('/console/onboarding/create-project')} + showFooter={!page.url.pathname.includes('/console/onboarding/create-project')} + {loadedProjects} + selectedProject={page.data?.project}>