diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000000..488e054bae --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,33 @@ +name: E2E + +on: + pull_request_target: + branches: ['**'] + paths-ignore: + - '**/*.md' + - 'static/**/*' + +env: + VITE_STRIPE_PUBLIC_KEY: ${{ vars.VITE_STRIPE_PUBLIC_KEY }} + +jobs: + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps chromium + - name: E2E Tests + run: npm run e2e + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a0490076e8..a93dc1709c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,6 +28,6 @@ jobs: - name: Linter run: npm run lint - name: Unit Tests - run: npm test + run: npm run test - name: Build Console run: npm run build diff --git a/.gitignore b/.gitignore index 27cc4e27e2..ec4979cd0c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ node_modules .env .env.* !.env.example +test-results/ +playwright-report/ .DS_STORE .cache @@ -145,4 +147,4 @@ dist .stylelintcache # SvelteKit build / generate output -.svelte-kit \ No newline at end of file +.svelte-kit diff --git a/package-lock.json b/package-lock.json index 0e8f02c597..279f65287f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,13 +6,13 @@ "": { "name": "@appwrite/console", "dependencies": { - "@appwrite.io/console": "^0.6.1", - "@appwrite.io/pink": "0.17.0", - "@appwrite.io/pink-icons": "0.17.0", + "@appwrite.io/console": "^0.6.2", + "@appwrite.io/pink": "0.20.0", + "@appwrite.io/pink-icons": "0.20.0", "@popperjs/core": "^2.11.8", "@sentry/svelte": "^7.66.0", "@sentry/tracing": "^7.66.0", - "@stripe/stripe-js": "^2.2.0", + "@stripe/stripe-js": "^3.4.0", "ai": "^2.2.11", "analytics": "^0.8.9", "dayjs": "^1.11.9", @@ -29,7 +29,7 @@ "devDependencies": { "@melt-ui/pp": "^0.1.4", "@melt-ui/svelte": "^0.61.2", - "@playwright/test": "^1.37.1", + "@playwright/test": "^1.44.0", "@sveltejs/adapter-static": "^3.0.1", "@sveltejs/kit": "^2.3.4", "@sveltejs/vite-plugin-svelte": "^3.0.1", @@ -157,28 +157,24 @@ "integrity": "sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==" }, "node_modules/@appwrite.io/console": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.6.1.tgz", - "integrity": "sha512-XlyitzDGmuBzJLBtFuZzL9vS4ZgAWzxuvWrILc7LwdNh2oklzhbJ8BTO8/CAxjxOwR6UOLKjXdoQnqOMqR66Yw==", - "dependencies": { - "cross-fetch": "3.1.5", - "isomorphic-form-data": "2.0.0" - } + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.6.2.tgz", + "integrity": "sha512-3qYknuFwhvTN2GnPB8G1w5DgxfVorGtfj4uuEaXbTmMUmjA8fHaW5VkJriuUxCi6/TpxDxqV/hhEkdoXL+5H4w==" }, "node_modules/@appwrite.io/pink": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@appwrite.io/pink/-/pink-0.17.0.tgz", - "integrity": "sha512-1FkVVqdqU5d0FlGIyLisCoLR3JaqGlQs3+BJydo6Mr9/bRWKcg4O9nw+e1P1Lx8StnVnDEM5CRCQdzyPQSNByw==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@appwrite.io/pink/-/pink-0.20.0.tgz", + "integrity": "sha512-17anov7EL+Ic/4fNtCYgG3wA+hoeBXn/j+ZjkfZFfX3g66M8XzVTaQfsAH+EMZ3YjvWuKlHtn+bY4BkGwvMIzQ==", "dependencies": { - "@appwrite.io/pink-icons": "0.17.0", + "@appwrite.io/pink-icons": "0.20.0", "normalize.css": "^8.0.1", "the-new-css-reset": "^1.11.2" } }, "node_modules/@appwrite.io/pink-icons": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-0.17.0.tgz", - "integrity": "sha512-JcYjVI+nPO8BTJzKlUH2RKyUYg/3pEy81dr3TrQB0HlsrngDq3akhaNCfnQzLg9Kicdl+Cr4Gq920Ha6zA4FQQ==" + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-0.20.0.tgz", + "integrity": "sha512-nfDy7qE6FQWVO1eC+3nwGjXkj/Zm2V50rhvyj0/HKq8RyWNWFGX31BZoMSim01vl/N5irBilpkaER6mLPp7oEw==" }, "node_modules/@babel/code-frame": { "version": "7.24.2", @@ -1895,12 +1891,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.43.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", - "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz", + "integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==", "dev": true, "dependencies": { - "playwright": "1.43.1" + "playwright": "1.44.0" }, "bin": { "playwright": "cli.js" @@ -2305,9 +2301,12 @@ } }, "node_modules/@stripe/stripe-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.4.0.tgz", - "integrity": "sha512-WFkQx1mbs2b5+7looI9IV1BLa3bIApuN3ehp9FP58xGg7KL9hCHDECgW3BwO9l9L+xBPVAD7Yjn1EhGe6EDTeA==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-3.4.0.tgz", + "integrity": "sha512-a2kUP7OrsV0SSIk3UxWa+cnrW+PPIyuCbWIBH8vxfHIqmyeQN/d0lsplZJ2h7MlLsU/sB3EyhNBkhLLT+zHwKw==", + "engines": { + "node": ">=12.16" + } }, "node_modules/@sveltejs/adapter-static": { "version": "3.0.1", @@ -3452,7 +3451,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -3945,6 +3945,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4002,14 +4003,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dependencies": { - "node-fetch": "2.6.7" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4223,6 +4216,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -4954,19 +4948,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5782,14 +5763,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isomorphic-form-data": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz", - "integrity": "sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==", - "dependencies": { - "form-data": "^2.3.2" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -7107,6 +7080,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -7115,6 +7089,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -7236,44 +7211,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7678,12 +7615,12 @@ } }, "node_modules/playwright": { - "version": "1.43.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", - "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz", + "integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==", "dev": true, "dependencies": { - "playwright-core": "1.43.1" + "playwright-core": "1.44.0" }, "bin": { "playwright": "cli.js" @@ -7696,9 +7633,9 @@ } }, "node_modules/playwright-core": { - "version": "1.43.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", - "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz", + "integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/package.json b/package.json index eb7eba70d2..f258ae4db6 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,17 @@ "test": "TZ=EST vitest run", "test:ui": "TZ=EST vitest --ui", "test:watch": "TZ=EST vitest watch", - "e2e": "playwright test tests/e2e" + "e2e": "playwright test tests/e2e", + "e2e:ui": "playwright test tests/e2e --ui" }, "dependencies": { - "@appwrite.io/pink": "0.17.0", - "@appwrite.io/pink-icons": "0.17.0", - "@appwrite.io/console": "^0.6.1", + "@appwrite.io/console": "^0.6.2", + "@appwrite.io/pink": "0.20.0", + "@appwrite.io/pink-icons": "0.20.0", "@popperjs/core": "^2.11.8", "@sentry/svelte": "^7.66.0", "@sentry/tracing": "^7.66.0", - "@stripe/stripe-js": "^2.2.0", + "@stripe/stripe-js": "^3.4.0", "ai": "^2.2.11", "analytics": "^0.8.9", "dayjs": "^1.11.9", @@ -41,7 +42,7 @@ "devDependencies": { "@melt-ui/pp": "^0.1.4", "@melt-ui/svelte": "^0.61.2", - "@playwright/test": "^1.37.1", + "@playwright/test": "^1.44.0", "@sveltejs/adapter-static": "^3.0.1", "@sveltejs/kit": "^2.3.4", "@sveltejs/vite-plugin-svelte": "^3.0.1", diff --git a/playwright.config.ts b/playwright.config.ts index 58a0a31917..d330159b0b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,7 +1,15 @@ import { type PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { + timeout: 120000, + reportSlowTests: null, + reporter: [['html', { open: 'never' }]], webServer: { + timeout: 120000, + env: { + VITE_APPWRITE_ENDPOINT: 'http://console-tests.appwrite.org/v1', + VITE_CONSOLE_MODE: 'cloud' + }, command: 'npm run build && npm run preview', port: 4173 } diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 93119dbbb7..744a4e3517 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -157,6 +157,7 @@ export enum Submit { AccountAuthenticatorDelete = 'submit_account_authenticator_delete', AccountRecoveryCodesCreate = 'submit_account_recovery_codes_create', AccountRecoveryCodesUpdate = 'submit_account_recovery_codes_update', + AccountDeleteIdentity = 'submit_account_delete_identity', UserCreate = 'submit_user_create', UserDelete = 'submit_user_delete', UserUpdateEmail = 'submit_user_update_email', diff --git a/src/lib/components/alert.svelte b/src/lib/components/alert.svelte index 20998db9d9..f37e83fe1f 100644 --- a/src/lib/components/alert.svelte +++ b/src/lib/components/alert.svelte @@ -1,11 +1,13 @@ -{#if $organization?.$id && $organization?.billingPlan === BillingPlan.STARTER && $readOnly && !$page.url.pathname.includes('/console/account')} +{#if $organization?.$id && $organization?.billingPlan === BillingPlan.STARTER && $readOnly && !hideBillingHeaderRoutes.includes($page.url.pathname)} @@ -21,14 +19,14 @@ + + + diff --git a/src/lib/components/billing/estimatedTotalBox.svelte b/src/lib/components/billing/estimatedTotalBox.svelte index 6ead403624..9327009195 100644 --- a/src/lib/components/billing/estimatedTotalBox.svelte +++ b/src/lib/components/billing/estimatedTotalBox.svelte @@ -5,7 +5,6 @@ import { formatCurrency } from '$lib/helpers/numbers'; import type { Coupon } from '$lib/sdk/billing'; import { plansInfo, type Tier } from '$lib/stores/billing'; - import { Box } from '..'; export let billingPlan: Tier; export let collaborators: string[]; @@ -26,9 +25,12 @@ ? grossCost - couponData.credits : 0 : grossCost; + $: trialEndDate = new Date( + billingPayDate.getTime() + currentPlan.trialDays * 24 * 60 * 60 * 1000 + ); - +

