Merge branch 'dev'
This commit is contained in:
commit
ab9aab31b9
34 changed files with 2347 additions and 426 deletions
|
@ -12,13 +12,13 @@ Peer-at Code est un site web qui permet d'offrir un parcours amusant, le but ét
|
||||||
|
|
||||||
4. Exécuter `pnpm dev` pour démarrer le serveur de développement
|
4. Exécuter `pnpm dev` pour démarrer le serveur de développement
|
||||||
|
|
||||||
Ouvre [http://localhost:3000](http://localhost:3000) avec ton navigateur pour accéder au site.
|
Ouvre [http://localhost:5173](http://localhost:5173) avec ton navigateur pour accéder au site.
|
||||||
|
|
||||||
## Déploiement
|
## Déploiement
|
||||||
|
|
||||||
1. Exécuter `pnpm build` pour générer le site
|
1. Exécuter `pnpm build` pour générer le site
|
||||||
|
|
||||||
2. Exécuter `pnpm start` pour démarrer le serveur de production
|
2. Exécuter `node build` pour démarrer le serveur de production
|
||||||
|
|
||||||
Ouvre [http://localhost:3000](http://localhost:3000) avec ton navigateur pour accéder au site.
|
Ouvre [http://localhost:3000](http://localhost:3000) avec ton navigateur pour accéder au site.
|
||||||
|
|
||||||
|
@ -38,4 +38,4 @@ N'oublie pas de rejoindre le [serveur Discord](https://discord.gg/72vuHcwUkE) po
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
[GPU GPL V3.0](https://github.com/Peer-at-Code/peer-at-code/blob/main/LICENSE)
|
[GPU GPL V3.0](https://git.peerat.dev/Peer-at-Code/peer-at-code-web/src/branch/main/LICENSE)
|
||||||
|
|
113
package.json
113
package.json
|
@ -1,62 +1,55 @@
|
||||||
{
|
{
|
||||||
"name": "peer-at-code",
|
"name": "peer-at-code",
|
||||||
"private": true,
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"type": "module",
|
||||||
"dev": "next dev",
|
"private": true,
|
||||||
"build": "next build",
|
"scripts": {
|
||||||
"start": "next start",
|
"dev": "vite dev",
|
||||||
"lint": "next lint",
|
"build": "vite build",
|
||||||
"prettier:format": "prettier --write .",
|
"preview": "vite preview",
|
||||||
"prettier:check": "prettier --check \"**/*.{ts,tsx,json}\"",
|
"test": "npm run test:integration && npm run test:unit",
|
||||||
"eslint:format": "eslint . --ext ts --ext tsx --ext js"
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
},
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"repository": {
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
"type": "git",
|
"format": "prettier --plugin-search-dir . --write .",
|
||||||
"url": "git+https://github.com/Peer-at-Code/peer-at-code.git"
|
"test:integration": "playwright test",
|
||||||
},
|
"test:unit": "vitest"
|
||||||
"license": "GNU General Public License v3.0",
|
},
|
||||||
"bugs": {
|
"dependencies": {
|
||||||
"url": "https://github.com/Peer-at-Code/peer-at-code/issues"
|
"class-variance-authority": "^0.7.0",
|
||||||
},
|
"clsx": "^2.0.0",
|
||||||
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
|
"lucide-svelte": "^0.279.0",
|
||||||
"dependencies": {
|
"marked": "^7.0.1",
|
||||||
"@radix-ui/react-dialog": "^1.0.3",
|
"svelte-boring-avatars": "^1.2.4",
|
||||||
"@radix-ui/react-popover": "^1.0.5",
|
"tailwind-merge": "^1.14.0"
|
||||||
"boring-avatars": "^1.7.0",
|
},
|
||||||
"clsx": "^1.2.1",
|
"devDependencies": {
|
||||||
"edge-csrf": "^1.0.3",
|
"@melt-ui/pp": "^0.1.2",
|
||||||
"framer-motion": "^10.12.4",
|
"@melt-ui/svelte": "^0.50.0",
|
||||||
"js-cookie": "^3.0.1",
|
"@playwright/test": "^1.28.1",
|
||||||
"next": "13.4.0",
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
"react": "18.2.0",
|
"@sveltejs/adapter-node": "^1.3.1",
|
||||||
"react-dom": "18.2.0",
|
"@sveltejs/kit": "^1.20.4",
|
||||||
"react-hook-form": "^7.43.1",
|
"@types/marked": "^5.0.1",
|
||||||
"react-markdown": "^8.0.5",
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||||
"remark-breaks": "^3.0.2",
|
"@typescript-eslint/parser": "^5.45.0",
|
||||||
"remark-gfm": "^3.0.1",
|
"autoprefixer": "^10.4.14",
|
||||||
"remixicon": "^2.5.0",
|
"eslint": "^8.28.0",
|
||||||
"sharp": "^0.32.1",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"swr": "^2.0.3",
|
"eslint-plugin-svelte": "^2.30.0",
|
||||||
"tailwind-merge": "^1.9.0",
|
"postcss": "^8.4.27",
|
||||||
"zod": "^3.20.2"
|
"prettier": "^2.8.8",
|
||||||
},
|
"prettier-plugin-svelte": "^2.10.1",
|
||||||
"devDependencies": {
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"svelte": "^4.0.5",
|
||||||
"@types/js-cookie": "^3.0.3",
|
"svelte-check": "^3.4.3",
|
||||||
"@types/node": "18.11.18",
|
"svelte-sequential-preprocessor": "^2.0.1",
|
||||||
"@types/react": "18.0.27",
|
"sveltekit-superforms": "^1.7.0",
|
||||||
"@types/react-dom": "18.0.10",
|
"tailwindcss": "^3.3.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
"tslib": "^2.4.1",
|
||||||
"@typescript-eslint/parser": "^5.50.0",
|
"typescript": "^5.0.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"vite": "^4.4.2",
|
||||||
"eslint": "8.33.0",
|
"vitest": "^0.32.2",
|
||||||
"eslint-config-next": "13.3.1",
|
"zod": "^3.21.4"
|
||||||
"eslint-config-prettier": "^8.6.0",
|
}
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"postcss": "^8.4.21",
|
|
||||||
"prettier": "^2.8.3",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.2.7",
|
|
||||||
"tailwindcss": "^3.2.4",
|
|
||||||
"typescript": "4.9.5"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
644
pnpm-lock.yaml
generated
644
pnpm-lock.yaml
generated
|
@ -11,70 +11,46 @@ dependencies:
|
||||||
specifier: ^1.7.0
|
specifier: ^1.7.0
|
||||||
version: 1.7.0
|
version: 1.7.0
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^1.2.1
|
specifier: ^2.0.0
|
||||||
version: 1.2.1
|
version: 2.0.0
|
||||||
edge-csrf:
|
lucide-svelte:
|
||||||
specifier: ^1.0.3
|
specifier: ^0.279.0
|
||||||
version: 1.0.3(next@13.4.0)
|
version: 0.279.0(svelte@4.1.1)
|
||||||
framer-motion:
|
marked:
|
||||||
specifier: ^10.12.4
|
specifier: ^7.0.1
|
||||||
version: 10.12.4(react-dom@18.2.0)(react@18.2.0)
|
version: 7.0.1
|
||||||
js-cookie:
|
svelte-boring-avatars:
|
||||||
specifier: ^3.0.1
|
specifier: ^1.2.4
|
||||||
version: 3.0.1
|
version: 1.2.4
|
||||||
next:
|
|
||||||
specifier: 13.4.0
|
|
||||||
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: 18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
react-dom:
|
|
||||||
specifier: 18.2.0
|
|
||||||
version: 18.2.0(react@18.2.0)
|
|
||||||
react-hook-form:
|
|
||||||
specifier: ^7.43.1
|
|
||||||
version: 7.43.9(react@18.2.0)
|
|
||||||
react-markdown:
|
|
||||||
specifier: ^8.0.5
|
|
||||||
version: 8.0.6(@types/react@18.0.27)(react@18.2.0)
|
|
||||||
remark-breaks:
|
|
||||||
specifier: ^3.0.2
|
|
||||||
version: 3.0.2
|
|
||||||
remark-gfm:
|
|
||||||
specifier: ^3.0.1
|
|
||||||
version: 3.0.1
|
|
||||||
remixicon:
|
|
||||||
specifier: ^2.5.0
|
|
||||||
version: 2.5.0
|
|
||||||
sharp:
|
|
||||||
specifier: ^0.32.1
|
|
||||||
version: 0.32.1
|
|
||||||
swr:
|
|
||||||
specifier: ^2.0.3
|
|
||||||
version: 2.1.2(react@18.2.0)
|
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^1.9.0
|
specifier: ^1.14.0
|
||||||
version: 1.12.0
|
version: 1.14.0
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.20.2
|
specifier: ^3.21.4
|
||||||
version: 3.21.4
|
version: 3.21.4
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tailwindcss/forms':
|
'@melt-ui/pp':
|
||||||
specifier: ^0.5.3
|
specifier: ^0.1.2
|
||||||
version: 0.5.3(tailwindcss@3.3.1)
|
version: 0.1.2(@melt-ui/svelte@0.50.0)(svelte@4.1.1)
|
||||||
'@types/js-cookie':
|
'@melt-ui/svelte':
|
||||||
specifier: ^3.0.3
|
specifier: ^0.50.0
|
||||||
version: 3.0.3
|
version: 0.50.0(svelte@4.1.1)
|
||||||
'@types/node':
|
'@playwright/test':
|
||||||
specifier: 18.11.18
|
specifier: ^1.28.1
|
||||||
version: 18.11.18
|
version: 1.36.2
|
||||||
'@types/react':
|
'@sveltejs/adapter-auto':
|
||||||
specifier: 18.0.27
|
specifier: ^2.0.0
|
||||||
version: 18.0.27
|
version: 2.1.0(@sveltejs/kit@1.22.3)
|
||||||
'@types/react-dom':
|
'@sveltejs/adapter-node':
|
||||||
specifier: 18.0.10
|
specifier: ^1.3.1
|
||||||
version: 18.0.10
|
version: 1.3.1(@sveltejs/kit@1.22.3)
|
||||||
|
'@sveltejs/kit':
|
||||||
|
specifier: ^1.20.4
|
||||||
|
version: 1.22.3(svelte@4.1.1)(vite@4.4.7)
|
||||||
|
'@types/marked':
|
||||||
|
specifier: ^5.0.1
|
||||||
|
version: 5.0.1
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ^5.50.0
|
specifier: ^5.50.0
|
||||||
version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.33.0)(typescript@4.9.5)
|
version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.33.0)(typescript@4.9.5)
|
||||||
|
@ -103,14 +79,35 @@ devDependencies:
|
||||||
specifier: ^2.8.3
|
specifier: ^2.8.3
|
||||||
version: 2.8.7
|
version: 2.8.7
|
||||||
prettier-plugin-tailwindcss:
|
prettier-plugin-tailwindcss:
|
||||||
specifier: ^0.2.7
|
specifier: ^0.4.1
|
||||||
version: 0.2.7(prettier@2.8.7)
|
version: 0.4.1(prettier-plugin-svelte@2.10.1)(prettier@2.8.8)
|
||||||
|
svelte:
|
||||||
|
specifier: ^4.0.5
|
||||||
|
version: 4.1.1
|
||||||
|
svelte-check:
|
||||||
|
specifier: ^3.4.3
|
||||||
|
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.2.4
|
specifier: ^3.2.4
|
||||||
version: 3.3.1(postcss@8.4.21)
|
version: 3.3.1(postcss@8.4.21)
|
||||||
typescript:
|
typescript:
|
||||||
specifier: 4.9.5
|
specifier: ^5.0.0
|
||||||
version: 4.9.5
|
version: 5.1.6
|
||||||
|
vite:
|
||||||
|
specifier: ^4.4.2
|
||||||
|
version: 4.4.7(@types/node@20.4.5)
|
||||||
|
vitest:
|
||||||
|
specifier: ^0.32.2
|
||||||
|
version: 0.32.4
|
||||||
|
zod:
|
||||||
|
specifier: ^3.21.4
|
||||||
|
version: 3.21.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
@ -118,7 +115,9 @@ packages:
|
||||||
resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==}
|
resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.13.11
|
'@jridgewell/gen-mapping': 0.3.3
|
||||||
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@emotion/is-prop-valid@0.8.8:
|
/@emotion/is-prop-valid@0.8.8:
|
||||||
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
||||||
|
@ -333,8 +332,26 @@ packages:
|
||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@radix-ui/primitive@1.0.0:
|
/@floating-ui/core@1.5.0:
|
||||||
resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==}
|
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:
|
||||||
|
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
|
||||||
|
engines: {node: '>=10.10.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.21.0
|
'@babel/runtime': 7.21.0
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -375,109 +392,62 @@ packages:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.21.0
|
'@jridgewell/set-array': 1.1.2
|
||||||
'@radix-ui/primitive': 1.0.0
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
dev: true
|
||||||
'@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-focus-guards': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-focus-scope': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-slot': 1.0.1(react@18.2.0)
|
|
||||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
|
||||||
aria-hidden: 1.2.3
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
react-remove-scroll: 2.5.5(@types/react@18.0.27)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@radix-ui/react-dismissable-layer@1.0.3(react-dom@18.2.0)(react@18.2.0):
|
/@jridgewell/resolve-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==}
|
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
||||||
peerDependencies:
|
engines: {node: '>=6.0.0'}
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
dev: true
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
|
||||||
|
/@jridgewell/set-array@1.1.2:
|
||||||
|
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@jridgewell/sourcemap-codec@1.4.14:
|
||||||
|
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@jridgewell/sourcemap-codec@1.4.15:
|
||||||
|
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@jridgewell/trace-mapping@0.3.18:
|
||||||
|
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.21.0
|
'@jridgewell/resolve-uri': 3.1.0
|
||||||
'@radix-ui/primitive': 1.0.0
|
'@jridgewell/sourcemap-codec': 1.4.14
|
||||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-use-escape-keydown': 1.0.2(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@radix-ui/react-focus-guards@1.0.0(react@18.2.0):
|
/@melt-ui/pp@0.1.2(@melt-ui/svelte@0.50.0)(svelte@4.1.1):
|
||||||
resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==}
|
resolution: {integrity: sha512-GZeqp7UWLNZUC2dJpREnZrWMR88vy27WO7C3cIBz4KW3/CFD19FjNkd3VbSRfcRryrMkdnEs9nu2VUa8/0u58w==}
|
||||||
|
engines: {pnpm: '>=8.6.3'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
'@melt-ui/svelte': '>= 0.29.0'
|
||||||
|
svelte: ^3.55.0 || ^4.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.21.0
|
'@melt-ui/svelte': 0.50.0(svelte@4.1.1)
|
||||||
react: 18.2.0
|
svelte: 4.1.1
|
||||||
dev: false
|
dev: true
|
||||||
|
|
||||||
/@radix-ui/react-focus-scope@1.0.2(react-dom@18.2.0)(react@18.2.0):
|
/@melt-ui/svelte@0.50.0(svelte@4.1.1):
|
||||||
resolution: {integrity: sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==}
|
resolution: {integrity: sha512-NcWwxwStXq77/yOuBfnGkuJdUta3M4SwqZECdaRpAQ61BHI3qz7WW2ZM42JmDvGSs9W6ww2kZFNF8XNTO92CdA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
svelte: '>=3 <5'
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.21.0
|
'@floating-ui/core': 1.5.0
|
||||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
'@floating-ui/dom': 1.5.3
|
||||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
dequal: 2.0.3
|
||||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
focus-trap: 7.5.2
|
||||||
react: 18.2.0
|
nanoid: 4.0.2
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
svelte: 4.1.1
|
||||||
dev: false
|
dev: true
|
||||||
|
|
||||||
/@radix-ui/react-id@1.0.0(react@18.2.0):
|
/@nodelib/fs.scandir@2.1.5:
|
||||||
resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
peerDependencies:
|
engines: {node: '>= 8'}
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.21.0
|
|
||||||
'@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@radix-ui/react-popover@1.0.5(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-GRHZ8yD12MrN2NLobHPE8Rb5uHTxd9x372DE9PPNnBjpczAQHcZ5ne0KXG4xpf+RDdXSzdLv9ym6mYJCDTaUZg==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.21.0
|
|
||||||
'@radix-ui/primitive': 1.0.0
|
|
||||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-focus-guards': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-focus-scope': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
|
||||||
'@radix-ui/react-popper': 1.1.1(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@radix-ui/react-slot': 1.0.1(react@18.2.0)
|
|
||||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
|
||||||
aria-hidden: 1.2.3
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
react-remove-scroll: 2.5.5(@types/react@18.0.27)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@radix-ui/react-popper@1.1.1(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.21.0
|
'@babel/runtime': 7.21.0
|
||||||
'@floating-ui/react-dom': 0.7.2(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
'@floating-ui/react-dom': 0.7.2(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
@ -642,9 +612,8 @@ packages:
|
||||||
resolution: {integrity: sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==}
|
resolution: {integrity: sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/json-schema@7.0.11:
|
/@types/estree@1.0.1:
|
||||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/json5@0.0.29:
|
/@types/json5@0.0.29:
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
@ -833,7 +802,6 @@ packages:
|
||||||
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
||||||
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==}
|
||||||
|
@ -880,32 +848,7 @@ packages:
|
||||||
resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
|
resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.5.0
|
dequal: 2.0.3
|
||||||
dev: false
|
|
||||||
|
|
||||||
/aria-query@5.1.3:
|
|
||||||
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
|
||||||
dependencies:
|
|
||||||
deep-equal: 2.2.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/array-buffer-byte-length@1.0.0:
|
|
||||||
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
|
|
||||||
dependencies:
|
|
||||||
call-bind: 1.0.2
|
|
||||||
is-array-buffer: 3.0.2
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/array-includes@3.1.6:
|
|
||||||
resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
dependencies:
|
|
||||||
call-bind: 1.0.2
|
|
||||||
define-properties: 1.2.0
|
|
||||||
es-abstract: 1.21.2
|
|
||||||
get-intrinsic: 1.2.0
|
|
||||||
is-string: 1.0.7
|
|
||||||
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==}
|
||||||
|
@ -975,7 +918,7 @@ packages:
|
||||||
/axobject-query@3.1.1:
|
/axobject-query@3.1.1:
|
||||||
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
|
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-equal: 2.2.0
|
dequal: 2.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/bail@2.0.2:
|
/bail@2.0.2:
|
||||||
|
@ -1110,6 +1053,16 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/code-red@1.0.3:
|
||||||
|
resolution: {integrity: sha512-kVwJELqiILQyG5aeuyKFbdsI1fmQy1Cmf7dQ8eGmVuJoaRVdwey7WaMknr2ZFeVSYSKT0rExsa8EGw0aoI/1QQ==}
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
|
'@types/estree': 1.0.1
|
||||||
|
acorn: 8.10.0
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
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==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
|
@ -1156,6 +1109,14 @@ packages:
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/css-tree@2.3.1:
|
||||||
|
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
|
||||||
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||||
|
dependencies:
|
||||||
|
mdn-data: 2.0.30
|
||||||
|
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==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
@ -1251,7 +1212,7 @@ 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: false
|
dev: true
|
||||||
|
|
||||||
/detect-libc@2.0.1:
|
/detect-libc@2.0.1:
|
||||||
resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
|
resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
|
||||||
|
@ -1742,6 +1703,16 @@ packages:
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/estree-walker@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/estree-walker@3.0.3:
|
||||||
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
dependencies:
|
||||||
|
'@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==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -1823,12 +1794,6 @@ packages:
|
||||||
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/for-each@0.3.3:
|
|
||||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
|
||||||
dependencies:
|
|
||||||
is-callable: 1.2.7
|
|
||||||
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
|
||||||
|
@ -2246,60 +2211,7 @@ packages:
|
||||||
/is-shared-array-buffer@1.0.2:
|
/is-shared-array-buffer@1.0.2:
|
||||||
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
|
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
'@types/estree': 1.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-string@1.0.7:
|
|
||||||
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
dependencies:
|
|
||||||
has-tostringtag: 1.0.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-symbol@1.0.4:
|
|
||||||
resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
dependencies:
|
|
||||||
has-symbols: 1.0.3
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-typed-array@1.1.10:
|
|
||||||
resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
dependencies:
|
|
||||||
available-typed-arrays: 1.0.5
|
|
||||||
call-bind: 1.0.2
|
|
||||||
for-each: 0.3.3
|
|
||||||
gopd: 1.0.1
|
|
||||||
has-tostringtag: 1.0.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-weakmap@2.0.1:
|
|
||||||
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-weakref@1.0.2:
|
|
||||||
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
|
|
||||||
dependencies:
|
|
||||||
call-bind: 1.0.2
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-weakset@2.0.2:
|
|
||||||
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
|
|
||||||
dependencies:
|
|
||||||
call-bind: 1.0.2
|
|
||||||
get-intrinsic: 1.2.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-wsl@2.2.0:
|
|
||||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
dependencies:
|
|
||||||
is-docker: 2.2.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/isarray@2.0.5:
|
|
||||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/isexe@2.0.0:
|
/isexe@2.0.0:
|
||||||
|
@ -2385,6 +2297,15 @@ packages:
|
||||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/local-pkg@0.4.3:
|
||||||
|
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/locate-character@3.0.0:
|
||||||
|
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==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -2412,12 +2333,9 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
|
|
||||||
/markdown-table@3.0.3:
|
/magic-string@0.27.0:
|
||||||
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
|
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||||
dev: false
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
/mdast-util-definitions@5.1.2:
|
|
||||||
resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==}
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 3.0.11
|
'@types/mdast': 3.0.11
|
||||||
'@types/unist': 2.0.6
|
'@types/unist': 2.0.6
|
||||||
|
@ -2427,11 +2345,8 @@ packages:
|
||||||
/mdast-util-find-and-replace@2.2.2:
|
/mdast-util-find-and-replace@2.2.2:
|
||||||
resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==}
|
resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 3.0.11
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
escape-string-regexp: 5.0.0
|
dev: true
|
||||||
unist-util-is: 5.2.1
|
|
||||||
unist-util-visit-parents: 5.1.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-from-markdown@1.3.0:
|
/mdast-util-from-markdown@1.3.0:
|
||||||
resolution: {integrity: sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==}
|
resolution: {integrity: sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==}
|
||||||
|
@ -2452,100 +2367,9 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/mdast-util-gfm-autolink-literal@1.0.3:
|
/mdn-data@2.0.30:
|
||||||
resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==}
|
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||||
dependencies:
|
dev: true
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
ccount: 2.0.1
|
|
||||||
mdast-util-find-and-replace: 2.2.2
|
|
||||||
micromark-util-character: 1.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-gfm-footnote@1.0.2:
|
|
||||||
resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
mdast-util-to-markdown: 1.5.0
|
|
||||||
micromark-util-normalize-identifier: 1.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-gfm-strikethrough@1.0.3:
|
|
||||||
resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
mdast-util-to-markdown: 1.5.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-gfm-table@1.0.7:
|
|
||||||
resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
markdown-table: 3.0.3
|
|
||||||
mdast-util-from-markdown: 1.3.0
|
|
||||||
mdast-util-to-markdown: 1.5.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-gfm-task-list-item@1.0.2:
|
|
||||||
resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
mdast-util-to-markdown: 1.5.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-gfm@2.0.2:
|
|
||||||
resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==}
|
|
||||||
dependencies:
|
|
||||||
mdast-util-from-markdown: 1.3.0
|
|
||||||
mdast-util-gfm-autolink-literal: 1.0.3
|
|
||||||
mdast-util-gfm-footnote: 1.0.2
|
|
||||||
mdast-util-gfm-strikethrough: 1.0.3
|
|
||||||
mdast-util-gfm-table: 1.0.7
|
|
||||||
mdast-util-gfm-task-list-item: 1.0.2
|
|
||||||
mdast-util-to-markdown: 1.5.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-phrasing@3.0.1:
|
|
||||||
resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
unist-util-is: 5.2.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-to-hast@12.3.0:
|
|
||||||
resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==}
|
|
||||||
dependencies:
|
|
||||||
'@types/hast': 2.3.4
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
mdast-util-definitions: 5.1.2
|
|
||||||
micromark-util-sanitize-uri: 1.1.0
|
|
||||||
trim-lines: 3.0.1
|
|
||||||
unist-util-generated: 2.0.1
|
|
||||||
unist-util-position: 4.0.4
|
|
||||||
unist-util-visit: 4.1.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-to-markdown@1.5.0:
|
|
||||||
resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
'@types/unist': 2.0.6
|
|
||||||
longest-streak: 3.1.0
|
|
||||||
mdast-util-phrasing: 3.0.1
|
|
||||||
mdast-util-to-string: 3.2.0
|
|
||||||
micromark-util-decode-string: 1.0.2
|
|
||||||
unist-util-visit: 4.1.2
|
|
||||||
zwitch: 2.0.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/mdast-util-to-string@3.2.0:
|
|
||||||
resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
|
|
||||||
dependencies:
|
|
||||||
'@types/mdast': 3.0.11
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/merge2@1.4.1:
|
/merge2@1.4.1:
|
||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
|
@ -2861,6 +2685,12 @@ packages:
|
||||||
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/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
|
||||||
|
@ -3080,6 +2910,22 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pathe@1.1.1:
|
||||||
|
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/pathval@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/periscopic@3.1.0:
|
||||||
|
resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.1
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
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==}
|
||||||
|
|
||||||
|
@ -3805,8 +3651,84 @@ packages:
|
||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tailwind-merge@1.12.0:
|
/svelte-hmr@0.15.2(svelte@4.1.1):
|
||||||
resolution: {integrity: sha512-Y17eDp7FtN1+JJ4OY0Bqv9OA41O+MS8c1Iyr3T6JFLnOgLg3EvcyMKZAnQ8AGyvB5Nxm3t9Xb5Mhe139m8QT/g==}
|
resolution: {integrity: sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==}
|
||||||
|
engines: {node: ^12.20 || ^14.13.1 || >= 16}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.19.0 || ^4.0.0-next.0
|
||||||
|
dependencies:
|
||||||
|
svelte: 4.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/svelte-preprocess@5.0.4(postcss@8.4.27)(svelte@4.1.1)(typescript@5.1.6):
|
||||||
|
resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==}
|
||||||
|
engines: {node: '>= 14.10.0'}
|
||||||
|
requiresBuild: true
|
||||||
|
peerDependencies:
|
||||||
|
'@babel/core': ^7.10.2
|
||||||
|
coffeescript: ^2.5.1
|
||||||
|
less: ^3.11.3 || ^4.0.0
|
||||||
|
postcss: ^7 || ^8
|
||||||
|
postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0
|
||||||
|
pug: ^3.0.0
|
||||||
|
sass: ^1.26.8
|
||||||
|
stylus: ^0.55.0
|
||||||
|
sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||||
|
svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0
|
||||||
|
typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@babel/core':
|
||||||
|
optional: true
|
||||||
|
coffeescript:
|
||||||
|
optional: true
|
||||||
|
less:
|
||||||
|
optional: true
|
||||||
|
postcss:
|
||||||
|
optional: true
|
||||||
|
postcss-load-config:
|
||||||
|
optional: true
|
||||||
|
pug:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
stylus:
|
||||||
|
optional: true
|
||||||
|
sugarss:
|
||||||
|
optional: true
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/pug': 2.0.6
|
||||||
|
detect-indent: 6.1.0
|
||||||
|
magic-string: 0.27.0
|
||||||
|
postcss: 8.4.27
|
||||||
|
sorcery: 0.11.0
|
||||||
|
strip-indent: 3.0.0
|
||||||
|
svelte: 4.1.1
|
||||||
|
typescript: 5.1.6
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/svelte@4.1.1:
|
||||||
|
resolution: {integrity: sha512-Enick5fPFISLoVy0MFK45cG+YlQt6upw8skEK9zzTpJnH1DqEv8xOZwizCGSo3Q6HZ7KrZTM0J18poF7aQg5zw==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
dependencies:
|
||||||
|
'@ampproject/remapping': 2.2.1
|
||||||
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
|
acorn: 8.10.0
|
||||||
|
aria-query: 5.3.0
|
||||||
|
axobject-query: 3.2.1
|
||||||
|
code-red: 1.0.3
|
||||||
|
css-tree: 2.3.1
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
is-reference: 3.0.1
|
||||||
|
locate-character: 3.0.0
|
||||||
|
magic-string: 0.30.1
|
||||||
|
periscopic: 3.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/tailwind-merge@1.14.0:
|
||||||
|
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tailwindcss@3.3.1(postcss@8.4.21):
|
/tailwindcss@3.3.1(postcss@8.4.21):
|
||||||
|
@ -4190,7 +4112,3 @@ packages:
|
||||||
/zod@3.21.4:
|
/zod@3.21.4:
|
||||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/zwitch@2.0.4:
|
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
|
||||||
dev: false
|
|
||||||
|
|
103
src/app.css
Normal file
103
src/app.css
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
@import 'tailwindcss/base';
|
||||||
|
@import 'tailwindcss/components';
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Karrik';
|
||||||
|
src: url('/fonts/Karrik.woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code';
|
||||||
|
src: url('/fonts/FiraCode.woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
|
||||||
|
--primary: 222.2 47.4% 11.2%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--ring: 215 20.2% 65.1%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
|
||||||
|
--primary: 210 40% 98%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 85.7% 97.3%;
|
||||||
|
|
||||||
|
--ring: 217.2 32.6% 17.5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border text-white;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.console {
|
||||||
|
@apply relative top-0.5 inline-block;
|
||||||
|
}
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
textarea:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill:hover,
|
||||||
|
textarea:-webkit-autofill:focus {
|
||||||
|
-webkit-box-shadow: 0 0 0px 1000px hsl(258deg 15% 17%) inset;
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
}
|
23
src/lib/components/Badge.svelte
Normal file
23
src/lib/components/Badge.svelte
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/Utils';
|
||||||
|
|
||||||
|
export let name: string;
|
||||||
|
export let src: string;
|
||||||
|
export let alt: string;
|
||||||
|
export let level: number;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex w-24 flex-col space-y-2 text-center">
|
||||||
|
<img
|
||||||
|
src={`data:image;base64,${src}`}
|
||||||
|
{alt}
|
||||||
|
class={cn(`rounded-full border-2 lg:border-4`, {
|
||||||
|
'border-green-600': level === 1,
|
||||||
|
'border-yellow-600': level === 2,
|
||||||
|
'border-red-600': level === 3
|
||||||
|
})}
|
||||||
|
width={500}
|
||||||
|
height={500}
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-semibold break-all">{name}</span>
|
||||||
|
</div>
|
42
src/lib/components/Chapter.svelte
Normal file
42
src/lib/components/Chapter.svelte
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/Utils';
|
||||||
|
import type { Chapter } from '$lib/types';
|
||||||
|
|
||||||
|
import ChevronRight from './Icons/ChevronRight.svelte';
|
||||||
|
|
||||||
|
export let chapter: Chapter;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class={cn(
|
||||||
|
'font-code group relative flex h-full w-full rounded-md bg-primary-700 transition-colors duration-150 ',
|
||||||
|
{
|
||||||
|
'hover:bg-primary-600': chapter.show,
|
||||||
|
'opacity-50': !chapter.show
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{#if chapter.show}
|
||||||
|
<a
|
||||||
|
class="flex h-full w-full items-center gap-4 p-4"
|
||||||
|
href={chapter.show ? `/dashboard/chapters/${chapter.id}` : '#'}
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
||||||
|
<h2 class="text-base font-semibold">
|
||||||
|
{chapter.name}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<span class="translate-x-0 transform-gpu duration-300 group-hover:translate-x-2">
|
||||||
|
<ChevronRight />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<span class="flex h-full w-full items-center gap-4 p-4">
|
||||||
|
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
||||||
|
<h2 class="text-base font-semibold">
|
||||||
|
{chapter.name}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</li>
|
95
src/lib/components/Navbar.svelte
Normal file
95
src/lib/components/Navbar.svelte
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Avatar from './Avatar.svelte';
|
||||||
|
import AlignLeft from './Icons/AlignLeft.svelte';
|
||||||
|
import X from './Icons/X.svelte';
|
||||||
|
|
||||||
|
export let isOpen: boolean;
|
||||||
|
|
||||||
|
$: user = $page.data.user;
|
||||||
|
$: segments = $page.url.pathname.slice(1).split('/');
|
||||||
|
$: breadcrumb = segments.map((segment, index) => {
|
||||||
|
return { name: segment, href: '/' + segments.slice(0, index + 1).join('/') };
|
||||||
|
}) as { name: string; href: string }[];
|
||||||
|
|
||||||
|
function handleToggle() {
|
||||||
|
isOpen = !isOpen;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
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 items-center">
|
||||||
|
<button on:click={handleToggle} class="block sm:hidden">
|
||||||
|
{#if isOpen}
|
||||||
|
<X class="h-5 w-5 text-muted" />
|
||||||
|
{:else}
|
||||||
|
<AlignLeft class="h-5 w-5 text-muted" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{#if !isOpen && segments.length}
|
||||||
|
<div class="hidden items-center justify-center capitalize text-highlight-secondary sm:flex">
|
||||||
|
{#each breadcrumb as segment}
|
||||||
|
{@const last = segment === breadcrumb[breadcrumb.length - 1]}
|
||||||
|
<a class="hover:text-primary hover:underline" href={segment.href}>
|
||||||
|
{segment.name}
|
||||||
|
</a>
|
||||||
|
{#if !last}
|
||||||
|
<span class="mx-1 text-highlight-secondary">/</span>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<!-- {#if !isOpen && segment}
|
||||||
|
<div class="flex items-center justify-center capitalize text-highlight-secondary">
|
||||||
|
{segment}
|
||||||
|
</div>
|
||||||
|
{/if} -->
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
{#if !isOpen}
|
||||||
|
<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 ? (
|
||||||
|
<Popover
|
||||||
|
open={isMenuOpen}
|
||||||
|
onOpenChange={setIsMenuOpen}
|
||||||
|
trigger={
|
||||||
|
<button class="mx-auto flex items-center gap-2">
|
||||||
|
<AvatarComponent name={me.pseudo} src={me.avatar} class="h-9 w-9" />
|
||||||
|
<span>{me?.pseudo}</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<nav class="flex w-32 flex-col gap-2">
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-1 p-2 text-destructive hover:bg-destructive/10"
|
||||||
|
onClick={() => router.push('/logout')}
|
||||||
|
>
|
||||||
|
Se déconnecter
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<div class="animate-pulse">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="h-9 w-9 rounded-full bg-highlight-primary" />
|
||||||
|
<div class="h-4 w-14 rounded-full bg-highlight-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)} -->
|
||||||
|
</div>
|
||||||
|
</div>
|
82
src/lib/components/Puzzle.svelte
Normal file
82
src/lib/components/Puzzle.svelte
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { cn } from '$lib/Utils';
|
||||||
|
import type { Puzzle } from '$lib/types';
|
||||||
|
import ChevronRight from './Icons/ChevronRight.svelte';
|
||||||
|
|
||||||
|
export let puzzle: Puzzle;
|
||||||
|
|
||||||
|
const chapterId = $page.params.chapterId;
|
||||||
|
|
||||||
|
$: tags = puzzle.tags?.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class={cn(
|
||||||
|
'font-code group relative flex h-full w-full rounded-md border-2 bg-primary-700 transition-colors duration-150',
|
||||||
|
{
|
||||||
|
'border-green-600/30': puzzle.tags?.find((tag) => tag.name.toLowerCase() === 'easy'),
|
||||||
|
'border-yellow-600/30': puzzle.tags?.find((tag) => tag.name.toLowerCase() === 'medium'),
|
||||||
|
'border-red-600/30': puzzle.tags?.find((tag) => tag.name.toLowerCase() === 'hard'),
|
||||||
|
'border-highlight-primary': !puzzle.tags?.length,
|
||||||
|
'hover:bg-primary-600': puzzle.show,
|
||||||
|
'opacity-50': !puzzle.show
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{#if puzzle.show}
|
||||||
|
<a
|
||||||
|
class="flex h-full w-full items-center gap-4 p-4"
|
||||||
|
href="/dashboard/chapters/{chapterId}/puzzle/{puzzle.id}"
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
||||||
|
<h2 class="text-base font-semibold">
|
||||||
|
{puzzle.name}
|
||||||
|
<span class="text-sm text-highlight-secondary">
|
||||||
|
({puzzle.score ? `${puzzle.score}` : '?'}/{puzzle.scoreMax} points)
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<div class="flex items-center gap-x-6">
|
||||||
|
{#if puzzle.tags?.length}
|
||||||
|
<div class="flex gap-x-2 text-sm">
|
||||||
|
{#each puzzle.tags as tag}
|
||||||
|
<span
|
||||||
|
class="inline-block rounded-md bg-primary-800 px-2 py-1 text-highlight-secondary"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="translate-x-0 transform-gpu duration-300 group-hover:translate-x-2">
|
||||||
|
<ChevronRight />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<span class="flex h-full w-full items-center gap-4 p-4">
|
||||||
|
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
||||||
|
<h2 class="text-base font-semibold">
|
||||||
|
{puzzle.name}
|
||||||
|
<span class="text-sm text-highlight-secondary">
|
||||||
|
({puzzle.score ? `${puzzle.score}` : '?'}/{puzzle.scoreMax} points)
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<div class="flex items-center gap-x-6">
|
||||||
|
{#if puzzle.tags?.length}
|
||||||
|
<div class="flex gap-x-2 text-sm">
|
||||||
|
{#each puzzle.tags as tag}
|
||||||
|
<span
|
||||||
|
class="inline-block rounded-md bg-primary-800 px-2 py-1 text-highlight-secondary"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</li>
|
220
src/lib/components/Sidenav.svelte
Normal file
220
src/lib/components/Sidenav.svelte
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import { cn } from '$lib/Utils';
|
||||||
|
|
||||||
|
import Badge from '$lib/components/Icons/Badge.svelte';
|
||||||
|
import Code from '$lib/components/Icons/Code.svelte';
|
||||||
|
import Dashboard from '$lib/components/Icons/Dashboard.svelte';
|
||||||
|
import Leaderboard from '$lib/components/Icons/Leaderboard.svelte';
|
||||||
|
import Settings from '$lib/components/Icons/Settings.svelte';
|
||||||
|
import Discord from './Icons/Discord.svelte';
|
||||||
|
import Git from './Icons/Git.svelte';
|
||||||
|
import Help from './Icons/Help.svelte';
|
||||||
|
import Mail from './Icons/Mail.svelte';
|
||||||
|
|
||||||
|
$: path = $page.url.pathname;
|
||||||
|
$: isActive = (slug: string) => path === slug;
|
||||||
|
|
||||||
|
export let isOpen: boolean;
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{
|
||||||
|
name: 'Dashboard',
|
||||||
|
slug: '/dashboard',
|
||||||
|
icon: Dashboard
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Classement',
|
||||||
|
slug: '/dashboard/leaderboard',
|
||||||
|
icon: Leaderboard
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chapitres',
|
||||||
|
slug: '/dashboard/chapters',
|
||||||
|
icon: Code
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Badges',
|
||||||
|
slug: '/dashboard/badges',
|
||||||
|
icon: Badge
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Paramètres',
|
||||||
|
slug: '/dashboard/settings',
|
||||||
|
icon: Settings
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<aside
|
||||||
|
class={cn(
|
||||||
|
'absolute z-10 h-screen w-28 border-r border-highlight-primary bg-gradient-to-b from-primary-800 to-primary-900 shadow-md transition-all duration-300 ease-in-out sm:relative sm:flex sm:flex-col lg:w-60',
|
||||||
|
{
|
||||||
|
'bottom-0 -translate-x-full sm:translate-x-0': !isOpen,
|
||||||
|
'bottom-0 w-full sm:w-28': isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="flex h-full flex-col">
|
||||||
|
<div class="flex w-full justify-center p-[8.5px]">
|
||||||
|
<img
|
||||||
|
src="/assets/brand/peerat.png"
|
||||||
|
alt="Logo"
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
loading="eager"
|
||||||
|
draggable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="px-4">
|
||||||
|
<hr class="border-highlight-primary" />
|
||||||
|
</div>
|
||||||
|
<div class="px-4 pt-4">
|
||||||
|
<ul class="flex flex-col gap-4">
|
||||||
|
{#each navItems as item}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
href={item.slug}
|
||||||
|
class={cn(
|
||||||
|
'flex justify-center rounded-md px-3 py-3 text-sm transition-colors duration-150 lg:justify-start',
|
||||||
|
{
|
||||||
|
'bg-primary-700': isActive(item.slug),
|
||||||
|
'group hover:bg-primary-700': !isActive(item.slug)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<svelte:component
|
||||||
|
this={item.icon}
|
||||||
|
class={cn({
|
||||||
|
'stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0':
|
||||||
|
!isActive(item.slug)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn('hidden lg:block', {
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen,
|
||||||
|
'text-highlight-secondary transition-colors duration-150 group-hover:text-primary':
|
||||||
|
!isActive(item.slug)
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 pt-4">
|
||||||
|
<hr class="border-highlight-primary" />
|
||||||
|
</div>
|
||||||
|
<div class="px-4 pt-4">
|
||||||
|
<ul class="flex flex-col gap-4">
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Help class="stroke-highlight-secondary transition-colors duration-150" />
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Aide
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
href="mailto:cyberbottle@peerat.dev"
|
||||||
|
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">
|
||||||
|
<Mail
|
||||||
|
class="stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Mail
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
href="//discord.gg/72vuHcwUkE"
|
||||||
|
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">
|
||||||
|
<Discord
|
||||||
|
class="fill-highlight-secondary transition-colors duration-150 group-hover:fill-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
href="//git.peerat.dev"
|
||||||
|
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">
|
||||||
|
<Git
|
||||||
|
class="stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Git
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
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>
|
52
src/lib/components/ui/Input.svelte
Normal file
52
src/lib/components/ui/Input.svelte
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
import { cn } from '$lib';
|
||||||
|
|
||||||
|
type FormInputEvent<T extends Event = Event> = T & {
|
||||||
|
currentTarget: EventTarget & HTMLInputElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputEvents = {
|
||||||
|
blur: FormInputEvent<FocusEvent>;
|
||||||
|
change: FormInputEvent<Event>;
|
||||||
|
click: FormInputEvent<MouseEvent>;
|
||||||
|
focus: FormInputEvent<FocusEvent>;
|
||||||
|
keydown: FormInputEvent<KeyboardEvent>;
|
||||||
|
keypress: FormInputEvent<KeyboardEvent>;
|
||||||
|
keyup: FormInputEvent<KeyboardEvent>;
|
||||||
|
mouseover: FormInputEvent<MouseEvent>;
|
||||||
|
mouseenter: FormInputEvent<MouseEvent>;
|
||||||
|
mouseleave: FormInputEvent<MouseEvent>;
|
||||||
|
paste: FormInputEvent<ClipboardEvent>;
|
||||||
|
input: FormInputEvent<InputEvent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type $$Props = HTMLInputAttributes;
|
||||||
|
type $$Events = InputEvents;
|
||||||
|
|
||||||
|
let className: $$Props['class'] = undefined;
|
||||||
|
export let value: $$Props['value'] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
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',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:value
|
||||||
|
on:blur
|
||||||
|
on:change
|
||||||
|
on:click
|
||||||
|
on:focus
|
||||||
|
on:keydown
|
||||||
|
on:keypress
|
||||||
|
on:keyup
|
||||||
|
on:mouseover
|
||||||
|
on:mouseenter
|
||||||
|
on:mouseleave
|
||||||
|
on:paste
|
||||||
|
on:input
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
84
src/lib/types/Database.ts
Normal file
84
src/lib/types/Database.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
export interface User {
|
||||||
|
email: string;
|
||||||
|
pseudo: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
description: string;
|
||||||
|
avatar: string;
|
||||||
|
groups: Group[];
|
||||||
|
score: number;
|
||||||
|
tries: number;
|
||||||
|
completions: number;
|
||||||
|
rank: number;
|
||||||
|
completionsList: Completion[];
|
||||||
|
badges: Badge[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Badge {
|
||||||
|
name: string;
|
||||||
|
level: number;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Completion {
|
||||||
|
puzzleName: string;
|
||||||
|
tries: number;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Group {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
chapter?: number;
|
||||||
|
puzzle?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Puzzle {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
scoreMax: number;
|
||||||
|
show: boolean;
|
||||||
|
tags: Tag[] | null;
|
||||||
|
tries?: number;
|
||||||
|
score?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Chapter {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
puzzles: Puzzle[];
|
||||||
|
show?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tag {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Leaderboard {
|
||||||
|
score: number;
|
||||||
|
tries: number;
|
||||||
|
completions: number;
|
||||||
|
pseudo: string;
|
||||||
|
groups: Group[];
|
||||||
|
rank: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeaderboardEvent {
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
rank: number;
|
||||||
|
players: [
|
||||||
|
{
|
||||||
|
pseudo: string;
|
||||||
|
tries: number;
|
||||||
|
completion: number;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
41
src/routes/+layout.svelte
Normal file
41
src/routes/+layout.svelte
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import '../app.css';
|
||||||
|
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
$: origin = $page.url.origin;
|
||||||
|
$: domain = $page.url.hostname;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Peer-at Code</title>
|
||||||
|
|
||||||
|
<meta name="title" content="Peer-at Code" />
|
||||||
|
<meta name="description" content="Apprendre la programmation et la cybersécurité en s'amusant." />
|
||||||
|
<meta name="theme-color" content="#110F15" />
|
||||||
|
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="language" content="French" />
|
||||||
|
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content={origin} />
|
||||||
|
<meta property="og:title" content="Peer-at Code" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Apprendre la programmation et la cybersécurité en s'amusant."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Twitter -->
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:url" content={origin} />
|
||||||
|
<meta property="twitter:title" content="Peer-at Code" />
|
||||||
|
<meta
|
||||||
|
property="twitter:description"
|
||||||
|
content="Apprendre la programmation et la cybersécurité en s'amusant."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<script defer data-domain={domain} src="https://plosibl.peerat.dev/js/script.js"></script>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<slot />
|
6
src/routes/dashboard/+layout.server.ts
Normal file
6
src/routes/dashboard/+layout.server.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { redirect, type ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const load: ServerLoad = async ({ locals: { user }, parent }) => {
|
||||||
|
await parent();
|
||||||
|
if (!user) throw redirect(303, '/sign-in');
|
||||||
|
};
|
22
src/routes/dashboard/+layout.svelte
Normal file
22
src/routes/dashboard/+layout.svelte
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<script class="ts">
|
||||||
|
import Navbar from '$lib/components/Navbar.svelte';
|
||||||
|
import Sidenav from '$lib/components/Sidenav.svelte';
|
||||||
|
import Toaster from '$lib/components/Toaster.svelte';
|
||||||
|
|
||||||
|
let isOpen = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex h-screen w-full flex-col">
|
||||||
|
<div class="flex flex-1 overflow-hidden">
|
||||||
|
<Sidenav bind:isOpen />
|
||||||
|
<div class="flex flex-1 flex-col">
|
||||||
|
<Navbar bind:isOpen />
|
||||||
|
<Toaster />
|
||||||
|
<div
|
||||||
|
class="flex w-full flex-1 transform flex-col overflow-y-scroll p-4 duration-300 ease-in-out sm:p-8"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
25
src/routes/dashboard/badges/+page.svelte
Normal file
25
src/routes/dashboard/badges/+page.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Badge from '$lib/components/Badge.svelte';
|
||||||
|
|
||||||
|
$: user = $page.data.user;
|
||||||
|
$: badges = user?.badges?.sort((a, b) => a.level - b.level);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex h-full w-full flex-col gap-4">
|
||||||
|
<header class="flex flex-col">
|
||||||
|
<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>
|
||||||
|
</header>
|
||||||
|
<main class="flex flex-col justify-between gap-4">
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
{#if badges && badges.length}
|
||||||
|
{#each badges as badge}
|
||||||
|
<Badge name={badge.name} src={badge.logo} alt={badge.name} level={badge.level} />
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<p class="text-muted">Aucun badge</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</section>
|
34
src/routes/dashboard/chapters/+page.svelte
Normal file
34
src/routes/dashboard/chapters/+page.svelte
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
import type { Chapter as IChapter } from '$lib/types';
|
||||||
|
|
||||||
|
import Chapter from '$lib/components/Chapter.svelte';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
$: chapters = data.chapters;
|
||||||
|
|
||||||
|
const toBeContinued: IChapter = {
|
||||||
|
id: Math.random() * 999,
|
||||||
|
name: 'To be continued ...',
|
||||||
|
puzzles: [],
|
||||||
|
show: false
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex w-full flex-col space-y-6">
|
||||||
|
<header class="sticky flex items-center justify-between">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="text-xl font-semibold">Chapitres</h1>
|
||||||
|
<p class="text-muted">
|
||||||
|
Les chapitres sont les différentes parties du jeu. Chaque chapitre est composé de plusieurs
|
||||||
|
puzzles.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{#each chapters as chapter}
|
||||||
|
<Chapter {chapter} />
|
||||||
|
{/each}
|
||||||
|
<Chapter chapter={toBeContinued} />
|
||||||
|
</section>
|
32
src/routes/dashboard/chapters/[chapterId]/+page.server.ts
Normal file
32
src/routes/dashboard/chapters/[chapterId]/+page.server.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import type { Chapter } from '$lib/types';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, fetch, cookies, params: { chapterId } }) => {
|
||||||
|
await parent();
|
||||||
|
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/chapter/${chapterId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw redirect(302, '/dashboard/chapters');
|
||||||
|
}
|
||||||
|
|
||||||
|
const chapter = (await res.json()) as Chapter;
|
||||||
|
|
||||||
|
if (!chapter || !chapter.show) {
|
||||||
|
throw redirect(302, '/dashboard/chapters');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
chapter
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
24
src/routes/dashboard/chapters/[chapterId]/+page.svelte
Normal file
24
src/routes/dashboard/chapters/[chapterId]/+page.svelte
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
import Puzzle from '$lib/components/Puzzle.svelte';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
data.chapter.puzzles = data.chapter.puzzles.sort((a, b) => a.scoreMax - b.scoreMax);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex w-full flex-col space-y-6">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col justify-between md:flex-row md:items-center">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h1 class="text-xl font-semibold">{data.chapter.name}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="flex flex-col gap-4">
|
||||||
|
{#each data.chapter.puzzles as puzzle}
|
||||||
|
<Puzzle {puzzle} />
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, params: { chapterId } }) => {
|
||||||
|
await parent();
|
||||||
|
throw redirect(303, chapterId ? `/dashboard/chapters/${chapterId}` : `/dashboard/chapters`);
|
||||||
|
}) satisfies PageServerLoad;
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
import { error, redirect, type Actions, fail } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import type Puzzle from '$lib/components/Puzzle.svelte';
|
||||||
|
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 } }) => {
|
||||||
|
await parent();
|
||||||
|
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
if (isNaN(parseInt(puzzleId))) {
|
||||||
|
throw redirect(303, `/dashboard/chapters/${chapterId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await fetch(`${API_URL}/chapter/${chapterId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw redirect(303, `/dashboard/chapters`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chapter = (await res.json()) as Chapter;
|
||||||
|
|
||||||
|
if (!chapter || !chapter.show) {
|
||||||
|
throw redirect(303, `/dashboard/chapters`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chapter.puzzles.some((puzzle) => puzzle.id === parseInt(puzzleId))) {
|
||||||
|
throw redirect(303, `/dashboard/chapters/${chapterId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = await fetch(`${API_URL}/puzzle/${puzzleId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw error(404, 'Puzzle not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const puzzle = await res.json();
|
||||||
|
|
||||||
|
if (!puzzle) {
|
||||||
|
throw error(404, 'Puzzle not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
puzzle: puzzle as Puzzle,
|
||||||
|
url: `${API_URL}/puzzleResponse/${puzzleId}`,
|
||||||
|
session
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default: async ({ params, request, cookies }) => {
|
||||||
|
throw redirect(303, `/dashboard/chapters/${params.chapterId}/puzzle/${params.puzzleId}`);
|
||||||
|
}
|
||||||
|
} satisfies Actions;
|
|
@ -0,0 +1,158 @@
|
||||||
|
<script lang="ts">
|
||||||
|
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 { cn } from '$lib';
|
||||||
|
import { addToast } from '$lib/components/Toaster.svelte';
|
||||||
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
$: puzzle = data.puzzle;
|
||||||
|
$: chapterId = $page.params.chapterId;
|
||||||
|
|
||||||
|
const renderer = new marked.Renderer();
|
||||||
|
|
||||||
|
renderer.link = (href, title, text) => {
|
||||||
|
const html = marked.Renderer.prototype.link.call(renderer, href, title, text);
|
||||||
|
return html.replace(
|
||||||
|
/^<a /,
|
||||||
|
'<a target="_blank" rel="nofollow" class="text-brand hover:text-brand/90" '
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.br = () => {
|
||||||
|
return '<br />';
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.codespan = (code) => {
|
||||||
|
return `<code class="cursor-default select-none text-transparent transition-colors delay-150 hover:text-highlight-secondary">${code}</code>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: MarkedOptions = {
|
||||||
|
breaks: true,
|
||||||
|
renderer,
|
||||||
|
gfm: true
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex h-full w-full flex-col justify-between gap-4">
|
||||||
|
<h1 class="text-2xl font-bold sm:text-3xl md:text-4xl">
|
||||||
|
{puzzle.name}
|
||||||
|
<span class="text-xl text-highlight-secondary">({puzzle.scoreMax} points)</span>
|
||||||
|
</h1>
|
||||||
|
<div class="h-screen w-full overflow-y-auto break-all font-fira text-xs sm:text-base">
|
||||||
|
{@html marked(puzzle.content, options)}
|
||||||
|
</div>
|
||||||
|
{#if !puzzle.score}
|
||||||
|
<form
|
||||||
|
class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row"
|
||||||
|
method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
use:enhance={async ({ formData, cancel }) => {
|
||||||
|
if (formData.get('answer') === '') {
|
||||||
|
addToast({
|
||||||
|
data: {
|
||||||
|
title: 'Réponse vide',
|
||||||
|
description: 'Vous devez entrer une réponse'
|
||||||
|
},
|
||||||
|
closeDelay: 5000
|
||||||
|
});
|
||||||
|
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) {
|
||||||
|
addToast({
|
||||||
|
data: {
|
||||||
|
title: 'Bravo !',
|
||||||
|
description: `Vous avez trouvé la bonne réponse en ${data.tries} tentative${
|
||||||
|
data.tries > 1 ? 's' : ''
|
||||||
|
} !`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (data && data.tries)
|
||||||
|
addToast({
|
||||||
|
data: {
|
||||||
|
title: 'Mauvaise réponse',
|
||||||
|
description: `Vous avez effectué ${data.tries} tentative${
|
||||||
|
data.tries > 1 ? 's' : ''
|
||||||
|
} !`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
else if (res.ok && data?.success)
|
||||||
|
addToast({
|
||||||
|
data: {
|
||||||
|
title: 'Bravo !',
|
||||||
|
description: `Vous avez trouvé la bonne réponse !`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
else if (res.status === 423)
|
||||||
|
addToast({
|
||||||
|
data: {
|
||||||
|
title: 'Puzzle désactivé',
|
||||||
|
description: `Ce puzzle est désactivé pour le moment.`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return async ({ result }) => {
|
||||||
|
if (result.type === 'redirect') {
|
||||||
|
goto(result.location, {
|
||||||
|
invalidateAll: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-col gap-2 sm:flex-row sm:gap-4">
|
||||||
|
<div class="flex flex-col gap-y-2">
|
||||||
|
<label for="answer">Réponse</label>
|
||||||
|
<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 class="flex flex-col gap-y-2">
|
||||||
|
<label for="code_file">Fichier</label>
|
||||||
|
<Input name="code_file" type="file" accept=".py,.js,.ts,.java,.rs,.c" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button class="w-full sm:w-44" variant="brand">Valider</Button>
|
||||||
|
</form>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col items-center justify-between gap-2 sm:flex-row">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p>
|
||||||
|
Tentative{puzzle.tries && puzzle.tries > 1 ? 's' : ''} :{' '}
|
||||||
|
<span class="text-brand-accent">{puzzle.tries}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Score : <span class="text-brand-accent">{puzzle.score}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button href="/dashboard/chapters/{chapterId}" class="w-full sm:w-44" variant="brand">
|
||||||
|
Retour aux puzzles
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
28
src/routes/dashboard/leaderboard/+page.server.ts
Normal file
28
src/routes/dashboard/leaderboard/+page.server.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
import type { Leaderboard } from '$lib/types';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, fetch, cookies }) => {
|
||||||
|
await parent();
|
||||||
|
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/leaderboard`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return {
|
||||||
|
leaderboard: [] as Leaderboard[]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const leaderboard = (await res.json()) as Leaderboard[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
leaderboard
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
75
src/routes/dashboard/leaderboard/+page.svelte
Normal file
75
src/routes/dashboard/leaderboard/+page.svelte
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
$: players = data.leaderboard;
|
||||||
|
|
||||||
|
const SCORE_COLORS = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex h-full w-full flex-col gap-4">
|
||||||
|
<header class="sticky flex items-center justify-between">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h2 class="text-xl font-semibold">Tableau des scores</h2>
|
||||||
|
<p class="text-muted">Suivez la progression des élèves en direct</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- <Separator /> -->
|
||||||
|
<main class="flex flex-col justify-between gap-4 pb-4">
|
||||||
|
<!-- {data && <Podium score={scores} />} -->
|
||||||
|
<!-- {data && data.end_date && (
|
||||||
|
<Timer class="flex justify-end" targetDate={new Date(data.end_date)} />
|
||||||
|
)} -->
|
||||||
|
<ul class="flex flex-col gap-2">
|
||||||
|
{#each players as player}
|
||||||
|
<!-- {@const players = group.players.sort((a, b) => b.score - a.score)} -->
|
||||||
|
<!-- {@const last = players[players.length - 1]} -->
|
||||||
|
<li class="flex justify-between gap-2">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<span
|
||||||
|
class={cn('font-semibold text-highlight-secondary', SCORE_COLORS[player.rank - 1])}
|
||||||
|
>
|
||||||
|
{player.rank}
|
||||||
|
</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||||
|
<span class="text-lg">{player.pseudo}</span>
|
||||||
|
<span class="text-sm text-highlight-secondary">
|
||||||
|
<!-- {#if players.length > 1}
|
||||||
|
{#each players as player}
|
||||||
|
{player.pseudo || 'Anonyme'}{#if player !== last}, {/if}
|
||||||
|
{' '}
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
{player.players[0].pseudo}
|
||||||
|
{/if} -->
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-sm font-semibold">Completion(s)</span>
|
||||||
|
<span class="text-lg text-highlight-secondary">{player.completions}</span>
|
||||||
|
<!-- <span class="text-sm font-semibold"
|
||||||
|
>Essai{group.players.reduce((a, b) => a + b.tries, 0) || 0 ? 's' : ''}</span
|
||||||
|
>
|
||||||
|
<span class="text-lg text-highlight-secondary"
|
||||||
|
>{group.players.reduce((a, b) => a + b.tries, 0) || 0}</span
|
||||||
|
> -->
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-sm font-semibold">Score</span>
|
||||||
|
<span class="text-lg text-highlight-secondary">{player.score}</span>
|
||||||
|
<!-- <span class="text-lg text-highlight-secondary">
|
||||||
|
{group.players.reduce((a, b) => a + b.score, 0)}
|
||||||
|
</span> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
</section>
|
67
src/routes/dashboard/settings/+page.svelte
Normal file
67
src/routes/dashboard/settings/+page.svelte
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import type { ActionData } from './$types';
|
||||||
|
|
||||||
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
|
|
||||||
|
import plausible from '$lib/stores/Plausible';
|
||||||
|
|
||||||
|
$: user = $page.data.user;
|
||||||
|
|
||||||
|
export let form: ActionData;
|
||||||
|
|
||||||
|
$: optedOut = $plausible;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="flex flex-col gap-4" method="POST" use:enhance>
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<Input
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="philipzcwbarlow@peerat.dev"
|
||||||
|
value={user?.email}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for="firstname">Prénom</label>
|
||||||
|
<Input name="firstname" type="text" placeholder="Philip" value={user?.firstname} />
|
||||||
|
|
||||||
|
<label for="lastname">Nom</label>
|
||||||
|
<Input name="lastname" type="text" placeholder="Barlow" value={user?.lastname} />
|
||||||
|
|
||||||
|
<label for="pseudo"> Nom d'utilisateur </label>
|
||||||
|
<Input name="pseudo" type="text" placeholder="Cypher Wolf" value={user?.pseudo} />
|
||||||
|
|
||||||
|
<label for="description"> Description </label>
|
||||||
|
<Input
|
||||||
|
name="description"
|
||||||
|
placeholder="Je serai le plus grand pirate de l'espace"
|
||||||
|
type="text"
|
||||||
|
value={user?.description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label for="optout"> Ne pas me tracer de manière anonyme </label>
|
||||||
|
<input
|
||||||
|
class="h-4 w-4"
|
||||||
|
name="optout"
|
||||||
|
type="checkbox"
|
||||||
|
value={optedOut}
|
||||||
|
on:change={() => plausible.set(!optedOut)}
|
||||||
|
checked={optedOut}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-highlight-secondary">
|
||||||
|
Nous utilisons Plausible pour analyser l'utilisation de notre site web de manière anonyme.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button variant="brand">
|
||||||
|
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
||||||
|
Modifier
|
||||||
|
</Button>
|
||||||
|
</form>
|
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>
|
13
src/routes/logout/+page.server.ts
Normal file
13
src/routes/logout/+page.server.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { redirect, type ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const load: ServerLoad = async ({ cookies, locals }) => {
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
cookies.delete('session', { path: '/' });
|
||||||
|
}
|
||||||
|
|
||||||
|
locals.user = undefined;
|
||||||
|
|
||||||
|
throw redirect(303, '/');
|
||||||
|
};
|
1
src/routes/logout/+page.svelte
Normal file
1
src/routes/logout/+page.svelte
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<script lang="ts"></script>
|
58
src/routes/sign-in/+page.server.ts
Normal file
58
src/routes/sign-in/+page.server.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
|
||||||
|
import { redirect, type Actions, fail } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
pseudo: z.string().trim(),
|
||||||
|
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 = {
|
||||||
|
default: async ({ request, cookies }) => {
|
||||||
|
const form = await superValidate(request, schema);
|
||||||
|
|
||||||
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
...form.data
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const token = res.headers.get('Authorization')?.split(' ')[1];
|
||||||
|
|
||||||
|
if (!token) throw new Error('No token found');
|
||||||
|
|
||||||
|
cookies.set('session', token, {
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
|
||||||
|
throw redirect(303, '/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
form.errors.passwd = ["Nom d'utilisateur ou mot de passe incorrect"];
|
||||||
|
|
||||||
|
return fail(400, {
|
||||||
|
form
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} satisfies Actions;
|
63
src/routes/sign-in/+page.svelte
Normal file
63
src/routes/sign-in/+page.svelte
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
|
|
||||||
|
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 'redirect':
|
||||||
|
goto(result.location, {
|
||||||
|
replaceState: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</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">
|
||||||
|
<h1 class="mx-auto text-xl font-bold">Connexion</h1>
|
||||||
|
<form class="flex flex-col justify-center gap-2" method="POST" use:enhance>
|
||||||
|
<label for="pseudo"> Nom d'utilisateur </label>
|
||||||
|
<Input name="pseudo" placeholder="Barlow" type="text" required bind:value={$form.pseudo} />
|
||||||
|
{#if $errors.pseudo}<span class="text-sm text-red-500">{$errors.pseudo}</span>{/if}
|
||||||
|
|
||||||
|
<label for="passwd"> Mot de passe </label>
|
||||||
|
<Input
|
||||||
|
name="passwd"
|
||||||
|
placeholder="************"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
bind:value={$form.passwd}
|
||||||
|
/>
|
||||||
|
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
|
||||||
|
|
||||||
|
<Button class="mt-2" variant="brand">
|
||||||
|
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
||||||
|
Se connecter
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ul class="flex justify-between">
|
||||||
|
<li>
|
||||||
|
<a class="text-highlight-secondary hover:text-brand" href="/sign-up">S'inscrire</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="text-highlight-secondary hover:text-brand" href="/forgot-password"
|
||||||
|
>Mot de passe oublié</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
137
src/routes/sign-up/+page.server.ts
Normal file
137
src/routes/sign-up/+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 registerSchema = z.object({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email({
|
||||||
|
message: 'Email invalide'
|
||||||
|
})
|
||||||
|
.trim(),
|
||||||
|
firstname: z.string().trim(),
|
||||||
|
lastname: 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({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email({
|
||||||
|
message: 'Email invalide'
|
||||||
|
})
|
||||||
|
.trim(),
|
||||||
|
firstname: z.string().trim(),
|
||||||
|
lastname: z.string().trim(),
|
||||||
|
pseudo: z.string().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(registerSchema);
|
||||||
|
|
||||||
|
return {
|
||||||
|
form
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
register: async ({ request }) => {
|
||||||
|
const form = await superValidate(request, registerSchema);
|
||||||
|
|
||||||
|
if (!form.valid) {
|
||||||
|
return fail(400, { form });
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/register`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
pseudo: form.data.pseudo,
|
||||||
|
firstname: form.data.firstname,
|
||||||
|
lastname: form.data.lastname,
|
||||||
|
email: form.data.email
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return {
|
||||||
|
form
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 400) {
|
||||||
|
const { email_valid } = await res.json();
|
||||||
|
|
||||||
|
if (!email_valid) form.errors.email = ['Un compte avec cette adresse email existe déjà'];
|
||||||
|
|
||||||
|
return fail(400, { form });
|
||||||
|
}
|
||||||
|
|
||||||
|
form.errors.passwd = ["Une erreur s'est produite"];
|
||||||
|
|
||||||
|
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({
|
||||||
|
firstname: form.data.firstname,
|
||||||
|
lastname: form.data.lastname,
|
||||||
|
pseudo: form.data.pseudo,
|
||||||
|
email: form.data.email,
|
||||||
|
code: parseInt(form.data.code),
|
||||||
|
passwd: form.data.passwd
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
133
src/routes/sign-up/+page.svelte
Normal file
133
src/routes/sign-up/+page.svelte
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
<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 ? 'Confirmation' : 'Inscription'}
|
||||||
|
</h2>
|
||||||
|
<form
|
||||||
|
class="flex flex-col justify-center gap-2"
|
||||||
|
method="POST"
|
||||||
|
action={confirmation ? '?/confirmation' : '?/register'}
|
||||||
|
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}
|
||||||
|
|
||||||
|
<label for="firstname">Prénom</label>
|
||||||
|
<Input
|
||||||
|
bind:value={$form.firstname}
|
||||||
|
name="firstname"
|
||||||
|
type="text"
|
||||||
|
placeholder="Philip"
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{#if $errors.firstname}<span class="text-sm text-red-500">{$errors.firstname}</span>{/if}
|
||||||
|
|
||||||
|
<label for="lastname">Nom</label>
|
||||||
|
<Input
|
||||||
|
bind:value={$form.lastname}
|
||||||
|
name="lastname"
|
||||||
|
type="text"
|
||||||
|
placeholder="Barlow"
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{#if $errors.lastname}<span class="text-sm text-red-500">{$errors.lastname}</span>{/if}
|
||||||
|
|
||||||
|
<label for="pseudo"> Nom d'utilisateur </label>
|
||||||
|
<Input
|
||||||
|
bind:value={$form.pseudo}
|
||||||
|
name="pseudo"
|
||||||
|
type="text"
|
||||||
|
placeholder="Cypher Wolf"
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{#if $errors.pseudo}<span class="text-sm text-red-500">{$errors.pseudo}</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 ? "S'inscrire" : 'Continuer'}
|
||||||
|
</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>
|
17
svelte.config.js
Normal file
17
svelte.config.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { preprocessMeltUI } from '@melt-ui/pp';
|
||||||
|
import sequence from 'svelte-sequential-preprocessor';
|
||||||
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
|
/** @type {import('@sveltejs/kit').Config}*/
|
||||||
|
const config = {
|
||||||
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: sequence([vitePreprocess(), preprocessMeltUI()]),
|
||||||
|
kit: {
|
||||||
|
// 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.
|
||||||
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default config;
|
Loading…
Add table
Reference in a new issue