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..b84e56787 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@4a33959", "@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@4a33959", "@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..0e9e225a8 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@4a33959 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4a33959(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@4a33959 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4a33959(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@4a33959': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4a33959} 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@4a33959': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4a33959} 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@4a33959(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@4a33959(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/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')} = 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) { @@ -212,7 +235,7 @@ ? $currentPlan.name : selectedOrg?.tierName; // fallback - $: derivedKey = `${selectedOrg?.$id}-${currentProject?.$id}`; + $: derivedKey = `${selectedOrg?.$id}-${currentProject?.$id}-${ID.unique()}`; $: organizationId = currentProject?.teamId; 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 73846afa2..a8e6e30c7 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -82,7 +82,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, @@ -335,11 +335,11 @@ 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 --> + import { InputLine } from '$lib/elements/forms'; + import type { Models } from '@appwrite.io/console'; + import { Layout, Typography, Icon } from '@appwrite.io/pink-svelte'; + import { Button } from '$lib/elements/forms'; + import { IconPlus } from '@appwrite.io/pink-icons-svelte'; + import { getDefaultSpatialData } from '../../../store'; + + interface Props { + label: string; + value: number[][]; + limited: boolean; + column: Models.ColumnLine; + } + + let { label, value = $bindable(), limited = false, column }: Props = $props(); + + const defaultData = getDefaultSpatialData('linestring') as number[][]; + function onAddPoint() { + value = [...(value || defaultData), getDefaultSpatialData('point') as number[]]; + } + + function onDeletePoint() { + if (value && value?.length > 2) value = value.slice(0, value.length - 1); + } + + function handlePointChange(pointIndex: number, coordIndex: number, newValue: number) { + if (value && value[pointIndex]) { + value[pointIndex][coordIndex] = newValue; + value = [...value]; + } + } + + function handleAddDefault() { + value = getDefaultSpatialData('linestring') as number[][]; + } + + const nullable = $derived(!limited ? !column.required : false); + + + + + + {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/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/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..6f1ea8681 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 @@ -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, @@ -305,6 +309,12 @@ return IconViewList; case 'relationship': return IconRelationship; + case 'point': + return IconPoint; + case 'linestring': + return IconLine; + case 'polygon': + return IconPolygon; } } @@ -909,6 +919,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])} 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/(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 @@