Compare commits
8 commits
fce8817541
...
13f972b1f1
Author | SHA1 | Date | |
---|---|---|---|
|
13f972b1f1 | ||
|
b7202656d1 | ||
|
e658974929 | ||
|
2291e12b3a | ||
|
7321d48266 | ||
|
f1dea1016a | ||
|
664ce0a29b | ||
|
5b47fd7b53 |
23 changed files with 708 additions and 288 deletions
11
package.json
11
package.json
|
@ -18,12 +18,14 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"lucide-svelte": "^0.279.0",
|
||||||
"marked": "^7.0.1",
|
"marked": "^7.0.1",
|
||||||
"svelte-boring-avatars": "^1.2.4",
|
"svelte-boring-avatars": "^1.2.4",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0"
|
||||||
"zod": "^3.21.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@melt-ui/pp": "^0.1.2",
|
||||||
|
"@melt-ui/svelte": "^0.50.0",
|
||||||
"@playwright/test": "^1.28.1",
|
"@playwright/test": "^1.28.1",
|
||||||
"@sveltejs/adapter-auto": "^2.0.0",
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
"@sveltejs/adapter-node": "^1.3.1",
|
"@sveltejs/adapter-node": "^1.3.1",
|
||||||
|
@ -41,10 +43,13 @@
|
||||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
"svelte": "^4.0.5",
|
"svelte": "^4.0.5",
|
||||||
"svelte-check": "^3.4.3",
|
"svelte-check": "^3.4.3",
|
||||||
|
"svelte-sequential-preprocessor": "^2.0.1",
|
||||||
|
"sveltekit-superforms": "^1.7.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vite": "^4.4.2",
|
"vite": "^4.4.2",
|
||||||
"vitest": "^0.32.2"
|
"vitest": "^0.32.2",
|
||||||
|
"zod": "^3.21.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
126
pnpm-lock.yaml
generated
126
pnpm-lock.yaml
generated
|
@ -11,6 +11,9 @@ dependencies:
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
lucide-svelte:
|
||||||
|
specifier: ^0.279.0
|
||||||
|
version: 0.279.0(svelte@4.1.1)
|
||||||
marked:
|
marked:
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.1
|
version: 7.0.1
|
||||||
|
@ -20,11 +23,14 @@ dependencies:
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^1.14.0
|
specifier: ^1.14.0
|
||||||
version: 1.14.0
|
version: 1.14.0
|
||||||
zod:
|
|
||||||
specifier: ^3.21.4
|
|
||||||
version: 3.21.4
|
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@melt-ui/pp':
|
||||||
|
specifier: ^0.1.2
|
||||||
|
version: 0.1.2(@melt-ui/svelte@0.50.0)(svelte@4.1.1)
|
||||||
|
'@melt-ui/svelte':
|
||||||
|
specifier: ^0.50.0
|
||||||
|
version: 0.50.0(svelte@4.1.1)
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.28.1
|
specifier: ^1.28.1
|
||||||
version: 1.36.2
|
version: 1.36.2
|
||||||
|
@ -76,6 +82,12 @@ devDependencies:
|
||||||
svelte-check:
|
svelte-check:
|
||||||
specifier: ^3.4.3
|
specifier: ^3.4.3
|
||||||
version: 3.4.6(postcss@8.4.27)(svelte@4.1.1)
|
version: 3.4.6(postcss@8.4.27)(svelte@4.1.1)
|
||||||
|
svelte-sequential-preprocessor:
|
||||||
|
specifier: ^2.0.1
|
||||||
|
version: 2.0.1
|
||||||
|
sveltekit-superforms:
|
||||||
|
specifier: ^1.7.0
|
||||||
|
version: 1.7.0(@sveltejs/kit@1.22.3)(svelte@4.1.1)(zod@3.21.4)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
@ -91,6 +103,9 @@ devDependencies:
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.32.2
|
specifier: ^0.32.2
|
||||||
version: 0.32.4
|
version: 0.32.4
|
||||||
|
zod:
|
||||||
|
specifier: ^3.21.4
|
||||||
|
version: 3.21.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
@ -110,7 +125,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/gen-mapping': 0.3.3
|
'@jridgewell/gen-mapping': 0.3.3
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@esbuild/android-arm64@0.18.17:
|
/@esbuild/android-arm64@0.18.17:
|
||||||
resolution: {integrity: sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==}
|
resolution: {integrity: sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==}
|
||||||
|
@ -347,6 +361,23 @@ packages:
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@floating-ui/core@1.5.0:
|
||||||
|
resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/utils': 0.1.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@floating-ui/dom@1.5.3:
|
||||||
|
resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.5.0
|
||||||
|
'@floating-ui/utils': 0.1.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@floating-ui/utils@0.1.3:
|
||||||
|
resolution: {integrity: sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@humanwhocodes/config-array@0.11.10:
|
/@humanwhocodes/config-array@0.11.10:
|
||||||
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
|
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
|
||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
|
@ -381,31 +412,49 @@ packages:
|
||||||
'@jridgewell/set-array': 1.1.2
|
'@jridgewell/set-array': 1.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/resolve-uri@3.1.0:
|
/@jridgewell/resolve-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/set-array@1.1.2:
|
/@jridgewell/set-array@1.1.2:
|
||||||
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/sourcemap-codec@1.4.14:
|
/@jridgewell/sourcemap-codec@1.4.14:
|
||||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/sourcemap-codec@1.4.15:
|
/@jridgewell/sourcemap-codec@1.4.15:
|
||||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@jridgewell/trace-mapping@0.3.18:
|
/@jridgewell/trace-mapping@0.3.18:
|
||||||
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': 3.1.0
|
'@jridgewell/resolve-uri': 3.1.0
|
||||||
'@jridgewell/sourcemap-codec': 1.4.14
|
'@jridgewell/sourcemap-codec': 1.4.14
|
||||||
|
|
||||||
|
/@melt-ui/pp@0.1.2(@melt-ui/svelte@0.50.0)(svelte@4.1.1):
|
||||||
|
resolution: {integrity: sha512-GZeqp7UWLNZUC2dJpREnZrWMR88vy27WO7C3cIBz4KW3/CFD19FjNkd3VbSRfcRryrMkdnEs9nu2VUa8/0u58w==}
|
||||||
|
engines: {pnpm: '>=8.6.3'}
|
||||||
|
peerDependencies:
|
||||||
|
'@melt-ui/svelte': '>= 0.29.0'
|
||||||
|
svelte: ^3.55.0 || ^4.0.0
|
||||||
|
dependencies:
|
||||||
|
'@melt-ui/svelte': 0.50.0(svelte@4.1.1)
|
||||||
|
svelte: 4.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@melt-ui/svelte@0.50.0(svelte@4.1.1):
|
||||||
|
resolution: {integrity: sha512-NcWwxwStXq77/yOuBfnGkuJdUta3M4SwqZECdaRpAQ61BHI3qz7WW2ZM42JmDvGSs9W6ww2kZFNF8XNTO92CdA==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: '>=3 <5'
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.5.0
|
||||||
|
'@floating-ui/dom': 1.5.3
|
||||||
|
dequal: 2.0.3
|
||||||
|
focus-trap: 7.5.2
|
||||||
|
nanoid: 4.0.2
|
||||||
|
svelte: 4.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@nodelib/fs.scandir@2.1.5:
|
/@nodelib/fs.scandir@2.1.5:
|
||||||
|
@ -612,7 +661,6 @@ packages:
|
||||||
|
|
||||||
/@types/estree@1.0.1:
|
/@types/estree@1.0.1:
|
||||||
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
|
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/json-schema@7.0.12:
|
/@types/json-schema@7.0.12:
|
||||||
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
||||||
|
@ -823,7 +871,6 @@ packages:
|
||||||
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
|
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ajv@6.12.6:
|
/ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
|
@ -875,7 +922,6 @@ packages:
|
||||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/array-union@2.1.0:
|
/array-union@2.1.0:
|
||||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||||
|
@ -906,7 +952,6 @@ packages:
|
||||||
resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
|
resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/balanced-match@1.0.2:
|
/balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
@ -1042,7 +1087,6 @@ packages:
|
||||||
acorn: 8.10.0
|
acorn: 8.10.0
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
periscopic: 3.1.0
|
periscopic: 3.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/color-convert@2.0.1:
|
/color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
|
@ -1088,7 +1132,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
mdn-data: 2.0.30
|
mdn-data: 2.0.30
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/cssesc@3.0.0:
|
/cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
|
@ -1127,7 +1170,6 @@ packages:
|
||||||
/dequal@2.0.3:
|
/dequal@2.0.3:
|
||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/detect-indent@6.1.0:
|
/detect-indent@6.1.0:
|
||||||
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
||||||
|
@ -1362,7 +1404,6 @@ packages:
|
||||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.1
|
'@types/estree': 1.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/esutils@2.0.3:
|
/esutils@2.0.3:
|
||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
|
@ -1432,6 +1473,12 @@ packages:
|
||||||
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/focus-trap@7.5.2:
|
||||||
|
resolution: {integrity: sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==}
|
||||||
|
dependencies:
|
||||||
|
tabbable: 6.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/fraction.js@4.2.0:
|
/fraction.js@4.2.0:
|
||||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1631,7 +1678,6 @@ packages:
|
||||||
resolution: {integrity: sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==}
|
resolution: {integrity: sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.1
|
'@types/estree': 1.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/isexe@2.0.0:
|
/isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
@ -1694,7 +1740,6 @@ packages:
|
||||||
|
|
||||||
/locate-character@3.0.0:
|
/locate-character@3.0.0:
|
||||||
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/locate-path@6.0.0:
|
/locate-path@6.0.0:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
|
@ -1720,6 +1765,14 @@ packages:
|
||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lucide-svelte@0.279.0(svelte@4.1.1):
|
||||||
|
resolution: {integrity: sha512-u9j8tMPxWsv5iXJvrUU/jpyML/k49flr7440UE8QM9V3u0OZt5+qaY5TMiPDTVRMdEELBg4d4ueW1+3Mo3VT4A==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: '>=3 <5'
|
||||||
|
dependencies:
|
||||||
|
svelte: 4.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/magic-string@0.27.0:
|
/magic-string@0.27.0:
|
||||||
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -1732,7 +1785,6 @@ packages:
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
dev: true
|
|
||||||
|
|
||||||
/marked@7.0.1:
|
/marked@7.0.1:
|
||||||
resolution: {integrity: sha512-m8Aze620Ts62yaciz2DghZGUkUfdgvSNRicS2/XtQkStMNoce3NWjOD2b/jWF32+XXK6udM6pRhv2dKNlneAFA==}
|
resolution: {integrity: sha512-m8Aze620Ts62yaciz2DghZGUkUfdgvSNRicS2/XtQkStMNoce3NWjOD2b/jWF32+XXK6udM6pRhv2dKNlneAFA==}
|
||||||
|
@ -1742,7 +1794,6 @@ packages:
|
||||||
|
|
||||||
/mdn-data@2.0.30:
|
/mdn-data@2.0.30:
|
||||||
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/merge2@1.4.1:
|
/merge2@1.4.1:
|
||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
|
@ -1829,6 +1880,12 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/nanoid@4.0.2:
|
||||||
|
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
||||||
|
engines: {node: ^14 || ^16 || >=18}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/natural-compare-lite@1.4.0:
|
/natural-compare-lite@1.4.0:
|
||||||
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
|
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1945,7 +2002,6 @@ packages:
|
||||||
'@types/estree': 1.0.1
|
'@types/estree': 1.0.1
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
is-reference: 3.0.1
|
is-reference: 3.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/picocolors@1.0.0:
|
/picocolors@1.0.0:
|
||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
|
@ -2315,7 +2371,6 @@ packages:
|
||||||
/source-map-js@1.0.2:
|
/source-map-js@1.0.2:
|
||||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/stackback@0.0.2:
|
/stackback@0.0.2:
|
||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
@ -2486,6 +2541,14 @@ packages:
|
||||||
typescript: 5.1.6
|
typescript: 5.1.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/svelte-sequential-preprocessor@2.0.1:
|
||||||
|
resolution: {integrity: sha512-N5JqlBni6BzElxmuFrOPxOJnjsxh1cFDACLEVKs8OHBcx8ZNRO1p5SxuQex1m3qbLzAC8G99EHeWcxGkjyKjLQ==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
dependencies:
|
||||||
|
svelte: 4.1.1
|
||||||
|
tslib: 2.6.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/svelte@4.1.1:
|
/svelte@4.1.1:
|
||||||
resolution: {integrity: sha512-Enick5fPFISLoVy0MFK45cG+YlQt6upw8skEK9zzTpJnH1DqEv8xOZwizCGSo3Q6HZ7KrZTM0J18poF7aQg5zw==}
|
resolution: {integrity: sha512-Enick5fPFISLoVy0MFK45cG+YlQt6upw8skEK9zzTpJnH1DqEv8xOZwizCGSo3Q6HZ7KrZTM0J18poF7aQg5zw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
@ -2503,6 +2566,21 @@ packages:
|
||||||
locate-character: 3.0.0
|
locate-character: 3.0.0
|
||||||
magic-string: 0.30.1
|
magic-string: 0.30.1
|
||||||
periscopic: 3.1.0
|
periscopic: 3.1.0
|
||||||
|
|
||||||
|
/sveltekit-superforms@1.7.0(@sveltejs/kit@1.22.3)(svelte@4.1.1)(zod@3.21.4):
|
||||||
|
resolution: {integrity: sha512-mHfQp2sps2K55zsf3GdowgdokYDl5N4IOanHL/Ydi7F+YL5I9Ry2CrZoCIB4uPaX0/hUKW5DbAcHPLGq+sAAaQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@sveltejs/kit': 1.x
|
||||||
|
svelte: 3.x || 4.x
|
||||||
|
zod: 3.x
|
||||||
|
dependencies:
|
||||||
|
'@sveltejs/kit': 1.22.3(svelte@4.1.1)(vite@4.4.7)
|
||||||
|
svelte: 4.1.1
|
||||||
|
zod: 3.21.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/tabbable@6.2.0:
|
||||||
|
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tailwind-merge@1.14.0:
|
/tailwind-merge@1.14.0:
|
||||||
|
@ -2841,4 +2919,4 @@ packages:
|
||||||
|
|
||||||
/zod@3.21.4:
|
/zod@3.21.4:
|
||||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||||
dev: false
|
dev: true
|
||||||
|
|
|
@ -19,5 +19,5 @@
|
||||||
width={500}
|
width={500}
|
||||||
height={500}
|
height={500}
|
||||||
/>
|
/>
|
||||||
<span class="text-sm font-semibold">{name}</span>
|
<span class="text-sm font-semibold break-all">{name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="z-50 flex w-full flex-row items-center justify-between border-b border-solid border-highlight-primary px-4 sm:px-8 py-4"
|
class="z-50 flex w-full flex-row items-center justify-between border-b border-solid border-highlight-primary px-4 py-4 sm:px-8"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-center space-x-2 sm:space-x-0">
|
<div class="flex flex-row items-center space-x-2 sm:space-x-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
@ -50,8 +50,19 @@
|
||||||
{/if} -->
|
{/if} -->
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<Avatar />
|
{#if !isOpen}
|
||||||
{user?.pseudo}
|
<a href="/logout">
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-1 rounded-md p-2 text-destructive hover:bg-destructive/10"
|
||||||
|
>
|
||||||
|
Se déconnecter
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<Avatar />
|
||||||
|
{user?.pseudo}
|
||||||
|
</div>
|
||||||
<!-- {!isLoading && me ? (
|
<!-- {!isLoading && me ? (
|
||||||
<Popover
|
<Popover
|
||||||
open={isMenuOpen}
|
open={isMenuOpen}
|
||||||
|
|
|
@ -117,20 +117,14 @@
|
||||||
<div class="px-4 pt-4">
|
<div class="px-4 pt-4">
|
||||||
<ul class="flex flex-col gap-4">
|
<ul class="flex flex-col gap-4">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<span
|
||||||
on:click={() => {
|
class="group pointer-events-none flex justify-center rounded-md px-3 py-3 text-sm opacity-50 transition-colors duration-150 lg:justify-start"
|
||||||
isOpen = false;
|
|
||||||
}}
|
|
||||||
href="/dashboard/help"
|
|
||||||
class="group flex justify-center rounded-md px-3 py-3 text-sm transition-colors duration-150 hover:bg-primary-700 lg:justify-start"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Help
|
<Help class="stroke-highlight-secondary transition-colors duration-150" />
|
||||||
class="stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0"
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
class={cn(
|
class={cn(
|
||||||
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
'hidden text-highlight-secondary transition-colors duration-150 lg:block',
|
||||||
{
|
{
|
||||||
'block sm:hidden': isOpen,
|
'block sm:hidden': isOpen,
|
||||||
hidden: !isOpen
|
hidden: !isOpen
|
||||||
|
@ -140,7 +134,7 @@
|
||||||
Aide
|
Aide
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
|
55
src/lib/components/Toaster.svelte
Normal file
55
src/lib/components/Toaster.svelte
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
export type ToastData = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
elements: { content, title, description, close },
|
||||||
|
helpers,
|
||||||
|
states: { toasts },
|
||||||
|
actions: { portal }
|
||||||
|
} = createToaster<ToastData>();
|
||||||
|
|
||||||
|
export const addToast = helpers.addToast;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { createToaster, melt } from '@melt-ui/svelte';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import { X } from 'lucide-svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="fixed right-0 top-0 z-50 m-4 flex flex-col items-end gap-2 sm:top-20" use:portal>
|
||||||
|
{#each $toasts as { id, data } (id)}
|
||||||
|
<div
|
||||||
|
use:melt={$content(id)}
|
||||||
|
animate:flip={{ duration: 500 }}
|
||||||
|
in:fly={{ duration: 150, x: '100%' }}
|
||||||
|
out:fly={{ duration: 150, x: '100%' }}
|
||||||
|
class="rounded-lg border border-primary-600 bg-highlight-primary text-white shadow-md"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative flex w-[24rem] max-w-[calc(100vw-2rem)] items-center justify-between gap-4 p-5"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h3 use:melt={$title(id)} class="flex items-center gap-2 font-semibold">
|
||||||
|
{data.title}
|
||||||
|
<span class="square-1.5 rounded-full" />
|
||||||
|
</h3>
|
||||||
|
<div use:melt={$description(id)}>
|
||||||
|
{data.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
use:melt={$close(id)}
|
||||||
|
class="text-magnum-500 square-6 hover:bg-magnum-900/50 absolute right-4 top-4 grid place-items-center
|
||||||
|
rounded-full"
|
||||||
|
>
|
||||||
|
<X class="square-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
|
@ -1,5 +1,6 @@
|
||||||
import { redirect, type ServerLoad } from '@sveltejs/kit';
|
import { redirect, type ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ locals: { user } }) => {
|
export const load: ServerLoad = async ({ locals: { user }, parent }) => {
|
||||||
|
await parent();
|
||||||
if (!user) throw redirect(303, '/sign-in');
|
if (!user) throw redirect(303, '/sign-in');
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script class="ts">
|
<script class="ts">
|
||||||
import Navbar from '$lib/components/Navbar.svelte';
|
import Navbar from '$lib/components/Navbar.svelte';
|
||||||
import Sidenav from '$lib/components/Sidenav.svelte';
|
import Sidenav from '$lib/components/Sidenav.svelte';
|
||||||
|
import Toaster from '$lib/components/Toaster.svelte';
|
||||||
|
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
</script>
|
</script>
|
||||||
|
@ -10,8 +11,9 @@
|
||||||
<Sidenav bind:isOpen />
|
<Sidenav bind:isOpen />
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col">
|
||||||
<Navbar bind:isOpen />
|
<Navbar bind:isOpen />
|
||||||
|
<Toaster />
|
||||||
<div
|
<div
|
||||||
class="flex w-full flex-1 transform flex-col overflow-y-scroll p-4 sm:p-8 duration-300 ease-in-out"
|
class="flex w-full flex-1 transform flex-col overflow-y-scroll p-4 duration-300 ease-in-out sm:p-8"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,18 +3,18 @@
|
||||||
import Badge from '$lib/components/Badge.svelte';
|
import Badge from '$lib/components/Badge.svelte';
|
||||||
|
|
||||||
$: user = $page.data.user;
|
$: user = $page.data.user;
|
||||||
|
$: badges = user?.badges?.sort((a, b) => a.level - b.level);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="flex h-full w-full flex-col space-y-4">
|
<section class="flex h-full w-full flex-col gap-4">
|
||||||
<header class="flex flex-col">
|
<header class="flex flex-col">
|
||||||
<h1 class="text-xl font-semibold">Mes badges</h1>
|
<h1 class="text-xl font-semibold">Mes badges</h1>
|
||||||
<p class="text-muted">Vos badges sont affichés ici, vous pouvez les partager avec vos amis</p>
|
<p class="text-muted">Vos badges sont affichés ici, vous pouvez les partager avec vos amis</p>
|
||||||
</header>
|
</header>
|
||||||
<!-- <Separator /> -->
|
<main class="flex flex-col justify-between gap-4">
|
||||||
<main class="flex flex-col justify-between space-x-0 space-y-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<div class="flex space-x-2">
|
{#if badges && badges.length}
|
||||||
{#if user?.badges}
|
{#each badges as badge}
|
||||||
{#each user.badges as badge}
|
|
||||||
<Badge name={badge.name} src={badge.logo} alt={badge.name} level={badge.level} />
|
<Badge name={badge.name} src={badge.logo} alt={badge.name} level={badge.level} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<section class="flex w-full flex-col space-y-6">
|
<section class="flex w-full flex-col space-y-6">
|
||||||
<header class="sticky flex items-center justify-between">
|
<header class="sticky flex items-center justify-between">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="text-xl font-semibold">Chaptires</h1>
|
<h1 class="text-xl font-semibold">Chapitres</h1>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
Les chapitres sont les différentes parties du jeu. Chaque chapitre est composé de plusieurs
|
Les chapitres sont les différentes parties du jeu. Chaque chapitre est composé de plusieurs
|
||||||
puzzles.
|
puzzles.
|
||||||
|
|
|
@ -1,49 +1,40 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
import Puzzle from '$lib/components/Puzzle.svelte';
|
import Puzzle from '$lib/components/Puzzle.svelte';
|
||||||
|
|
||||||
export let data;
|
import type { Puzzle as IPuzzle } from '$lib/types';
|
||||||
|
|
||||||
$: chapter = data.chapter;
|
export let data: PageData;
|
||||||
|
|
||||||
|
data.chapter.puzzles = data.chapter.puzzles.sort((a, b) => a.scoreMax - b.scoreMax);
|
||||||
|
|
||||||
|
const toBeContinued: IPuzzle = {
|
||||||
|
id: 0,
|
||||||
|
name: 'To be continued...',
|
||||||
|
content: 'Maybe you will find the One Piece one day...',
|
||||||
|
scoreMax: 999,
|
||||||
|
show: false,
|
||||||
|
tags: [],
|
||||||
|
tries: 0,
|
||||||
|
score: 0
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="flex w-full flex-col space-y-6">
|
<section class="flex w-full flex-col space-y-6">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col justify-between md:flex-row md:items-center">
|
<div class="flex flex-col justify-between md:flex-row md:items-center">
|
||||||
<div class="flex items-center gap-x-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1 class="text-xl font-semibold">{chapter.name}</h1>
|
<h1 class="text-xl font-semibold">{data.chapter.name}</h1>
|
||||||
<!-- {!isInEventGroup(chapter) && isBeforeStart(chapter) && (
|
|
||||||
<Dialog
|
|
||||||
key={chapter.id}
|
|
||||||
title={chapter.name}
|
|
||||||
open={isOpen[chapter.id]}
|
|
||||||
onOpenChange={() => handleClick(chapter.id)}
|
|
||||||
trigger={
|
|
||||||
<button class="flex items-center gap-x-2 text-sm font-semibold text-muted hover:text-brand">
|
|
||||||
{/* <Icon name="group-line" /> */}
|
|
||||||
Rejoindre un groupe
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
class="right-96 p-4"
|
|
||||||
>
|
|
||||||
<GroupForm chapter={chapter} token={token} />
|
|
||||||
</Dialog>
|
|
||||||
)} -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="mt-4 flex flex-col space-y-4">
|
<ul class="flex flex-col gap-4">
|
||||||
{#each chapter.puzzles as puzzle}
|
{#each data.chapter.puzzles as puzzle}
|
||||||
<Puzzle {puzzle} />
|
<Puzzle {puzzle} />
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if data.chapter.puzzles.length > 1}
|
||||||
|
<Puzzle puzzle={toBeContinued} />
|
||||||
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
<!-- {isInEventGroup(chapter) && (
|
|
||||||
<ul class="mt-4 flex flex-col space-y-4">
|
|
||||||
{filteredData &&
|
|
||||||
filteredData
|
|
||||||
.sort((a, b) => a.scoreMax - b.scoreMax)
|
|
||||||
.map((puzzle) => (
|
|
||||||
<PuzzleProp key={puzzle.id} puzzle={puzzle} chapter={chapter} />
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)} -->
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,10 +1,26 @@
|
||||||
import { API_URL } from '$env/static/private';
|
import { API_URL } from '$env/static/private';
|
||||||
import { error, redirect, type Actions } from '@sveltejs/kit';
|
import { error, redirect, type Actions, fail } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
import type Puzzle from '$lib/components/Puzzle.svelte';
|
import type Puzzle from '$lib/components/Puzzle.svelte';
|
||||||
import type { Chapter } from '$lib/types';
|
import type { Chapter } from '$lib/types';
|
||||||
|
|
||||||
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const puzzleSchema = z.object({
|
||||||
|
// answer: z.string().trim(),
|
||||||
|
// answer need to be filled
|
||||||
|
answer: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Réponse manquante'
|
||||||
|
})
|
||||||
|
.refine((val) => val.trim() !== '', {
|
||||||
|
message: 'Réponse manquante'
|
||||||
|
}),
|
||||||
|
file: z.any().optional()
|
||||||
|
});
|
||||||
|
|
||||||
export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzleId } }) => {
|
export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzleId } }) => {
|
||||||
await parent();
|
await parent();
|
||||||
|
|
||||||
|
@ -51,42 +67,14 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzl
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
puzzle: puzzle as Puzzle
|
puzzle: puzzle as Puzzle,
|
||||||
|
url: `${API_URL}/puzzleResponse/${puzzleId}`,
|
||||||
|
session
|
||||||
};
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: async (event) => {
|
default: async ({ params, request, cookies }) => {
|
||||||
const { id } = event.params;
|
throw redirect(303, `/dashboard/chapters/${params.chapterId}/puzzle/${params.puzzleId}`);
|
||||||
|
|
||||||
const data = await event.request.formData();
|
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}/puzzleResponse/${id}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${event.cookies.get('session')}`
|
|
||||||
},
|
|
||||||
body: data
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: res.ok
|
|
||||||
};
|
|
||||||
|
|
||||||
// throw redirect(303, `/dashboard/puzzles/${id}`);
|
|
||||||
|
|
||||||
// if (res.ok) {
|
|
||||||
// const token = res.headers.get('Authorization')?.split(' ')[1];
|
|
||||||
|
|
||||||
// if (!token) throw new Error('No token found');
|
|
||||||
|
|
||||||
// event.cookies.set('session', token, {
|
|
||||||
// path: '/'
|
|
||||||
// });
|
|
||||||
|
|
||||||
// throw redirect(303, '/dashboard');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// throw redirect(303, '/sign-in');
|
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|
|
@ -1,16 +1,33 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import { marked, type MarkedOptions } from 'marked';
|
import { marked, type MarkedOptions } from 'marked';
|
||||||
|
|
||||||
|
import { cn } from '$lib';
|
||||||
|
import { addToast } from '$lib/components/Toaster.svelte';
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
import Input from '$lib/components/ui/Input.svelte';
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
$: puzzle = data.puzzle;
|
const { puzzle } = data;
|
||||||
|
|
||||||
|
let completed = false;
|
||||||
|
|
||||||
|
function toast(title: string, description: string) {
|
||||||
|
addToast({
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
description
|
||||||
|
},
|
||||||
|
closeDelay: 2500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$: chapterId = $page.params.chapterId;
|
$: chapterId = $page.params.chapterId;
|
||||||
|
|
||||||
const renderer = new marked.Renderer();
|
const renderer = new marked.Renderer();
|
||||||
|
@ -24,7 +41,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.br = () => {
|
renderer.br = () => {
|
||||||
return '<br /><br />';
|
return '<br />';
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.codespan = (code) => {
|
renderer.codespan = (code) => {
|
||||||
|
@ -46,27 +63,69 @@
|
||||||
<div class="h-screen w-full overflow-y-auto break-all font-fira text-xs sm:text-base">
|
<div class="h-screen w-full overflow-y-auto break-all font-fira text-xs sm:text-base">
|
||||||
{@html marked(puzzle.content, options)}
|
{@html marked(puzzle.content, options)}
|
||||||
</div>
|
</div>
|
||||||
{#if !puzzle.score}
|
{#if !puzzle.score && !completed}
|
||||||
<form
|
<form
|
||||||
class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row"
|
class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row"
|
||||||
method="POST"
|
method="POST"
|
||||||
enctype="multipart/form-data"
|
enctype="multipart/form-data"
|
||||||
use:enhance
|
use:enhance={async ({ formElement, formData, action, cancel, submitter }) => {
|
||||||
|
if (formData.get('answer') === '') {
|
||||||
|
toast('Erreur', 'Vous devez entrer une réponse !');
|
||||||
|
return cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(data.url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${data.session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok || res.status === 403 || res.status === 406 || res.status === 423) {
|
||||||
|
const data = res.ok || res.status === 406 ? await res.json() : null;
|
||||||
|
|
||||||
|
if (data && data.score) {
|
||||||
|
completed = true;
|
||||||
|
toast('Bonne réponse', 'Vous avez trouvé la bonne réponse!');
|
||||||
|
} else if (data && data.tries)
|
||||||
|
toast('Mauvaise réponse', `Vous avez effectué ${data.tries} tentative(s)`);
|
||||||
|
else if (res.ok && data?.success)
|
||||||
|
toast('Bonne réponse', 'Vous avez trouvé la bonne réponse!');
|
||||||
|
else if (res.status === 423)
|
||||||
|
toast('Mauvaise réponse', "Ce n'est pas la bonne réponse, réessayez!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return async ({ result }) => {
|
||||||
|
if (result.type === 'redirect') {
|
||||||
|
goto(result.location, {
|
||||||
|
invalidateAll: true,
|
||||||
|
replaceState: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex w-full flex-col gap-2 sm:flex-row sm:gap-4">
|
<div class="flex w-full flex-col gap-2 sm:flex-row sm:gap-4">
|
||||||
<div class="flex flex-col gap-y-2">
|
<div class="flex flex-col gap-y-2">
|
||||||
<label for="answer">Réponse</label>
|
<label for="answer">Réponse</label>
|
||||||
<Input name="answer" placeholder="CAPTAIN, LOOK !" />
|
<textarea
|
||||||
|
class={cn(
|
||||||
|
'flex h-10 w-full rounded-md border border-primary-600 bg-highlight-primary px-3 py-2 text-sm ring-offset-highlight-primary file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted focus:bg-primary-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||||
|
)}
|
||||||
|
name="answer"
|
||||||
|
placeholder="CAPTAIN, LOOK !"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-y-2">
|
<div class="flex flex-col gap-y-2">
|
||||||
<label for="code_file">Fichier</label>
|
<label for="code_file">Fichier</label>
|
||||||
<Input name="code_file" type="file" />
|
<Input name="code_file" type="file" accept=".py,.js,.ts,.java,.rs,.c" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button class="w-full sm:w-44" variant="brand">Valider</Button>
|
<Button class="w-full sm:w-44" variant="brand">Valider</Button>
|
||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-center justify-between flex-col sm:flex-row gap-2">
|
<div class="flex flex-col items-center justify-between gap-2 sm:flex-row">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p>
|
<p>
|
||||||
Tentative{puzzle.tries && puzzle.tries > 1 ? 's' : ''} :{' '}
|
Tentative{puzzle.tries && puzzle.tries > 1 ? 's' : ''} :{' '}
|
||||||
|
@ -76,10 +135,9 @@
|
||||||
Score : <span class="text-brand-accent">{puzzle.score}</span>
|
Score : <span class="text-brand-accent">{puzzle.score}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- <Button type="button" onClick={() => router.push(getURL(`/dashboard/puzzles`))}>
|
<Button href="/dashboard/chapters/{chapterId}" class="w-full sm:w-44" variant="brand">
|
||||||
Retour aux puzzles
|
Retour aux puzzles
|
||||||
</Button> -->
|
</Button>
|
||||||
<Button href="/dashboard/chapters/{chapterId}" class="w-full sm:w-44" variant="brand">Retour aux puzzles</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,6 @@ export const load = (async ({ parent, fetch, cookies }) => {
|
||||||
|
|
||||||
const session = cookies.get('session');
|
const session = cookies.get('session');
|
||||||
|
|
||||||
// TODO: change this
|
|
||||||
const res = await fetch(`${API_URL}/leaderboard`, {
|
const res = await fetch(`${API_URL}/leaderboard`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${session}`
|
Authorization: `Bearer ${session}`
|
||||||
|
@ -23,8 +22,6 @@ export const load = (async ({ parent, fetch, cookies }) => {
|
||||||
|
|
||||||
const leaderboard = (await res.json()) as Leaderboard[];
|
const leaderboard = (await res.json()) as Leaderboard[];
|
||||||
|
|
||||||
console.log(leaderboard);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
leaderboard
|
leaderboard
|
||||||
};
|
};
|
||||||
|
|
137
src/routes/forgot-password/+page.server.ts
Normal file
137
src/routes/forgot-password/+page.server.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
|
||||||
|
import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const forgotSchema = z.object({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email({
|
||||||
|
message: 'Email invalide'
|
||||||
|
})
|
||||||
|
.trim(),
|
||||||
|
code: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Code manquant'
|
||||||
|
})
|
||||||
|
.regex(/^[0-9]{4}$/, {
|
||||||
|
message: 'Code invalide, il doit contenir 4 chiffres'
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
passwd: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirmationSchema = z.object({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email({
|
||||||
|
message: 'Email invalide'
|
||||||
|
})
|
||||||
|
.trim(),
|
||||||
|
code: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Code manquant'
|
||||||
|
})
|
||||||
|
.regex(/^[0-9]{4}$/, {
|
||||||
|
message: 'Code invalide, il doit contenir 4 chiffres'
|
||||||
|
}),
|
||||||
|
passwd: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const load = (async ({ locals: { user } }) => {
|
||||||
|
if (user) throw redirect(303, '/dashboard');
|
||||||
|
|
||||||
|
const form = await superValidate(forgotSchema);
|
||||||
|
|
||||||
|
return {
|
||||||
|
form
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
forgot: async ({ request, cookies }) => {
|
||||||
|
const form = await superValidate(request, forgotSchema);
|
||||||
|
|
||||||
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
email: form.data.email
|
||||||
|
} as Record<string, unknown>;
|
||||||
|
|
||||||
|
if (form.data.code) {
|
||||||
|
data.code = parseInt(form.data.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.data.passwd) {
|
||||||
|
data.password = form.data.passwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/user/fpw`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
cookies.set('session', token, {
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
|
||||||
|
throw redirect(303, '/dashboard');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
form
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
form.errors.passwd = ['Code invalide ou expiré'];
|
||||||
|
|
||||||
|
return fail(400, {
|
||||||
|
form
|
||||||
|
});
|
||||||
|
},
|
||||||
|
confirmation: async ({ request, cookies }) => {
|
||||||
|
const form = await superValidate(request, confirmationSchema);
|
||||||
|
|
||||||
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/confirmation`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: form.data.email,
|
||||||
|
passwd: form.data.passwd,
|
||||||
|
code: parseInt(form.data.code)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
|
||||||
|
|
||||||
|
if (!token) throw new Error('No token');
|
||||||
|
|
||||||
|
cookies.set('session', token, {
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
|
||||||
|
throw redirect(303, '/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
form.errors.code = [`Une erreur s'est produite (${res.status} ${res.statusText})`];
|
||||||
|
|
||||||
|
return fail(400, {
|
||||||
|
form
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} satisfies Actions;
|
95
src/routes/forgot-password/+page.svelte
Normal file
95
src/routes/forgot-password/+page.svelte
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
|
import type { PageData, Snapshot } from './$types';
|
||||||
|
|
||||||
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
const { form, errors, enhance } = superForm(data.form, {
|
||||||
|
onResult({ result }) {
|
||||||
|
switch (result.type) {
|
||||||
|
case 'success':
|
||||||
|
confirmation = true;
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
confirmation = false;
|
||||||
|
break;
|
||||||
|
case 'redirect':
|
||||||
|
goto(result.location, {
|
||||||
|
replaceState: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let confirmation = false;
|
||||||
|
|
||||||
|
export const snapshot: Snapshot = {
|
||||||
|
capture: () => confirmation,
|
||||||
|
restore: (value) => (confirmation = value)
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex h-screen w-full">
|
||||||
|
<div class="flex w-full flex-col items-center justify-center">
|
||||||
|
<div class="flex w-full max-w-xs flex-col gap-4">
|
||||||
|
<h2 class="mx-auto text-xl font-bold">
|
||||||
|
{confirmation ? 'Changer le mot de passe' : 'Mot de passe oublié'}
|
||||||
|
</h2>
|
||||||
|
<form class="flex flex-col justify-center gap-2" method="POST" action="?/forgot" use:enhance>
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<Input
|
||||||
|
bind:value={$form.email}
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="philipzcwbarlow@peerat.dev"
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{#if $errors.email}<span class="text-sm text-red-500">{$errors.email}</span>{/if}
|
||||||
|
|
||||||
|
{#if confirmation}
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-2"
|
||||||
|
transition:fade={{
|
||||||
|
duration: 300
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label for="passwd"> Mot de passe </label>
|
||||||
|
<Input name="passwd" placeholder="************" type="password" />
|
||||||
|
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
|
||||||
|
|
||||||
|
<label for="code"> Code </label>
|
||||||
|
<Input name="code" placeholder="1234" type="text" />
|
||||||
|
{#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Button class="mt-2" variant="brand">
|
||||||
|
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
||||||
|
{confirmation ? 'Modifier' : 'Envoyer le mail'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ul class="flex justify-between">
|
||||||
|
<li>
|
||||||
|
<a class="text-highlight-secondary hover:text-brand" href="/sign-in">Se connecter</a>
|
||||||
|
</li>
|
||||||
|
{#if confirmation}
|
||||||
|
<li>
|
||||||
|
<button formaction="?/register" class="text-highlight-secondary hover:text-brand"
|
||||||
|
>Pas reçu ?</button
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,6 +1,6 @@
|
||||||
import { redirect, type ServerLoad } from '@sveltejs/kit';
|
import { redirect, type ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const GET: ServerLoad = async ({ cookies, locals }) => {
|
export const load: ServerLoad = async ({ cookies, locals }) => {
|
||||||
const session = cookies.get('session');
|
const session = cookies.get('session');
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
1
src/routes/logout/+page.svelte
Normal file
1
src/routes/logout/+page.svelte
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<script lang="ts"></script>
|
|
@ -1,36 +1,39 @@
|
||||||
import { redirect, type Actions, fail } from '@sveltejs/kit';
|
|
||||||
import type { PageServerLoad } from './$types';
|
|
||||||
import { API_URL } from '$env/static/private';
|
import { API_URL } from '$env/static/private';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { redirect, type Actions, fail } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load = (async ({ locals: { user } }) => {
|
import type { PageServerLoad } from './$types';
|
||||||
if (user) throw redirect(303, '/dashboard');
|
|
||||||
}) satisfies PageServerLoad;
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
pseudo: z.string().trim(),
|
pseudo: z.string().trim(),
|
||||||
passwd: z.string()
|
passwd: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const load = (async ({ locals: { user } }) => {
|
||||||
|
if (user) throw redirect(303, '/dashboard');
|
||||||
|
|
||||||
|
const form = await superValidate(schema);
|
||||||
|
|
||||||
|
return {
|
||||||
|
form
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: async (event) => {
|
default: async ({ request, cookies }) => {
|
||||||
const data = await event.request.formData();
|
const form = await superValidate(request, schema);
|
||||||
|
|
||||||
const parse = schema.safeParse(Object.fromEntries(data.entries()));
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
if (!parse.success) {
|
|
||||||
const errors = parse.error.errors.map((error) => {
|
|
||||||
const { path, message } = error;
|
|
||||||
return { field: path[0], message };
|
|
||||||
});
|
|
||||||
return fail(400, { errors });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}/login`, {
|
const res = await fetch(`${API_URL}/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
...parse.data
|
...form.data
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,15 +42,17 @@ export const actions = {
|
||||||
|
|
||||||
if (!token) throw new Error('No token found');
|
if (!token) throw new Error('No token found');
|
||||||
|
|
||||||
event.cookies.set('session', token, {
|
cookies.set('session', token, {
|
||||||
path: '/'
|
path: '/'
|
||||||
});
|
});
|
||||||
|
|
||||||
throw redirect(303, '/dashboard');
|
throw redirect(303, '/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.errors.passwd = ["Nom d'utilisateur ou mot de passe incorrect"];
|
||||||
|
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
errors: [{ field: 'passwd', message: "Nom d'utilisateur ou mot de passe incorrect" }]
|
form
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
import type { ActionData } from './$types';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
|
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
import Input from '$lib/components/ui/Input.svelte';
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
|
|
||||||
export let form: ActionData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
const { form, errors, enhance } = superForm(data.form, {
|
||||||
|
onResult({ result }) {
|
||||||
|
switch (result.type) {
|
||||||
|
case 'redirect':
|
||||||
|
goto(result.location, {
|
||||||
|
replaceState: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-screen w-full">
|
<div class="flex h-screen w-full">
|
||||||
|
@ -15,20 +29,18 @@
|
||||||
<h1 class="mx-auto text-xl font-bold">Connexion</h1>
|
<h1 class="mx-auto text-xl font-bold">Connexion</h1>
|
||||||
<form class="flex flex-col justify-center gap-2" method="POST" use:enhance>
|
<form class="flex flex-col justify-center gap-2" method="POST" use:enhance>
|
||||||
<label for="pseudo"> Nom d'utilisateur </label>
|
<label for="pseudo"> Nom d'utilisateur </label>
|
||||||
<Input name="pseudo" placeholder="Barlow" type="text" required />
|
<Input name="pseudo" placeholder="Barlow" type="text" required bind:value={$form.pseudo} />
|
||||||
{#if form?.errors.find((error) => error.field === 'pseudo')}
|
{#if $errors.pseudo}<span class="text-sm text-red-500">{$errors.pseudo}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors.find((error) => error.field === 'pseudo')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<label for="passwd"> Mot de passe </label>
|
<label for="passwd"> Mot de passe </label>
|
||||||
<Input name="passwd" placeholder="************" type="password" required />
|
<Input
|
||||||
{#if form?.errors.find((error) => error.field === 'passwd')}
|
name="passwd"
|
||||||
<p class="text-sm text-red-500">
|
placeholder="************"
|
||||||
{form?.errors.find((error) => error.field === 'passwd')?.message}
|
type="password"
|
||||||
</p>
|
required
|
||||||
{/if}
|
bind:value={$form.passwd}
|
||||||
|
/>
|
||||||
|
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
|
||||||
|
|
||||||
<Button class="mt-2" variant="brand">
|
<Button class="mt-2" variant="brand">
|
||||||
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
||||||
|
@ -39,11 +51,11 @@
|
||||||
<li>
|
<li>
|
||||||
<a class="text-highlight-secondary hover:text-brand" href="/sign-up">S'inscrire</a>
|
<a class="text-highlight-secondary hover:text-brand" href="/sign-up">S'inscrire</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li>
|
<li>
|
||||||
<a class="text-highlight-secondary hover:text-brand" href="/forgot-password"
|
<a class="text-highlight-secondary hover:text-brand" href="/forgot-password"
|
||||||
>Mot de passe oublié</a
|
>Mot de passe oublié</a
|
||||||
>
|
>
|
||||||
</li> -->
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,12 +4,9 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
|
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const load = (async ({ locals: { user } }) => {
|
|
||||||
if (user) throw redirect(303, '/dashboard');
|
|
||||||
}) satisfies PageServerLoad;
|
|
||||||
|
|
||||||
const registerSchema = z.object({
|
const registerSchema = z.object({
|
||||||
email: z
|
email: z
|
||||||
.string()
|
.string()
|
||||||
|
@ -19,7 +16,17 @@ const registerSchema = z.object({
|
||||||
.trim(),
|
.trim(),
|
||||||
firstname: z.string().trim(),
|
firstname: z.string().trim(),
|
||||||
lastname: z.string().trim(),
|
lastname: z.string().trim(),
|
||||||
pseudo: z.string().trim()
|
pseudo: z.string().trim(),
|
||||||
|
code: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Code manquant'
|
||||||
|
})
|
||||||
|
.length(4, {
|
||||||
|
message: 'Code invalide, il doit contenir 4 chiffres'
|
||||||
|
})
|
||||||
|
.regex(/^[0-9]+$/)
|
||||||
|
.optional(),
|
||||||
|
passwd: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const confirmationSchema = z.object({
|
const confirmationSchema = z.object({
|
||||||
|
@ -32,80 +39,80 @@ const confirmationSchema = z.object({
|
||||||
firstname: z.string().trim(),
|
firstname: z.string().trim(),
|
||||||
lastname: z.string().trim(),
|
lastname: z.string().trim(),
|
||||||
pseudo: z.string().trim(),
|
pseudo: z.string().trim(),
|
||||||
code: z.string(),
|
code: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Code manquant'
|
||||||
|
})
|
||||||
|
.regex(/^[0-9]{4}$/, {
|
||||||
|
message: 'Code invalide, il doit contenir 4 chiffres'
|
||||||
|
}),
|
||||||
passwd: z.string()
|
passwd: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const load = (async ({ locals: { user } }) => {
|
||||||
|
if (user) throw redirect(303, '/dashboard');
|
||||||
|
|
||||||
|
const form = await superValidate(registerSchema);
|
||||||
|
|
||||||
|
return {
|
||||||
|
form
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
register: async (event) => {
|
register: async ({ request }) => {
|
||||||
const data = await event.request.formData();
|
const form = await superValidate(request, registerSchema);
|
||||||
|
|
||||||
const parse = registerSchema.safeParse(Object.fromEntries(data.entries()));
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
if (!parse.success) {
|
|
||||||
const errors = parse.error.errors.map((error) => {
|
|
||||||
const { path, message } = error;
|
|
||||||
return { field: path[0], message };
|
|
||||||
});
|
|
||||||
return fail(400, { errors });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}/register`, {
|
const res = await fetch(`${API_URL}/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
pseudo: parse.data.pseudo,
|
pseudo: form.data.pseudo,
|
||||||
firstname: parse.data.firstname,
|
firstname: form.data.firstname,
|
||||||
lastname: parse.data.lastname,
|
lastname: form.data.lastname,
|
||||||
email: parse.data.email
|
email: form.data.email
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return {
|
return {
|
||||||
success: true
|
form
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.status === 400) {
|
if (res.status === 400) {
|
||||||
const { email_valid } = await res.json();
|
const { email_valid } = await res.json();
|
||||||
|
|
||||||
const errors = [];
|
if (!email_valid) form.errors.email = ['Un compte avec cette adresse email existe déjà'];
|
||||||
|
|
||||||
if (!email_valid)
|
return fail(400, { form });
|
||||||
errors.push({
|
|
||||||
field: 'email',
|
|
||||||
message: 'Cet email est déjà utilisé'
|
|
||||||
});
|
|
||||||
|
|
||||||
return fail(400, { errors });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.errors.passwd = ["Une erreur s'est produite"];
|
||||||
|
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
errors: [{ field: 'passwd', message: "Une erreur s'est produite" }]
|
form
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
confirmation: async (event) => {
|
confirmation: async ({ request, cookies }) => {
|
||||||
const data = await event.request.formData();
|
const form = await superValidate(request, confirmationSchema);
|
||||||
|
|
||||||
const parse = confirmationSchema.safeParse(Object.fromEntries(data.entries()));
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
if (!parse.success) {
|
|
||||||
const errors = parse.error.errors.map((error) => {
|
|
||||||
const { path, message } = error;
|
|
||||||
return { field: path[0], message };
|
|
||||||
});
|
|
||||||
return fail(400, { errors });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}/confirmation`, {
|
const res = await fetch(`${API_URL}/confirmation`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
firstname: parse.data.firstname,
|
firstname: form.data.firstname,
|
||||||
lastname: parse.data.lastname,
|
lastname: form.data.lastname,
|
||||||
pseudo: parse.data.pseudo,
|
pseudo: form.data.pseudo,
|
||||||
email: parse.data.email,
|
email: form.data.email,
|
||||||
code: parseInt(parse.data.code),
|
code: parseInt(form.data.code),
|
||||||
passwd: parse.data.passwd
|
passwd: form.data.passwd
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,15 +121,17 @@ export const actions = {
|
||||||
|
|
||||||
if (!token) throw new Error('No token');
|
if (!token) throw new Error('No token');
|
||||||
|
|
||||||
event.cookies.set('token', token, {
|
cookies.set('session', token, {
|
||||||
path: '/'
|
path: '/'
|
||||||
});
|
});
|
||||||
|
|
||||||
throw redirect(303, '/dashboard');
|
throw redirect(303, '/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.errors.code = [`Une erreur s'est produite (${res.status} ${res.statusText})`];
|
||||||
|
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
errors: [{ field: 'passwd', message: "Une erreur s'est produite" }]
|
form
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|
|
@ -1,25 +1,39 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import type { ActionData, Snapshot } from './$types';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
|
import type { PageData, Snapshot } from './$types';
|
||||||
|
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
import Input from '$lib/components/ui/Input.svelte';
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
|
|
||||||
export let form: ActionData;
|
export let data: PageData;
|
||||||
|
|
||||||
let data = {
|
const { form, errors, enhance } = superForm(data.form, {
|
||||||
email: '',
|
onResult({ result }) {
|
||||||
firstname: '',
|
switch (result.type) {
|
||||||
lastname: '',
|
case 'success':
|
||||||
pseudo: '',
|
confirmation = true;
|
||||||
confirmation: false
|
break;
|
||||||
};
|
case 'error':
|
||||||
|
confirmation = false;
|
||||||
|
break;
|
||||||
|
case 'redirect':
|
||||||
|
goto(result.location, {
|
||||||
|
replaceState: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let confirmation = false;
|
||||||
|
|
||||||
export const snapshot: Snapshot = {
|
export const snapshot: Snapshot = {
|
||||||
capture: () => data,
|
capture: () => confirmation,
|
||||||
restore: (value) => (data = value)
|
restore: (value) => (confirmation = value)
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -27,83 +41,59 @@
|
||||||
<div class="flex w-full flex-col items-center justify-center">
|
<div class="flex w-full flex-col items-center justify-center">
|
||||||
<div class="flex w-full max-w-xs flex-col gap-4">
|
<div class="flex w-full max-w-xs flex-col gap-4">
|
||||||
<h2 class="mx-auto text-xl font-bold">
|
<h2 class="mx-auto text-xl font-bold">
|
||||||
{data.confirmation ? 'Confirmation' : 'Inscription'}
|
{confirmation ? 'Confirmation' : 'Inscription'}
|
||||||
</h2>
|
</h2>
|
||||||
<form
|
<form
|
||||||
class="flex flex-col justify-center gap-2"
|
class="flex flex-col justify-center gap-2"
|
||||||
method="POST"
|
method="POST"
|
||||||
use:enhance={({}) => {
|
action={confirmation ? '?/confirmation' : '?/register'}
|
||||||
return async ({ result }) => {
|
use:enhance
|
||||||
switch (result.type) {
|
|
||||||
case 'success':
|
|
||||||
data.confirmation = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
action={data.confirmation ? '/sign-up?/confirmation' : '/sign-up?/register'}
|
|
||||||
>
|
>
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={data.email}
|
bind:value={$form.email}
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="philipzcwbarlow@peerat.dev"
|
placeholder="philipzcwbarlow@peerat.dev"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{#if form?.errors?.find((error) => error.field === 'email')}
|
{#if $errors.email}<span class="text-sm text-red-500">{$errors.email}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors.find((error) => error.field === 'email')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<label for="firstname">Prénom</label>
|
<label for="firstname">Prénom</label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={data.firstname}
|
bind:value={$form.firstname}
|
||||||
name="firstname"
|
name="firstname"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Philip"
|
placeholder="Philip"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{#if form?.errors?.find((error) => error.field === 'firstname')}
|
{#if $errors.firstname}<span class="text-sm text-red-500">{$errors.firstname}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors?.find((error) => error.field === 'firstname')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<label for="lastname">Nom</label>
|
<label for="lastname">Nom</label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={data.lastname}
|
bind:value={$form.lastname}
|
||||||
name="lastname"
|
name="lastname"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Barlow"
|
placeholder="Barlow"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{#if form?.errors?.find((error) => error.field === 'lastname')}
|
{#if $errors.lastname}<span class="text-sm text-red-500">{$errors.lastname}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors?.find((error) => error.field === 'lastname')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<label for="pseudo"> Nom d'utilisateur </label>
|
<label for="pseudo"> Nom d'utilisateur </label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={data.pseudo}
|
bind:value={$form.pseudo}
|
||||||
name="pseudo"
|
name="pseudo"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Cypher Wolf"
|
placeholder="Cypher Wolf"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{#if form?.errors?.find((error) => error.field === 'pseudo')}
|
{#if $errors.pseudo}<span class="text-sm text-red-500">{$errors.pseudo}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors?.find((error) => error.field === 'pseudo')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if data.confirmation}
|
{#if confirmation}
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-2"
|
class="flex flex-col gap-2"
|
||||||
transition:fade={{
|
transition:fade={{
|
||||||
|
@ -112,35 +102,27 @@
|
||||||
>
|
>
|
||||||
<label for="passwd"> Mot de passe </label>
|
<label for="passwd"> Mot de passe </label>
|
||||||
<Input name="passwd" placeholder="************" type="password" />
|
<Input name="passwd" placeholder="************" type="password" />
|
||||||
{#if form?.errors?.find((error) => error.field === 'passwd')}
|
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors?.find((error) => error.field === 'passwd')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
<label for="code"> Code </label>
|
<label for="code"> Code </label>
|
||||||
<Input name="code" placeholder="1234" type="text" />
|
<Input name="code" placeholder="1234" type="text" />
|
||||||
{#if form?.errors?.find((error) => error.field === 'code')}
|
{#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if}
|
||||||
<p class="text-sm text-red-500">
|
|
||||||
{form?.errors?.find((error) => error.field === 'code')?.message}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Button class="mt-2" variant="brand">
|
<Button class="mt-2" variant="brand">
|
||||||
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
||||||
{data.confirmation ? "S'inscrire" : 'Continuer'}
|
{confirmation ? "S'inscrire" : 'Continuer'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ul class="flex justify-between">
|
<ul class="flex justify-between">
|
||||||
<li>
|
<li>
|
||||||
<a class="text-highlight-secondary hover:text-brand" href="/sign-in">Se connecter</a>
|
<a class="text-highlight-secondary hover:text-brand" href="/sign-in">Se connecter</a>
|
||||||
</li>
|
</li>
|
||||||
{#if data.confirmation}
|
{#if confirmation}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button formaction="?/register" class="text-highlight-secondary hover:text-brand"
|
||||||
formaction="/sign-up?/confirmation"
|
>Pas reçu ?</button
|
||||||
class="text-highlight-secondary hover:text-brand">Pas reçu ?</button
|
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
import { preprocessMeltUI } from '@melt-ui/pp';
|
||||||
|
import sequence from 'svelte-sequential-preprocessor';
|
||||||
import adapter from '@sveltejs/adapter-node';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
|
/** @type {import('@sveltejs/kit').Config}*/
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
const config = {
|
||||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: sequence([vitePreprocess(), preprocessMeltUI()]),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||||
|
@ -14,5 +14,4 @@ const config = {
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
Loading…
Add table
Reference in a new issue