Adding profile update for username, firstname, and lastname.

This commit is contained in:
Bradley Shellnut 2025-01-07 16:30:17 -08:00
parent da0df78c05
commit c5e310bf95
14 changed files with 268 additions and 50 deletions

View file

@ -40,7 +40,7 @@
"@storybook/test": "^8.4.7", "@storybook/test": "^8.4.7",
"@sveltejs/adapter-node": "^5.2.9", "@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/enhanced-img": "^0.4.4", "@sveltejs/enhanced-img": "^0.4.4",
"@sveltejs/kit": "^2.15.1", "@sveltejs/kit": "^2.15.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/cookie": "^1.0.0", "@types/cookie": "^1.0.0",
"@types/node": "^22.10.5", "@types/node": "^22.10.5",
@ -63,7 +63,7 @@
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"svelte-sequential-preprocessor": "^2.0.2", "svelte-sequential-preprocessor": "^2.0.2",
"svelte-sonner": "^0.3.28", "svelte-sonner": "^0.3.28",
"sveltekit-flash-message": "^2.4.4", "sveltekit-flash-message": "^2.4.5",
"sveltekit-superforms": "^2.22.1", "sveltekit-superforms": "^2.22.1",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.0", "tailwind-variants": "^0.3.0",
@ -92,8 +92,8 @@
"@oslojs/webauthn": "^1.0.0", "@oslojs/webauthn": "^1.0.0",
"@scalar/hono-api-reference": "^0.5.162", "@scalar/hono-api-reference": "^0.5.162",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.16",
"arctic": "^3.1.0", "arctic": "^3.1.0",
"argon2": "^0.41.1", "argon2": "^0.41.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",

View file

