diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..8477c38 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1 @@ +PUBLIC_CLIENT_PB="http://127.0.0.1:8090" diff --git a/apps/web/.eslintrc.cjs b/apps/web/.eslintrc.cjs index 0b75758..58c2ca9 100644 --- a/apps/web/.eslintrc.cjs +++ b/apps/web/.eslintrc.cjs @@ -27,5 +27,11 @@ module.exports = { parser: '@typescript-eslint/parser' } } - ] + ], + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { varsIgnorePattern: '^\\$\\$(Props|Events|Slots)$' } + ] + } }; diff --git a/apps/web/.prettierrc b/apps/web/.prettierrc index 9023878..7ebb855 100644 --- a/apps/web/.prettierrc +++ b/apps/web/.prettierrc @@ -3,10 +3,7 @@ "singleQuote": true, "trailingComma": "none", "printWidth": 100, - "plugins": [ - "prettier-plugin-svelte", - "prettier-plugin-tailwindcss" - ], + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "overrides": [ { "files": "*.svelte", @@ -15,4 +12,4 @@ } } ] -} \ No newline at end of file +} diff --git a/apps/web/components.json b/apps/web/components.json index 1ce8803..a032c44 100644 --- a/apps/web/components.json +++ b/apps/web/components.json @@ -1,13 +1,13 @@ { - "$schema": "https://shadcn-svelte.com/schema.json", - "style": "new-york", - "tailwind": { - "config": "tailwind.config.js", - "css": "src/app.pcss", - "baseColor": "neutral" - }, - "aliases": { - "components": "$lib/components", - "utils": "$lib/utils" - } -} \ No newline at end of file + "$schema": "https://shadcn-svelte.com/schema.json", + "style": "new-york", + "tailwind": { + "config": "tailwind.config.js", + "css": "src/app.pcss", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils" + } +} diff --git a/apps/web/package.json b/apps/web/package.json index f6871f5..df21861 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -36,10 +36,11 @@ }, "type": "module", "dependencies": { + "bits-ui": "^0.15.1", "clsx": "^2.1.0", "pocketbase": "^0.21.0", "radix-icons-svelte": "^1.2.1", "tailwind-merge": "^2.2.1", "tailwind-variants": "^0.1.20" } -} \ No newline at end of file +} diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index c8a60ad..72d83b3 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + bits-ui: + specifier: ^0.15.1 + version: 0.15.1(svelte@4.2.9) clsx: specifier: ^2.1.0 version: 2.1.0 @@ -103,7 +106,6 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - dev: true /@babel/runtime@7.23.9: resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==} @@ -356,6 +358,23 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@floating-ui/core@1.6.0: + resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} + dependencies: + '@floating-ui/utils': 0.2.1 + dev: false + + /@floating-ui/dom@1.6.1: + resolution: {integrity: sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==} + dependencies: + '@floating-ui/core': 1.6.0 + '@floating-ui/utils': 0.2.1 + dev: false + + /@floating-ui/utils@0.2.1: + resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -376,6 +395,12 @@ packages: resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} dev: true + /@internationalized/date@3.5.1: + resolution: {integrity: sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==} + dependencies: + '@swc/helpers': 0.5.3 + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -412,6 +437,20 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@melt-ui/svelte@0.68.0(svelte@4.2.9): + resolution: {integrity: sha512-/QvA98hnYEodZtHJ71+ocum/WWp30hVNt3F8uiZKnNYwZDaiQYjlyR9AaGKYcZLCe6R68op1mfCzc0kTzJilyA==} + peerDependencies: + svelte: '>=3 <5' + dependencies: + '@floating-ui/core': 1.6.0 + '@floating-ui/dom': 1.6.1 + '@internationalized/date': 3.5.1 + dequal: 2.0.3 + focus-trap: 7.5.4 + nanoid: 5.0.4 + svelte: 4.2.9 + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -616,6 +655,12 @@ packages: - supports-color dev: true + /@swc/helpers@0.5.3: + resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==} + dependencies: + tslib: 2.6.2 + dev: false + /@types/cookie@0.6.0: resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} dev: true @@ -629,7 +674,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -791,7 +835,6 @@ packages: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -841,7 +884,6 @@ packages: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 - dev: true /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} @@ -868,7 +910,6 @@ packages: resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} dependencies: dequal: 2.0.3 - dev: true /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -877,6 +918,17 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /bits-ui@0.15.1(svelte@4.2.9): + resolution: {integrity: sha512-1Np8bT6W6SC2tKESfm0CySW+7+xU5S0GuUZqIxC41atZE3WIRiRlzXEYHxW88w6UaLFzZ51ns4E7pchkdV5XCQ==} + peerDependencies: + svelte: ^4.0.0 + dependencies: + '@internationalized/date': 3.5.1 + '@melt-ui/svelte': 0.68.0(svelte@4.2.9) + nanoid: 5.0.4 + svelte: 4.2.9 + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -958,7 +1010,6 @@ packages: acorn: 8.11.3 estree-walker: 3.0.3 periscopic: 3.1.0 - dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -996,7 +1047,6 @@ packages: dependencies: mdn-data: 2.0.30 source-map-js: 1.0.2 - dev: true /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -1027,7 +1077,6 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true /detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -1259,7 +1308,6 @@ packages: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.5 - dev: true /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -1327,6 +1375,12 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true + /focus-trap@7.5.4: + resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} + dependencies: + tabbable: 6.2.0 + dev: false + /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} @@ -1503,7 +1557,6 @@ packages: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} dependencies: '@types/estree': 1.0.5 - dev: true /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1575,7 +1628,6 @@ packages: /locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - dev: true /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -1604,11 +1656,9 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: true /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -1679,6 +1729,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@5.0.4: + resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==} + engines: {node: ^18 || >=20} + hasBin: true + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -1778,7 +1834,6 @@ packages: '@types/estree': 1.0.5 estree-walker: 3.0.3 is-reference: 3.0.2 - dev: true /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -2328,7 +2383,10 @@ packages: locate-character: 3.0.0 magic-string: 0.30.5 periscopic: 3.1.0 - dev: true + + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: false /tailwind-merge@1.14.0: resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} @@ -2427,7 +2485,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} diff --git a/apps/web/postcss.config.cjs b/apps/web/postcss.config.cjs index 65e0bb1..fe10e55 100644 --- a/apps/web/postcss.config.cjs +++ b/apps/web/postcss.config.cjs @@ -1,13 +1,13 @@ -const tailwindcss = require("tailwindcss"); -const autoprefixer = require("autoprefixer"); +const tailwindcss = require('tailwindcss'); +const autoprefixer = require('autoprefixer'); const config = { - plugins: [ - //Some plugins, like tailwindcss/nesting, need to run before Tailwind, - tailwindcss(), - //But others, like autoprefixer, need to run after, - autoprefixer - ] + plugins: [ + //Some plugins, like tailwindcss/nesting, need to run before Tailwind, + tailwindcss(), + //But others, like autoprefixer, need to run after, + autoprefixer + ] }; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/apps/web/src/app.d.ts b/apps/web/src/app.d.ts index 743f07b..3ba5e57 100644 --- a/apps/web/src/app.d.ts +++ b/apps/web/src/app.d.ts @@ -1,11 +1,13 @@ +import PocketBase from 'pocketbase'; // See https://kit.svelte.dev/docs/types#app // for information about these interfaces declare global { namespace App { + interface Locals { + pocketBase: PocketBase; + } // interface Error {} - // interface Locals {} // interface PageData {} - // interface PageState {} // interface Platform {} } } diff --git a/apps/web/src/app.pcss b/apps/web/src/app.pcss index 8524b77..5cd56fc 100644 --- a/apps/web/src/app.pcss +++ b/apps/web/src/app.pcss @@ -1,78 +1,78 @@ @tailwind base; @tailwind components; @tailwind utilities; - + @layer base { - :root { - --background: 0 0% 100%; - --foreground: 0 0% 3.9%; - - --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; - - --popover: 0 0% 100%; - --popover-foreground: 0 0% 3.9%; - - --card: 0 0% 100%; - --card-foreground: 0 0% 3.9%; - - --border: 0 0% 89.8%; - --input: 0 0% 89.8%; - - --primary: 0 0% 9%; - --primary-foreground: 0 0% 98%; - - --secondary: 0 0% 96.1%; - --secondary-foreground: 0 0% 9%; - - --accent: 0 0% 96.1%; - --accent-foreground: 0 0% 9%; - - --destructive: 0 72.2% 50.6%; - --destructive-foreground: 0 0% 98%; - - --ring: 0 0% 3.9%; - - --radius: 0.5rem; - } - - .dark { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - - --popover: 0 0% 3.9%; - --popover-foreground: 0 0% 98%; - - --card: 0 0% 3.9%; - --card-foreground: 0 0% 98%; - - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - - --primary: 0 0% 98%; - --primary-foreground: 0 0% 9%; - - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - - --ring: 0 0% 83.1%; - } + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + + --destructive: 0 72.2% 50.6%; + --destructive-foreground: 0 0% 98%; + + --ring: 0 0% 3.9%; + + --radius: 0.5rem; + } + + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + + --ring: 0 0% 83.1%; + } } - + @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} \ No newline at end of file + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts new file mode 100644 index 0000000..a2fa874 --- /dev/null +++ b/apps/web/src/hooks.server.ts @@ -0,0 +1,22 @@ +import type { Handle } from '@sveltejs/kit'; +import PocketBase from 'pocketbase'; +import { pb } from '$lib/pocketbase'; +import { PUBLIC_CLIENT_PB } from '$env/static/public'; + +/** @type {import('@sveltejs/kit').Handle} */ +export const handle: Handle = async ({ event, resolve }) => { + event.locals.pocketBase = new PocketBase(PUBLIC_CLIENT_PB); + + pb.set(event.locals.pocketBase); + + event.locals.pocketBase.authStore.loadFromCookie(event.request.headers.get('cookie') ?? ''); + + const response = await resolve(event); + + response.headers.set( + 'set-cookie', + event.locals.pocketBase.authStore.exportToCookie({ secure: false }) + ); + + return response; +}; diff --git a/apps/web/src/lib/components/ui/button/button.svelte b/apps/web/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..fb61a87 --- /dev/null +++ b/apps/web/src/lib/components/ui/button/button.svelte @@ -0,0 +1,25 @@ + + + + + diff --git a/apps/web/src/lib/components/ui/button/index.ts b/apps/web/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..e300752 --- /dev/null +++ b/apps/web/src/lib/components/ui/button/index.ts @@ -0,0 +1,49 @@ +import type { Button as ButtonPrimitive } from 'bits-ui'; +import { tv, type VariantProps } from 'tailwind-variants'; +import Root from './button.svelte'; + +const buttonVariants = tv({ + base: 'inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', + variants: { + variant: { + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: + 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } +}); + +type Variant = VariantProps['variant']; +type Size = VariantProps['size']; + +type Props = ButtonPrimitive.Props & { + variant?: Variant; + size?: Size; +}; + +type Events = ButtonPrimitive.Events; + +export { + Root, + type Props, + type Events, + // + Root as Button, + type Props as ButtonProps, + type Events as ButtonEvents, + buttonVariants +}; diff --git a/apps/web/src/lib/components/ui/input/index.ts b/apps/web/src/lib/components/ui/input/index.ts new file mode 100644 index 0000000..38f0a88 --- /dev/null +++ b/apps/web/src/lib/components/ui/input/index.ts @@ -0,0 +1,27 @@ +import Root from './input.svelte'; + +type FormInputEvent = T & { + currentTarget: EventTarget & HTMLInputElement; +}; +export type InputEvents = { + blur: FormInputEvent; + change: FormInputEvent; + click: FormInputEvent; + focus: FormInputEvent; + focusin: FormInputEvent; + focusout: FormInputEvent; + keydown: FormInputEvent; + keypress: FormInputEvent; + keyup: FormInputEvent; + mouseover: FormInputEvent; + mouseenter: FormInputEvent; + mouseleave: FormInputEvent; + paste: FormInputEvent; + input: FormInputEvent; +}; + +export { + Root, + // + Root as Input +}; diff --git a/apps/web/src/lib/components/ui/input/input.svelte b/apps/web/src/lib/components/ui/input/input.svelte new file mode 100644 index 0000000..9f8cfd6 --- /dev/null +++ b/apps/web/src/lib/components/ui/input/input.svelte @@ -0,0 +1,35 @@ + + + diff --git a/apps/web/src/lib/components/ui/label/index.ts b/apps/web/src/lib/components/ui/label/index.ts new file mode 100644 index 0000000..808d141 --- /dev/null +++ b/apps/web/src/lib/components/ui/label/index.ts @@ -0,0 +1,7 @@ +import Root from './label.svelte'; + +export { + Root, + // + Root as Label +}; diff --git a/apps/web/src/lib/components/ui/label/label.svelte b/apps/web/src/lib/components/ui/label/label.svelte new file mode 100644 index 0000000..026d2b3 --- /dev/null +++ b/apps/web/src/lib/components/ui/label/label.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/apps/web/src/lib/constants.ts b/apps/web/src/lib/constants.ts new file mode 100644 index 0000000..0b83934 --- /dev/null +++ b/apps/web/src/lib/constants.ts @@ -0,0 +1 @@ +export const AUTH_COOKIE_REF = 'auth-cookie'; diff --git a/apps/web/src/lib/pocketbase.ts b/apps/web/src/lib/pocketbase.ts new file mode 100644 index 0000000..89a0427 --- /dev/null +++ b/apps/web/src/lib/pocketbase.ts @@ -0,0 +1,14 @@ +import PocketBase from 'pocketbase'; +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; +import { PUBLIC_CLIENT_PB } from '$env/static/public'; + +export const pb = writable(undefined, (set) => { + if (!browser) { + return; + } + + const pocketbaseInstance = new PocketBase(PUBLIC_CLIENT_PB); + + set(pocketbaseInstance); +}); diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index 230a1fb..680ffb9 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -1,62 +1,60 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; -import { cubicOut } from "svelte/easing"; -import type { TransitionConfig } from "svelte/transition"; +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; +import { cubicOut } from 'svelte/easing'; +import type { TransitionConfig } from 'svelte/transition'; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)); } type FlyAndScaleParams = { - y?: number; - x?: number; - start?: number; - duration?: number; + y?: number; + x?: number; + start?: number; + duration?: number; }; export const flyAndScale = ( - node: Element, - params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } + node: Element, + params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } ): TransitionConfig => { - const style = getComputedStyle(node); - const transform = style.transform === "none" ? "" : style.transform; + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; - const scaleConversion = ( - valueA: number, - scaleA: [number, number], - scaleB: [number, number] - ) => { - const [minA, maxA] = scaleA; - const [minB, maxB] = scaleB; + const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => { + const [minA, maxA] = scaleA; + const [minB, maxB] = scaleB; - const percentage = (valueA - minA) / (maxA - minA); - const valueB = percentage * (maxB - minB) + minB; + const percentage = (valueA - minA) / (maxA - minA); + const valueB = percentage * (maxB - minB) + minB; - return valueB; - }; + return valueB; + }; - const styleToString = ( - style: Record - ): string => { - return Object.keys(style).reduce((str, key) => { - if (style[key] === undefined) return str; - return str + `${key}:${style[key]};`; - }, ""); - }; + const styleToString = (style: Record): string => { + return Object.keys(style).reduce((str, key) => { + if (style[key] === undefined) return str; + return str + `${key}:${style[key]};`; + }, ''); + }; - return { - duration: params.duration ?? 200, - delay: 0, - css: (t) => { - const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); - const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); - const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); + return { + duration: params.duration ?? 200, + delay: 0, + css: (t) => { + const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); + const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); + const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); - return styleToString({ - transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, - opacity: t - }); - }, - easing: cubicOut - }; -}; \ No newline at end of file + return styleToString({ + transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, + opacity: t + }); + }, + easing: cubicOut + }; +}; + +export const serializeNonPOJOs = (obj: unknown) => { + return structuredClone(obj); +}; diff --git a/apps/web/src/routes/(auth)/+layout.server.ts b/apps/web/src/routes/(auth)/+layout.server.ts new file mode 100644 index 0000000..359b4bc --- /dev/null +++ b/apps/web/src/routes/(auth)/+layout.server.ts @@ -0,0 +1,10 @@ +import { redirect } from '@sveltejs/kit'; +import type { LayoutServerLoad } from './$types'; + +export const load = (async ({ locals }) => { + if (locals.pocketBase.authStore.isValid) { + throw redirect(303, '/'); + } + + return {}; +}) satisfies LayoutServerLoad; diff --git a/apps/web/src/routes/(auth)/login/+page.server.ts b/apps/web/src/routes/(auth)/login/+page.server.ts new file mode 100644 index 0000000..20fc83f --- /dev/null +++ b/apps/web/src/routes/(auth)/login/+page.server.ts @@ -0,0 +1,48 @@ +import { redirect } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request, locals }: { request: Request; locals: App.Locals }) => { + if (locals.pocketBase.authStore.isValid) { + return; + } + + const formData = await request.formData(); + + const email = formData.get('email'); + const password = formData.get('password'); + + try { + if (typeof email !== 'string') { + throw new Error('Email must be a string'); + } + + if (email.length < 5) { + throw new Error('Please enter a valid e-mail address'); + } + + if (typeof password !== 'string') { + throw new Error('Password must be a string'); + } + + if (password.length < 8) { + throw new Error('Password must be at least 8 characters in length'); + } + + await locals.pocketBase.collection('users').authWithPassword(email, password); + } catch (error) { + console.error(error); + + if (!(error instanceof Error)) { + return { + email, + password, + error: 'Unknown error occured when signing up user' + }; + } + + return { error: error.message, email, password }; + } + + throw redirect(303, '/'); + } +}; diff --git a/apps/web/src/routes/(auth)/login/+page.svelte b/apps/web/src/routes/(auth)/login/+page.svelte new file mode 100644 index 0000000..9bdf34d --- /dev/null +++ b/apps/web/src/routes/(auth)/login/+page.svelte @@ -0,0 +1,25 @@ + + +

Log in

+ +
+ + + + +
+ +{#if form?.error} +

{form.error}

+{/if} + +

Don't have an account?

+ +Sign up diff --git a/apps/web/src/routes/(auth)/signup/+page.server.ts b/apps/web/src/routes/(auth)/signup/+page.server.ts new file mode 100644 index 0000000..ae464a3 --- /dev/null +++ b/apps/web/src/routes/(auth)/signup/+page.server.ts @@ -0,0 +1,65 @@ +import { redirect } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request, locals }: { request: Request; locals: App.Locals }) => { + if (locals.pocketBase.authStore.isValid) { + return; + } + + const formData = await request.formData(); + + const name = formData.get('name'); + const email = formData.get('email'); + const password = formData.get('password'); + + try { + if (typeof name !== 'string') { + throw new Error('Name must be a string'); + } + + if (name.length === 0) { + throw new Error('Please enter a valid name'); + } + + if (typeof email !== 'string') { + throw new Error('Email must be a string'); + } + + if (email.length < 5) { + throw new Error('Please enter a valid e-mail address'); + } + + if (typeof password !== 'string') { + throw new Error('Password must be a string'); + } + + if (password.length < 8) { + throw new Error('Password must be at least 8 characters in length'); + } + + await locals.pocketBase.collection('users').create({ + email, + password, + name, + passwordConfirm: password + }); + + await locals.pocketBase.collection('users').authWithPassword(email, password); + } catch (error) { + console.error(error); + + if (!(error instanceof Error)) { + return { + name, + email, + password, + error: 'Unknown error occured when signing up user' + }; + } + + return { error: error.message, name, email, password }; + } + + throw redirect(303, '/'); + } +}; diff --git a/apps/web/src/routes/(auth)/signup/+page.svelte b/apps/web/src/routes/(auth)/signup/+page.svelte new file mode 100644 index 0000000..e1606db --- /dev/null +++ b/apps/web/src/routes/(auth)/signup/+page.svelte @@ -0,0 +1,23 @@ + + +

