Compare commits

...

8 commits

Author SHA1 Message Date
Théo
13f972b1f1 fix: misc check 2023-09-17 23:04:35 +02:00
Théo
b7202656d1 feat: puzzle done 2023-09-17 23:04:28 +02:00
Théo
e658974929 fix: misc logs 2023-09-17 23:04:17 +02:00
Théo
2291e12b3a feat: forgor 💀 2023-09-17 23:04:10 +02:00
Théo
7321d48266 feat: changed form behavior 2023-09-17 23:04:00 +02:00
Théo
f1dea1016a fix: logout 2023-09-17 23:03:49 +02:00
Théo
664ce0a29b fix: minor ui changes 2023-09-17 23:03:42 +02:00
Théo
5b47fd7b53 feat: lib ui 2023-09-17 23:03:19 +02:00
23 changed files with 708 additions and 288 deletions

View file

@ -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
View file

@ -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

View file

@ -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>

View file

@ -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}

View file

@ -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

View 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>

View file

@ -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');
}; };

View file

@ -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>

View file

@ -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}

View file

@ -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.

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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
}; };

View 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;

View 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>

View file

@ -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) {

View file

@ -0,0 +1 @@
<script lang="ts"></script>

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -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}

View file

@ -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;