@ -19,7 +19,7 @@ importers:
version: 0.4.2(hono@4.6.16)(zod@3.24.1) version: 0.4.2(hono@4.6.16)(zod@3.24.1)
'@inlang/paraglide-sveltekit': '@inlang/paraglide-sveltekit':
specifier: ^0.15.4 specifier: ^0.15.4
version: 0.15.4(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))) version: 0.15.4(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))
'@internationalized/date': '@internationalized/date':
specifier: ^3.5.5 specifier: ^3.5.5
version: 3.6.0 version: 3.6.0
@ -54,11 +54,11 @@ importers:
specifier: ^0.1.1 specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.4.17) version: 0.1.1(tailwindcss@3.4.17)
'@tailwindcss/forms': '@tailwindcss/forms':
specifier: ^0.5.9 specifier: ^0.5.10
version: 0.5.9(tailwindcss@3.4.17) version: 0.5.10(tailwindcss@3.4.17)
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.15 specifier: ^0.5.16
version: 0.5.15(tailwindcss@3.4.17) version: 0.5.16(tailwindcss@3.4.17)
arctic: arctic:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0 version: 3.1.0
@ -170,13 +170,13 @@ importers:
version: 8.4.7(storybook@8.4.7) version: 8.4.7(storybook@8.4.7)
'@sveltejs/adapter-node': '@sveltejs/adapter-node':
specifier: ^5.2.9 specifier: ^5.2.9
version: 5.2.11(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))) version: 5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))
'@sveltejs/enhanced-img': '@sveltejs/enhanced-img':
specifier: ^0.4.4 specifier: ^0.4.4
version: 0.4.4(rollup@4.29.1)(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) version: 0.4.4(rollup@4.29.1)(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
'@sveltejs/kit': '@sveltejs/kit':
specifier: ^2.15.1 specifier: ^2.15.2
version: 2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) version: 2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
'@sveltejs/vite-plugin-svelte': '@sveltejs/vite-plugin-svelte':
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) version: 5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
@ -212,7 +212,7 @@ importers:
version: 0.30.1 version: 0.30.1
formsnap: formsnap:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0(svelte@5.16.5)(sveltekit-superforms@2.22.1(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2)) version: 2.0.0(svelte@5.16.5)(sveltekit-superforms@2.22.1(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2))
lucide-svelte: lucide-svelte:
specifier: ^0.469.0 specifier: ^0.469.0
version: 0.469.0(svelte@5.16.5) version: 0.469.0(svelte@5.16.5)
@ -241,11 +241,11 @@ importers:
specifier: ^0.3.28 specifier: ^0.3.28
version: 0.3.28(svelte@5.16.5) version: 0.3.28(svelte@5.16.5)
sveltekit-flash-message: sveltekit-flash-message:
specifier: ^2.4.4 specifier: ^2.4.5
version: 2.4.4(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5) version: 2.4.5(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)
sveltekit-superforms: sveltekit-superforms:
specifier: ^2.22.1 specifier: ^2.22.1
version: 2.22.1(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2) version: 2.22.1(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2)
tailwind-merge: tailwind-merge:
specifier: ^2.6.0 specifier: ^2.6.0
version: 2.6.0 version: 2.6.0
@ -2206,8 +2206,8 @@ packages:
svelte: ^5.0.0 svelte: ^5.0.0
vite: '>= 5.0.0' vite: '>= 5.0.0'
'@sveltejs/kit@2.15.1': '@sveltejs/kit@2.15.2':
resolution: {integrity: sha512-8t7D3hQHbUDMiaQ2RVnjJJ/+Ur4Fn/tkeySJCsHtX346Q9cp3LAnav8xXdfuqYNJwpUGX0x3BqF1uvbmXQw93A==} resolution: {integrity: sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==}
engines: {node: '>=18.13'} engines: {node: '>=18.13'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -2238,15 +2238,15 @@ packages:
peerDependencies: peerDependencies:
tailwindcss: '>=3.2.0' tailwindcss: '>=3.2.0'
'@tailwindcss/forms@0.5.9': '@tailwindcss/forms@0.5.10':
resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==} resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
peerDependencies: peerDependencies:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20' tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
'@tailwindcss/typography@0.5.15': '@tailwindcss/typography@0.5.16':
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==}
peerDependencies: peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
'@testing-library/dom@10.4.0': '@testing-library/dom@10.4.0':
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
@ -4724,8 +4724,8 @@ packages:
resolution: {integrity: sha512-sWJRa4qOfRdSORSVw9GhfDEwsbsYsegnDzBevUCF6k/Eis/QqCu9lJ6I0+d/E2wOWCjOhlcJ3+jl/Iur+5mmCw==} resolution: {integrity: sha512-sWJRa4qOfRdSORSVw9GhfDEwsbsYsegnDzBevUCF6k/Eis/QqCu9lJ6I0+d/E2wOWCjOhlcJ3+jl/Iur+5mmCw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
sveltekit-flash-message@2.4.4: sveltekit-flash-message@2.4.5:
resolution: {integrity: sha512-CFN03chH/FMEJcBZ/8zKm7RqGee/pwb57Spbbx8QCQPhe7N9ofZHd9iYV2vVy4E9glBo/oQ1IG7VQje6L092wg==} resolution: {integrity: sha512-CPJwgZbXkPs7Tsl8vK81d6FPqWO4NiuwAkx57zaOIuhDu9wSq0V4rc4TuRtVwWTjOmHwnXClRana+WCXwKHOFQ==}
peerDependencies: peerDependencies:
'@sveltejs/kit': 1.x || 2.x '@sveltejs/kit': 1.x || 2.x
svelte: 3.x || 4.x || >=5.0.0-next.51 svelte: 3.x || 4.x || >=5.0.0-next.51
@ -5964,12 +5964,12 @@ snapshots:
- debug - debug
- supports-color - supports-color
'@inlang/paraglide-sveltekit@0.15.4(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))': '@inlang/paraglide-sveltekit@0.15.4(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))':
dependencies: dependencies:
'@inlang/paraglide-js': 1.11.7 '@inlang/paraglide-js': 1.11.7
'@inlang/paraglide-vite': 1.3.4 '@inlang/paraglide-vite': 1.3.4
'@lix-js/client': 2.2.1 '@lix-js/client': 2.2.1
'@sveltejs/kit': 2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
commander: 12.1.0 commander: 12.1.0
dedent: 1.5.1 dedent: 1.5.1
devalue: 4.3.3 devalue: 4.3.3
@ -6851,12 +6851,12 @@ snapshots:
dependencies: dependencies:
storybook: 8.4.7 storybook: 8.4.7
'@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))': '@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))':
dependencies: dependencies:
'@rollup/plugin-commonjs': 28.0.2(rollup@4.29.1) '@rollup/plugin-commonjs': 28.0.2(rollup@4.29.1)
'@rollup/plugin-json': 6.1.0(rollup@4.29.1) '@rollup/plugin-json': 6.1.0(rollup@4.29.1)
'@rollup/plugin-node-resolve': 16.0.0(rollup@4.29.1) '@rollup/plugin-node-resolve': 16.0.0(rollup@4.29.1)
'@sveltejs/kit': 2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
rollup: 4.29.1 rollup: 4.29.1
'@sveltejs/enhanced-img@0.4.4(rollup@4.29.1)(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))': '@sveltejs/enhanced-img@0.4.4(rollup@4.29.1)(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))':
@ -6871,7 +6871,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
'@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))': '@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))':
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
'@types/cookie': 0.6.0 '@types/cookie': 0.6.0
@ -6919,12 +6919,12 @@ snapshots:
dependencies: dependencies:
tailwindcss: 3.4.17 tailwindcss: 3.4.17
'@tailwindcss/forms@0.5.9(tailwindcss@3.4.17)': '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)':
dependencies: dependencies:
mini-svg-data-uri: 1.4.4 mini-svg-data-uri: 1.4.4
tailwindcss: 3.4.17 tailwindcss: 3.4.17
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.17)': '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17)':
dependencies: dependencies:
lodash.castarray: 4.4.0 lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6 lodash.isplainobject: 4.0.6
@ -8070,11 +8070,11 @@ snapshots:
combined-stream: 1.0.8 combined-stream: 1.0.8
mime-types: 2.1.35 mime-types: 2.1.35
formsnap@2.0.0(svelte@5.16.5)(sveltekit-superforms@2.22.1(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2)): formsnap@2.0.0(svelte@5.16.5)(sveltekit-superforms@2.22.1(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2)):
dependencies: dependencies:
svelte: 5.16.5 svelte: 5.16.5
svelte-toolbelt: 0.5.0(svelte@5.16.5) svelte-toolbelt: 0.5.0(svelte@5.16.5)
sveltekit-superforms: 2.22.1(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2) sveltekit-superforms: 2.22.1(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2)
forwarded@0.2.0: {} forwarded@0.2.0: {}
@ -9452,14 +9452,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
sveltekit-flash-message@2.4.4(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5): sveltekit-flash-message@2.4.5(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5):
dependencies: dependencies:
'@sveltejs/kit': 2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
svelte: 5.16.5 svelte: 5.16.5
sveltekit-superforms@2.22.1(@sveltejs/kit@2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2): sveltekit-superforms@2.22.1(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(@types/json-schema@7.0.15)(svelte@5.16.5)(typescript@5.7.2):
dependencies: dependencies:
'@sveltejs/kit': 2.15.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)) '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0)))(svelte@5.16.5)(vite@6.0.7(@types/node@22.10.5)(jiti@1.21.7)(tsx@4.19.2)(yaml@2.7.0))
devalue: 5.1.1 devalue: 5.1.1
memoize-weak: 1.0.2 memoize-weak: 1.0.2
svelte: 5.16.5 svelte: 5.16.5