{currentPlan.name} plan

{formatCurrency(currentPlan.price)}

@@ -83,15 +85,12 @@

- {@const trialEndDate = new Date( - billingPayDate.getTime() + currentPlan.trialDays * 24 * 60 * 60 * 1000 - )}

Your payment method will be charged this amount plus usage fees every 30 days {!currentPlan.trialDays ? `starting ${toLocaleDate(billingPayDate.toString())}` : ` after your trial period ends on ${toLocaleDate(trialEndDate.toString())}`}.

- + - +
diff --git a/src/lib/components/billing/index.ts b/src/lib/components/billing/index.ts index 83da97477b..1a5a5680b7 100644 --- a/src/lib/components/billing/index.ts +++ b/src/lib/components/billing/index.ts @@ -4,3 +4,4 @@ export { default as SelectPaymentMethod } from './selectPaymentMethod.svelte'; export { default as UsageRates } from './usageRates.svelte'; export { default as EstimatedTotalBox } from './estimatedTotalBox.svelte'; export { default as PlanComparisonBox } from './planComparisonBox.svelte'; +export { default as EmptyCardCloud } from './emptyCardCloud.svelte'; diff --git a/src/lib/components/billing/paymentModal.svelte b/src/lib/components/billing/paymentModal.svelte index 56bcdab204..16388b948c 100644 --- a/src/lib/components/billing/paymentModal.svelte +++ b/src/lib/components/billing/paymentModal.svelte @@ -92,6 +92,7 @@ + diff --git a/src/routes/console/wizard/cloudOrganizationChangeTier/planExcess.svelte b/src/lib/components/billing/planExcess.svelte similarity index 100% rename from src/routes/console/wizard/cloudOrganizationChangeTier/planExcess.svelte rename to src/lib/components/billing/planExcess.svelte diff --git a/src/lib/components/billing/selectPaymentMethod.svelte b/src/lib/components/billing/selectPaymentMethod.svelte index ec1e375700..8738148877 100644 --- a/src/lib/components/billing/selectPaymentMethod.svelte +++ b/src/lib/components/billing/selectPaymentMethod.svelte @@ -84,7 +84,10 @@ {:else} - +