Sign up

+ +
+ + + + + +
+ +{#if form?.error} +

{form.error}

+{/if} + +

Already have an account?

+ +Log in diff --git a/apps/web/src/routes/(board)/+layout.server.ts b/apps/web/src/routes/(board)/+layout.server.ts new file mode 100644 index 0000000..f144f49 --- /dev/null +++ b/apps/web/src/routes/(board)/+layout.server.ts @@ -0,0 +1,10 @@ +import { redirect } from '@sveltejs/kit'; +import type { LayoutServerLoad } from './$types'; + +export const load = (async ({ locals }) => { + if (!locals.pocketBase.authStore.isValid) { + throw redirect(303, '/signup'); + } + + return {}; +}) satisfies LayoutServerLoad; diff --git a/apps/web/src/routes/(board)/board/+page.svelte b/apps/web/src/routes/(board)/board/+page.svelte new file mode 100644 index 0000000..305df77 --- /dev/null +++ b/apps/web/src/routes/(board)/board/+page.svelte @@ -0,0 +1,8 @@ + + +

Create a new dashboard

+ +
+ +
diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte index f67d7ab..17abf18 100644 --- a/apps/web/src/routes/+layout.svelte +++ b/apps/web/src/routes/+layout.svelte @@ -1 +1,5 @@ - \ No newline at end of file + + + diff --git a/apps/web/src/routes/+page.server.ts b/apps/web/src/routes/+page.server.ts new file mode 100644 index 0000000..96bcf6f --- /dev/null +++ b/apps/web/src/routes/+page.server.ts @@ -0,0 +1,7 @@ +import type { PageServerLoad } from './$types'; + +export const load = (async ({ locals }) => { + return { + authenticated: locals.pocketBase.authStore.isValid + }; +}) satisfies PageServerLoad; diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte index 5982b0a..51e11f3 100644 --- a/apps/web/src/routes/+page.svelte +++ b/apps/web/src/routes/+page.svelte @@ -1,2 +1,26 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

+ + +

Login with SvelteKit and Pocketbase

+ +{#if data.authenticated} + Create a new game +{:else} + +{/if} + + diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 8d260d9..38fb24e 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -1,64 +1,64 @@ -import { fontFamily } from "tailwindcss/defaultTheme"; +import { fontFamily } from 'tailwindcss/defaultTheme'; /** @type {import('tailwindcss').Config} */ const config = { - darkMode: ["class"], - content: ["./src/**/*.{html,js,svelte,ts}"], - safelist: ["dark"], + darkMode: ['class'], + content: ['./src/**/*.{html,js,svelte,ts}'], + safelist: ['dark'], theme: { container: { center: true, - padding: "2rem", + padding: '2rem', screens: { - "2xl": "1400px" + '2xl': '1400px' } }, extend: { colors: { - border: "hsl(var(--border) / )", - input: "hsl(var(--input) / )", - ring: "hsl(var(--ring) / )", - background: "hsl(var(--background) / )", - foreground: "hsl(var(--foreground) / )", + border: 'hsl(var(--border) / )', + input: 'hsl(var(--input) / )', + ring: 'hsl(var(--ring) / )', + background: 'hsl(var(--background) / )', + foreground: 'hsl(var(--foreground) / )', primary: { - DEFAULT: "hsl(var(--primary) / )", - foreground: "hsl(var(--primary-foreground) / )" + DEFAULT: 'hsl(var(--primary) / )', + foreground: 'hsl(var(--primary-foreground) / )' }, secondary: { - DEFAULT: "hsl(var(--secondary) / )", - foreground: "hsl(var(--secondary-foreground) / )" + DEFAULT: 'hsl(var(--secondary) / )', + foreground: 'hsl(var(--secondary-foreground) / )' }, destructive: { - DEFAULT: "hsl(var(--destructive) / )", - foreground: "hsl(var(--destructive-foreground) / )" + DEFAULT: 'hsl(var(--destructive) / )', + foreground: 'hsl(var(--destructive-foreground) / )' }, muted: { - DEFAULT: "hsl(var(--muted) / )", - foreground: "hsl(var(--muted-foreground) / )" + DEFAULT: 'hsl(var(--muted) / )', + foreground: 'hsl(var(--muted-foreground) / )' }, accent: { - DEFAULT: "hsl(var(--accent) / )", - foreground: "hsl(var(--accent-foreground) / )" + DEFAULT: 'hsl(var(--accent) / )', + foreground: 'hsl(var(--accent-foreground) / )' }, popover: { - DEFAULT: "hsl(var(--popover) / )", - foreground: "hsl(var(--popover-foreground) / )" + DEFAULT: 'hsl(var(--popover) / )', + foreground: 'hsl(var(--popover-foreground) / )' }, card: { - DEFAULT: "hsl(var(--card) / )", - foreground: "hsl(var(--card-foreground) / )" + DEFAULT: 'hsl(var(--card) / )', + foreground: 'hsl(var(--card-foreground) / )' } }, borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)" + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' }, fontFamily: { sans: [...fontFamily.sans] } } - }, + } }; export default config;