View file

@ -0,0 +1,22 @@
import { z } from 'zod';
const MAX_UPLOAD_SIZE = 1024 * 1024 * 3; // 3MB
const ACCEPTED_FILE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
export const updateProfileDto = z.object({
first_name: z
.string()
.trim()
.min(1, { message: 'Must be at least 1 characters' })
.max(50, { message: 'Must be less than 50 characters' })
.optional(),
last_name: z
.string()
.trim()
.min(1, { message: 'Must be at least 1 characters' })
.max(50, { message: 'Must be less than 50 characters' })
.optional(),
username: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }),
});
export type UpdateProfileDto = z.infer<typeof updateProfileDto>;

View file

@ -3,7 +3,9 @@ import { userDto } from './user.dto';
export const updateUserDto = userDto export const updateUserDto = userDto
.pick({ .pick({
avatar: true avatar: true,
first_name: true,
last_name: true
}) })
.optional(); .optional();

View file

@ -6,6 +6,9 @@ const ACCEPTED_FILE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp
export const userDto = z.object({ export const userDto = z.object({
id: z.string(), id: z.string(),
email: z.string().email(), email: z.string().email(),
first_name: z.string().trim().min(1, { message: 'Must be at least 1 characters' }).max(50, { message: 'Must be less than 50 characters' }),
last_name: z.string().trim().min(1, { message: 'Must be at least 1 characters' }).max(50, { message: 'Must be less than 50 characters' }),
username: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }),
avatar: z avatar: z
.instanceof(File) .instanceof(File)
.optional() .optional()

View file

@ -36,4 +36,8 @@ export class UserRolesRepository {
async delete(id: string, db = this.drizzle.db) { async delete(id: string, db = this.drizzle.db) {
return db.delete(user_roles_table).where(eq(user_roles_table.id, id)).returning().then(takeFirstOrThrow); return db.delete(user_roles_table).where(eq(user_roles_table.id, id)).returning().then(takeFirstOrThrow);
} }
async deleteAllByUserId(userId: string, db = this.drizzle.db) {
return db.delete(user_roles_table).where(eq(user_roles_table.user_id, userId));
}
} }

View file

@ -48,4 +48,13 @@ export class UserRolesService {
trx, trx,
); );
} }
async removeAllRolesFromUser(userId: string, trx: Transaction | null = null) {
if (!trx) {
return this.userRolesRepository.deleteAllByUserId(userId);
}
return this.userRolesRepository.deleteAllByUserId(userId, trx);
}
} }

