mirror of
https://github.com/BradNut/musicle-svelte
synced 2025-09-08 17:40:21 +00:00
Adding APIs, schemas for base roles etc.
This commit is contained in:
parent
12d8384fa4
commit
be1a0aecfe
41 changed files with 1441 additions and 94 deletions
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
22
|
||||
|
|
@ -14,10 +14,10 @@ export default defineConfig({
|
|||
dialect: 'postgresql',
|
||||
casing: 'snake_case',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!
|
||||
url: process.env.DATABASE_URL ?? '',
|
||||
},
|
||||
migrations: {
|
||||
table: 'migrations',
|
||||
schema: 'public'
|
||||
}
|
||||
schema: 'public',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@chromatic-com/storybook": "^3.2.3",
|
||||
"@dicebear/collection": "^9.2.2",
|
||||
"@dicebear/converter": "^9.2.2",
|
||||
"@dicebear/core": "^9.2.2",
|
||||
"@faker-js/faker": "^9.3.0",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@storybook/addon-essentials": "^8.4.7",
|
||||
|
|
@ -35,6 +38,8 @@
|
|||
"@sveltejs/enhanced-img": "^0.4.4",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tanstack/svelte-query": "^5.62.9",
|
||||
"@tanstack/svelte-query-devtools": "^5.62.9",
|
||||
"@types/cookie": "^1.0.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/pg": "^8.11.10",
|
||||
|
|
@ -48,7 +53,6 @@
|
|||
"storybook": "^8.4.7",
|
||||
"svelte": "^5.16.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-headless-table": "^0.18.3",
|
||||
"svelte-meta-tags": "^4.0.4",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
|
|
|
|||
649
pnpm-lock.yaml
649
pnpm-lock.yaml
|
|
@ -123,6 +123,15 @@ importers:
|
|||
'@chromatic-com/storybook':
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.3(react@18.3.1)(storybook@8.4.7)
|
||||
'@dicebear/collection':
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/converter':
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2
|
||||
'@dicebear/core':
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2
|
||||
'@faker-js/faker':
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
|
|
@ -162,6 +171,12 @@ importers:
|
|||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(svelte@5.16.0)(vite@6.0.6(@types/node@22.10.2)(jiti@1.21.7)(yaml@2.6.1))
|
||||
'@tanstack/svelte-query':
|
||||
specifier: ^5.62.9
|
||||
version: 5.62.9(svelte@5.16.0)
|
||||
'@tanstack/svelte-query-devtools':
|
||||
specifier: ^5.62.9
|
||||
version: 5.62.9(@tanstack/svelte-query@5.62.9(svelte@5.16.0))(svelte@5.16.0)
|
||||
'@types/cookie':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
|
|
@ -201,9 +216,6 @@ importers:
|
|||
svelte-check:
|
||||
specifier: ^4.0.0
|
||||
version: 4.1.1(picomatch@4.0.2)(svelte@5.16.0)(typescript@5.7.2)
|
||||
svelte-headless-table:
|
||||
specifier: ^0.18.3
|
||||
version: 0.18.3(svelte@5.16.0)
|
||||
svelte-meta-tags:
|
||||
specifier: ^4.0.4
|
||||
version: 4.0.4(svelte@5.16.0)(typescript@5.7.2)
|
||||
|
|
@ -342,6 +354,200 @@ packages:
|
|||
peerDependencies:
|
||||
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
|
||||
|
||||
'@dicebear/adventurer-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-XVAjhUWjav6luTZ7txz8zVJU/H0DiUy4uU1Z7IO5MDO6kWvum+If1+0OUgEWYZwM+RDI7rt2CgVP910DyZGd1w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/adventurer@9.2.2':
|
||||
resolution: {integrity: sha512-WjBXCP9EXbUul2zC3BS2/R3/4diw1uh/lU4jTEnujK1mhqwIwanFboIMzQsasNNL/xf+m3OHN7MUNJfHZ1fLZA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/avataaars-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-pRj16P27dFDBI3LtdiHUDwIXIGndHAbZf5AxaMkn6/+0X93mVQ/btVJDXyW0G96WCsyC88wKAWr6/KJotPxU6Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/avataaars@9.2.2':
|
||||
resolution: {integrity: sha512-WqJPQEt0OhBybTpI0TqU1uD1pSk9M2+VPIwvBye/dXo46b+0jHGpftmxjQwk6tX8z0+mRko8pwV5n+cWht1/+w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/big-ears-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-IPHt8fi3dv9cyfBJBZ4s8T+PhFCrQvOCf91iRHBT3iOLNPdyZpI5GNLmGiV0XMAvIDP5NvA5+f6wdoBLhYhbDA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/big-ears@9.2.2':
|
||||
resolution: {integrity: sha512-hz4UXdPq4qqZpu0YVvlqM4RDFhk5i0WgPcuwj/MOLlgTjuj63uHUhCQSk6ZiW1DQOs12qpwUBMGWVHxBRBas9g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/big-smile@9.2.2':
|
||||
resolution: {integrity: sha512-D4td0GL8or1nTNnXvZqkEXlzyqzGPWs3znOnm1HIohtFTeIwXm72Ob2lNDsaQJSJvXmVlwaQQ0CCTvyCl8Stjw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/bottts-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-lSgpqmSJtlnyxVuUgNdBwyzuA0O9xa5zRJtz7x2KyWbicXir5iYdX0MVMCkp1EDvlcxm9rGJsclktugOyakTlw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/bottts@9.2.2':
|
||||
resolution: {integrity: sha512-wugFkzw8JNWV1nftq/Wp/vmQsLAXDxrMtRK3AoMODuUpSVoP3EHRUfKS043xggOsQFvoj0HZ7kadmhn0AMLf5A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/collection@9.2.2':
|
||||
resolution: {integrity: sha512-vZAmXhPWCK3sf8Fj9/QflFC6XOLroJOT5K1HdnzHaPboEvffUQideGCrrEamnJtlH0iF0ZDXh8gqmwy2fu+yHA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/converter@9.2.2':
|
||||
resolution: {integrity: sha512-704MOowKUOGI88tOJIYFSkd2HXbkNtPdjMrPTx7j+7gAqImCBZ298KVUk0TCo36VCUfN0EtgngUuBZUxyPTadQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@dicebear/core@9.2.2':
|
||||
resolution: {integrity: sha512-ROhgHG249dPtcXgBHcqPEsDeAPRPRD/9d+tZCjLYyueO+cXDlIA8dUlxpwIVcOuZFvCyW6RJtqo8BhNAi16pIQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@dicebear/croodles-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-/4mNirxoQ+z1kHXnpDRbJ1JV1ZgXogeTeNp0MaFYxocCgHfJ7ckNM23EE1I7akoo9pqPxrKlaeNzGAjKHdS9vA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/croodles@9.2.2':
|
||||
resolution: {integrity: sha512-OzvAXQWsOgMwL3Sl+lBxCubqSOWoBJpC78c4TKnNTS21rR63TtXUyVdLLzgKVN4YHRnvMgtPf8F/W9YAgIDK4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/dylan@9.2.2':
|
||||
resolution: {integrity: sha512-s7e3XliC1YXP+Wykj+j5kwdOWFRXFzYHYk/PB4oZ1F3sJandXiG0HS4chaNu4EoP0yZgKyFMUVTGZx+o6tMaYg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/fun-emoji@9.2.2':
|
||||
resolution: {integrity: sha512-M+rYTpB3lfwz18f+/i+ggNwNWUoEj58SJqXJ1wr7Jh/4E5uL+NmJg9JGwYNaVtGbCFrKAjSaILNUWGQSFgMfog==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/glass@9.2.2':
|
||||
resolution: {integrity: sha512-imCMxcg+XScHYtQq2MUv1lCzhQSCUglMlPSezKEpXhTxgbgUpmGlSGVkOfmX5EEc7SQowKkF1W/1gNk6CXvBaQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/icons@9.2.2':
|
||||
resolution: {integrity: sha512-Tqq2OVCdS7J02DNw58xwlgLGl40sWEckbqXT3qRvIF63FfVq+wQZBGuhuiyAURcSgvsc3h2oQeYFi9iXh7HTOA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/identicon@9.2.2':
|
||||
resolution: {integrity: sha512-POVKFulIrcuZf3rdAgxYaSm2XUg/TJg3tg9zq9150reEGPpzWR7ijyJ03dzAADPzS3DExfdYVT9+z3JKwwJnTQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/initials@9.2.2':
|
||||
resolution: {integrity: sha512-/xNnsEmsstWjmF77htAOuwOMhFlP6eBVXgcgFlTl/CCH/Oc6H7t0vwX1he8KLQBBzjGpvJcvIAn4Wh9rE4D5/A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/lorelei-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-Eys9Os6nt2Xll7Mvu66CfRR2YggTopWcmFcRZ9pPdohS96kT0MsLI2iTcfZXQ51K8hvT3IbwoGc86W8n0cDxAQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/lorelei@9.2.2':
|
||||
resolution: {integrity: sha512-koXqVr/vcWUPo00VP5H6Czsit+uF1tmwd2NK7Q/e34/9Sd1f4QLLxHjjBNm/iNjCI1+UNTOvZ2Qqu0N5eo7Flw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/micah@9.2.2':
|
||||
resolution: {integrity: sha512-NCajcJV5yw8uMKiACp694w1T/UyYme2CUEzyTzWHgWnQ+drAuCcH8gpAoLWd67viNdQB/MTpNlaelUgTjmI4AQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/miniavs@9.2.2':
|
||||
resolution: {integrity: sha512-vvkWXttdw+KHF3j+9qcUFzK+P0nbNnImGjvN48wwkPIh2h08WWFq0MnoOls4IHwUJC4GXBjWtiyVoCxz6hhtOA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/notionists-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-AhOzk+lz6kB4uxGun8AJhV+W1nttnMlxmxd+5KbQ/txCIziYIaeD3il44wsAGegEpGFvAZyMYtR/jjfHcem3TA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/notionists@9.2.2':
|
||||
resolution: {integrity: sha512-Z9orRaHoj7Y9Ap4wEu8XOrFACsG1KbbBQUPV1R50uh6AHwsyNrm4cS84ICoGLvxgLNHHOae3YCjd8aMu2z19zg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/open-peeps@9.2.2':
|
||||
resolution: {integrity: sha512-6PeQDHYyjvKrGSl/gP+RE5dSYAQGKpcGnM65HorgyTIugZK7STo0W4hvEycedupZ3MCCEH8x/XyiChKM2sHXog==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/personas@9.2.2':
|
||||
resolution: {integrity: sha512-705+ObNLC0w1fcgE/Utav+8bqO+Esu53TXegpX5j7trGEoIMf2bThqJGHuhknZ3+T2az3Wr89cGyOGlI0KLzLA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/pixel-art-neutral@9.2.2':
|
||||
resolution: {integrity: sha512-CdUY77H6Aj7dKLW3hdkv7tu0XQJArUjaWoXihQxlhl3oVYplWaoyu9omYy5pl8HTqs8YgVTGljjMXYoFuK0JUw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/pixel-art@9.2.2':
|
||||
resolution: {integrity: sha512-BvbFdrpzQl04+Y9UsWP63YGug+ENGC7GMG88qbEFWxb/IqRavGa4H3D0T4Zl2PSLiw7f2Ctv98bsCQZ1PtCznQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/rings@9.2.2':
|
||||
resolution: {integrity: sha512-eD1J1k364Arny+UlvGrk12HP/XGG6WxPSm4BarFqdJGSV45XOZlwqoi7FlcMr9r9yvE/nGL8OizbwMYusEEdjw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/shapes@9.2.2':
|
||||
resolution: {integrity: sha512-e741NNWBa7fg0BjomxXa0fFPME2XCIR0FA+VHdq9AD2taTGHEPsg5x1QJhCRdK6ww85yeu3V3ucpZXdSrHVw5Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/thumbs@9.2.2':
|
||||
resolution: {integrity: sha512-FkPLDNu7n5kThLSk7lR/0cz/NkUqgGdZGfLZv6fLkGNGtv6W+e2vZaO7HCXVwIgJ+II+kImN41zVIZ6Jlll7pQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
|
|
@ -1349,6 +1555,9 @@ packages:
|
|||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@photostructure/tz-lookup@11.0.0':
|
||||
resolution: {integrity: sha512-QMV5/dWtY/MdVPXZs/EApqzyhnqDq1keYEqpS+Xj2uidyaqw2Nk/fWcsszdruIXjdqp1VoWNzsgrO6bUHU1mFw==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -1365,6 +1574,82 @@ packages:
|
|||
resolution: {integrity: sha512-B4iV6QxW//Fn17+qF1EMZRmoThIUJlCtcO85yoRDJnMyHeAthjz4ig9OTkfGGXKtQhcdPX0me75gU5K9J897+w==}
|
||||
engines: {node: '>=18.16.0'}
|
||||
|
||||
'@resvg/resvg-js-android-arm-eabi@2.6.2':
|
||||
resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@resvg/resvg-js-android-arm64@2.6.2':
|
||||
resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@resvg/resvg-js-darwin-arm64@2.6.2':
|
||||
resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@resvg/resvg-js-darwin-x64@2.6.2':
|
||||
resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
|
||||
resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@resvg/resvg-js-linux-arm64-gnu@2.6.2':
|
||||
resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@resvg/resvg-js-linux-arm64-musl@2.6.2':
|
||||
resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@resvg/resvg-js-linux-x64-gnu@2.6.2':
|
||||
resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@resvg/resvg-js-linux-x64-musl@2.6.2':
|
||||
resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@resvg/resvg-js-win32-arm64-msvc@2.6.2':
|
||||
resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@resvg/resvg-js-win32-ia32-msvc@2.6.2':
|
||||
resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@resvg/resvg-js-win32-x64-msvc@2.6.2':
|
||||
resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@resvg/resvg-js@2.6.2':
|
||||
resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.2':
|
||||
resolution: {integrity: sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==}
|
||||
engines: {node: '>=16.0.0 || 14 >= 14.17'}
|
||||
|
|
@ -1761,6 +2046,23 @@ packages:
|
|||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20'
|
||||
|
||||
'@tanstack/query-core@5.62.9':
|
||||
resolution: {integrity: sha512-lwePd8hNYhyQ4nM/iRQ+Wz2cDtspGeZZHFZmCzHJ7mfKXt+9S301fULiY2IR2byJYY6Z03T427E5PoVfMexHjw==}
|
||||
|
||||
'@tanstack/query-devtools@5.62.9':
|
||||
resolution: {integrity: sha512-b1NZzDLVf6laJsB1Cfm3ieuYzM+WqoO8qpm9v+3Etwd+Ph4zkhUMiT+wcWj5AhEPsXiRodKYiiW048VDNdBxNg==}
|
||||
|
||||
'@tanstack/svelte-query-devtools@5.62.9':
|
||||
resolution: {integrity: sha512-baESJCUDBIJIRiwfodW/j0BU8c5uADk37A0UfadOU8cWFt9M67zawKltJmut7AoEXbwLWYqTO+3HU2XKCyJEWw==}
|
||||
peerDependencies:
|
||||
'@tanstack/svelte-query': ^5.62.9
|
||||
svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0
|
||||
|
||||
'@tanstack/svelte-query@5.62.9':
|
||||
resolution: {integrity: sha512-2M/CpePioU4IRw1OOdA+/KwA+0swJAb3c0uipFOoWkuT11uC4tTe8UD/lYRQpJYFFn5hML7KYRxVOW0HH60XiA==}
|
||||
peerDependencies:
|
||||
svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0
|
||||
|
||||
'@testing-library/dom@10.4.0':
|
||||
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1800,6 +2102,9 @@ packages:
|
|||
'@types/jsonwebtoken@9.0.7':
|
||||
resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==}
|
||||
|
||||
'@types/luxon@3.4.2':
|
||||
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
|
||||
|
||||
'@types/mdx@2.0.13':
|
||||
resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==}
|
||||
|
||||
|
|
@ -2027,6 +2332,10 @@ packages:
|
|||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
batch-cluster@13.0.0:
|
||||
resolution: {integrity: sha512-EreW0Vi8TwovhYUHBXXRA5tthuU2ynGsZFlboyMJHCCUXYa2AjgwnE3ubBOJs2xJLcuXFJbi6c/8pH5+FVj8Og==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
before-after-hook@2.2.3:
|
||||
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
||||
|
||||
|
|
@ -2695,6 +3004,17 @@ packages:
|
|||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
exiftool-vendored.exe@13.0.0:
|
||||
resolution: {integrity: sha512-4zAMuFGgxZkOoyQIzZMHv1HlvgyJK3AkNqjAgm8A8V0UmOZO7yv3pH49cDV1OduzFJqgs6yQ6eG4OGydhKtxlg==}
|
||||
os: [win32]
|
||||
|
||||
exiftool-vendored.pl@13.0.1:
|
||||
resolution: {integrity: sha512-+BRRzjselpWudKR0ltAW5SUt9T82D+gzQN8DdOQUgnSVWWp7oLCeTGBRptbQz+436Ihn/mPzmo/xnf0cv/Qw1A==}
|
||||
os: ['!win32']
|
||||
|
||||
exiftool-vendored@28.8.0:
|
||||
resolution: {integrity: sha512-R7tirJLr9fWuH9JS/KFFLB+O7jNGKuPXGxREc6YybYangEudGb+X8ERsYXk9AifMiAWh/2agNfbgkbcQcF+MxA==}
|
||||
|
||||
expect-type@1.1.0:
|
||||
resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
|
@ -2898,6 +3218,10 @@ packages:
|
|||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
help-me@5.0.0:
|
||||
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
|
||||
|
||||
|
|
@ -3188,6 +3512,10 @@ packages:
|
|||
peerDependencies:
|
||||
svelte: ^3 || ^4 || ^5.0.0-next.42
|
||||
|
||||
luxon@3.5.0:
|
||||
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
lz-string@1.5.0:
|
||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||
hasBin: true
|
||||
|
|
@ -4018,16 +4346,6 @@ packages:
|
|||
svelte: ^4.0.0 || ^5.0.0-next.0
|
||||
typescript: '>=5.0.0'
|
||||
|
||||
svelte-headless-table@0.18.3:
|
||||
resolution: {integrity: sha512-1zVnqXW0dvn6ZceYa94k+ziK+w5Dj9nlWYTQGXBv2JhM0resj9w7CWpclZK1TJwAALfEeH4InPBPO87L5fr+nQ==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
svelte-keyed@2.0.0:
|
||||
resolution: {integrity: sha512-7TeEn+QbJC2OJrHiuM0T8vMBkms3DNpTE+Ir+NtnVBnBMA78aL4f1ft9t0Hn/pBbD/TnIXi4YfjFRAgtN+DZ5g==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
svelte-meta-tags@4.0.4:
|
||||
resolution: {integrity: sha512-i0vgxGreM3lXTTxLSuPQLE1n56KAFACHWIXRj7fJCTpd/5D16O97Ha/OXDZS4Lsk+D347VEK4LeMoacsftbeKw==}
|
||||
peerDependencies:
|
||||
|
|
@ -4112,11 +4430,6 @@ packages:
|
|||
typescript:
|
||||
optional: true
|
||||
|
||||
svelte-render@2.0.1:
|
||||
resolution: {integrity: sha512-RpB0SurwXm4xhjvHHtjeqMmvd645FURb79GFOotScOSqnKK5vpqBgoBPGC0pp+E/eZgDSQ9rRAdn/+N4ys1mXQ==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
svelte-sequential-preprocessor@2.0.2:
|
||||
resolution: {integrity: sha512-DIFm0kSNscVxtBmKkBiygAHB5otoqN1aVmJ3t57jZhJfCB7Np/lUSoTtSrvPFjmlBbMeOsb1VQ06cut1+rBYOg==}
|
||||
engines: {node: '>=16'}
|
||||
|
|
@ -4126,11 +4439,6 @@ packages:
|
|||
peerDependencies:
|
||||
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.1
|
||||
|
||||
svelte-subscribe@2.0.1:
|
||||
resolution: {integrity: sha512-eKXIjLxB4C7eQWPqKEdxcGfNXm2g/qJ67zmEZK/GigCZMfrTR3m7DPY93R6MX+5uoqM1FRYxl8LZ1oy4URWi2A==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
svelte-toolbelt@0.4.6:
|
||||
resolution: {integrity: sha512-k8OUvXBUifHZcAlWeY/HLg/4J0v5m2iOfOhn8fDmjt4AP8ZluaDh9eBFus9lFiLX6O5l6vKqI1dKL5wy7090NQ==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
|
|
@ -4239,6 +4547,13 @@ packages:
|
|||
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tmp-promise@3.0.3:
|
||||
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
|
||||
|
||||
tmp@0.2.3:
|
||||
resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
|
@ -4656,6 +4971,171 @@ snapshots:
|
|||
- '@chromatic-com/playwright'
|
||||
- react
|
||||
|
||||
'@dicebear/adventurer-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/adventurer@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/avataaars-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/avataaars@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/big-ears-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/big-ears@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/big-smile@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/bottts-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/bottts@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/collection@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/adventurer': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/adventurer-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/avataaars': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/avataaars-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/big-ears': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/big-ears-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/big-smile': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/bottts': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/bottts-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/core': 9.2.2
|
||||
'@dicebear/croodles': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/croodles-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/dylan': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/fun-emoji': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/glass': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/icons': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/identicon': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/initials': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/lorelei': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/lorelei-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/micah': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/miniavs': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/notionists': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/notionists-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/open-peeps': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/personas': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/pixel-art': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/pixel-art-neutral': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/rings': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/shapes': 9.2.2(@dicebear/core@9.2.2)
|
||||
'@dicebear/thumbs': 9.2.2(@dicebear/core@9.2.2)
|
||||
|
||||
'@dicebear/converter@9.2.2':
|
||||
dependencies:
|
||||
'@resvg/resvg-js': 2.6.2
|
||||
exiftool-vendored: 28.8.0
|
||||
sharp: 0.33.5
|
||||
tmp-promise: 3.0.3
|
||||
|
||||
'@dicebear/core@9.2.2':
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@dicebear/croodles-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/croodles@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/dylan@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/fun-emoji@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/glass@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/icons@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/identicon@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/initials@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/lorelei-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/lorelei@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/micah@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/miniavs@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/notionists-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/notionists@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/open-peeps@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/personas@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/pixel-art-neutral@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/pixel-art@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/rings@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/shapes@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@dicebear/thumbs@9.2.2(@dicebear/core@9.2.2)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.2.2
|
||||
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@emnapi/runtime@1.3.1':
|
||||
|
|
@ -5505,6 +5985,8 @@ snapshots:
|
|||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@photostructure/tz-lookup@11.0.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
|
|
@ -5517,6 +5999,57 @@ snapshots:
|
|||
'@poppinss/macroable@1.0.3':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-android-arm-eabi@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-android-arm64@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-darwin-arm64@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-darwin-x64@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-linux-arm64-gnu@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-linux-arm64-musl@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-linux-x64-gnu@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-linux-x64-musl@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-win32-arm64-msvc@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-win32-ia32-msvc@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js-win32-x64-msvc@2.6.2':
|
||||
optional: true
|
||||
|
||||
'@resvg/resvg-js@2.6.2':
|
||||
optionalDependencies:
|
||||
'@resvg/resvg-js-android-arm-eabi': 2.6.2
|
||||
'@resvg/resvg-js-android-arm64': 2.6.2
|
||||
'@resvg/resvg-js-darwin-arm64': 2.6.2
|
||||
'@resvg/resvg-js-darwin-x64': 2.6.2
|
||||
'@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2
|
||||
'@resvg/resvg-js-linux-arm64-gnu': 2.6.2
|
||||
'@resvg/resvg-js-linux-arm64-musl': 2.6.2
|
||||
'@resvg/resvg-js-linux-x64-gnu': 2.6.2
|
||||
'@resvg/resvg-js-linux-x64-musl': 2.6.2
|
||||
'@resvg/resvg-js-win32-arm64-msvc': 2.6.2
|
||||
'@resvg/resvg-js-win32-ia32-msvc': 2.6.2
|
||||
'@resvg/resvg-js-win32-x64-msvc': 2.6.2
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.2(rollup@4.29.1)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.29.1)
|
||||
|
|
@ -5993,6 +6526,22 @@ snapshots:
|
|||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.4.17
|
||||
|
||||
'@tanstack/query-core@5.62.9': {}
|
||||
|
||||
'@tanstack/query-devtools@5.62.9': {}
|
||||
|
||||
'@tanstack/svelte-query-devtools@5.62.9(@tanstack/svelte-query@5.62.9(svelte@5.16.0))(svelte@5.16.0)':
|
||||
dependencies:
|
||||
'@tanstack/query-devtools': 5.62.9
|
||||
'@tanstack/svelte-query': 5.62.9(svelte@5.16.0)
|
||||
esm-env: 1.2.1
|
||||
svelte: 5.16.0
|
||||
|
||||
'@tanstack/svelte-query@5.62.9(svelte@5.16.0)':
|
||||
dependencies:
|
||||
'@tanstack/query-core': 5.62.9
|
||||
svelte: 5.16.0
|
||||
|
||||
'@testing-library/dom@10.4.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
|
|
@ -6032,13 +6581,14 @@ snapshots:
|
|||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
optional: true
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/jsonwebtoken@9.0.7':
|
||||
dependencies:
|
||||
'@types/node': 22.10.2
|
||||
|
||||
'@types/luxon@3.4.2': {}
|
||||
|
||||
'@types/mdx@2.0.13': {}
|
||||
|
||||
'@types/node@22.10.2':
|
||||
|
|
@ -6287,6 +6837,8 @@ snapshots:
|
|||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
batch-cluster@13.0.0: {}
|
||||
|
||||
before-after-hook@2.2.3: {}
|
||||
|
||||
better-opn@3.0.2:
|
||||
|
|
@ -6922,6 +7474,23 @@ snapshots:
|
|||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
exiftool-vendored.exe@13.0.0:
|
||||
optional: true
|
||||
|
||||
exiftool-vendored.pl@13.0.1:
|
||||
optional: true
|
||||
|
||||
exiftool-vendored@28.8.0:
|
||||
dependencies:
|
||||
'@photostructure/tz-lookup': 11.0.0
|
||||
'@types/luxon': 3.4.2
|
||||
batch-cluster: 13.0.0
|
||||
he: 1.2.0
|
||||
luxon: 3.5.0
|
||||
optionalDependencies:
|
||||
exiftool-vendored.exe: 13.0.0
|
||||
exiftool-vendored.pl: 13.0.1
|
||||
|
||||
expect-type@1.1.0: {}
|
||||
|
||||
express-rate-limit@7.5.0(express@4.21.2):
|
||||
|
|
@ -7147,6 +7716,8 @@ snapshots:
|
|||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
help-me@5.0.0: {}
|
||||
|
||||
hono-pino@0.7.0(hono@4.6.14)(pino@9.6.0):
|
||||
|
|
@ -7419,6 +7990,8 @@ snapshots:
|
|||
dependencies:
|
||||
svelte: 5.16.0
|
||||
|
||||
luxon@3.5.0: {}
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
magic-string@0.30.17:
|
||||
|
|
@ -8270,17 +8843,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- picomatch
|
||||
|
||||
svelte-headless-table@0.18.3(svelte@5.16.0):
|
||||
dependencies:
|
||||
svelte: 5.16.0
|
||||
svelte-keyed: 2.0.0(svelte@5.16.0)
|
||||
svelte-render: 2.0.1(svelte@5.16.0)
|
||||
svelte-subscribe: 2.0.1(svelte@5.16.0)
|
||||
|
||||
svelte-keyed@2.0.0(svelte@5.16.0):
|
||||
dependencies:
|
||||
svelte: 5.16.0
|
||||
|
||||
svelte-meta-tags@4.0.4(svelte@5.16.0)(typescript@5.7.2):
|
||||
dependencies:
|
||||
schema-dts: 1.1.2(typescript@5.7.2)
|
||||
|
|
@ -8313,11 +8875,6 @@ snapshots:
|
|||
postcss-load-config: 4.0.2(postcss@8.4.49)
|
||||
typescript: 5.7.2
|
||||
|
||||
svelte-render@2.0.1(svelte@5.16.0):
|
||||
dependencies:
|
||||
svelte: 5.16.0
|
||||
svelte-subscribe: 2.0.1(svelte@5.16.0)
|
||||
|
||||
svelte-sequential-preprocessor@2.0.2:
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
|
@ -8327,10 +8884,6 @@ snapshots:
|
|||
dependencies:
|
||||
svelte: 5.16.0
|
||||
|
||||
svelte-subscribe@2.0.1(svelte@5.16.0):
|
||||
dependencies:
|
||||
svelte: 5.16.0
|
||||
|
||||
svelte-toolbelt@0.4.6(svelte@5.16.0):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
|
|
@ -8502,6 +9055,12 @@ snapshots:
|
|||
|
||||
tinyspy@3.0.2: {}
|
||||
|
||||
tmp-promise@3.0.3:
|
||||
dependencies:
|
||||
tmp: 0.2.3
|
||||
|
||||
tmp@0.2.3: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
|
|
|||
1
project.inlang/project_id
Normal file
1
project.inlang/project_id
Normal file
|
|
@ -0,0 +1 @@
|
|||
eb7d81211e6faa5e843476a33150228f5db02e3c62748d6eff4e45b709691d18
|
||||
|
|
@ -4,5 +4,7 @@ import { sequence } from '@sveltejs/kit/hooks';
|
|||
import { startServer } from '$lib/server/api';
|
||||
|
||||
const handleParaglide: Handle = i18n.handle();
|
||||
|
||||
startServer();
|
||||
|
||||
export const handle: Handle = sequence(handleParaglide);
|
||||
|
|
|
|||
16
src/lib/components/ui/card/card-content.svelte
Normal file
16
src/lib/components/ui/card/card-content.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} class={cn("p-6", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
16
src/lib/components/ui/card/card-description.svelte
Normal file
16
src/lib/components/ui/card/card-description.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p bind:this={ref} class={cn("text-muted-foreground text-sm", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
16
src/lib/components/ui/card/card-footer.svelte
Normal file
16
src/lib/components/ui/card/card-footer.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} class={cn("flex items-center p-6 pt-0", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
16
src/lib/components/ui/card/card-header.svelte
Normal file
16
src/lib/components/ui/card/card-header.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} class={cn("flex flex-col space-y-1.5 p-6 pb-0", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
25
src/lib/components/ui/card/card-title.svelte
Normal file
25
src/lib/components/ui/card/card-title.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
level = 3,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={level}
|
||||
bind:this={ref}
|
||||
class={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card.svelte
Normal file
20
src/lib/components/ui/card/card.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("bg-card text-card-foreground rounded-lg border shadow-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
22
src/lib/components/ui/card/index.ts
Normal file
22
src/lib/components/ui/card/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import Root from "./card.svelte";
|
||||
import Content from "./card-content.svelte";
|
||||
import Description from "./card-description.svelte";
|
||||
import Footer from "./card-footer.svelte";
|
||||
import Header from "./card-header.svelte";
|
||||
import Title from "./card-title.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
};
|
||||
28
src/lib/components/ui/table/index.ts
Normal file
28
src/lib/components/ui/table/index.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import Root from "./table.svelte";
|
||||
import Body from "./table-body.svelte";
|
||||
import Caption from "./table-caption.svelte";
|
||||
import Cell from "./table-cell.svelte";
|
||||
import Footer from "./table-footer.svelte";
|
||||
import Head from "./table-head.svelte";
|
||||
import Header from "./table-header.svelte";
|
||||
import Row from "./table-row.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Body,
|
||||
Caption,
|
||||
Cell,
|
||||
Footer,
|
||||
Head,
|
||||
Header,
|
||||
Row,
|
||||
//
|
||||
Root as Table,
|
||||
Body as TableBody,
|
||||
Caption as TableCaption,
|
||||
Cell as TableCell,
|
||||
Footer as TableFooter,
|
||||
Head as TableHead,
|
||||
Header as TableHeader,
|
||||
Row as TableRow,
|
||||
};
|
||||
16
src/lib/components/ui/table/table-body.svelte
Normal file
16
src/lib/components/ui/table/table-body.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tbody bind:this={ref} class={cn("[&_tr:last-child]:border-0", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</tbody>
|
||||
16
src/lib/components/ui/table/table-caption.svelte
Normal file
16
src/lib/components/ui/table/table-caption.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<caption bind:this={ref} class={cn("text-muted-foreground mt-4 text-sm", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</caption>
|
||||
20
src/lib/components/ui/table/table-cell.svelte
Normal file
20
src/lib/components/ui/table/table-cell.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLTdAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLTdAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<td
|
||||
bind:this={ref}
|
||||
class={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</td>
|
||||
16
src/lib/components/ui/table/table-footer.svelte
Normal file
16
src/lib/components/ui/table/table-footer.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tfoot bind:this={ref} class={cn("bg-muted/50 font-medium", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</tfoot>
|
||||
23
src/lib/components/ui/table/table-head.svelte
Normal file
23
src/lib/components/ui/table/table-head.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLThAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLThAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<th
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</th>
|
||||
16
src/lib/components/ui/table/table-header.svelte
Normal file
16
src/lib/components/ui/table/table-header.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<thead bind:this={ref} class={cn("[&_tr]:border-b", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</thead>
|
||||
23
src/lib/components/ui/table/table-row.svelte
Normal file
23
src/lib/components/ui/table/table-row.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tr
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tr>
|
||||
18
src/lib/components/ui/table/table.svelte
Normal file
18
src/lib/components/ui/table/table.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLTableAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLTableAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<div class="relative w-full overflow-auto">
|
||||
<table bind:this={ref} class={cn("w-full caption-bottom text-sm", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</table>
|
||||
</div>
|
||||
23
src/lib/components/user-avatar.svelte
Normal file
23
src/lib/components/user-avatar.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts" module>
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
interface Props extends HTMLAttributes<HTMLImageElement> {
|
||||
user: {
|
||||
id: string;
|
||||
avatar: string | null;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createAvatar } from '@dicebear/core';
|
||||
import { funEmoji } from '@dicebear/collection';
|
||||
|
||||
const { user, ...props }: Props = $props();
|
||||
const avatar =
|
||||
user.avatar ||
|
||||
createAvatar(funEmoji, {
|
||||
seed: user.id
|
||||
}).toDataUri();
|
||||
</script>
|
||||
|
||||
<img {...props} src={avatar} />
|
||||
|
|
@ -1 +1,3 @@
|
|||
export * from '../../roles/tables/roles.table';
|
||||
export * from '../../users/tables/user-roles.table';
|
||||
export * from '../../users/tables/users.table';
|
||||
|
|
|
|||
107
src/lib/server/api/mfa/mfa.controller.ts
Normal file
107
src/lib/server/api/mfa/mfa.controller.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { requireFullAuth, requireTempAuth } from '$lib/server/api/common/middleware/require-auth.middleware';
|
||||
import { Controller } from '$lib/server/api/common/types/controller';
|
||||
import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto';
|
||||
import { TotpService } from '$lib/server/api/mfa/totp.service';
|
||||
import { RecoveryCodesService } from '$lib/server/api/users/recovery-codes.service';
|
||||
import { UsersService } from '$lib/server/api/users/users.service';
|
||||
import { StatusCodes } from '$lib/utils/status-codes';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { inject, injectable } from '@needle-di/core';
|
||||
import { decodeBase64 } from '@oslojs/encoding';
|
||||
import { cookieExpiresAt, createSessionTokenCookie, setSessionCookie } from '../common/utils/cookies';
|
||||
import { CredentialsType } from '../databases/postgres/tables';
|
||||
import { createTwoFactorSchema } from '../dtos/create-totp.dto';
|
||||
import { LoginRequestsService } from '../login/loginrequest.service';
|
||||
|
||||
@injectable()
|
||||
export class MfaController extends Controller {
|
||||
constructor(
|
||||
private loginRequestService = inject(LoginRequestsService),
|
||||
private recoveryCodesService = inject(RecoveryCodesService),
|
||||
private totpService = inject(TotpService),
|
||||
private usersService = inject(UsersService),
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.controller
|
||||
.get('/totp', requireTempAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
c.var.logger.info(`The user ${user.id} is requesting TOTP credentials`);
|
||||
const totpCredential = await this.totpService.findOneByUserId(user.id);
|
||||
return c.json({ totpCredential });
|
||||
})
|
||||
.post('/totp', requireTempAuth, zValidator('json', createTwoFactorSchema), async (c) => {
|
||||
const user = c.var.user;
|
||||
const { key } = c.req.valid('json');
|
||||
const totpCredential = await this.totpService.create(user.id, decodeBase64(key));
|
||||
if (totpCredential) {
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: true });
|
||||
return c.json({ totpCredential });
|
||||
}
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
})
|
||||
.delete('/totp', requireFullAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
try {
|
||||
await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP);
|
||||
await this.recoveryCodesService.deleteAllRecoveryCodesByUserId(user.id);
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: false });
|
||||
console.log('TOTP deleted');
|
||||
return c.body(null, StatusCodes.NO_CONTENT);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
.get('/totp/recoveryCodes', requireFullAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
// You can only view recovery codes once and that is on creation
|
||||
const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id);
|
||||
if (existingCodes && existingCodes.length > 0) {
|
||||
console.log('Recovery Codes found', existingCodes);
|
||||
// Filter out codes that are not used and only return the code
|
||||
const codes = existingCodes.filter((code) => !code.used).map((code) => code.code);
|
||||
return c.json({ recoveryCodes: codes });
|
||||
}
|
||||
const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id);
|
||||
return c.json({ recoveryCodes });
|
||||
})
|
||||
.post('/totp/recoveryCodes', requireFullAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
try {
|
||||
const user = c.var.user;
|
||||
const { code } = c.req.valid('json');
|
||||
c.var.logger.info(`Verifying code ${code} for user ${user.id}`);
|
||||
const verified = await this.recoveryCodesService.verify(user.id, code);
|
||||
if (verified) {
|
||||
return c.json({}, StatusCodes.OK);
|
||||
}
|
||||
return c.json('Invalid code', StatusCodes.BAD_REQUEST);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
.post('/totp/verify', requireTempAuth, zValidator('json', verifyTotpDto), async (c) => {
|
||||
try {
|
||||
const user = c.var.user;
|
||||
const { code } = c.req.valid('json');
|
||||
c.var.logger.info(`Verifying code ${code} for user ${user.id}`);
|
||||
const verified = await this.totpService.verify(user.id, code);
|
||||
if (verified) {
|
||||
await this.usersService.updateUser(user.id, { mfa_enabled: true });
|
||||
const session = await this.loginRequestService.createUserSession(user.id, c.req, true, true);
|
||||
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||
console.log('set cookie', sessionCookie);
|
||||
setSessionCookie(c, sessionCookie);
|
||||
return c.json({}, StatusCodes.OK);
|
||||
}
|
||||
return c.json('Invalid code', StatusCodes.BAD_REQUEST);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return c.status(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
36
src/lib/server/api/mfa/tables/two-factor.table.ts
Normal file
36
src/lib/server/api/mfa/tables/two-factor.table.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { id, timestamps } from '../../common/utils/drizzle';
|
||||
import { usersTable } from '../../users/tables/users.table';
|
||||
import { generateId } from '../../common/utils/crypto';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Table */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const twoFactorTable = pgTable('two_factor', {
|
||||
id: id()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
secret: text().notNull(),
|
||||
enabled: boolean().notNull().default(false),
|
||||
user_id: id()
|
||||
.notNull()
|
||||
.references(() => usersTable.id)
|
||||
.unique('two_factor_user_id_unique'),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Relations */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [twoFactorTable.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Types */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export type TwoFactor = InferSelectModel<typeof twoFactorTable>;
|
||||
57
src/lib/server/api/mfa/totp.service.ts
Normal file
57
src/lib/server/api/mfa/totp.service.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { inject, injectable } from '@needle-di/core';
|
||||
import { decodeBase64, encodeBase64 } from '@oslojs/encoding';
|
||||
import { generateTOTP, verifyTOTP } from '@oslojs/otp';
|
||||
import { EncryptionService } from '../common/services/encryption.service';
|
||||
import type { CredentialsType } from '../databases/postgres/tables';
|
||||
import { CredentialsRepository } from '../users/credentials.repository';
|
||||
|
||||
@injectable()
|
||||
export class TotpService {
|
||||
constructor(
|
||||
private credentialsRepository = inject(CredentialsRepository),
|
||||
private encryptionService = inject(EncryptionService),
|
||||
) {}
|
||||
|
||||
async findOneByUserId(userId: string) {
|
||||
return this.credentialsRepository.findTOTPCredentialsByUserId(userId);
|
||||
}
|
||||
|
||||
async findOneByUserIdOrThrow(userId: string) {
|
||||
const credential = await this.findOneByUserId(userId);
|
||||
if (!credential) {
|
||||
throw new Error('TOTP credential not found');
|
||||
}
|
||||
return credential;
|
||||
}
|
||||
|
||||
async create(userId: string, key: Uint8Array) {
|
||||
try {
|
||||
return await this.credentialsRepository.create({
|
||||
user_id: userId,
|
||||
secret_data: encodeBase64(this.encryptionService.encrypt(key)),
|
||||
type: 'totp',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteOneByUserId(userId: string) {
|
||||
return this.credentialsRepository.deleteByUserId(userId);
|
||||
}
|
||||
|
||||
async deleteOneByUserIdAndType(userId: string, type: CredentialsType) {
|
||||
return this.credentialsRepository.deleteByUserIdAndType(userId, type);
|
||||
}
|
||||
|
||||
async verify(userId: string, code: string) {
|
||||
const credential = await this.credentialsRepository.findTOTPCredentialsByUserId(userId);
|
||||
console.log(`TOTP credential: ${JSON.stringify(credential)}`);
|
||||
if (!credential) {
|
||||
throw new Error('TOTP credential not found');
|
||||
}
|
||||
const secret = this.encryptionService.decrypt(decodeBase64(credential.secret_data));
|
||||
return verifyTOTP(secret, 30, 6, code);
|
||||
}
|
||||
}
|
||||
38
src/lib/server/api/roles/tables/roles.table.ts
Normal file
38
src/lib/server/api/roles/tables/roles.table.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { pgTable, text } from 'drizzle-orm/pg-core';
|
||||
import { user_roles } from '../../users/tables/user-roles.table';
|
||||
import { timestamps } from '../../common/utils/drizzle';
|
||||
import { id } from '../../common/utils/drizzle';
|
||||
import { generateId } from '../../common/utils/crypto';
|
||||
|
||||
export enum RoleName {
|
||||
ADMIN = 'admin',
|
||||
EDITOR = 'editor',
|
||||
MODERATOR = 'moderator',
|
||||
USER = 'user',
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Table */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const rolesTable = pgTable('roles', {
|
||||
id: id()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
name: text().unique().notNull(),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Relations */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const role_relations = relations(rolesTable, ({ many }) => ({
|
||||
user_roles: many(user_roles),
|
||||
}));
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Types */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export type Roles = InferSelectModel<typeof rolesTable>;
|
||||
|
||||
|
||||
29
src/lib/server/api/users/tables/credentials.table.ts
Normal file
29
src/lib/server/api/users/tables/credentials.table.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { InferSelectModel } from 'drizzle-orm';
|
||||
import { pgTable, text } from 'drizzle-orm/pg-core';
|
||||
import { id, timestamps } from '../../common/utils/drizzle';
|
||||
import { usersTable } from './users.table';
|
||||
import { generateId } from '../../common/utils/crypto';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Table */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export enum CredentialsType {
|
||||
SECRET = 'secret',
|
||||
PASSWORD = 'password',
|
||||
TOTP = 'totp',
|
||||
HOTP = 'hotp',
|
||||
}
|
||||
|
||||
export const credentialsTable = pgTable('credentials', {
|
||||
id: id()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
user_id: id()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
type: text().notNull().default(CredentialsType.PASSWORD),
|
||||
secret_data: text().notNull(),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
export type Credentials = InferSelectModel<typeof credentialsTable>;
|
||||
50
src/lib/server/api/users/tables/user-roles.table.ts
Normal file
50
src/lib/server/api/users/tables/user-roles.table.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { type InferSelectModel, relations, getTableColumns } from 'drizzle-orm';
|
||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { rolesTable } from '../../roles/tables/roles.table';
|
||||
import { usersTable } from './users.table';
|
||||
import { generateId } from '../../common/utils/crypto';
|
||||
import { id, timestamps } from '../../common/utils/drizzle';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Table */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const user_roles = pgTable('user_roles', {
|
||||
id: id()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
user_id: uuid()
|
||||
.notNull()
|
||||
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
||||
role_id: uuid()
|
||||
.notNull()
|
||||
.references(() => rolesTable.id, { onDelete: 'cascade' }),
|
||||
primary: boolean().default(false),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Relations */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const user_role_relations = relations(user_roles, ({ one }) => ({
|
||||
role: one(rolesTable, {
|
||||
fields: [user_roles.role_id],
|
||||
references: [rolesTable.id],
|
||||
}),
|
||||
user: one(usersTable, {
|
||||
fields: [user_roles.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Types */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export type UserRoles = InferSelectModel<typeof user_roles>;
|
||||
export type UserRolesWithRelations = UserRoles & {};
|
||||
|
||||
const userRolesColumns = getTableColumns(user_roles);
|
||||
|
||||
export const publicUserColumns = {
|
||||
id: userRolesColumns.id,
|
||||
...timestamps,
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { pgTable, text } from 'drizzle-orm/pg-core';
|
||||
import { boolean, pgTable, text } from 'drizzle-orm/pg-core';
|
||||
import { getTableColumns, type InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { citext, id, timestamps } from '../../common/utils/drizzle';
|
||||
import { generateId } from '../../common/utils/crypto';
|
||||
import { user_roles } from './user-roles.table';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Table */
|
||||
|
|
@ -10,15 +11,22 @@ export const usersTable = pgTable('users', {
|
|||
id: id()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
username: text().unique(),
|
||||
email: citext().unique().notNull(),
|
||||
first_name: text(),
|
||||
last_name: text(),
|
||||
email_verified: boolean().default(false),
|
||||
mfa_enabled: boolean().notNull().default(false),
|
||||
avatar: text(),
|
||||
...timestamps
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Relations */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const usersRelations = relations(usersTable, () => ({}));
|
||||
export const userRelations = relations(usersTable, ({ many }) => ({
|
||||
user_roles: many(user_roles),
|
||||
}));
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Types */
|
||||
|
|
@ -30,7 +38,10 @@ const userColumns = getTableColumns(usersTable);
|
|||
|
||||
export const publicUserColumns = {
|
||||
id: userColumns.id,
|
||||
username: userColumns.username,
|
||||
email: userColumns.email,
|
||||
avatar: userColumns.avatar,
|
||||
...timestamps
|
||||
first_name: userColumns.first_name,
|
||||
last_name: userColumns.last_name,
|
||||
...timestamps,
|
||||
};
|
||||
|
|
|
|||
34
src/lib/tanstack-query/iam.ts
Normal file
34
src/lib/tanstack-query/iam.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import type { InferRequestType } from 'hono';
|
||||
import { parseApiResponse } from '$lib/utils/api';
|
||||
import type { Api, ApiMutation } from '$lib/utils/types';
|
||||
import { TanstackQueryModule } from './query-module';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Types */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type RequestLogin = Api['iam']['login']['request']['$post'];
|
||||
type VerifyLogin = Api['iam']['login']['verify']['$post'];
|
||||
type Logout = Api['iam']['logout']['$post'];
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Api */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export class IamModule extends TanstackQueryModule<'iam'> {
|
||||
logout(): ApiMutation<Logout> {
|
||||
return {
|
||||
mutationFn: async () => await this.api.iam.logout.$post().then(parseApiResponse)
|
||||
};
|
||||
}
|
||||
requestLogin(): ApiMutation<RequestLogin> {
|
||||
return {
|
||||
mutationFn: async (data: InferRequestType<RequestLogin>) =>
|
||||
await this.api.iam.login.request.$post(data).then(parseApiResponse)
|
||||
};
|
||||
}
|
||||
verifyLogin(): ApiMutation<VerifyLogin> {
|
||||
return {
|
||||
mutationFn: async (data: InferRequestType<VerifyLogin>) =>
|
||||
await this.api.iam.login.verify.$post(data).then(parseApiResponse)
|
||||
};
|
||||
}
|
||||
}
|
||||
11
src/lib/tanstack-query/index.ts
Normal file
11
src/lib/tanstack-query/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { IamModule } from './iam';
|
||||
import type { ClientRequestOptions } from 'hono';
|
||||
import { UsersModule } from './users';
|
||||
import { TanstackQueryModule } from './query-module';
|
||||
|
||||
class TanstackQueryHandler extends TanstackQueryModule {
|
||||
iam = new IamModule(this.opts);
|
||||
users = new UsersModule(this.opts);
|
||||
}
|
||||
|
||||
export const queryHandler = (opts?: ClientRequestOptions) => new TanstackQueryHandler(opts);
|
||||
13
src/lib/tanstack-query/query-module.ts
Normal file
13
src/lib/tanstack-query/query-module.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import type { ClientRequestOptions } from 'hono';
|
||||
import { api } from '$lib/utils/api';
|
||||
|
||||
export abstract class TanstackQueryModule<T extends string | null = null> {
|
||||
protected readonly opts: ClientRequestOptions | undefined;
|
||||
protected readonly api: ReturnType<typeof api>;
|
||||
public namespace: T | null = null;
|
||||
|
||||
constructor(opts?: ClientRequestOptions) {
|
||||
this.opts = opts;
|
||||
this.api = api(opts);
|
||||
}
|
||||
}
|
||||
37
src/lib/tanstack-query/users.ts
Normal file
37
src/lib/tanstack-query/users.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { parseApiResponse } from '$lib/utils/api';
|
||||
import type { Api, ApiMutation, ApiQuery } from '$lib/utils/types';
|
||||
import type { InferRequestType } from 'hono';
|
||||
import { TanstackQueryModule } from './query-module';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Types */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type Me = Api['users']['me']['$get'];
|
||||
type UpdateEmailRequest = Api['users']['me']['email']['request']['$post'];
|
||||
type VerifyEmailRequest = Api['users']['me']['email']['verify']['$post'];
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Api */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export class UsersModule extends TanstackQueryModule<'users'> {
|
||||
me(): ApiQuery<Me> {
|
||||
return {
|
||||
queryKey: [this.namespace, 'me'],
|
||||
queryFn: async () => await this.api.users.me.$get().then(parseApiResponse)
|
||||
};
|
||||
}
|
||||
|
||||
updateEmailRequest(): ApiMutation<UpdateEmailRequest> {
|
||||
return {
|
||||
mutationFn: async (args: InferRequestType<UpdateEmailRequest>) =>
|
||||
await this.api.users.me.email.request.$post(args).then(parseApiResponse)
|
||||
};
|
||||
}
|
||||
|
||||
verifyEmailRequest(): ApiMutation<VerifyEmailRequest> {
|
||||
return {
|
||||
mutationFn: async (args: InferRequestType<VerifyEmailRequest>) =>
|
||||
await this.api.users.me.email.verify.$post(args).then(parseApiResponse)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,28 @@
|
|||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Sheet from '$lib/components/ui/sheet/index.js';
|
||||
import ThemeDropdown from '@/components/theme-dropdown.svelte';
|
||||
import { createMutation } from '@tanstack/svelte-query';
|
||||
import { authContext } from '$lib/hooks/session.svelte.js';
|
||||
import { queryHandler } from '$lib/tanstack-query/index.js';
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import UserAvatar from '$lib/components/user-avatar.svelte';
|
||||
import ThemeDropdown from '$lib/components/theme-dropdown.svelte';
|
||||
|
||||
const { children, data } = $props();
|
||||
|
||||
$effect.pre(() => {
|
||||
authContext.setAuthedUser(data.authedUser);
|
||||
});
|
||||
|
||||
const logoutMutation = createMutation({
|
||||
...queryHandler().iam.logout(),
|
||||
onSuccess: async () => {
|
||||
await data.queryClient.invalidateQueries();
|
||||
invalidateAll();
|
||||
goto('/login');
|
||||
}
|
||||
});
|
||||
queryHandler;
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen w-full flex-col">
|
||||
|
|
@ -71,7 +90,7 @@
|
|||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="secondary" size="icon" class="rounded-lg">
|
||||
<!-- <UserAvatar class="h-8 w-8 rounded-lg" user={data.authedUser} /> -->
|
||||
<UserAvatar class="h-8 w-8 rounded-lg" user={data.authedUser} />
|
||||
<span class="sr-only">Toggle user menu</span>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
|
|
@ -82,7 +101,7 @@
|
|||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item >Logout</DropdownMenu.Item>
|
||||
<!-- onclick={$logoutMutation.mutate} -->
|
||||
onclick={$logoutMutation.mutate}
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
|
|
|||
7
src/routes/(app)/+layout.ts
Normal file
7
src/routes/(app)/+layout.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { queryHandler } from '$lib/tanstack-query';
|
||||
|
||||
export const load = async ({ parent, fetch }) => {
|
||||
const { queryClient } = await parent();
|
||||
const authedUser = await queryClient.fetchQuery(queryHandler({ fetch }).users.me());
|
||||
return { authedUser };
|
||||
};
|
||||
|
|
@ -1,14 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { i18n } from "$lib/i18n";
|
||||
import { QueryClientProvider } from "@tanstack/svelte-query";
|
||||
import { SvelteQueryDevtools } from "@tanstack/svelte-query-devtools";
|
||||
import { ParaglideJS } from "@inlang/paraglide-sveltekit";
|
||||
import { ModeWatcher } from "mode-watcher";
|
||||
import '../app.css';
|
||||
let { children } = $props();
|
||||
import "../app.css";
|
||||
|
||||
let { data, children } = $props();
|
||||
</script>
|
||||
|
||||
<ParaglideJS {i18n}>
|
||||
<QueryClientProvider client={data.queryClient}>
|
||||
<ParaglideJS {i18n}>
|
||||
<ModeWatcher />
|
||||
<main>
|
||||
{@render children()}
|
||||
</main>
|
||||
</ParaglideJS>
|
||||
<SvelteQueryDevtools />
|
||||
</ParaglideJS>
|
||||
</QueryClientProvider>
|
||||
|
|
|
|||
15
src/routes/+layout.ts
Normal file
15
src/routes/+layout.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { browser } from '$app/environment';
|
||||
import { QueryClient } from '@tanstack/svelte-query';
|
||||
|
||||
export const load = async () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
enabled: browser,
|
||||
staleTime: 60 * 1000
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { queryClient };
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
<a href="/demo/paraglide">paraglide</a>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { AvailableLanguageTag } from '$lib/paraglide/runtime';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
|
||||
function switchToLanguage(newLanguage: AvailableLanguageTag) {
|
||||
const canonicalPath = i18n.route($page.url.pathname);
|
||||
const localisedPath = i18n.resolveRoute(canonicalPath, newLanguage);
|
||||
goto(localisedPath);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>
|
||||
<div>
|
||||
<button onclick={() => switchToLanguage('en')}>en</button>
|
||||
<button onclick={() => switchToLanguage('es')}>es</button>
|
||||
</div>
|
||||
Loading…
Reference in a new issue