diff --git a/eslint.config.js b/eslint.config.js index 05ce3e85f..32ef292d0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -32,7 +32,9 @@ export default ts.config( 'svelte/no-at-html-tags': 'off', 'svelte/no-useless-mustaches': 'off', 'svelte/no-reactive-reassign': 'off', - 'svelte/no-reactive-literals': 'off' + 'svelte/no-reactive-literals': 'off', + // TODO: @itznotabug, this requires a big refactor! + 'svelte/no-navigation-without-resolve': 'off' } }, { diff --git a/package.json b/package.json index cd4e9b060..69a51845e 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,11 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134", + "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@7747562", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f2198f1", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f4da718", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f2198f1", "@faker-js/faker": "^9.9.0", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", @@ -54,7 +54,7 @@ "@melt-ui/svelte": "^0.86.5", "@playwright/test": "^1.51.1", "@sveltejs/adapter-static": "^3.0.8", - "@sveltejs/kit": "^2.36.2", + "@sveltejs/kit": "^2.42.1", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 747776222..826d1cce5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,20 +12,20 @@ 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@6031134 - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134 + specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@7747562 + version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@7747562 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 '@appwrite.io/pink-icons-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f2198f1 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f2198f1(svelte@5.25.3) '@appwrite.io/pink-legacy': specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f4da718 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f4da718(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f2198f1 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f2198f1(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -34,7 +34,7 @@ importers: version: 2.11.8 '@sentry/sveltekit': specifier: ^8.38.0 - version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) + version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) '@stripe/stripe-js': specifier: ^3.5.0 version: 3.5.0 @@ -101,10 +101,10 @@ importers: version: 1.51.1 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.8(@sveltejs/kit@2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))) + version: 3.0.8(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))) '@sveltejs/kit': - specifier: ^2.36.2 - version: 2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) + specifier: ^2.42.1 + version: 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) '@sveltejs/vite-plugin-svelte': specifier: ^5.0.3 version: 5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) @@ -260,8 +260,8 @@ 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@6031134': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@7747562': + resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@7747562} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -269,8 +269,8 @@ packages: peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f2198f1': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f2198f1} version: 2.0.0-RC.1 peerDependencies: svelte: ^4.0.0 @@ -284,8 +284,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f4da718': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f4da718} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f2198f1': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f2198f1} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -352,8 +352,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true @@ -373,8 +373,8 @@ packages: resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} '@csstools/color-helpers@5.0.2': @@ -1264,8 +1264,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.36.3': - resolution: {integrity: sha512-MVzwZz1GFznEQbL3f0i2v9AIc3lZH01izQj6XfIrthmZAwOzyXJCgXbLRss8vk//HfYsE4w6Tz+ukbf3rScPNQ==} + '@sveltejs/kit@2.42.1': + resolution: {integrity: sha512-SoWdb/OxEKHMlXZ78eNZa4pR1YdOGnUfcFj5NylJv+ZaTrnHK5xapw+7RJP2MiS1D4T+sEH25/tmMtmEl8p6VA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2607,6 +2607,9 @@ packages: magic-string@0.30.18: resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.7: resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} engines: {node: '>=12'} @@ -3050,8 +3053,8 @@ packages: engines: {node: '>=10'} hasBin: true - seroval-plugins@1.3.2: - resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} + seroval-plugins@1.3.3: + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 @@ -3700,13 +3703,13 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134': {} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@7747562': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f2198f1(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3719,7 +3722,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f4da718(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@f2198f1(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 @@ -3822,9 +3825,9 @@ snapshots: dependencies: '@babel/types': 7.27.0 - '@babel/parser@7.28.3': + '@babel/parser@7.28.4': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@babel/runtime@7.27.0': dependencies: @@ -3853,7 +3856,7 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.28.2': + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -4671,14 +4674,14 @@ snapshots: magic-string: 0.30.7 svelte: 5.25.3 - '@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))': + '@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))': dependencies: '@sentry/core': 8.55.0 '@sentry/node': 8.55.0 '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0) '@sentry/svelte': 8.55.0(svelte@5.25.3) '@sentry/vite-plugin': 2.22.6 - '@sveltejs/kit': 2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) + '@sveltejs/kit': 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) magic-string: 0.30.7 magicast: 0.2.8 sorcery: 1.0.0 @@ -4750,11 +4753,11 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))': + '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))': dependencies: - '@sveltejs/kit': 2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) + '@sveltejs/kit': 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)) - '@sveltejs/kit@2.36.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))': + '@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) @@ -5121,7 +5124,7 @@ snapshots: '@vue/compiler-core@3.5.13': dependencies: - '@babel/parser': 7.28.3 + '@babel/parser': 7.28.4 '@vue/shared': 3.5.13 entities: 4.5.0 estree-walker: 2.0.2 @@ -5134,13 +5137,13 @@ snapshots: '@vue/compiler-sfc@3.5.13': dependencies: - '@babel/parser': 7.28.3 + '@babel/parser': 7.28.4 '@vue/compiler-core': 3.5.13 '@vue/compiler-dom': 3.5.13 '@vue/compiler-ssr': 3.5.13 '@vue/shared': 3.5.13 estree-walker: 2.0.2 - magic-string: 0.30.18 + magic-string: 0.30.19 postcss: 8.5.6 source-map-js: 1.2.1 @@ -6282,6 +6285,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.7: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -6703,7 +6710,7 @@ snapshots: semver@7.7.1: {} - seroval-plugins@1.3.2(seroval@1.3.2): + seroval-plugins@1.3.3(seroval@1.3.2): dependencies: seroval: 1.3.2 @@ -6786,7 +6793,7 @@ snapshots: dependencies: csstype: 3.1.3 seroval: 1.3.2 - seroval-plugins: 1.3.2(seroval@1.3.2) + seroval-plugins: 1.3.3(seroval@1.3.2) solid-swr-store@0.10.7(solid-js@1.9.5)(swr-store@0.10.6): dependencies: diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 957b10e6b..1f4722965 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -6,7 +6,7 @@ import { user } from '$lib/stores/user'; import { ENV, MODE, VARS, isCloud } from '$lib/system'; import { AppwriteException } from '@appwrite.io/console'; import { browser } from '$app/environment'; -import { getReferrerAndUtmSource } from '$lib/helpers/utm'; +import { getReferrerAndUtmSource, getTrackedQueryParams } from '$lib/helpers/utm'; function plausible(domain: string): AnalyticsPlugin { if (!browser) return { name: 'analytics-plugin-plausible' }; @@ -65,7 +65,7 @@ export function trackEvent(name: string, data: object = null): void { }; } - data = { ...data, ...getReferrerAndUtmSource() }; + data = { ...data, ...getReferrerAndUtmSource(), ...getTrackedQueryParams() }; if (ENV.DEV || ENV.PREVIEW) { console.debug(`[Analytics] Event ${name} ${path}`, data); @@ -429,5 +429,6 @@ export enum Submit { RecordUpdate = 'submit_dns_record_update', RecordDelete = 'submit_dns_record_delete', SearchClear = 'submit_clear_search', - FrameworkDetect = 'submit_framework_detect' + FrameworkDetect = 'submit_framework_detect', + TestimonialView = 'testimonial_view' } diff --git a/src/lib/actions/timer.ts b/src/lib/actions/timer.ts index 1f6bff7e1..07d589a8b 100644 --- a/src/lib/actions/timer.ts +++ b/src/lib/actions/timer.ts @@ -21,13 +21,16 @@ export const timer: Action = (node, props) => { } function step() { - const diffInSeconds = Math.floor((new Date().getTime() - startDate.getTime()) / 1000); - const minutes = Math.floor(diffInSeconds / 60); + const elapsedSeconds = Math.max( + 0, + Math.floor((new Date().getTime() - startDate.getTime()) / 1000) + ); + const minutes = Math.floor(elapsedSeconds / 60); if (minutes > 0) { - const seconds = diffInSeconds % 60; + const seconds = elapsedSeconds % 60; node.textContent = `${minutes}m ${seconds}s`; } else { - node.textContent = calculateTime(diffInSeconds); + node.textContent = calculateTime(elapsedSeconds); } frame = window.requestAnimationFrame(step); } diff --git a/src/lib/components/billing/alerts/newDevUpgradePro.svelte b/src/lib/components/billing/alerts/newDevUpgradePro.svelte index 4631c1721..733736f02 100644 --- a/src/lib/components/billing/alerts/newDevUpgradePro.svelte +++ b/src/lib/components/billing/alerts/newDevUpgradePro.svelte @@ -23,7 +23,7 @@ } -{#if show && $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && !page.url.pathname.includes('/console/account')} +{#if show && $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && !page.url.pathname.includes(base + '/account')} plan.$id === $currentPlan?.$id); + + // experiment to remove scale plan temporarily + $: plansWithoutScale = plans.filter((plan) => plan.$id != BillingPlan.SCALE); - {#each plans as plan} + {#each plansWithoutScale as plan} = null; + function closeAllMenus() { + if (orgMenuStates?.open) { + orgMenuStates.open.set(false); + } + + if (projectsMenuStates?.open) { + projectsMenuStates.open.set(false); + } + + projectsBottomSheetOpen = false; + organisationBottomSheetOpen = false; + } + + beforeNavigate(closeAllMenus); + + onDestroy(closeAllMenus); + function createOrg() { trackEvent(Click.OrganizationClickCreate, { source: 'breadcrumbs' }); if (isCloud) { @@ -105,6 +128,8 @@ async function createProjectsBottomSheet( organization: Organization ): Promise { + const isOnProjects = page.route.id.includes('project-[region]-[project]'); + if (!isOnProjects) return null; isLoadingProjects = true; @@ -212,12 +237,10 @@ ? $currentPlan.name : selectedOrg?.tierName; // fallback - $: derivedKey = `${selectedOrg?.$id}-${currentProject?.$id}`; + $: derivedKey = `${selectedOrg?.$id}-${currentProject?.$id}-${ID.unique()}`; $: organizationId = currentProject?.teamId; - $: isOnProjects = page.route.id.includes('project-[region]-[project]'); - $: shouldReloadProjects = isLoadingProjects ? false : currentProject && loadedProjects.projects.length diff --git a/src/lib/components/filters/content.svelte b/src/lib/components/filters/content.svelte index 85f2960da..a42161f43 100644 --- a/src/lib/components/filters/content.svelte +++ b/src/lib/components/filters/content.svelte @@ -6,10 +6,11 @@ InputText, InputTags, InputSelectCheckbox, - InputDateTime + InputDateTime, + InputPoint } from '$lib/elements/forms'; import { onMount, createEventDispatcher } from 'svelte'; - import { operators, addFilter, queries, tags } from './store'; + import { operators, addFilter, queries, tags, ValidOperators } from './store'; import type { Column } from '$lib/helpers/types'; import type { Writable } from 'svelte/store'; import { TagList } from '.'; @@ -18,6 +19,7 @@ let { value = $bindable(null), + distanceValue = $bindable(null), columns, columnId = $bindable(null), arrayValues = $bindable([]), @@ -27,6 +29,7 @@ // We cast to any to not cause type errors in the input components /* eslint @typescript-eslint/no-explicit-any: 'off' */ value?: any; + distanceValue?: number | null; columns: Writable; columnId?: string | null; arrayValues?: string[]; @@ -61,21 +64,57 @@ })); }); + // Check if the current operator is a distance-based operator + let isDistanceOperator = $derived( + operatorKey && + [ + ValidOperators.DistanceEqual, + ValidOperators.DistanceNotEqual, + ValidOperators.DistanceGreaterThan, + ValidOperators.DistanceLessThan + ].includes(operatorKey as ValidOperators) + ); + onMount(() => { value = column?.array ? [] : null; if (column?.type === 'datetime') { const now = new Date(); value = now.toISOString().slice(0, 16); } + // Initialize spatial data with default values + if (column?.type === 'point') { + value = [0, 0]; + } else if (column?.type === 'linestring') { + value = [ + [0, 0], + [1, 1] + ]; + } else if (column?.type === 'polygon') { + value = [ + [ + [0, 0], + [1, 1], + [2, 2], + [0, 0] + ] + ]; + } }); const dispatch = createEventDispatcher<{ clear: void; apply: { applied: number } }>(); function addFilterAndReset() { - addFilter(columnsArray, columnId, operatorKey, value, arrayValues); + // For distance operators, pass the distance as a separate parameter + if (isDistanceOperator && distanceValue !== null && value !== null) { + addFilter(columnsArray, columnId, operatorKey, value, arrayValues, distanceValue); + } else { + addFilter(columnsArray, columnId, operatorKey, value, arrayValues); + } + columnId = null; operatorKey = null; value = null; + distanceValue = null; arrayValues = []; dispatch('apply', { applied: appliedTags.length }); if (singleCondition) { @@ -143,11 +182,29 @@ {#key value} {/key} + {:else if column.type === 'point' || column.type === 'linestring' || column.type === 'polygon'} + { + if (!value) value = [0, 0]; + value[index] = newValue; + }} /> {:else} {/if} {/if} + + {#if isDistanceOperator} +
+ +
+ {/if} {/if} {#if !singleCondition} + {@render addLineButton?.()} +
+ {/if} +
diff --git a/src/lib/elements/forms/inputPoint.svelte b/src/lib/elements/forms/inputPoint.svelte new file mode 100644 index 000000000..4c0b4ce40 --- /dev/null +++ b/src/lib/elements/forms/inputPoint.svelte @@ -0,0 +1,54 @@ + + + + + {#if nullable} + {#each nullableSkeletonShape as _, index} + + {/each} + {:else} + {#each values as _, index} + onChangePoint(index, Number.parseFloat(`${e.detail}`))} /> + {/each} + {/if} + {#if deletePoints} + + {/if} + + diff --git a/src/lib/elements/forms/inputPolygon.svelte b/src/lib/elements/forms/inputPolygon.svelte new file mode 100644 index 000000000..c2bcbb4db --- /dev/null +++ b/src/lib/elements/forms/inputPolygon.svelte @@ -0,0 +1,59 @@ + + + + {#each values as value, index} + + onAddPoint(index)} + {nullable} + onDeletePoint={() => onDeletePoint(index)} + onChangePoint={(pointIndex, coordIndex, newValue) => + onChangePoint(index, pointIndex, coordIndex, newValue)} + allowLineDelete={index < 2} + {minDeletableIndex}> + {#snippet addLineButton()} + {#if index === values.length - 1} + + {/if} + {/snippet} + + + {/each} + diff --git a/src/lib/helpers/faker.ts b/src/lib/helpers/faker.ts index 16355677a..56987f879 100644 --- a/src/lib/helpers/faker.ts +++ b/src/lib/helpers/faker.ts @@ -3,6 +3,7 @@ import type { Columns } from '$routes/(console)/project-[region]-[project]/datab import { ID, type Models } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { isWithinSafeRange } from '$lib/helpers/numbers'; +import type { NestedNumberArray } from './types'; export async function generateColumns( project: Models.Project, @@ -42,7 +43,24 @@ export async function generateColumns( size: 1000, required: false }), - client.tablesDB.createBooleanColumn({ databaseId, tableId, key: 'active', required: false }) + client.tablesDB.createBooleanColumn({ + databaseId, + tableId, + key: 'active', + required: false + }), + client.tablesDB.createPointColumn({ + databaseId, + tableId, + key: 'location', + required: false + }), + client.tablesDB.createLineColumn({ + databaseId, + tableId, + key: 'route', + required: false + }) ]); } @@ -66,7 +84,10 @@ export function generateFakeRecords( const id = ID.unique(); ids.push(id); - let row: Record> = { + let row: Record< + string, + string | number | boolean | Array + > = { $id: id }; @@ -78,7 +99,12 @@ export function generateFakeRecords( age: faker.number.int({ min: 18, max: 80 }), city: faker.location.city(), description: faker.lorem.sentence(), - active: faker.datatype.boolean() + active: faker.datatype.boolean(), + location: [faker.location.longitude(), faker.location.latitude()], + route: Array.from({ length: 5 }, () => [ + faker.location.longitude(), + faker.location.latitude() + ]) }; } else { for (const column of filteredColumns) { @@ -116,10 +142,10 @@ function generateStringValue(key: string, maxLength: number): string { function generateValueForColumn( column: Columns -): string | number | boolean | null | Array { +): string | number | boolean | null | Array { if (column.array) { const arraySize = faker.number.int({ min: 1, max: 5 }); - const items: Array = []; + const items: Array = []; for (let i = 0; i < arraySize; i++) { const itemAttribute = { ...column, array: false }; @@ -135,7 +161,9 @@ function generateValueForColumn( return generateSingleValue(column); } -function generateSingleValue(column: Columns): string | number | boolean | null { +function generateSingleValue( + column: Columns +): string | number | boolean | NestedNumberArray | null { switch (column.type) { case 'string': { if ('format' in column && column.format) { @@ -192,6 +220,25 @@ function generateSingleValue(column: Columns): string | number | boolean | null return faker.date.recent({ days: 365 }).toISOString(); } + case 'point': { + return [faker.location.longitude(), faker.location.latitude()]; + } + + case 'linestring': { + return Array.from({ length: 5 }, () => [ + faker.location.longitude(), + faker.location.latitude() + ]); + } + + case 'polygon': { + const points = Array.from({ length: 5 }, () => [ + faker.location.longitude(), + faker.location.latitude() + ]); + points.push(points[0]); // close polygon + return [points]; + } default: { return faker.lorem.word(); } diff --git a/src/lib/helpers/types.ts b/src/lib/helpers/types.ts index abb4c133a..df6fba0d9 100644 --- a/src/lib/helpers/types.ts +++ b/src/lib/helpers/types.ts @@ -35,7 +35,10 @@ const columnTypes = [ 'boolean', 'datetime', 'relationship', - 'enum' + 'enum', + 'point', + 'linestring', + 'polygon' ] as const; export type ColumnType = (typeof columnTypes)[number]; export type Column = PinkColumn & { @@ -77,3 +80,5 @@ export type TableRootProp = { addAvailableId: (id: string) => void; removeAvailableId: (id: string) => void; }; + +export type NestedNumberArray = Array; diff --git a/src/lib/helpers/utm.ts b/src/lib/helpers/utm.ts index df2cb2158..d37abd88c 100644 --- a/src/lib/helpers/utm.ts +++ b/src/lib/helpers/utm.ts @@ -1,3 +1,17 @@ +const LINKEDIN_TRACKING_ID = 'li_fat_id'; +const GOOGLE_TRACKING_ID = 'gclid'; +const TWITTER_TRACKING_ID = 'twclid'; +const REDDIT_TRACKING_ID = 'rdt_cid'; + +const TRACKED_QUERY_PARAMS = [ + LINKEDIN_TRACKING_ID, + GOOGLE_TRACKING_ID, + TWITTER_TRACKING_ID, + REDDIT_TRACKING_ID +] as const; + +type TrackedParams = Record<(typeof TRACKED_QUERY_PARAMS)[number], string>; + export function getReferrerAndUtmSource() { if (sessionStorage) { let values = {}; @@ -15,3 +29,26 @@ export function getReferrerAndUtmSource() { } return {}; } + +export function getTrackedQueryParams(): Partial { + if (typeof window === 'undefined' || !window.location) { + return {}; + } + + try { + const urlParams = new URLSearchParams(window.location.search); + const params: Partial = {}; + + for (const paramName of TRACKED_QUERY_PARAMS) { + const value = urlParams.get(paramName); + if (value) { + params[paramName] = value; + } + } + + return params; + } catch (error) { + console.warn('Failed to parse URL search parameters:', error); + return {}; + } +} diff --git a/src/lib/images/promos/spatial-columns-api-dark.png b/src/lib/images/promos/spatial-columns-api-dark.png new file mode 100644 index 000000000..232763d19 Binary files /dev/null and b/src/lib/images/promos/spatial-columns-api-dark.png differ diff --git a/src/lib/images/promos/spatial-columns-api-light.png b/src/lib/images/promos/spatial-columns-api-light.png new file mode 100644 index 000000000..f62f20d4d Binary files /dev/null and b/src/lib/images/promos/spatial-columns-api-light.png differ diff --git a/src/lib/images/promos/tables-api-dark.png b/src/lib/images/promos/tables-api-dark.png deleted file mode 100644 index 7ac69f3fe..000000000 Binary files a/src/lib/images/promos/tables-api-dark.png and /dev/null differ diff --git a/src/lib/images/promos/tables-api-light.png b/src/lib/images/promos/tables-api-light.png deleted file mode 100644 index 0ab288301..000000000 Binary files a/src/lib/images/promos/tables-api-light.png and /dev/null differ diff --git a/src/lib/images/testimonials/hassan.png b/src/lib/images/testimonials/hassan.png new file mode 100644 index 000000000..5c0e3e45b Binary files /dev/null and b/src/lib/images/testimonials/hassan.png differ diff --git a/src/lib/images/testimonials/phil.jpg b/src/lib/images/testimonials/phil.jpg new file mode 100644 index 000000000..b358163e0 Binary files /dev/null and b/src/lib/images/testimonials/phil.jpg differ diff --git a/src/lib/images/testimonials/ryan-oconner-testimonial.png b/src/lib/images/testimonials/ryan-oconner-testimonial.png new file mode 100644 index 000000000..417592184 Binary files /dev/null and b/src/lib/images/testimonials/ryan-oconner-testimonial.png differ diff --git a/src/lib/images/testimonials/xue.webp b/src/lib/images/testimonials/xue.webp new file mode 100644 index 000000000..4f7fe1ce5 Binary files /dev/null and b/src/lib/images/testimonials/xue.webp differ diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index bfc06264f..5f76c05f2 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -205,7 +205,7 @@ {/if} - {#if !$isNewWizardStatusOpen} + {#if !$isNewWizardStatusOpen && isProjectPage} {#if currentReview?.image} {:else} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 3564a1c27..d23fa3346 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -620,7 +620,7 @@ export const billingURL = derived( ($page) => `${base}/organization-${$page.data?.organization?.$id}/billing` ); -export const hideBillingHeaderRoutes = ['/console/create-organization', '/console/account']; +export const hideBillingHeaderRoutes = [base + '/create-organization', base + '/account']; export function calculateExcess(addon: AggregationTeam, plan: Plan) { return { diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 83643eeb9..69e86cf77 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -83,7 +83,7 @@ keys: ['g', 'p'], group: 'navigation', disabled: - (page.url.pathname.includes('/console/organization-') && + (page.url.pathname.includes(base + '/organization-') && !page.url.pathname.endsWith('/members') && !page.url.pathname.endsWith('/settings')) || !$canSeeProjects, @@ -336,14 +336,14 @@ diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index b43ed863f..61c770347 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -174,7 +174,7 @@ '', clientSecret, paymentMethodId, - `/console/apply-credit?${params}` + `${base}/apply-credit?${params}` ); org = await sdk.forConsole.billing.validateOrganization(org.teamId, collaborators); } diff --git a/src/routes/(console)/bottomAlerts.ts b/src/routes/(console)/bottomAlerts.ts index 19aa57b54..fbbee52af 100644 --- a/src/routes/(console)/bottomAlerts.ts +++ b/src/routes/(console)/bottomAlerts.ts @@ -1,8 +1,8 @@ import { isCloud } from '$lib/system'; import { isSameDay } from '$lib/helpers/date'; import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts'; -import TablesApiLight from '$lib/images/promos/tables-api-light.png'; -import TablesApiDark from '$lib/images/promos/tables-api-dark.png'; +import SpatialColumnsLight from '$lib/images/promos/spatial-columns-api-light.png'; +import SpatialColumnsDark from '$lib/images/promos/spatial-columns-api-dark.png'; import InversionQueriesDark from '$lib/images/promos/inversion-queries-dark.png'; import InversionQueriesLight from '$lib/images/promos/inversion-queries-light.png'; import TimeHelperQueriesDark from '$lib/images/promos/time-helper-queries-dark.png'; @@ -11,21 +11,20 @@ import TimeHelperQueriesLight from '$lib/images/promos/time-helper-queries-light const listOfPromotions: BottomModalAlertItem[] = []; if (isCloud) { - const tablesApiPromo: BottomModalAlertItem = { - id: 'modal:tables_api_announcement', + const spatialColumnsPromo: BottomModalAlertItem = { + id: 'modal:spatial_columns_announcement', src: { - dark: TablesApiDark, - light: TablesApiLight + dark: SpatialColumnsDark, + light: SpatialColumnsLight }, - title: 'Announcing the new TablesDB UI and more!', - message: - 'This is Appwrite Databases’ most significant update so far, introducing new terminology, a new UI, and a supporting TablesDB API.', + title: 'Announcing API for spatial columns', + message: 'Store and query geo data directly in your database.', plan: 'free', importance: 8, scope: 'project', cta: { text: 'Read announcement', - link: () => 'https://apwr.dev/E64A90f', + link: () => 'https://appwrite.io/blog/post/announcing-spatial-columns', external: true, hideOnClick: true }, @@ -71,7 +70,7 @@ if (isCloud) { }, show: true }; - listOfPromotions.push(inversionQueriesPromo, timeHelperQueriesPromo, tablesApiPromo); + listOfPromotions.push(spatialColumnsPromo, inversionQueriesPromo, timeHelperQueriesPromo); } export function addBottomModalAlerts() { diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index 37de669fd..de2afad0f 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -143,7 +143,7 @@ '', clientSecret, paymentMethodId, - `/console/create-organization?${params}` + `${base}/create-organization?${params}` ); await validate(org.teamId, collaborators); } diff --git a/src/routes/(console)/onboarding/create-project/+page.svelte b/src/routes/(console)/onboarding/create-project/+page.svelte index 326a3b74a..9c85c6326 100644 --- a/src/routes/(console)/onboarding/create-project/+page.svelte +++ b/src/routes/(console)/onboarding/create-project/+page.svelte @@ -82,13 +82,13 @@ {:else} Appwrite Logo { const { scopes } = await parent(); if (!scopes.includes('projects.read') && scopes.includes('billing.read')) { - return redirect(301, `/console/organization-${params.organization}/billing`); + return redirect(301, `${base}/organization-${params.organization}/billing`); } depends(Dependencies.ORGANIZATION); diff --git a/src/routes/(console)/organization-[organization]/billing/+page.ts b/src/routes/(console)/organization-[organization]/billing/+page.ts index e3d06fab1..4ebc585b0 100644 --- a/src/routes/(console)/organization-[organization]/billing/+page.ts +++ b/src/routes/(console)/organization-[organization]/billing/+page.ts @@ -5,12 +5,13 @@ import { sdk } from '$lib/stores/sdk'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; import { isCloud } from '$lib/system'; +import { base } from '$app/paths'; export const load: PageLoad = async ({ parent, depends }) => { const { organization, scopes, currentPlan, countryList, locale } = await parent(); if (!scopes.includes('billing.read')) { - return redirect(301, `/console/organization-${organization.$id}`); + return redirect(301, `${base}/organization-${organization.$id}`); } depends(Dependencies.PAYMENT_METHODS); diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 2dac1238a..6a4a43f01 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -146,7 +146,7 @@ function getBillingData(currentPlan, currentAggregation, isSmallViewport) { const projectsList = getProjectsList(currentAggregation); - const base = { + const basePlan = { id: 'base-plan', expandable: false, cells: { @@ -303,7 +303,7 @@ { id: `usage-details`, cells: { - item: `Usage details`, + item: `Usage details`, usage: '', price: '' } @@ -311,7 +311,7 @@ ] })); const noProjects = []; - return [base, ...addons, ...projects, ...noProjects]; + return [basePlan, ...addons, ...projects, ...noProjects]; } $: billingData = getBillingData(currentPlan, currentAggregation, $isSmallViewport); diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index 364014828..153e85c5a 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -277,7 +277,7 @@ '', clientSecret, paymentMethodId, - '/console/change-plan?' + params.toString() + base + '/change-plan?' + params.toString() ); await validate(org.teamId, collaborators); } diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte index 528512596..542ea62ad 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/changeOrganization.svelte @@ -1,5 +1,6 @@ + + + + { + if (e.detail) defaultChecked = false; + }} + description="Indicate whether this column is required" /> + + { + if (e.detail) { + $required = false; + handleDefaultState(false); + } else { + data.default = null; + } + }} + description="Enable to set a predefined value for this column" /> + + + {#if defaultChecked} + + Default + Optional + + {/if} + pushCoordinate()} + onDeletePoint={deleteCoordinate} + onChangePoint={(pointIndex: number, coordIndex: number, newValue: number) => { + if (data.default) { + data.default[pointIndex][coordIndex] = newValue; + data.default = [...data.default]; + } + }} /> + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/point.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/point.svelte new file mode 100644 index 000000000..bd3d76e9f --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/point.svelte @@ -0,0 +1,136 @@ + + + + + { + if (e.detail) defaultChecked = false; + }} + description="Indicate whether this column is required" /> + + { + if (e.detail) { + $required = false; + handleDefaultState(false); + } else { + data.default = null; + } + }} + description="Enable to set a predefined value for this column" /> + + + {#if defaultChecked} + + Default + Optional + + {/if} + + { + if (data.default) { + data.default[index] = newValue; + data.default = [...data.default]; + } + }} /> + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/polygon.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/polygon.svelte new file mode 100644 index 000000000..782aa6ad0 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/polygon.svelte @@ -0,0 +1,161 @@ + + + + + { + if (e.detail) defaultChecked = false; + else data.default = null; + }} + description="Indicate whether this column is required" /> + + { + if (e.detail) { + $required = false; + handleDefaultState(false); + } else { + data.default = null; + } + }} + description="Enable to set a predefined value for this column" /> + + + {#if defaultChecked} + + Default + Optional + + {/if} + + { + if (data.default && data.default[lineIndex] && data.default[lineIndex][pointIndex]) { + data.default[lineIndex][pointIndex][coordIndex] = newValue; + data.default = [...data.default]; + } + }} /> + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte index 195a640d1..f9e8c2248 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte @@ -138,7 +138,7 @@ $: search = data.relatedTable || undefined; - $: if (search) { + $: if (search && !editing) { const exists = tableList?.tables?.some((c) => c.$id.toLocaleLowerCase().includes(search.toLocaleLowerCase()) ); @@ -191,6 +191,7 @@ bind:value={data.key} helper="Allowed characters: a-z, A-Z, 0-9, -, ." required /> + {#if way === 'two'} (IndexType.Key); - let columnOptions = $table.columns - .filter((column) => !isRelationship(column)) - .map((column) => ({ - value: column.key, - label: column.key - })); + let columnOptions = $derived( + $table.columns + .filter((column) => { + if (selectedType === IndexType.Spatial) { + return isSpatialType(column); // keep only spatial + } + return !isRelationship(column) && !isSpatialType(column); // keep non-relationship and non-spatial + }) + .map((column) => ({ value: column.key, label: column.key })) + ); let columnList = $state([{ value: '', order: '', length: null }]); + const types = [ + { value: IndexType.Key, label: 'Key' }, + { value: IndexType.Unique, label: 'Unique' }, + { value: IndexType.Fulltext, label: 'Fulltext' }, + { value: IndexType.Spatial, label: 'Spatial' } + ]; + + // order options derived from selected type + let orderOptions = $derived.by(() => + selectedType === IndexType.Spatial + ? [ + { value: 'ASC', label: 'ASC' }, + { value: 'DESC', label: 'DESC' }, + { value: null, label: 'NONE' } + ] + : [ + { value: 'ASC', label: 'ASC' }, + { value: 'DESC', label: 'DESC' } + ] + ); + + // spatial type selected -> reset column list to single empty column + // and the column already is not spatial type + $effect(() => { + if (selectedType === IndexType.Spatial && !columnList.at(0).value) { + columnList = [{ value: '', order: null, length: null }]; + } + }); + function generateIndexKey() { let indexKeys = $indexes.map((index) => index.key); @@ -55,29 +83,41 @@ } function initialize() { + const column = $table.columns.filter((column) => externalColumnKey === column.key); + const isSpatial = column.length && isSpatialType(column[0]); + const order = isSpatial ? null : 'ASC'; + selectedType = isSpatial ? IndexType.Spatial : IndexType.Key; columnList = externalColumnKey - ? [{ value: externalColumnKey, order: 'ASC', length: null }] - : [{ value: '', order: 'ASC', length: null }]; - selectedType = IndexType.Key; + ? [{ value: externalColumnKey, order, length: null }] + : [{ value: '', order, length: null }]; key = `index_${$indexes.length + 1}`; } - const addColumnDisabled = $derived(!columnList.at(-1)?.value || !columnList.at(-1)?.order); + const addColumnDisabled = $derived( + selectedType === IndexType.Spatial || + !columnList.at(-1)?.value || + (!columnList.at(-1)?.order && columnList.at(-1)?.order !== null) + ); const isOnIndexesPage = $derived(page.route.id?.endsWith('/indexes')); const navigatorPathToIndexes = $derived( `${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${$table?.$id}/indexes` ); + let initializedForOpen = $state(false); $effect(() => { - if (showCreateIndex) { + if (showCreateIndex && !initializedForOpen) { initialize(); key = generateIndexKey(); + initializedForOpen = true; + } + if (!showCreateIndex && initializedForOpen) { + initializedForOpen = false; } }); export async function create() { - if (!(key && selectedType && !addColumnDisabled)) { + if (!key || !selectedType || (selectedType !== IndexType.Spatial && addColumnDisabled)) { addNotification({ type: 'error', message: 'Selected column key or type invalid' @@ -86,14 +126,15 @@ } try { + const orders = columnList.map((a) => a.order).filter((order) => order !== null); await sdk.forProject(page.params.region, page.params.project).tablesDB.createIndex({ databaseId, tableId: $table.$id, key, type: selectedType, columns: columnList.map((a) => a.value), - orders: columnList.map((a) => a.order), - lengths: columnList.map((a) => (a.length ? Number(a.length) : null)) + lengths: columnList.map((a) => (a.length ? Number(a.length) : null)), + ...(orders.length ? { orders } : {}) }); await Promise.allSettled([ @@ -151,9 +192,14 @@
array mode for advanced edit button --> {#if columns.length} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/columnItem.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/columnItem.svelte index b8dc1dd58..f9214261a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/columnItem.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/columnItem.svelte @@ -28,22 +28,18 @@ function removeArrayItem(key: string, index: number) { const currentArray = Array.isArray($formStore[key]) ? $formStore[key] : []; const filteredArray = currentArray.filter((_: object, i: number) => i !== index); - const next = { - ...formValues, - [key]: filteredArray.length === 0 ? [] : filteredArray - }; - - formStore.set(next); + formStore.update((values) => { + values[key] = filteredArray.length === 0 ? [] : filteredArray; + return values; + }); } function addArrayItem(key: string) { const currentArray = Array.isArray($formStore[key]) ? $formStore[key] : null; - const next = { - ...formValues, - [key]: currentArray ? [...currentArray, null] : [null] - }; - - formStore.set(next); + formStore.update((values) => { + values[key] = currentArray ? [...currentArray, null] : [null]; + return values; + }); } function getColumnType(column: Columns) { @@ -58,8 +54,7 @@ case 'enum': return 'Enum'; default: - 'String'; - break; + return 'String'; } } return `${capitalize(column.type)}${column.array ? '[]' : ''}`; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/line.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/line.svelte new file mode 100644 index 000000000..b1d290798 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/line.svelte @@ -0,0 +1,67 @@ + + + + + + {label} + + {#if nullable} + optional + {:else} + Line + {/if} + + + {#if !value} + + {/if} + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/point.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/point.svelte new file mode 100644 index 000000000..e5cf15683 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/point.svelte @@ -0,0 +1,53 @@ + + + + + + {label} + + {#if nullable} + optional + {:else} + Point + {/if} + + + {#if !value} + + {/if} + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/polygon.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/polygon.svelte new file mode 100644 index 000000000..4e204f61a --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/polygon.svelte @@ -0,0 +1,89 @@ + + + + + + {label} + + {#if nullable} + optional + {:else} + Polygon + {/if} + + + {#if !value} + + {/if} + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte index a40747bf6..f790579c5 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte @@ -69,7 +69,8 @@ } else { if (value && typeof value === 'object') { row = value as Models.Row; - singleRel = row?.$id; + // set for combobox and select. + newItemValue = singleRel = row?.$id; } } }); @@ -86,7 +87,7 @@ databaseId, tableId: column.relatedTable, // limit `5` as `25` would look too much on sheet! - queries: [Query.select(displayNames), Query.limit(2)] + queries: [Query.select(displayNames), Query.limit(5)] }); cachedRowsCopyList = rows; @@ -111,7 +112,8 @@ } function getAvailableOptions(excludeIndex?: number): SelectOption[] { - return options?.filter((option) => { + const source = options ?? []; + return source?.filter((option) => { const otherItems = excludeIndex !== undefined ? relatedList.filter((_, idx) => idx !== excludeIndex) @@ -368,23 +370,30 @@ {#if showInput} - - + {@const availableOptions = getAvailableOptions()} + {@const noAvailableOptions = availableOptions.length <= 0} + {#key availableOptions} + + -
- -
-
+
+ +
+
+ {/key} {/if} @@ -400,44 +409,77 @@ {:else} - { - if (newItemValue === null) { - value = null; - singleRel = null; - } else { - const selectedRow = rowList.rows.find((row) => row.$id === newItemValue); + {#key options} + {#if limited} + + { + if (newItemValue === null) { + value = null; + singleRel = null; + } else { + const selectedRow = rowList.rows.find( + (row) => row.$id === newItemValue + ); - if (selectedRow) { - value = selectedRow; - singleRel = newItemValue; - } - } + if (selectedRow) { + value = selectedRow; + singleRel = newItemValue; + } + } + }} + leadingIcon={!limited ? IconRelationship : undefined} /> + {:else} + {@const noOptions = options.length <= 0} + { + if (newItemValue === null) { + value = null; + singleRel = null; + } else { + const selectedRow = rowList.rows.find( + (row) => row.$id === newItemValue + ); - newItemValue = null; - }} - leadingIcon={!limited ? IconRelationship : undefined} /> + if (selectedRow) { + value = selectedRow; + singleRel = newItemValue; + } + } + }} + leadingIcon={!limited ? IconRelationship : undefined} /> + {/if} - {#if !limited && singleRel} -
- -
- {/if} + {#if !limited && singleRel} +
+ +
+ {/if} + {/key}
{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/string.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/string.svelte index 06f0a4184..491d8ceda 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/string.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/string.svelte @@ -3,6 +3,7 @@ import { Layout, Link } from '@appwrite.io/pink-svelte'; import { IconText } from '@appwrite.io/pink-icons-svelte'; import { InputText, InputTextarea } from '$lib/elements/forms'; + import { isSpatialType } from '../../store'; let { id, @@ -21,7 +22,10 @@ | Models.ColumnString | Models.ColumnInteger | Models.ColumnFloat - | Models.ColumnBoolean; + | Models.ColumnBoolean + | Models.ColumnPoint + | Models.ColumnLine + | Models.ColumnPolygon; } = $props(); const autofocus = $derived(limited); @@ -40,7 +44,6 @@ function parseValue(str: string | null): number | boolean | string | null { const trimmed = str?.trim() ?? null; - if (!trimmed) return null; switch (column.type) { @@ -60,7 +63,10 @@ if (lower === 'false' || lower === '0') return false; return null; } - + case 'point': + case 'linestring': + case 'polygon': + return trimmed; case 'string': default: return trimmed; @@ -68,7 +74,9 @@ } $effect(() => { - if (array && Array.isArray(value)) { + if (isSpatialType(column) && Array.isArray(value)) { + stringValue = JSON.stringify(value); + } else if (array && Array.isArray(value)) { stringValue = value.map(String).join(', '); } else if (value !== null && value !== undefined) { stringValue = String(value); @@ -83,13 +91,14 @@ .split(',') .map((item) => parseValue(item)) .filter((item) => item !== null); - if (JSON.stringify(newArray) !== JSON.stringify(value)) { value = newArray as string[] | number[] | boolean[]; } } else { - const parsedValue = parseValue(stringValue); - if (parsedValue !== value) { + const parsedValue = isSpatialType(column) + ? JSON.parse(stringValue) + : parseValue(stringValue); + if (JSON.stringify(parsedValue) !== JSON.stringify(value)) { value = parsedValue; } } @@ -124,7 +133,7 @@ }; -{#if columnSize >= 50 || array} +{#if columnSize >= 50 || array || isSpatialType(column)} - {#if array} + {#if array || isSpatialType(column)} Advanced edit {/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte index 0f1e77295..b1fa02ce4 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte @@ -7,7 +7,7 @@ import { writable } from 'svelte/store'; import ColumnForm from './columns/columnForm.svelte'; import { Permissions } from '$lib/components/permissions'; - import { type Columns } from '../store'; + import { type Columns, spreadsheetRenderKey } from '../store'; import { ID, type Models } from '@appwrite.io/console'; import { Alert, Layout, Typography, Selector } from '@appwrite.io/pink-svelte'; import SideSheet from '../layout/sidesheet.svelte'; @@ -15,6 +15,7 @@ import { Dependencies } from '$lib/constants'; import { tick } from 'svelte'; import { isRelationship, isRelationshipToMany } from './store'; + import { hash } from '$lib/helpers/string'; type CreateRow = { id?: string; @@ -93,13 +94,15 @@ $createRow.row = prepareRowPayload($createRow); try { - await sdk.forProject(page.params.region, page.params.project).tablesDB.createRow({ - databaseId: page.params.database, - tableId: page.params.table, - rowId: $createRow.id ?? ID.unique(), - data: $createRow.row, - permissions: $createRow.permissions - }); + const row = await sdk + .forProject(page.params.region, page.params.project) + .tablesDB.createRow({ + databaseId: page.params.database, + tableId: page.params.table, + rowId: $createRow.id ?? ID.unique(), + data: $createRow.row, + permissions: $createRow.permissions + }); addNotification({ message: 'Row has been created', @@ -109,7 +112,11 @@ trackEvent(Submit.RowCreate, { customId: !!$createRow.id }); - await invalidate(Dependencies.ROW); + + await invalidate(Dependencies.ROWS); + + // re-render spreadsheet on addition! + spreadsheetRenderKey.set(hash(row.$id)); if (createMore) { resetCreateRow(); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts index 76ffa95ca..835262ef7 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts @@ -1,4 +1,5 @@ import { page } from '$app/state'; +import type { Column } from '$lib/helpers/types'; import type { Columns } from '../store'; import { type Models, Query } from '@appwrite.io/console'; @@ -26,6 +27,16 @@ export function isString(column: Columns): column is Models.ColumnString { return column?.type === 'string'; } +export function isSpatialType( + column: Columns | Column +): column is Models.ColumnPoint | Models.ColumnLine | Models.ColumnPolygon { + if (!column) return false; + + const spatialTypes = ['point', 'linestring', 'polygon']; + + return spatialTypes.includes(column.type.toLowerCase()); +} + /** * Returns select queries for all main and related fields in a table. */ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index c419cee39..ee2b81bb4 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -4,7 +4,7 @@ import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Confirm, Id, SortButton } from '$lib/components'; import { Dependencies, SPREADSHEET_PAGE_LIMIT } from '$lib/constants'; - import { Button as ConsoleButton, InputChoice, InputSelect } from '$lib/elements/forms'; + import { Button as ConsoleButton, InputSelect } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { preferences } from '$lib/stores/preferences'; import { sdk } from '$lib/stores/sdk'; @@ -16,6 +16,7 @@ buildWildcardColumnsQuery, isRelationship, isRelationshipToMany, + isSpatialType, isString } from './rows/store'; import { @@ -66,10 +67,13 @@ IconDotsHorizontal, IconFingerPrint, IconHashtag, + IconLine, IconLink, IconLocationMarker, IconMail, + IconPoint, IconPlus, + IconPolygon, IconRelationship, IconSwitchHorizontal, IconText, @@ -78,7 +82,7 @@ } from '@appwrite.io/pink-icons-svelte'; import type { HeaderCellAction, RowCellAction } from './sheetOptions.svelte'; import SheetOptions from './sheetOptions.svelte'; - import { isSmallViewport } from '$lib/stores/viewport'; + import { isSmallViewport, isTabletViewport } from '$lib/stores/viewport'; import SpreadsheetContainer from './layout/spreadsheet.svelte'; import EditRowCell from './rows/cell/edit.svelte'; import { copy } from '$lib/helpers/copy'; @@ -107,7 +111,13 @@ const organizationId = data.organization.$id ?? data.project.teamId; const minimumWidth = 168; - const emptyCellsLimit = $isSmallViewport ? 12 : 18; + const emptyCellsLimit = $spreadsheetLoading + ? 30 + : $isSmallViewport + ? 12 + : $isTabletViewport + ? 18 + : 24; let selectedRows = []; let spreadsheetContainer: SpreadsheetContainer; @@ -117,7 +127,6 @@ let showDelete = false; let showColumnDelete = false; - let deleteConfirmationChecked = false; let selectedRowForDelete: Models.Row['$id'] | null = null; onMount(async () => { @@ -305,6 +314,12 @@ return IconViewList; case 'relationship': return IconRelationship; + case 'point': + return IconPoint; + case 'linestring': + return IconLine; + case 'polygon': + return IconPolygon; } } @@ -909,6 +924,10 @@ variant="secondary" size="s" /> {/if} + {:else if isSpatialType(rowColumn) && row[columnId] !== null} + + {JSON.stringify(row[columnId])} + {:else} {@const value = row[columnId]} {@const formatted = formatColumn(row[columnId])} @@ -1073,8 +1092,12 @@ {@const isSingle = selectedRowForDelete !== null} @@ -1125,17 +1148,6 @@ To change the selection edit the relationship settings. - -
    - - Delete {isSingle ? 'row' : 'rows'} from - {$table.name} - -
{:else}

This action is irreversible.

diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts index 607773b8b..ef94e87b3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts @@ -1,5 +1,5 @@ import { page } from '$app/stores'; -import type { Column } from '$lib/helpers/types'; +import type { Column, ColumnType } from '$lib/helpers/types'; import type { Models } from '@appwrite.io/console'; import { derived, writable } from 'svelte/store'; import type { SortDirection } from '$lib/components'; @@ -15,6 +15,9 @@ export type Columns = | Models.ColumnIp | Models.ColumnString | Models.ColumnUrl + | Models.ColumnPoint + | Models.ColumnLine + | Models.ColumnPolygon | (Models.ColumnRelationship & { default?: never }); type Table = Omit & { @@ -188,3 +191,32 @@ export const PROHIBITED_ROW_KEYS = [ ]; export const sheetHeightStore = writable('74.5vh'); + +export const getDefaultSpatialData = ( + type: Extract +) => { + if (type === 'point') return [0, 0]; + else if (type === 'linestring') + return [ + [0, 0], + [0, 0] + ]; + else if (type === 'polygon') + return [ + [ + [0, 0], + [0, 0], + [0, 0], + [0, 0] + ] + ]; +}; + +export const getSingleRingPolygon = () => { + return [ + [0, 0], + [0, 0], + [0, 0], + [0, 0] + ]; +}; diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte index 5d698c934..1add28d37 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte @@ -78,7 +78,7 @@ {/snippet} - + diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/sheet.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/sheet.svelte index af47ba07c..48a313d48 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/sheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/sheet.svelte @@ -21,6 +21,7 @@ import { Copy } from '$lib/components'; import { logStatusConverter } from './store'; import { LogsRequest, LogsResponse } from '$lib/components/logs'; + import { timer } from '$lib/actions/timer'; export let selectedLogId: string; export let logs: Models.Execution[]; @@ -139,7 +140,11 @@ Duration - {calculateTime(selectedLog.duration)} + {#if ['processing', 'waiting'].includes(selectedLog.status)} + + {:else} + {calculateTime(selectedLog.duration)} + {/if} diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte index bc4573006..b73c0a7f6 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte @@ -25,6 +25,7 @@ import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import { Button } from '$lib/elements/forms'; + import { timer } from '$lib/actions/timer'; export let columns: Column[]; export let executions: Models.ExecutionList; @@ -121,7 +122,11 @@ {:else if column.id === 'duration'} - {calculateTime(log.duration)} + {#if ['processing', 'waiting'].includes(log.status)} + + {:else} + {calculateTime(log.duration)} + {/if} {/if} {/each} diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/auth-preview-dark.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/auth-preview-dark.svg new file mode 100644 index 000000000..422126fcc --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/auth-preview-dark.svg @@ -0,0 +1,147 @@ + + + +
+ + + + + +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/auth-preview.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/auth-preview.svg new file mode 100644 index 000000000..1462d2e3d --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/auth-preview.svg @@ -0,0 +1,147 @@ + + + +
+ + + + + +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/claude.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/claude.svg new file mode 100644 index 000000000..085739302 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/claude.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/cursor-ai.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/cursor-ai.svg new file mode 100644 index 000000000..e3e9d49e3 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/cursor-ai.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/dark/claude.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/claude.svg new file mode 100644 index 000000000..14a4d2878 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/claude.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/dark/cursor-ai.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/cursor-ai.svg new file mode 100644 index 000000000..ad9146733 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/cursor-ai.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/dark/vscode.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/vscode.svg new file mode 100644 index 000000000..76b93c7b9 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/vscode.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/dark/windsurf.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/windsurf.svg new file mode 100644 index 000000000..d6bdbe5fc --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/dark/windsurf.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/vscode.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/vscode.svg new file mode 100644 index 000000000..7f5cffb05 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/vscode.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/assets/windsurf.svg b/src/routes/(console)/project-[region]-[project]/overview/assets/windsurf.svg new file mode 100644 index 000000000..85097f1a3 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/assets/windsurf.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/components/ClaudeIcon.svelte b/src/routes/(console)/project-[region]-[project]/overview/components/ClaudeIcon.svelte new file mode 100644 index 000000000..2998b15fc --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/components/ClaudeIcon.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/components/CursorIcon.svelte b/src/routes/(console)/project-[region]-[project]/overview/components/CursorIcon.svelte new file mode 100644 index 000000000..4c3cff7da --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/components/CursorIcon.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/components/VSCodeIcon.svelte b/src/routes/(console)/project-[region]-[project]/overview/components/VSCodeIcon.svelte new file mode 100644 index 000000000..35e7459f9 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/components/VSCodeIcon.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/components/WindsurfIcon.svelte b/src/routes/(console)/project-[region]-[project]/overview/components/WindsurfIcon.svelte new file mode 100644 index 000000000..3a29428e4 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/overview/components/WindsurfIcon.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte b/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte index 818a6adc6..c1c3811e4 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte @@ -12,6 +12,8 @@ } from '@appwrite.io/pink-svelte'; import { addPlatform, continuePlatform } from './platforms/+page.svelte'; import { app } from '$lib/stores/app'; + import AuthPreview from './assets/auth-preview.svg'; + import AuthPreviewDark from './assets/auth-preview-dark.svg'; import { IconArrowRight, IconNodeJs, @@ -20,10 +22,9 @@ } from '@appwrite.io/pink-icons-svelte'; import DatabaseImgSource from './assets/database.png'; import DatabaseImgSourceDark from './assets/database-dark.png'; - import UsersImgSource from './assets/users.svg'; - import UsersImgSourceDark from './assets/users-dark.svg'; import DiscordImgSource from './assets/discord.png'; import DiscordImgSourceDark from './assets/discord-dark.png'; + import { mcpTools } from '../store'; import PlatformIosImgSource from './assets/platform-ios.svg'; import PlatformIosImgSourceDark from './assets/platform-ios-dark.svg'; import PlatformAndroidImgSource from './assets/platform-android.svg'; @@ -102,9 +103,7 @@
- + { openPlatformWizard(0, platformMap.get('Web')); @@ -479,10 +478,12 @@
- + + justifyContent="space-between" + style={`flex: ${$isSmallViewport ? '1 1 auto' : '0 0 30%'}; min-width: ${$isSmallViewport ? 'auto' : '240px'}`}> Set up Auth
+
+ + +
+ + + MCP server + + Deploy the Appwrite MCP server with a single + click, or view the docs for instructions. + + + + One-click install + + {#each mcpTools as tool} + + + {tool.label} + + {/each} + + + +
+
{ trackEvent(Click.OnboardingDiscordClick); - }}> + }} + style={`flex: ${$isSmallViewport ? '1 1 auto' : '1 1 28%'};`}>
- - - Discord + + + + Discord + +
+ +
Join our Discord for support, tips and product updates -
- -
@@ -635,7 +695,7 @@ margin-bottom: calc(-1 * var(--base-16, 16px)); align-self: flex-end; background-size: cover; - background-position: left bottom; + background-position: right bottom; background-repeat: no-repeat; } :global(.full-height-card div) { @@ -647,6 +707,30 @@ width: 250%; } } + .double-width-card .full-height-card { + height: 240px; + width: 100%; + + @media (max-width: 1024px) { + height: 220px; + } + + @media (max-width: 768px) { + height: auto; + } + } + + .auth-image { + @media (max-width: 1024px) { + height: 180px; + background-position: center bottom; + } + + @media (max-width: 768px) { + height: 160px; + margin-right: 0; + } + } .arrow-icon { color: var(--border-neutral-strong); @@ -656,7 +740,7 @@ } } .arrow-icon-discord { - align-items: flex-end; + align-items: flex-start; } .platform-image { diff --git a/src/routes/(console)/project-[region]-[project]/settings/domains/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/domains/+page.svelte index ba7f3a22b..1ba22bc3f 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/domains/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/domains/+page.svelte @@ -58,7 +58,7 @@ +
- + external>Learn more {:else} @@ -184,10 +186,10 @@ title="No installation was added to the project yet" description="Add an installation to connect repositories"> - + @@ -198,3 +200,36 @@ {#if showGitDisconnect} {/if} + + + MCP server + Spin up an MCP server to stream rich project context to AI tooling, unlocking deeper code understanding + and better completions. + + + + + Deploy the Appwrite MCP server with a single click, or view the docs for instructions. + + + + {#each mcpTools as tool} + + + {tool.label} + + {/each} + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/domain.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/domain.svelte index 18f3159f8..23001b4b1 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/domain.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/domain.svelte @@ -56,8 +56,10 @@ $effect(() => { domainIsValid; /* silences lint for unused var */ - if (domain !== newDomain) { - domainStatus = 'pending'; + if (newDomain) { + if (domain !== newDomain) { + domainStatus = 'pending'; + } checkDomain(newDomain); } }); diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte index 3321ce726..1dbbd143e 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte @@ -53,7 +53,7 @@ let domain = data.domain; let domainIsValid = true; let framework = data?.template?.frameworks[0]; - let branch = 'main'; + let branch = ''; let rootDir = './'; let connectBehaviour: 'now' | 'later' = 'now'; let repositoryBehaviour: 'new' | 'existing' = 'new'; diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/sheet.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/sheet.svelte index a0c6f9b81..8adf821c3 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/sheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/sheet.svelte @@ -19,6 +19,7 @@ import { Copy } from '$lib/components'; import { LogsRequest, LogsResponse } from '$lib/components/logs'; import { site } from '../store'; + import { timer } from '$lib/actions/timer'; export let open = false; export let selectedLogId: string; @@ -112,7 +113,12 @@ Duration - {calculateTime(selectedLog.duration)} + {#if ['processing', 'waiting'].includes(selectedLog.status)} + + {:else} + {calculateTime(selectedLog.duration)} + {/if}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index 1d5011dd2..9d74a095d 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -14,6 +14,7 @@ import { calculateTime } from '$lib/helpers/timeConversion'; import { getBadgeTypeFromStatusCode } from '$lib/helpers/httpStatus'; import { Button } from '$lib/elements/forms'; + import { timer } from '$lib/actions/timer'; export let columns: Column[]; export let logs: Models.ExecutionList; @@ -83,7 +84,11 @@ {log.requestMethod} {:else if column.id === 'duration'} - {calculateTime(log.duration)} + {#if ['processing', 'waiting'].includes(log.status)} + + {:else} + {calculateTime(log.duration)} + {/if} {:else if column.id === 'responseStatusCode'}
$page.data.project as Models.Project); @@ -73,3 +77,26 @@ function createStats() { } export const stats = createStats(); + +export const mcpTools = [ + { + href: 'https://apwr.dev/docs-mcp-cursor?ref=docs', + icon: CursorIcon, + label: 'Cursor' + }, + { + href: 'https://appwrite.io/docs/tooling/mcp/claude', + icon: ClaudeIcon, + label: 'Claude Desktop' + }, + { + href: 'https://apwr.dev/docs-mcp-vscode?ref=docs', + icon: VSCodeIcon, + label: 'VS code' + }, + { + href: 'https://appwrite.io/docs/tooling/mcp/windsurf', + icon: WindsurfIcon, + label: 'Windsurf editor' + } +]; diff --git a/src/routes/(public)/(guest)/education/+page.svelte b/src/routes/(public)/(guest)/education/+page.svelte index 88ce060a8..7ed9fbf93 100644 --- a/src/routes/(public)/(guest)/education/+page.svelte +++ b/src/routes/(public)/(guest)/education/+page.svelte @@ -7,13 +7,14 @@ import AppwriteLogoLight from '$lib/images/appwrite-logo-light.svg'; import GithubLogoDark from '$lib/images/github-logo-dark.svg'; import GithubLogoLight from '$lib/images/github-logo-light.svg'; + import { base } from '$app/paths'; function onGithubLogin() { localStorage.setItem('githubEducationProgram', 'true'); sdk.forConsole.account.createOAuth2Session({ provider: OAuthProvider.Github, - success: window.location.origin + '/console/education?success', - failure: window.location.origin + '/console/education?failure', + success: window.location.origin + base + '/education?success', + failure: window.location.origin + base + '/education?failure', scopes: ['read:user', 'user:email'] }); } diff --git a/src/routes/(public)/(guest)/register/+page.svelte b/src/routes/(public)/(guest)/register/+page.svelte index 6dcd39cc2..a1ca85c02 100644 --- a/src/routes/(public)/(guest)/register/+page.svelte +++ b/src/routes/(public)/(guest)/register/+page.svelte @@ -20,12 +20,35 @@ import { redirectTo } from '$routes/store'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; import { Layout, Link, Typography } from '@appwrite.io/pink-svelte'; + import { getRandomTestimonial } from '$lib/data/testimonials'; export let data; let name: string, mail: string, pass: string, disabled: boolean; let terms = false; + const randomTestimonial = getRandomTestimonial(); + const testimonialCampaign = { + $id: 'testimonial-signup', + template: 'review', + title: randomTestimonial.headline, + description: 'Join thousands of developers building amazing apps with Appwrite', + reviews: [ + { + name: randomTestimonial.name, + image: randomTestimonial.avatar, + description: randomTestimonial.title, + review: randomTestimonial.blurb + } + ] + }; + + trackEvent(Submit.TestimonialView, { + testimonial_id: randomTestimonial.id, + testimonial_name: randomTestimonial.name, + testimonial_company: randomTestimonial.title + }); + async function register() { try { disabled = true; @@ -43,7 +66,9 @@ trackEvent(Submit.AccountCreate, { campaign_name: data?.couponData?.code, email: mail, - name: name + name: name, + testimonial_id: randomTestimonial.id, + testimonial_name: randomTestimonial.name }); if (data?.couponData?.code) { @@ -94,7 +119,7 @@ Sign up - Appwrite - + Sign up
diff --git a/src/routes/(public)/auth/+layout.svelte b/src/routes/(public)/auth/+layout.svelte index a2ac0dc4f..da38c1dc4 100644 --- a/src/routes/(public)/auth/+layout.svelte +++ b/src/routes/(public)/auth/+layout.svelte @@ -1,4 +1,5 @@