View file

@ -12,6 +12,7 @@ import { rateLimit } from '../common/middleware/rate-limit.middleware';
import { changePasswordDto } from '$lib/dtos/settings/password/change-password.dto'; import { changePasswordDto } from '$lib/dtos/settings/password/change-password.dto';
import { StatusCodes } from '$lib/constants/status-codes'; import { StatusCodes } from '$lib/constants/status-codes';
import { SessionsService } from '../iam/sessions/sessions.service'; import { SessionsService } from '../iam/sessions/sessions.service';
import { updateProfileDto } from '$lib/dtos/settings/profile/update-profile.dto';
@injectable() @injectable()
export class UsersController extends Controller { export class UsersController extends Controller {
@ -67,6 +68,18 @@ export class UsersController extends Controller {
console.error('Error updating password', error); console.error('Error updating password', error);
return c.json({ error: 'Unable to update password' }, StatusCodes.INTERNAL_SERVER_ERROR); return c.json({ error: 'Unable to update password' }, StatusCodes.INTERNAL_SERVER_ERROR);
} }
})
.put('/me/profile', authState('session'), zValidator('json', updateProfileDto), async (c) => {
c.var.logger.debug(`Update profile: ${JSON.stringify(c.req.valid('json'))}`);
await this.usersService.update(c.var.session.userId, c.req.valid('json'));
const user = await this.usersRepository.findOneByIdOrThrow(c.var.session.userId);
return c.json(user);
})
.delete('/me', authState('session'), async (c) => {
await this.usersService.delete(c.var.session.userId);
await this.sessionsService.invalidateSession('');
this.sessionsService.deleteSessionCookie();
return c.json({ message: 'User deleted' });
}); });
} }
} }

View file