@@ -99,17 +102,23 @@ {#if showPaymentModal && isCloud && hasStripePublicKey} - - {#if showTaxId} -

- -
- {/if} - + + + {#if showTaxId} +
+ +
+ {/if} +
+
{/if} diff --git a/src/lib/components/card.svelte b/src/lib/components/card.svelte index 378aea1339..4cf714226a 100644 --- a/src/lib/components/card.svelte +++ b/src/lib/components/card.svelte @@ -49,6 +49,7 @@ class:is-border-dashed={isDashed} class:is-danger={danger} class:is-allowed-focus={href} + {...$$restProps} {style} on:click on:keyup={clickOnEnter} diff --git a/src/lib/components/cardPlanLimit.svelte b/src/lib/components/cardPlanLimit.svelte index 966337d2fa..51fd5242e5 100644 --- a/src/lib/components/cardPlanLimit.svelte +++ b/src/lib/components/cardPlanLimit.svelte @@ -1,7 +1,6 @@ @@ -9,8 +8,6 @@

Upgrade your plan to add more {service}

- +
diff --git a/src/lib/components/collapsible.svelte b/src/lib/components/collapsible.svelte index 245eec7810..f3bf946011 100644 --- a/src/lib/components/collapsible.svelte +++ b/src/lib/components/collapsible.svelte @@ -1,3 +1,7 @@ -
    + + +
    diff --git a/src/lib/components/collapsibleItem.svelte b/src/lib/components/collapsibleItem.svelte index e25dc8c177..d95d53ef06 100644 --- a/src/lib/components/collapsibleItem.svelte +++ b/src/lib/components/collapsibleItem.svelte @@ -4,42 +4,54 @@ export let withIndentation = false; export let open = false; export let disabled = false; + export let noContent = false; + export let isInfo = false; + export let gap = 16; -
  • -
    - - - -
    - - {#if $$slots.subtitle} - - {/if} +
  • + {#if noContent} +
    +
    +
    - -
    -
    -
    -
  • -
    -
    -
    + {:else} +
    + + + +
    + + + {#if $$slots.subtitle} + + {/if} +
    + +
    +
    +
    +
    +
    + +
    +
    + {/if}
  • diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 12ef33acb1..bd6b2881b2 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -24,8 +24,6 @@ import { BillingPlan } from '$lib/constants'; import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte'; import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte'; import LimitReached from '$lib/components/billing/alerts/limitReached.svelte'; -import { wizard } from './wizard'; -import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte'; import { trackEvent } from '$lib/actions/analytics'; export type Tier = 'tier-0' | 'tier-1' | 'tier-2'; @@ -239,7 +237,7 @@ export async function checkForUsageLimit(org: Organization) { { name: 'Upgrade plan', method: () => { - wizard.start(ChangeOrganizationTierCloud); + goto(`${base}/console/organization-${org.$id}/change-plan`); trackEvent('click_organization_upgrade', { from: 'button', source: 'limit_reached_notification' @@ -364,3 +362,10 @@ export async function checkForMissingPaymentMethod() { }); } } + +export const upgradeURL = derived( + page, + ($page) => `${base}/console/organization-${$page.data?.organization?.$id}/change-plan` +); + +export const hideBillingHeaderRoutes = ['/console/create-organization', '/console/account']; diff --git a/src/lib/stores/marketplace.ts b/src/lib/stores/marketplace.ts index 19f590a16d..7fe6e514e1 100644 --- a/src/lib/stores/marketplace.ts +++ b/src/lib/stores/marketplace.ts @@ -1762,6 +1762,46 @@ export const marketplace: MarketplaceTemplate[] = [ } ] }, + { + icon: 'icon-chip', + id: 'chat-with-anyscale', + name: 'Chat with AnyScale', + tagline: 'Create a chatbot using the AnyScale API.', + permissions: ['any'], + cron: '', + events: [], + timeout: 15, + usecases: ['AI'], + runtimes: [ + ...getRuntimes( + TemplateRuntimes.NODE, + 'npm install', + 'src/main.js', + 'node/chat-with-anyscale' + ) + ], + instructions: `For documentation and instructions check out file.`, + vcsProvider: 'github', + providerRepositoryId: 'templates', + providerOwner: 'appwrite', + providerBranch: 'main', + variables: [ + { + name: 'ANYSCALE_API_KEY', + description: `A unique key used to authenticate with the AnyScale API. Learn more.`, + placeholder: 'd03xxxxxxxx26', + required: true, + type: 'password' + }, + { + name: 'ANYSCALE_MAX_TOKENS', + description: `The maximum number of tokens that Anyscale responses should contain. Learn more.`, + placeholder: '', + required: false, + type: 'number' + } + ] + }, { icon: 'icon-music-note', id: 'music-generation-with-huggingface', diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 8d08dffdb9..2cb2ddd3fd 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -1,6 +1,4 @@ -import type { Models } from '@appwrite.io/console'; import type { SvelteComponent } from 'svelte'; -import { writable } from 'svelte/store'; import Apple from '../../routes/console/project-[project]/auth/(providers)/appleOAuth.svelte'; import Auth0 from '../../routes/console/project-[project]/auth/(providers)/auth0OAuth.svelte'; import Authentik from '../../routes/console/project-[project]/auth/(providers)/authentikOAuth.svelte'; @@ -11,176 +9,240 @@ import Microsoft from '../../routes/console/project-[project]/auth/(providers)/m import Oidc from '../../routes/console/project-[project]/auth/(providers)/oidcOAuth.svelte'; import Okta from '../../routes/console/project-[project]/auth/(providers)/oktaOAuth.svelte'; -export type Provider = Models.AuthProvider & { +export type Provider = { + name: string; icon: string; docs?: string; - component?: typeof SvelteComponent; + component: typeof SvelteComponent; }; -export type Providers = { - providers: Provider[]; +export const oAuthProviders: Record = { + amazon: { + name: 'Amazon', + icon: 'amazon', + docs: 'https://developer.amazon.com/apps-and-games/services-and-apis', + component: Main + }, + apple: { + name: 'Apple', + icon: 'apple', + docs: 'https://developer.apple.com/', + component: Apple + }, + auth0: { + name: 'Auth0', + icon: 'auth0', + docs: 'https://auth0.com/developers', + component: Auth0 + }, + authentik: { + name: 'Authentik', + icon: 'authentik', + docs: 'https://goauthentik.io/integrations/sources/oauth/', + component: Authentik + }, + autodesk: { + name: 'Autodesk', + icon: 'autodesk', + docs: 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/', + component: Main + }, + bitbucket: { + name: 'Bitbucket', + icon: 'bitbucket', + docs: 'https://developer.atlassian.com/bitbucket', + component: Main + }, + bitly: { + name: 'Bitly', + icon: 'bitly', + docs: 'https://dev.bitly.com/v4_documentation.html', + component: Main + }, + box: { + name: 'Box', + icon: 'box', + docs: 'https://developer.box.com/reference/', + component: Main + }, + dailymotion: { + name: 'Dailymotion', + icon: 'dailymotion', + docs: 'https://developers.dailymotion.com/api/', + component: Main + }, + discord: { + name: 'Discord', + icon: 'discord', + docs: 'https://discordapp.com/developers/docs/topics/oauth2', + component: Main + }, + disqus: { + name: 'Disqus', + icon: 'disqus', + docs: 'https://disqus.com/api/docs/auth/', + component: Main + }, + dropbox: { + name: 'Dropbox', + icon: 'dropbox', + docs: 'https://www.dropbox.com/developers/documentation', + component: Main + }, + etsy: { + name: 'Etsy', + icon: 'etsy', + docs: 'https://developers.etsy.com/', + component: Main + }, + facebook: { + name: 'Facebook', + icon: 'facebook', + docs: 'https://developers.facebook.com/', + component: Main + }, + github: { + name: 'GitHub', + icon: 'github', + docs: 'https://developer.github.com', + component: Main + }, + gitlab: { + name: 'GitLab', + icon: 'gitlab', + docs: 'https://docs.gitlab.com/ee/api/', + component: GitLab + }, + google: { + name: 'Google', + icon: 'google', + docs: 'https://support.google.com/googleapi/answer/6158849', + component: Google + }, + linkedin: { + name: 'LinkedIn', + icon: 'linkedin', + docs: 'https://developer.linkedin.com/', + component: Main + }, + microsoft: { + name: 'Microsoft', + icon: 'microsoft', + docs: 'https://developer.microsoft.com/en-us/', + component: Microsoft + }, + notion: { + name: 'Notion', + icon: 'notion', + docs: 'https://developers.notion.com/docs', + component: Main + }, + oidc: { + name: 'OIDC', + icon: 'oidc', + docs: 'https://openid.net/connect/faq/', + component: Oidc + }, + okta: { + name: 'Okta', + icon: 'okta', + docs: 'https://developer.okta.com', + component: Okta + }, + paypal: { + name: 'Paypal', + icon: 'paypal', + docs: 'https://developer.paypal.com/docs/api/overview/', + component: Main + }, + paypalsandbox: { + name: 'Paypal Sandbox', + icon: 'paypal', + docs: 'https://developer.paypal.com/docs/api/overview/', + component: Main + }, + podio: { + name: 'Podio', + icon: 'podio', + docs: 'https://developers.podio.com/doc/oauth-authorization', + component: Main + }, + salesforce: { + name: 'Salesforce', + icon: 'salesforce', + docs: 'https://developer.salesforce.com/docs/', + component: Main + }, + slack: { + name: 'Slack', + icon: 'slack', + docs: 'https://api.slack.com/', + component: Main + }, + spotify: { + name: 'Spotify', + icon: 'spotify', + docs: 'https://developer.spotify.com/documentation/general/guides/authorization-guide/', + component: Main + }, + stripe: { + name: 'Stripe', + icon: 'stripe', + docs: 'https://stripe.com/docs/api', + component: Main + }, + tradeshift: { + name: 'Tradeshift', + icon: 'tradeshift', + docs: 'https://developers.tradeshift.com/docs/api', + component: Main + }, + tradeshiftBox: { + name: 'Tradeshift Sandbox', + icon: 'tradeshift', + docs: 'https://developers.tradeshift.com/docs/api', + component: Main + }, + twitch: { + name: 'Twitch', + icon: 'twitch', + docs: 'https://dev.twitch.tv/docs/auth', + component: Main + }, + wordpress: { + name: 'Wordpress', + icon: 'wordpress', + docs: 'https://developer.wordpress.com/docs/oauth2/', + component: Main + }, + yahoo: { + name: 'Yahoo', + icon: 'yahoo', + docs: 'https://developer.yahoo.com/oauth2/guide/flows_authcode/', + component: Main + }, + yammer: { + name: 'Yammer', + icon: 'yammer', + docs: 'https://developer.yammer.com/docs/oauth-2', + component: Main + }, + yandex: { + name: 'Yandex', + icon: 'yandex', + docs: 'https://tech.yandex.com/oauth/', + component: Main + }, + zoho: { + name: 'Zoho', + icon: 'zoho', + docs: 'https://www.zoho.com/crm/developer/docs/api/oauth-overview.html', + component: Main + }, + zoom: { + name: 'Zoom', + icon: 'zoom', + docs: 'https://marketplace.zoom.us/docs/guides/auth/oauth/', + component: Main + } }; - -const setProviders = (project: Models.Project): Provider[] => { - return ( - project?.oAuthProviders.map((n) => { - let docs: Provider['docs']; - let icon: Provider['icon'] = n.key.toLowerCase(); - let component: Provider['component'] = Main; - - switch (n.key.toLowerCase()) { - case 'amazon': - docs = 'https://developer.amazon.com/apps-and-games/services-and-apis'; - break; - case 'apple': - docs = 'https://developer.apple.com/'; - component = Apple; - break; - case 'auth0': - docs = 'https://auth0.com/developers'; - component = Auth0; - break; - case 'authentik': - docs = 'https://goauthentik.io/integrations/sources/oauth/'; - component = Authentik; - break; - case 'autodesk': - docs = 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/'; - break; - case 'bitbucket': - docs = 'https://developer.atlassian.com/bitbucket'; - break; - case 'bitly': - docs = 'https://dev.bitly.com/v4_documentation.html'; - break; - case 'box': - docs = 'https://developer.box.com/reference/'; - break; - case 'dailymotion': - docs = 'https://developers.dailymotion.com/api/'; - break; - case 'discord': - docs = 'https://discordapp.com/developers/docs/topics/oauth2'; - break; - case 'disqus': - docs = 'https://disqus.com/api/docs/auth/'; - break; - case 'dropbox': - docs = 'https://www.dropbox.com/developers/documentation'; - break; - case 'etsy': - docs = 'https://developers.etsy.com/'; - break; - case 'facebook': - docs = 'https://developers.facebook.com/'; - break; - case 'github': - docs = 'https://developer.github.com'; - break; - case 'gitlab': - docs = 'https://docs.gitlab.com/ee/api/'; - component = GitLab; - break; - case 'google': - docs = 'https://support.google.com/googleapi/answer/6158849'; - component = Google; - break; - case 'linkedin': - docs = 'https://developer.linkedin.com/'; - break; - case 'microsoft': - docs = 'https://developer.microsoft.com/en-us/'; - component = Microsoft; - break; - case 'notion': - docs = 'https://developers.notion.com/docs'; - break; - case 'oidc': - docs = 'https://openid.net/connect/faq/'; - component = Oidc; - break; - case 'okta': - docs = 'https://developer.okta.com'; - component = Okta; - break; - case 'paypal': - docs = 'https://developer.paypal.com/docs/api/overview/'; - break; - case 'paypalsandbox': - icon = 'paypal'; - docs = 'https://developer.paypal.com/docs/api/overview/'; - break; - case 'podio': - docs = 'https://developers.podio.com/doc/oauth-authorization'; - break; - case 'salesforce': - docs = 'https://developer.salesforce.com/docs/'; - break; - case 'slack': - docs = 'https://api.slack.com/'; - break; - case 'spotify': - docs = - 'https://developer.spotify.com/documentation/general/guides/authorization-guide/'; - break; - case 'stripe': - docs = 'https://stripe.com/docs/api'; - break; - case 'tradeshift': - docs = 'https://developers.tradeshift.com/docs/api'; - break; - case 'tradeshiftbox': - icon = 'tradeshift'; - docs = 'https://developers.tradeshift.com/docs/api'; - break; - case 'twitch': - docs = 'https://dev.twitch.tv/docs/auth'; - break; - case 'wordpress': - docs = 'https://developer.wordpress.com/docs/oauth2/'; - break; - case 'yahoo': - docs = 'https://developer.yahoo.com/oauth2/guide/flows_authcode/'; - break; - case 'yammer': - docs = 'https://developer.yammer.com/docs/oauth-2'; - break; - case 'yandex': - docs = 'https://tech.yandex.com/oauth/'; - break; - case 'zoho': - docs = 'https://www.zoho.com/accounts/protocol/oauth/sign-in-using-zoho.html'; - break; - case 'zoom': - docs = 'https://marketplace.zoom.us/docs/guides/auth/oauth/'; - break; - default: - break; - } - - return { - ...n, - icon, - docs, - component - }; - }) ?? [] - ); -}; - -function createOAuthProviders() { - const { subscribe, set } = writable({ - providers: setProviders(null) - }); - return { - subscribe, - set, - load: (project: Models.Project) => { - const providers = setProviders(project); - - set({ providers }); - } - }; -} - -export const OAuthProviders = createOAuthProviders(); diff --git a/src/lib/wizards/functions/cover.svelte b/src/lib/wizards/functions/cover.svelte index bcc19f123e..08a18f1195 100644 --- a/src/lib/wizards/functions/cover.svelte +++ b/src/lib/wizards/functions/cover.svelte @@ -7,10 +7,6 @@ variables[variable.name] = variable.value ?? ''; }); - if (!runtime) { - runtime = template.runtimes[0].name; - } - templateStore.set(template); templateConfig.set({ $id: null, diff --git a/src/lib/wizards/functions/steps/templateConfiguration.svelte b/src/lib/wizards/functions/steps/templateConfiguration.svelte index 7da879c825..536d32b949 100644 --- a/src/lib/wizards/functions/steps/templateConfiguration.svelte +++ b/src/lib/wizards/functions/steps/templateConfiguration.svelte @@ -57,7 +57,7 @@ id="runtime" placeholder="Select runtime" required - disabled={options.length <= 1} + disabled={options.length < 1} {options} bind:value={$templateConfig.runtime} /> {/await} diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index bbd662525f..85ad4e83d9 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -42,12 +42,13 @@ export const load: LayoutLoad = async ({ depends, url }) => { const path = url.search ? `${url.search}&${redirectUrl}` : `?${redirectUrl}`; if (error.type === 'user_more_factors_required') { - if (url.pathname === '/mfa') return; + if (url.pathname === '/mfa') return {}; // Ensure any previous account/organizations are cleared redirect(303, `/mfa${path}`); } if (!acceptedRoutes.some((n) => url.pathname.startsWith(n))) { redirect(303, `/login${path}`); } + return {}; // Ensure any previous account/organizations are cleared } }; diff --git a/src/routes/console/(migration-wizard)/resource-form.svelte b/src/routes/console/(migration-wizard)/resource-form.svelte index 41d39cee0a..66a2fadb6b 100644 --- a/src/routes/console/(migration-wizard)/resource-form.svelte +++ b/src/routes/console/(migration-wizard)/resource-form.svelte @@ -193,8 +193,8 @@ isStandalone buttons={[ { - name: 'Learn more', - method() { + slot: 'Learn more', + onClick() { wizard.updateStep((p) => p - 1); } } @@ -213,8 +213,8 @@ isStandalone buttons={[ { - name: 'Edit credentials', - method() { + slot: 'Edit credentials', + onClick() { wizard.updateStep((p) => p - 1); } } diff --git a/src/routes/console/+layout.ts b/src/routes/console/+layout.ts index 3c2496e846..c1de917107 100644 --- a/src/routes/console/+layout.ts +++ b/src/routes/console/+layout.ts @@ -5,7 +5,9 @@ import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import type { LayoutLoad } from './$types'; -export const load: LayoutLoad = async ({ fetch, depends }) => { +export const load: LayoutLoad = async ({ fetch, depends, parent }) => { + await parent(); // ensure user is authenticated before proceeding + depends(Dependencies.RUNTIMES); depends(Dependencies.CONSOLE_VARIABLES); diff --git a/src/routes/console/account/+layout.ts b/src/routes/console/account/+layout.ts index 2341ee3c08..1c993bedf2 100644 --- a/src/routes/console/account/+layout.ts +++ b/src/routes/console/account/+layout.ts @@ -6,9 +6,18 @@ import { Dependencies } from '$lib/constants'; export const load: LayoutLoad = async ({ depends }) => { depends(Dependencies.FACTORS); + depends(Dependencies.IDENTITIES); + + const promises = [ + sdk.forConsole.account.listMfaFactors(), + sdk.forConsole.account.listIdentities() + ]; + + const [factors, identities] = await Promise.all(promises); return { - factors: await sdk.forConsole.account.listMfaFactors(), + factors, + identities, header: Header, breadcrumbs: Breadcrumbs }; diff --git a/src/routes/console/account/+page.svelte b/src/routes/console/account/+page.svelte index 1006770d86..b3cbe4f572 100644 --- a/src/routes/console/account/+page.svelte +++ b/src/routes/console/account/+page.svelte @@ -5,12 +5,14 @@ import UpdateEmail from './updateEmail.svelte'; import DeleteAccount from './deleteAccount.svelte'; import UpdateMfa from './updateMfa.svelte'; + import Identities from './identities.svelte'; + diff --git a/src/routes/console/account/identities.svelte b/src/routes/console/account/identities.svelte new file mode 100644 index 0000000000..44ad5b772e --- /dev/null +++ b/src/routes/console/account/identities.svelte @@ -0,0 +1,101 @@ + + + + Identities +

    + Identities are your connected GitHub accounts. You can sign in using these accounts. +

    + + + {#if $identities.length === 0} + +
    + No identities are currently available. Once you sign in via GitHub, you'll see + it here. +
    +
    + {:else} + + + Provider + Email + Created At + Expiry Date + + + + {#each $identities as identity (identity.$id)} + {@const provider = oAuthProviders[identity.provider]} + + +
    +
    + +
    + {provider.name} +
    +
    + + {identity.providerEmail} + + + {toLocaleDateTime(identity.$createdAt)} + + + {toLocaleDateTime(identity.providerAccessTokenExpiry)} + + + + +
    + {/each} +
    +
    + {/if} +
    +
    diff --git a/src/routes/console/account/mfaRecoveryCodes.svelte b/src/routes/console/account/mfaRecoveryCodes.svelte index a3a22c22ee..63ec5d0fa4 100644 --- a/src/routes/console/account/mfaRecoveryCodes.svelte +++ b/src/routes/console/account/mfaRecoveryCodes.svelte @@ -27,7 +27,7 @@
    • diff --git a/src/routes/console/account/store.ts b/src/routes/console/account/store.ts index e5638c5755..362054d6f8 100644 --- a/src/routes/console/account/store.ts +++ b/src/routes/console/account/store.ts @@ -6,3 +6,7 @@ export const factors = derived( page, ($page) => $page.data.factors as Models.MfaFactors & { recoveryCode: boolean } ); +export const identities = derived( + page, + ($page) => $page.data.identities.identities as Models.Identity[] +); diff --git a/src/routes/console/account/updateMfa.svelte b/src/routes/console/account/updateMfa.svelte index e15dc392ca..a257865f65 100644 --- a/src/routes/console/account/updateMfa.svelte +++ b/src/routes/console/account/updateMfa.svelte @@ -20,10 +20,19 @@ let showRegenerateRecoveryCodes = false; let codes: Models.MfaRecoveryCodes = null; + $: enabledFactors = Object.entries($factors) + .filter(([_, enabled]) => enabled) + .map(([factor, _]) => factor); + $: enabledMethods = enabledFactors.filter((factor) => factor !== 'recoveryCode'); + async function updateMfa() { try { await sdk.forConsole.account.updateMFA(!$user.mfa); await invalidate(Dependencies.ACCOUNT); + addNotification({ + message: `Multi-factor authentication has been ${$user.mfa ? 'enabled' : 'disabled'}`, + type: 'success' + }); trackEvent(Submit.AccountUpdateMfa, { mfa: !$user.mfa }); } catch (error) { addNotification({ @@ -63,6 +72,10 @@ trackError(error, Submit.AccountRecoveryCodesUpdate); } } + + $: if (!showRecoveryCodes) { + codes = null; + } @@ -87,99 +100,119 @@ href="https://appwrite.io/docs/products/auth/mfa">Learn more.

-
-
- Methods -
-
-
-
-
-
- Authenticator app - Use an authentication app to generate two-factor authentication - codes. -
+ {#if $user.mfa} +
+
+ Methods
-
- {#if $factors.totp} - - {:else} - - {/if} -
-
- - {#if $factors.email}
-
- Email verification - One-time codes will be sent to: {$user.email} + Authenticator app + Use an authentication app to generate two-factor authentication + codes.
+
+ {#if $factors.totp} + + + {:else} + + {/if} +
- {/if} - {#if $factors.phone} -
-
-
-
-
- SMS verification - One-time codes will be sent to: {$user.phone} + {#if $factors.email} +
+
+
+
+
+ Email verification + One-time codes will be sent to: {$user.email} +
-
- {/if} -
+ {/if} -
-
- Recovery + {#if $factors.phone} +
+
+
+
+
+ SMS verification + One-time codes will be sent to: {$user.phone} +
+
+
+ {/if}
-
-
-
-
+ {/if} + {/if}
@@ -190,7 +223,10 @@ - +