@ -8,7 +8,7 @@ import { DrizzleRepository } from '../common/factories/drizzle-repository.factor
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Types */ /* Types */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
type Create = Pick<InferSelectModel<typeof users_table>, 'avatar' | 'email' | 'username'>; type Create = Pick<InferSelectModel<typeof users_table>, 'avatar' | 'email' | 'username' | 'first_name' | 'last_name'>;
type Update = Partial<Create>; type Update = Partial<Create>;
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -38,7 +38,9 @@ export class UsersRepository extends DrizzleRepository {
async findOneByIdOrThrow(id: string, db = this.drizzle.db) { async findOneByIdOrThrow(id: string, db = this.drizzle.db) {
const user = await this.findOneById(id, db); const user = await this.findOneById(id, db);
if (!user) throw NotFound('User not found'); if (!user) {
throw NotFound('User not found');
}
return user; return user;
} }

View file

@ -9,27 +9,33 @@ import { UsersRepository } from './users.repository';
import { UserRolesService } from './user_roles.service'; import { UserRolesService } from './user_roles.service';
import { RoleName } from '../roles/tables/roles.table'; import { RoleName } from '../roles/tables/roles.table';
import { BadRequest } from '../common/utils/exceptions'; import { BadRequest } from '../common/utils/exceptions';
import { LoggerService } from '../common/services/logger.service';
import type { UpdateProfileDto } from '$lib/dtos/settings/profile/update-profile.dto';
@injectable() @injectable()
export class UsersService { export class UsersService {
constructor( constructor(
private drizzleService = inject(DrizzleService), private drizzleService = inject(DrizzleService),
private credentialsRepository = inject(CredentialsRepository), private credentialsRepository = inject(CredentialsRepository),
private loggerService = inject(LoggerService),
private usersRepository = inject(UsersRepository), private usersRepository = inject(UsersRepository),
private userRoleService = inject(UserRolesService), private userRoleService = inject(UserRolesService),
private storageService = inject(StorageService), private storageService = inject(StorageService),
private tokenService = inject(TokensService) private tokenService = inject(TokensService)
) {} ) {}
async update(userId: string, updateUserDto: UpdateUserDto) { async update(userId: string, updateUserDto: UpdateProfileDto) {
let key: string | null = null;
if (updateUserDto?.avatar) { if (updateUserDto?.avatar) {
const { key } = await this.storageService.upload({ file: updateUserDto.avatar }); const response = await this.storageService.upload({ file: updateUserDto.avatar });
await this.usersRepository.update(userId, { avatar: key }); key = response?.key;
} }
this.loggerService.log.info(`Updating user ${userId}, with avatar: ${key}, first_name: ${updateUserDto?.first_name}, last_name: ${updateUserDto?.last_name}`);
await this.usersRepository.update(userId, { avatar: key, first_name: updateUserDto?.first_name ?? '', last_name: updateUserDto?.last_name ?? '' });
} }
async createEmail(email: string) { async createEmail(email: string) {
return this.usersRepository.create({ avatar: null, email, username: email }); return this.usersRepository.create({ avatar: null, email, username: email, first_name: '', last_name: '' });
} }
async createWithPassword(username: string, password: string, email?: string | undefined) { async createWithPassword(username: string, password: string, email?: string | undefined) {
@ -46,7 +52,7 @@ export class UsersService {
return await this.drizzleService.db.transaction(async (trx) => { return await this.drizzleService.db.transaction(async (trx) => {
const createdUser = await this.usersRepository.create( const createdUser = await this.usersRepository.create(
{ username, email: email || '', avatar: null }, { username, email: email || '', avatar: null, first_name: '', last_name: '' },
trx, trx,
); );
@ -104,4 +110,12 @@ export class UsersService {
const { password } = data; const { password } = data;
return this.tokenService.verifyHashedToken(password, credential.secret_data); return this.tokenService.verifyHashedToken(password, credential.secret_data);
} }
async delete(userId: string) {
return await this.drizzleService.db.transaction(async (trx) => {
const deletedUser = await this.usersRepository.delete(userId, trx);
// await this.userRoleService.removeAllRolesFromUser(userId, trx);
return deletedUser;
});
}
} }

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/state';
import { cn } from '@/utils/ui'; import { cn } from '@/utils/ui';
let { children } = $props(); let { children } = $props();
@ -17,11 +17,11 @@
> >
<a <a
href="/settings" href="/settings"
class={cn($page.url.pathname === '/settings' && 'font-semibold text-primary')}>Profile</a class={cn(page.url.pathname === '/settings' && 'font-semibold text-primary')}>Profile</a
> >
<a <a
href="/settings/account" href="/settings/account"
class={cn($page.url.pathname === '/settings/account' && 'font-semibold text-primary')} class={cn(page.url.pathname === '/settings/account' && 'font-semibold text-primary')}
>Account</a >Account</a
> >
</nav> </nav>

View file

@ -0,0 +1,59 @@
import { notSignedInMessage } from '$lib/utils/flashMessages';
import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types';
import { fail, message, setError, superValidate } from 'sveltekit-superforms/server';
import { zod } from 'sveltekit-superforms/adapters';
import { updateProfileDto } from '$lib/dtos/settings/profile/update-profile.dto';
import type { Actions } from '@sveltejs/kit';
export const load: PageServerLoad = async (event) => {
const { parent } = event;
const { authedUser } = await parent();
console.log('authedUser', authedUser);
if (!authedUser) {
throw redirect(302, '/login', notSignedInMessage, event);
}
const updateProfileForm = await superValidate(zod(updateProfileDto), {
defaults: {
first_name: authedUser?.first_name ?? '',
last_name: authedUser?.last_name ?? '',
username: authedUser?.username ?? '',
},
});
return {
updateProfileForm,
};
};
export const actions: Actions = {
updateProfile: async (event) => {
const { locals } = event;
const authedUser = await locals.getAuthedUser();
if (!authedUser) {
throw redirect(302, '/login', notSignedInMessage, event);
}
const form = await superValidate(event, zod(updateProfileDto));
if (!form.valid) {
return fail(400, {
form,
});
}
console.log('form data', form.data);
const { error } = await locals.api.users.me.profile.$put({ json: form.data }).then(locals.parseApiResponse);
if (error) {
return setError(form, 'username', error);
}
return message(form, { text: 'Profile updated', type: 'success' });
},
};

View file

@ -0,0 +1,7 @@
<script>
import UpdateProfileCard from './update-profile-card.svelte';
let { data } = $props();
</script>
<UpdateProfileCard updateProfileForm={data.updateProfileForm} />

View file

@ -0,0 +1,83 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card';
import * as Form from '$lib/components/ui/form';
import { Input } from '$lib/components/ui/input';
import { updateProfileDto, type UpdateProfileDto } from '$lib/dtos/settings/profile/update-profile.dto.js';
import { fileProxy, superForm } from 'sveltekit-superforms/client';
import { zodClient } from 'sveltekit-superforms/adapters';
const { updateProfileForm }: { updateProfileForm: UpdateProfileDto } = $props();
const sf_update_profile = superForm(updateProfileForm, {
validators: zodClient(updateProfileDto),
resetForm: false,
});
const {
form: updateProfileFormData,
enhance: updateProfileEnhance,
submit: updateProfileFormSubmit,
} = sf_update_profile;
const avatar = fileProxy(updateProfileFormData, 'avatar')
</script>
<svelte:head>
<title>Acme | Settings</title>
</svelte:head>
<Card.Root>
<Card.Header>
<Card.Title>Update Profile</Card.Title>
</Card.Header>
<Card.Content>
<form method="POST" action="?/updateProfile" use:updateProfileEnhance>
<h3>Your Profile</h3>
<hr class="!border-t-2 mt-2 mb-6" />
<Form.Field form={sf_update_profile} name="username">
<Form.Control>
{#snippet children({ props })}
<Form.Label for="username">Username</Form.Label>
<Input autocomplete="username" {...props} bind:value={$updateProfileFormData.username} />
{/snippet}
</Form.Control>
<Form.Description />
<Form.FieldErrors />
</Form.Field>
<Form.Field form={sf_update_profile} name="first_name">
<Form.Control>
{#snippet children({ props })}
<Form.Label for="first_name">First Name</Form.Label>
<Input {...props} bind:value={$updateProfileFormData.first_name} />
{/snippet}
</Form.Control>
<Form.Description />
<Form.FieldErrors />
</Form.Field>
<Form.Field form={sf_update_profile} name="last_name">
<Form.Control>
{#snippet children({ props })}
<Form.Label for="last_name">Last Name</Form.Label>
<Input {...props} bind:value={$updateProfileFormData.last_name} />
{/snippet}
</Form.Control>
<Form.Description />
<Form.FieldErrors />
</Form.Field>
<!-- <Form.Field form={sf_update_profile} name="avatar">
<Form.Control>
{#snippet children({ props })}
<Form.Label for="avatar">Avatar</Form.Label>
<Input type="file" accept="image/png, image/jpg, image/jpeg, image/webp" name="avatar" enctype="multipart/form-data" bind:files={$avatar} />
{/snippet}
</Form.Control>
<Form.Description />
<Form.FieldErrors />
</Form.Field> -->
</form>
</Card.Content>
<Card.Footer class="border-t px-6 py-4">
<Form.Button onclick={() => updateProfileFormSubmit()}>Update Profile</Form.Button>
</Card.Footer>
</Card.Root>