Merge branch 'dev' into 'features/stat'
# Conflicts: # package-lock.json # src/router.js # vite.config.js
@@ -0,0 +1 @@
|
||||
VITE_API_URL=
|
||||
@@ -25,3 +25,5 @@ dist-ssr
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
|
||||
.env
|
||||
@@ -1109,12 +1109,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mdx-js/react": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz",
|
||||
@@ -2156,14 +2150,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.13.tgz",
|
||||
"integrity": "sha512-lruSgrYPVAJzKmX6EJYCg9nY+0A4VkeTLpTzf1jRD/XMjNbzD9yy7D499xmVKglwJczANYJXBvZSPGcRlon+0w==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.10.tgz",
|
||||
"integrity": "sha512-irO+aGxYx/rAhjEBLsGPO4JQ8dA+A43enIST0j4xQ2kYHatHi9tUcxkRRGpClGuUVU42mi+iQsFFzd4xxpoV3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/mocker": "4.0.13",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"@vitest/mocker": "4.0.10",
|
||||
"@vitest/utils": "4.0.10",
|
||||
"magic-string": "^0.30.21",
|
||||
"pixelmatch": "7.1.0",
|
||||
"pngjs": "^7.0.0",
|
||||
@@ -2175,18 +2169,18 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vitest": "4.0.13"
|
||||
"vitest": "4.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser-playwright": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.13.tgz",
|
||||
"integrity": "sha512-oaRY+/pvwS4/sN2rE2aZh9jdli8EkXm5AidmXEbWRu2wW0omG9PmgChWCX2jsD9qRLQxXTSLl5oKezANNF6LnQ==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.10.tgz",
|
||||
"integrity": "sha512-pm7Hl7BNyluox+uGJPnT7vCRDSI+ibHcWQRtnCACAZWxD6/b2gN+8pO0qTDPHpxDSTPKDS5sT2dKTHLcr+lsng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/browser": "4.0.13",
|
||||
"@vitest/mocker": "4.0.13",
|
||||
"@vitest/browser": "4.0.10",
|
||||
"@vitest/mocker": "4.0.10",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -2194,7 +2188,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"playwright": "*",
|
||||
"vitest": "4.0.13"
|
||||
"vitest": "4.0.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"playwright": {
|
||||
@@ -2203,14 +2197,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.13.tgz",
|
||||
"integrity": "sha512-w77N6bmtJ3CFnL/YHiYotwW/JI3oDlR3K38WEIqegRfdMSScaYxwYKB/0jSNpOTZzUjQkG8HHEz4sdWQMWpQ5g==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.10.tgz",
|
||||
"integrity": "sha512-g+brmtoKa/sAeIohNJnnWhnHtU6GuqqVOSQ4SxDIPcgZWZyhJs5RmF5LpqXs8Kq64lANP+vnbn5JLzhLj/G56g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"@vitest/utils": "4.0.10",
|
||||
"ast-v8-to-istanbul": "^0.3.8",
|
||||
"debug": "^4.4.3",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
@@ -2225,8 +2219,8 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "4.0.13",
|
||||
"vitest": "4.0.13"
|
||||
"@vitest/browser": "4.0.10",
|
||||
"vitest": "4.0.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
@@ -2303,13 +2297,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.13.tgz",
|
||||
"integrity": "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.10.tgz",
|
||||
"integrity": "sha512-e2OfdexYkjkg8Hh3L9NVEfbwGXq5IZbDovkf30qW2tOh7Rh9sVtmSr2ztEXOFbymNxS4qjzLXUQIvATvN4B+lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.0.13",
|
||||
"@vitest/spy": "4.0.10",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -2340,9 +2334,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.13.tgz",
|
||||
"integrity": "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.10.tgz",
|
||||
"integrity": "sha512-99EQbpa/zuDnvVjthwz5bH9o8iPefoQZ63WV8+bsRJZNw3qQSvSltfut8yu1Jc9mqOYi7pEbsKxYTi/rjaq6PA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2353,13 +2347,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.13.tgz",
|
||||
"integrity": "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.10.tgz",
|
||||
"integrity": "sha512-EXU2iSkKvNwtlL8L8doCpkyclw0mc/t4t9SeOnfOFPyqLmQwuceMPA4zJBa6jw0MKsZYbw7kAn+gl7HxrlB8UQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.13",
|
||||
"@vitest/utils": "4.0.10",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -2367,13 +2361,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.13.tgz",
|
||||
"integrity": "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.10.tgz",
|
||||
"integrity": "sha512-2N4X2ZZl7kZw0qeGdQ41H0KND96L3qX1RgwuCfy6oUsF2ISGD/HpSbmms+CkIOsQmg2kulwfhJ4CI0asnZlvkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.13",
|
||||
"@vitest/pretty-format": "4.0.10",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -2382,9 +2376,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.13.tgz",
|
||||
"integrity": "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.10.tgz",
|
||||
"integrity": "sha512-AsY6sVS8OLb96GV5RoG8B6I35GAbNrC49AO+jNRF9YVGb/g9t+hzNm1H6kD0NDp8tt7VJLs6hb7YMkDXqu03iw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -2392,13 +2386,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.13.tgz",
|
||||
"integrity": "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.10.tgz",
|
||||
"integrity": "sha512-kOuqWnEwZNtQxMKg3WmPK1vmhZu9WcoX69iwWjVz+jvKTsF1emzsv3eoPcDr6ykA3qP2bsCQE7CwqfNtAVzsmg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.13",
|
||||
"@vitest/pretty-format": "4.0.10",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -2690,18 +2684,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||
@@ -4439,16 +4421,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-chartjs-2": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz",
|
||||
"integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"chart.js": "^4.1.1",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-docgen": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.2.tgz",
|
||||
@@ -5227,9 +5199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
|
||||
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
|
||||
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5359,19 +5331,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.13.tgz",
|
||||
"integrity": "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.10.tgz",
|
||||
"integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.13",
|
||||
"@vitest/mocker": "4.0.13",
|
||||
"@vitest/pretty-format": "4.0.13",
|
||||
"@vitest/runner": "4.0.13",
|
||||
"@vitest/snapshot": "4.0.13",
|
||||
"@vitest/spy": "4.0.13",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"@vitest/expect": "4.0.10",
|
||||
"@vitest/mocker": "4.0.10",
|
||||
"@vitest/pretty-format": "4.0.10",
|
||||
"@vitest/runner": "4.0.10",
|
||||
"@vitest/snapshot": "4.0.10",
|
||||
"@vitest/spy": "4.0.10",
|
||||
"@vitest/utils": "4.0.10",
|
||||
"debug": "^4.4.3",
|
||||
"es-module-lexer": "^1.7.0",
|
||||
"expect-type": "^1.2.2",
|
||||
@@ -5397,13 +5369,12 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.0.13",
|
||||
"@vitest/browser-preview": "4.0.13",
|
||||
"@vitest/browser-webdriverio": "4.0.13",
|
||||
"@vitest/ui": "4.0.13",
|
||||
"@vitest/browser-playwright": "4.0.10",
|
||||
"@vitest/browser-preview": "4.0.10",
|
||||
"@vitest/browser-webdriverio": "4.0.10",
|
||||
"@vitest/ui": "4.0.10",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
@@ -5411,9 +5382,6 @@
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/debug": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -5441,16 +5409,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/@vitest/expect": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz",
|
||||
"integrity": "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==",
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.10.tgz",
|
||||
"integrity": "sha512-3QkTX/lK39FBNwARCQRSQr0TP9+ywSdxSX+LgbJ2M1WmveXP72anTbnp2yl5fH+dU6SUmBzNMrDHs80G8G2DZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.0.13",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"@vitest/spy": "4.0.10",
|
||||
"@vitest/utils": "4.0.10",
|
||||
"chai": "^6.2.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 605 B |
|
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="m80-280 150-400h86l150 400h-82l-34-96H196l-32 96H80Zm140-164h104l-48-150h-6l-50 150Zm328 164v-76l202-252H556v-72h282v76L638-352h202v72H548ZM360-760l120-120 120 120H360ZM480-80 360-200h240L480-80Z"/></svg>
|
||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M 480,-880 360,-760 H 600 Z M 360,-200 480,-80 600,-200 Z" />
|
||||
<path d="M 71.679924,-674.40094 H 398.50264 v 60.67705 l -208.59362,252.3436 h 214.5832 v 75.7812 H 65.690344 v -60.67705 L 274.28397,-598.61973 H 71.679924 Z" />
|
||||
<path d="M 769.04932,-356.43238 H 612.27858 l -24.73957,70.83329 H 486.75782 l 144.01033,-388.80185 h 119.53118 l 144.01033,388.80185 H 793.52847 Z m -131.77076,-72.13537 h 106.51035 l -53.12496,-154.68741 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 545 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M340-160q-125 0-212.5-87.5T40-460q0-125 87.5-212.5T340-760q52 0 98 16.5t84 45.5l42-42 56 56-42 42q29 38 45.5 84.5T640-460q0 125-87.5 212.5T340-160Zm440 0L640-300l56-56 44 44v-488h80v487l43-43 57 56-140 140ZM240-800v-80h200v80H240Zm100 560q92 0 156-64t64-156q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 92 64 156t156 64Zm-40-180h80v-200h-80v200Zm40-40Z"/></svg>
|
||||
|
After Width: | Height: | Size: 475 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M340-160q-125 0-212.5-87.5T40-460q0-125 87.5-212.5T340-760q52 0 98 16.5t84 45.5l42-42 56 56-42 42q29 38 45.5 84.5T640-460q0 125-87.5 212.5T340-160Zm400 0v-488l-44 44-56-56 140-140 140 140-57 56-43-43v487h-80ZM240-800v-80h200v80H240Zm100 560q92 0 156-64t64-156q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 92 64 156t156 64Zm-40-180h80v-200h-80v200Zm40-40Z"/></svg>
|
||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,79 @@
|
||||
import styles from "./Event.module.css";
|
||||
import {useState, useEffect} from "react";
|
||||
import {Link} from "react-router";
|
||||
import Button from "/src/components/ui/button/button.jsx"
|
||||
|
||||
function Event({event}){
|
||||
const [eventMenu, seteventMenu] = useState(false);
|
||||
const [windowSize, setWindowSize] = useState({
|
||||
width: null,
|
||||
height: null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize();
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={`${styles.eventContainer} glassCard`}>
|
||||
<div className={styles.eventContainerTop}>
|
||||
<div className={styles.eventContent}>
|
||||
<div className={`${styles.eventImageDiv}`}>
|
||||
{event.image == null ? (
|
||||
<img src="empty.png" className={styles.eventImage} alt="Pas de photo de l'événement" />
|
||||
) : (
|
||||
<img src={event.image + `.png`} alt="Photo de l'événement" />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.eventInfos}>
|
||||
<p className={styles.eventTitle}>{event.name}</p>
|
||||
<p>{event.description}</p>
|
||||
<div className={eventMenu ? styles.eventMenuOpen : styles.eventMenuClose}>
|
||||
<ul>
|
||||
{event.tasks
|
||||
.slice(0,5)
|
||||
.map((task, index) => (
|
||||
<li key={index}> - {task.name}</li>
|
||||
))}
|
||||
{event.tasks.length > 5 ? (
|
||||
<li>...</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{windowSize.width > 768 ? (
|
||||
<div className={styles.eventButtons}>
|
||||
<button
|
||||
className={`${styles.moreButton} ${eventMenu ? styles.moreButtonClicked : ""}`}
|
||||
onClick={()=>{seteventMenu(!eventMenu)}}>
|
||||
{">"}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.eventContainerBottom}>
|
||||
{(windowSize.width <= 768 || eventMenu) ? (
|
||||
<Link
|
||||
to={`/event/` + event.id}
|
||||
className={styles.moreLink}>
|
||||
<Button>En savoir plus →</Button>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Event;
|
||||
@@ -0,0 +1,135 @@
|
||||
.eventContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 1rem 0;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.eventContainerTop {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.eventContainerBottom{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.moreLink {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.eventImageDiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
width: auto;
|
||||
height: 6rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.eventImage {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
|
||||
.eventContent {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.eventButtons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
width: fit-content;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.eventButtons a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.moreButton {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #019AFF;
|
||||
font-size: 1.5rem;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.moreButton:hover {
|
||||
font-size: 2rem;
|
||||
background: rgba(191, 191, 191, 0.5);
|
||||
}
|
||||
|
||||
.moreButton:focus {
|
||||
outline: none;
|
||||
}
|
||||
.moreButton:focus-visible {
|
||||
outline: 2px solid black;
|
||||
}
|
||||
|
||||
.moreButtonClicked {
|
||||
rotate: 90deg;
|
||||
}
|
||||
|
||||
|
||||
.eventMenuOpen {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.eventMenuClose {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.eventImage {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.eventContainer{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.eventImageDiv {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.eventContent {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.eventInfos, .eventContainerBottom {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import styles from "./Filter.module.css";
|
||||
|
||||
function Filter({isFilterVisible, filters, setFilters}){
|
||||
const setAlphabeticalOrder = (order) => {
|
||||
setFilters(prev => ({
|
||||
...prev,
|
||||
alphabetical : prev.alphabetical === order ? null : order
|
||||
}));
|
||||
};
|
||||
|
||||
const setYearOrder = (order) => {
|
||||
setFilters(prev => ({
|
||||
...prev,
|
||||
yearOrder: prev.yearOrder === order ? null : order
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${styles.filterContainer} ${isFilterVisible ? styles.filterOpen : styles.filterClose}`}>
|
||||
<abbr title="Tri alphabétique croissant (A à Z)">
|
||||
<button className={`${styles.filterTag} glassCard ${filters.alphabetical !== "asc" ? "" : styles.active}`}
|
||||
onClick={() => setAlphabeticalOrder("asc")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="m80-280 150-400h86l150 400h-82l-34-96H196l-32 96H80Zm140-164h104l-48-150h-6l-50 150Zm328 164v-76l202-252H556v-72h282v76L638-352h202v72H548ZM360-760l120-120 120 120H360ZM480-80 360-200h240L480-80Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</abbr>
|
||||
|
||||
<abbr title="Tri alphabétique décroissant (Z à A)">
|
||||
<button className={`${styles.filterTag} glassCard ${filters.alphabetical !== "desc" ? "" : styles.active}`}
|
||||
onClick={() => setAlphabeticalOrder("desc")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M 480,-880 360,-760 H 600 Z M 360,-200 480,-80 600,-200 Z" />
|
||||
<path d="M 71.679924,-674.40094 H 398.50264 v 60.67705 l -208.59362,252.3436 h 214.5832 v 75.7812 H 65.690344 v -60.67705 L 274.28397,-598.61973 H 71.679924 Z" />
|
||||
<path d="M 769.04932,-356.43238 H 612.27858 l -24.73957,70.83329 H 486.75782 l 144.01033,-388.80185 h 119.53118 l 144.01033,388.80185 H 793.52847 Z m -131.77076,-72.13537 h 106.51035 l -53.12496,-154.68741 z" />
|
||||
</svg>
|
||||
</button>
|
||||
</abbr>
|
||||
|
||||
<abbr title="Tri chronologique croissant (du plus ancien au plus récent)">
|
||||
<button className={`${styles.filterTag} glassCard ${filters.yearOrder !== "asc" ? "" : styles.active}`}
|
||||
onClick={() => setYearOrder("asc")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M340-160q-125 0-212.5-87.5T40-460q0-125 87.5-212.5T340-760q52 0 98 16.5t84 45.5l42-42 56 56-42 42q29 38 45.5 84.5T640-460q0 125-87.5 212.5T340-160Zm400 0v-488l-44 44-56-56 140-140 140 140-57 56-43-43v487h-80ZM240-800v-80h200v80H240Zm100 560q92 0 156-64t64-156q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 92 64 156t156 64Zm-40-180h80v-200h-80v200Zm40-40Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</abbr>
|
||||
|
||||
<abbr title="Tri chronologique décroissant (du plus récent au plus ancien)">
|
||||
<button className={`${styles.filterTag} glassCard ${filters.yearOrder !== "desc" ? "" : styles.active}`}
|
||||
onClick={() => setYearOrder("desc")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M340-160q-125 0-212.5-87.5T40-460q0-125 87.5-212.5T340-760q52 0 98 16.5t84 45.5l42-42 56 56-42 42q29 38 45.5 84.5T640-460q0 125-87.5 212.5T340-160Zm440 0L640-300l56-56 44 44v-488h80v487l43-43 57 56-140 140ZM240-800v-80h200v80H240Zm100 560q92 0 156-64t64-156q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 92 64 156t156 64Zm-40-180h80v-200h-80v200Zm40-40Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</abbr>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Filter;
|
||||
@@ -0,0 +1,85 @@
|
||||
.filterContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
transition: max-height 0.5s ease, opacity 0.8s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
.filterOpen {
|
||||
max-height: 200px;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.filterClose{
|
||||
max-height: 200px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.filterTag {
|
||||
border: none;
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
|
||||
.filterTag:focus {
|
||||
outline: none;
|
||||
}
|
||||
.filterTag:focus-visible {
|
||||
outline: 2px solid black;
|
||||
}
|
||||
|
||||
.filterTag:hover {
|
||||
cursor: pointer;
|
||||
svg{
|
||||
fill: #019AFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.filterTag svg {
|
||||
transform: scale(1);
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
.active {
|
||||
border: 1px solid #019AFF;
|
||||
}
|
||||
|
||||
.active svg {
|
||||
fill: #019AFF;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px){
|
||||
.filterContainer {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filterOpen{
|
||||
display: flex;
|
||||
}
|
||||
.filterClose{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filterMenu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
abbr {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ function Header() {
|
||||
<nav className={`${mobileMenu ? styles.inactiveHeaderContent : styles.activeHeaderContent}`}>
|
||||
<div className={styles.headerLeftContent}>
|
||||
<NavLink className={styles.logoLink} to="/">
|
||||
<img src="/src/assets/logo.png" alt="logo" className={styles.logo} />
|
||||
<img src="logo.png" alt="logo" className={styles.logo} />
|
||||
</NavLink>
|
||||
<div className={styles.navLinks}>
|
||||
<NavLink to="/" className={({ isActive }) => isActive ? styles.activeLink : styles.link}>
|
||||
|
||||
@@ -179,7 +179,7 @@ svg {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
z-index: 1;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.activeNotificationDiv {
|
||||
|
||||
@@ -1,47 +1,22 @@
|
||||
/*import { useState } from "react";
|
||||
import login from "../../utils/login.js";
|
||||
import getUser from "../../utils/getUser.js";
|
||||
import styles from "./form.module.css";
|
||||
function LoginForm({ className }) {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await login(email, password);
|
||||
getUser();
|
||||
}
|
||||
return (
|
||||
<div className={`${className} ${"glassCard"}`}>
|
||||
<div className={styles.formgroup}>
|
||||
<input type="email" id="email" value={email} placeholder="Adresse mail" onChange={e => setEmail(e.target.value)} required/>
|
||||
</div>
|
||||
import { useState, useContext } from "react";
|
||||
import styles from "./loginForm.module.css";
|
||||
import Button from "../ui/button/button.jsx";
|
||||
import Input from "../ui/input/input.jsx";
|
||||
import { useNavigate } from "react-router";
|
||||
import { AuthContext } from "../../contexts/auth/AuthContext.js";
|
||||
|
||||
<div className={styles.formgroup}>
|
||||
<input type="password" id="password" value={password} placeholder="Mot de passe" onChange={e => setPassword(e.target.value)} required/>
|
||||
<br/>
|
||||
</div>
|
||||
<div className={styles.logButton} >
|
||||
<button className={styles.loginButton} onClick={e => handleSubmit(e)}>Se connecter</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default LoginForm;*/
|
||||
|
||||
import { useState } from "react";
|
||||
import login from "../../utils/login.js";
|
||||
import getUser from "../../utils/getUser.js";
|
||||
import styles from "./form.module.css";
|
||||
|
||||
function LoginForm({ className }) {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
const { login } = useContext(AuthContext);
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await login(email, password);
|
||||
getUser();
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
const toggleShowPassword = () => {
|
||||
@@ -51,27 +26,22 @@ function LoginForm({ className }) {
|
||||
return (
|
||||
<div className={`${className} ${"glassCard"}`}>
|
||||
<div className={styles.formgroup}>
|
||||
<input
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
className={styles.input}
|
||||
value={email}
|
||||
placeholder="Adresse mail"
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formgroup}>
|
||||
<div style={{ position: 'relative', width: '100%' }}>
|
||||
<input
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
id="password"
|
||||
className={styles.input}
|
||||
value={password}
|
||||
placeholder="Mot de passe"
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
style={{ width: '100%', paddingRight: '40px' }}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -92,8 +62,9 @@ function LoginForm({ className }) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.logButton}>
|
||||
<button className={styles.loginButton} onClick={handleSubmit}>Se connecter</button>
|
||||
<Button onClick={handleSubmit} variant={"default"}> Se connecter </Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import styles from "./modal.module.css"
|
||||
import Button from "../button/button.jsx";
|
||||
|
||||
const Modal = ({ open, onClose, children, title }) => {
|
||||
|
||||
if(!open) return null;
|
||||
|
||||
return <div className={styles.overlay}>
|
||||
|
||||
<div className={styles.modal}>
|
||||
|
||||
{title && <h2>{title}</h2>}
|
||||
|
||||
{children}
|
||||
|
||||
<div className={styles.modalFooter}> <Button onClick={onClose}>Fermer</Button> </div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Modal;
|
||||
@@ -0,0 +1,35 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
padding: 20px 20px 20px 20px;
|
||||
border-radius: 8px;
|
||||
min-width: 300px;
|
||||
max-width: 70%;
|
||||
position: relative;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
width: fit-content;
|
||||
border: 1px solid rgba(200, 200, 200, 0.3);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
color: #000;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.modalFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
export const AuthContext = createContext(null);
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import loginFunction from "../../utils/login.js";
|
||||
import getUser from "../../utils/getUser.js";
|
||||
import { AuthContext } from "./AuthContext";
|
||||
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const u = await getUser();
|
||||
setUser(u);
|
||||
setLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const login = async (email, password) => {
|
||||
const res = await loginFunction(email, password);
|
||||
if (!res || res.status !== 200) throw new Error("Login error");
|
||||
const u = await getUser();
|
||||
setUser(u);
|
||||
return res;
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
// TODO: Appeller fonction logout
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, loading, login, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -17,16 +17,17 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.content{
|
||||
padding: 0.7rem;
|
||||
z-index:1;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding: 1.5rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
|
||||
@@ -2,10 +2,18 @@ import { Outlet } from "react-router";
|
||||
import Footer from "./components/Footer/Footer.jsx"
|
||||
import Header from "./components/Header/Header.jsx";
|
||||
import Background from "./components/background/background.jsx";
|
||||
import { Navigate } from "react-router";
|
||||
import { useContext} from "react";
|
||||
import { AuthContext } from "./contexts/auth/AuthContext.js";
|
||||
|
||||
|
||||
function Layout(){
|
||||
|
||||
const { user, loading } = useContext(AuthContext);
|
||||
|
||||
if (loading) return <p>Loading</p>;
|
||||
if (!user) return <Navigate to="/login" />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Background>
|
||||
|
||||
@@ -3,10 +3,13 @@ import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import { RouterProvider } from "react-router";
|
||||
import router from './router';
|
||||
import { AuthProvider } from "./contexts/auth/authProvider.jsx";
|
||||
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
</AuthProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
import {useState} from "react";
|
||||
import Event from "../../components/Event/Event.jsx";
|
||||
import {Link} from "react-router";
|
||||
import styles from "./EventsPage.module.css";
|
||||
import Filter from "../../components/Filter/Filter.jsx";
|
||||
|
||||
|
||||
|
||||
function EventsPage(){
|
||||
const [isFilterVisible, setIsFilterVisible] = useState(false);
|
||||
const toggleFilters = () => {
|
||||
setIsFilterVisible(prev => !prev);
|
||||
};
|
||||
|
||||
const [filters, setFilters] = useState({
|
||||
alphabetical: "asc",
|
||||
yearOrder: "asc",
|
||||
});
|
||||
|
||||
const [events] = useState([
|
||||
{
|
||||
id: 1,
|
||||
name: "Marathon de la Ville",
|
||||
description: "Participez au marathon annuel et soutenez les associations locales.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Inscription des participants",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Centre sportif",
|
||||
description: "Accueillir et enregistrer tous les coureurs.",
|
||||
volunteers_count: 5
|
||||
},
|
||||
{
|
||||
name: "Distribution des dossards",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle principale",
|
||||
description: "Remettre les dossards et goodies aux participants.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Festival de Musique d’Été",
|
||||
description: "Un week-end de concerts en plein air avec des artistes locaux et internationaux.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Montage de la scène",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Parc central",
|
||||
description: "Installer la scène, les éclairages et le son.",
|
||||
volunteers_count: 6
|
||||
},
|
||||
{
|
||||
name: "Accueil du public",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Entrée principale",
|
||||
description: "Vérifier les billets et orienter les visiteurs.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Conférence Tech Innovante",
|
||||
description: "Journée de conférences et workshops autour des nouvelles technologies.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Préparation des salles",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Campus Tech",
|
||||
description: "Installer les chaises, projecteurs et matériel pour les conférences.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
{
|
||||
name: "Accueil des intervenants",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Hall principal",
|
||||
description: "Assister les speakers et orienter vers les salles.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
{
|
||||
name: "Gestion du buffet",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle de restauration",
|
||||
description: "Distribuer boissons et repas aux participants.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Atelier de Jardinage Urbain",
|
||||
description: "Apprenez à cultiver vos plantes en ville et participez à la création de jardins partagés.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Installation du matériel",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Jardin partagé",
|
||||
description: "Préparer outils et terreaux pour les participants.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
{
|
||||
name: "Encadrement des participants",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Jardin partagé",
|
||||
description: "Aider et conseiller sur la plantation et l’entretien.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Collecte de Sang",
|
||||
description: "Aidez à sauver des vies en participant à notre collecte de sang annuelle.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Accueil et enregistrement",
|
||||
start: "2020-09-24 22:21:20",
|
||||
end: "2020-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Enregistrer les donneurs et expliquer le processus.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
{
|
||||
name: "Préparation des salles",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Installer les lits, tables et matériel médical.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
{
|
||||
name: "Assistance post-don",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Accompagner les donneurs après le don et proposer des rafraîchissements.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
{
|
||||
name: "Accueil et enregistrement",
|
||||
start: "2020-09-24 22:21:20",
|
||||
end: "2020-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Enregistrer les donneurs et expliquer le processus.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
{
|
||||
name: "Préparation des salles",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Installer les lits, tables et matériel médical.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
{
|
||||
name: "Assistance post-don",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Accompagner les donneurs après le don et proposer des rafraîchissements.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
||||
const compareDate = (dateA, dateB) => {
|
||||
const dA = new Date(dateA);
|
||||
const dB = new Date(dateB);
|
||||
|
||||
if (filters.yearOrder === "asc") {
|
||||
return dB - dA;
|
||||
} else {
|
||||
return dA - dB;
|
||||
}
|
||||
};
|
||||
|
||||
const sortedEvents = [...events].sort((a, b) => {
|
||||
const sortedATasks = a.tasks.sort((taskA, taskB) =>
|
||||
compareDate(taskA.start, taskB.start)
|
||||
);
|
||||
const sortedBTasks = b.tasks.sort((taskA, taskB) =>
|
||||
compareDate(taskA.start, taskB.start)
|
||||
);
|
||||
|
||||
const timeComparison = compareDate(
|
||||
sortedATasks[0].start,
|
||||
sortedBTasks[0].start
|
||||
);
|
||||
|
||||
if (timeComparison !== 0) {
|
||||
return timeComparison;
|
||||
}
|
||||
|
||||
if (filters.alphabetical !== null) {
|
||||
return filters.alphabetical === "asc"
|
||||
? a.name.localeCompare(b.name)
|
||||
: b.name.localeCompare(a.name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.eventsContainer}>
|
||||
<div className={styles.eventsButtons}>
|
||||
<Link
|
||||
to="/event/create"
|
||||
className={`${styles.createButton} glassCard`}>
|
||||
+
|
||||
</Link>
|
||||
<div className={`${styles.filterContainer} ${isFilterVisible ? "glassCard" : ""}`}>
|
||||
<button className={`${isFilterVisible ? "" : styles.mobileFilter} ${styles.sortButton} glassCard`} onClick={toggleFilters}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div className={styles.filterMenu}>
|
||||
<Filter isFilterVisible={isFilterVisible} filters={filters} setFilters={setFilters}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.eventsList}>
|
||||
{sortedEvents.map((event, index) => (
|
||||
<Event key={index} event={event} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EventsPage;
|
||||
@@ -0,0 +1,99 @@
|
||||
.eventsContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.eventsList{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.eventsButtons {
|
||||
overflow: visible;
|
||||
position: fixed;
|
||||
top: 10rem;
|
||||
right: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.filterContainer{
|
||||
padding: 0;
|
||||
border-radius: 30px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.filterMenu{
|
||||
padding: 5px 5px 5px 5px;
|
||||
}
|
||||
|
||||
|
||||
.createButton, .sortButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.createButton {
|
||||
font-size: 2rem;
|
||||
color: #019AFF;
|
||||
}
|
||||
|
||||
.sortButton svg {
|
||||
transform: scale(0.8);
|
||||
fill: #019AFF;
|
||||
}
|
||||
|
||||
.sortButton:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.createButton:hover, .mobileFilter:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
|
||||
.sortButton:focus {
|
||||
outline: none;
|
||||
}
|
||||
.sortButton:focus-visible {
|
||||
outline: 2px solid black;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.eventsButtons {
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.filterContainer{
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.filterMenu{
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.createButton:hover, .mobileFilter:hover {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import styles from "./LoginPage.module.css";
|
||||
import LoginForm from "../../components/form/form.jsx";
|
||||
import LoginForm from "../../components/loginform/loginForm.jsx";
|
||||
import Background from "../../components/background/background.jsx";
|
||||
|
||||
function LoginPage() {
|
||||
return (
|
||||
return <Background>
|
||||
|
||||
<div className={styles.container}>
|
||||
<LoginForm />
|
||||
<LoginForm />
|
||||
</div>
|
||||
);
|
||||
|
||||
</Background>
|
||||
}
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-radius:12px;
|
||||
margin-left: 33.3%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import React, { useState } from "react";
|
||||
import styles from "./ProfilePage.module.css";
|
||||
|
||||
function Event({ title,date, tasks }) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const toggleExpand = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${styles.event} glassCard`}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div style={{display:'flex', flexDirection:'column'}}>
|
||||
<h3>{title}</h3>
|
||||
<p style={{ fontSize: '0.8rem', color: '#666', margin: '0 0 0 0' }}>{date}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={toggleExpand}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1.2rem',
|
||||
}}
|
||||
>
|
||||
{isExpanded ? '▼' : '▶'}
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
className={`${styles.eventTasks} ${isExpanded ? styles.expanded : ''}`}
|
||||
>
|
||||
{tasks.map((task, index) => (
|
||||
<li key={index}>-- {task}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function ProfilePage() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={`${styles.profileboard} glassCard`}>
|
||||
<div className={styles.contentWrapper}>
|
||||
<div className={styles.profilePictureContainer}>
|
||||
<img
|
||||
src="../src/assets/react.svg"
|
||||
alt="Photo de profil"
|
||||
className={styles.profilePicture}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<h2>LEBLOND Malo</h2>
|
||||
<div className={styles.topDescription}>
|
||||
<div className={styles.infoBlock}>
|
||||
<p><strong>Role :</strong> Président</p>
|
||||
<p><strong>Membre depuis :</strong> 2019</p>
|
||||
</div>
|
||||
<div className={styles.infoBlock}>
|
||||
<p><strong>Mail :</strong> mailpro@gmail.com</p>
|
||||
<p><strong>Téléphone :</strong> 08 67 87 32 21</p>
|
||||
</div>
|
||||
<button href="/../UpdateProfile.jsx">M</button>
|
||||
</div>
|
||||
<h2>Vos Événements :</h2>
|
||||
<Event
|
||||
title="Titre event 1"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 2"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 3"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 4"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 5"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 6"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 7"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilePage;
|
||||
@@ -0,0 +1,158 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.profilePicture {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
margin-left:4%;
|
||||
}
|
||||
|
||||
.topDescription {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.infoBlock {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.event {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.eventTasks {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out, opacity 0.2s ease;
|
||||
opacity: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.eventTasks.expanded {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.description h2{
|
||||
font-size:30px;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
.profilePicture{
|
||||
width:90%;
|
||||
}
|
||||
/* Tablette (768px et plus) */
|
||||
@media screen and (min-width: 768px) {
|
||||
.contentWrapper {
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
width: calc(100% - 170px);
|
||||
margin-left:4%;
|
||||
}
|
||||
|
||||
.topDescription {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.infoBlock {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.event {
|
||||
padding: 15px;
|
||||
}
|
||||
.description h2{
|
||||
font-size:30px;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
.profilePicture{
|
||||
width:90%;
|
||||
}
|
||||
}
|
||||
.description button{
|
||||
width:50px;
|
||||
height:50px;
|
||||
|
||||
}
|
||||
|
||||
/* Desktop (1024px et plus) */
|
||||
@media screen and (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 30%;
|
||||
height:auto;
|
||||
}
|
||||
.profilePicture{
|
||||
width:90%;
|
||||
}
|
||||
.description {
|
||||
width: calc(100% - 200px);
|
||||
margin-left:4%;
|
||||
}
|
||||
|
||||
.event {
|
||||
padding: 18px;
|
||||
}
|
||||
.description h2{
|
||||
font-size:30px;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import styles from "./RegisterPage.module.css";
|
||||
import Background from "../../components/background/background.jsx";
|
||||
import Input from "../../components/ui/input/input.jsx";
|
||||
import Button from "../../components/ui/button/button.jsx";
|
||||
import { useState } from "react";
|
||||
import register from "../../utils/register.js";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
|
||||
function RegisterPage() {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const res = await register(email, password, name, lastName);
|
||||
if (!res || res.status !== 200) throw new Error("Register error");
|
||||
navigate("/login");
|
||||
}
|
||||
|
||||
return <Background>
|
||||
|
||||
<div className={styles.container}>
|
||||
|
||||
<div className={"glassCard"}>
|
||||
|
||||
<h1>Créer un compte</h1>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder="Prénom"
|
||||
onChange={e => setName(e.target.value)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<Input
|
||||
value={lastName}
|
||||
placeholder="Nom"
|
||||
onChange={e => setLastName(e.target.value)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<Input
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder="Adresse mail"
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<div style={{ position: 'relative', width: '100%' }}>
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
placeholder="Mot de passe"
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '30px',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
{showPassword ? '👁️' : '🔒'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.logButton}>
|
||||
<Button variant={"transparent"} onClick={() => navigate("/login")}>Vous avez déjà un compte ?</Button>
|
||||
<Button variant={"default"} onClick={handleSubmit}> Se connecter </Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Background>
|
||||
}
|
||||
|
||||
export default RegisterPage;
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
width: 30%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-radius:12px;
|
||||
}
|
||||
|
||||
:global(.glassCard) {
|
||||
--glass-bg: #ffffff9f;
|
||||
}
|
||||
.logButton{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.formGroup{
|
||||
margin-top:23px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
import styles from "./VolunteersPage.module.css";
|
||||
|
||||
function Volunteer({ name, role, since, email, phone, photo }) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const toggleExpand = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${styles.profileboard} glassCard`}>
|
||||
<div className={styles.mainContent}>
|
||||
{!isExpanded ? (
|
||||
<div className={styles.basicInfo}>
|
||||
<h2>{name}</h2>
|
||||
<p><strong>Rôle :</strong> {role}</p>
|
||||
<p><strong>Membre depuis :</strong> {since}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.expandedContent}>
|
||||
<div className={styles.profilePictureContainer}>
|
||||
<img src={photo} alt={`${name}'s profile`} className={styles.profilePicture} />
|
||||
</div>
|
||||
<div className={styles.textInfo}>
|
||||
<div className={styles.basicInfo}>
|
||||
<h2>{name}</h2>
|
||||
<p><strong>Rôle :</strong> {role}</p>
|
||||
<p><strong>Membre depuis :</strong> {since}</p>
|
||||
</div>
|
||||
<div className={styles.contactInfo}>
|
||||
<p><strong>Email :</strong> {email}</p>
|
||||
<p><strong>Téléphone :</strong> {phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className={`${styles.expandButton} glassCard`}
|
||||
onClick={toggleExpand}
|
||||
>
|
||||
{isExpanded ? "▲" : "▼"}
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="voirPlus">
|
||||
<a href="#" className={styles.voirPlus}>VOIR PLUS</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VolunteerPage() {
|
||||
const volunteers = [
|
||||
{
|
||||
name: "LEBLOND Malo",
|
||||
role: "Président",
|
||||
since: "2019",
|
||||
email: "malo.leblond@example.com",
|
||||
phone: "08 67 87 32 21",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
{
|
||||
name: "DUPONT Jean",
|
||||
role: "Bénévole",
|
||||
since: "2020",
|
||||
email: "jean.dupont@example.com",
|
||||
phone: "06 12 34 56 78",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
{
|
||||
name: "MARTIN Sophie",
|
||||
role: "Bénévole",
|
||||
since: "2021",
|
||||
email: "sophie.martin@example.com",
|
||||
phone: "06 87 65 43 21",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
{
|
||||
name: "MARTIN Sophie",
|
||||
role: "Bénévole",
|
||||
since: "2021",
|
||||
email: "sophie.martin@example.com",
|
||||
phone: "06 87 65 43 21",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{volunteers.map((volunteer, index) => (
|
||||
<Volunteer
|
||||
key={index}
|
||||
name={volunteer.name}
|
||||
role={volunteer.role}
|
||||
since={volunteer.since}
|
||||
email={volunteer.email}
|
||||
phone={volunteer.phone}
|
||||
photo={volunteer.photo}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VolunteerPage;
|
||||
@@ -0,0 +1,125 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 3%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.basicInfo {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.expandedContent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.profilePicture {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.textInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.contactInfo {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.contactInfo p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.voirPlus {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.voirPlus:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.profilePictureContainer {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.voirPlus{
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
margin-top:50px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,20 @@ import Layout from "./layout.jsx";
|
||||
import HomePage from "./pages/Home/HomePage";
|
||||
import LoginPage from "./pages/Login/LoginPage";
|
||||
import StatPage from "./pages/Stat/StatPage";
|
||||
import RegisterPage from "./pages/Register/RegisterPage.jsx";
|
||||
import VolunteersPage from "./pages/Volunteers/VolunteersPage";
|
||||
import ProfilePage from "./pages/Profile/ProfilePage";
|
||||
import EventsPage from "./pages/Events/EventsPage.jsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/login",
|
||||
Component: LoginPage,
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
Component: RegisterPage,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
Component: Layout,
|
||||
@@ -20,6 +32,18 @@ const router = createBrowserRouter([
|
||||
{
|
||||
path: "/stat",
|
||||
Component:StatPage,
|
||||
},
|
||||
{
|
||||
path:"/profile",
|
||||
Component:ProfilePage,
|
||||
},
|
||||
{
|
||||
path: "/events",
|
||||
Component: EventsPage,
|
||||
},
|
||||
{
|
||||
path: "/volunteers",
|
||||
Component: VolunteersPage,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import Modal from "../components/ui/modal/modal.jsx";
|
||||
import Button from "../components/ui/button/button.jsx";
|
||||
import { useState } from "react";
|
||||
|
||||
export default {
|
||||
title: 'UI/Modal',
|
||||
component: Modal,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
open: {
|
||||
control: 'boolean',
|
||||
description: 'Variable qui gère si la modal est ouverte ou fermée.',
|
||||
},
|
||||
children: {
|
||||
control: Object,
|
||||
description: 'Contenu de la modal.',
|
||||
},
|
||||
title: {
|
||||
control: 'text',
|
||||
description: 'Titre de la modal',
|
||||
},
|
||||
onClose: {
|
||||
action: 'changed',
|
||||
description: 'Fonction appelée pour fermer la modal.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setIsOpen(true)}>Ouvrir</Button>
|
||||
<Modal {...args} open={isOpen} onClose={() => setIsOpen(false)}>
|
||||
{args.children}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
title: "Hello",
|
||||
children: <p>Lorem Ipsum...</p>,
|
||||
};
|
||||
@@ -1,12 +1,21 @@
|
||||
export default async function getUser() {
|
||||
await fetch('http://localhost:8000/api/me', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
try {
|
||||
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/me`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Erreur serveur : ${res.status}`);
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => console.log('Utilisateur connecté :', data))
|
||||
.catch(err => console.error('Erreur :', err));
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Erreur :', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default async function getXSRFToken() {
|
||||
const response = await fetch('http://localhost:8000/sanctum/csrf-cookie', {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
@@ -3,31 +3,29 @@ import getXSRFToken from "./getXSRF.js";
|
||||
export default async function login(email, password) {
|
||||
const csrfToken = await getXSRFToken();
|
||||
|
||||
|
||||
await fetch('http://localhost:8000/api/users/login', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
password: password
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Code HTTP :', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Connexion réussie ! Données utilisateur :', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erreur lors de la connexion :', error.message);
|
||||
try {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/login`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Code HTTP :', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Connexion réussie ! Données utilisateur :', data);
|
||||
|
||||
return { status: response.status, data };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import getXSRFToken from "./getXSRF.js";
|
||||
|
||||
export default async function register(email, password, name, lastname) {
|
||||
const csrfToken = await getXSRFToken();
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/register`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password, name, lastname }),
|
||||
});
|
||||
|
||||
console.log('Code HTTP :', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return { status: response.status, data };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -11,31 +11,29 @@ const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(file
|
||||
|
||||
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
projects: [{
|
||||
extends: true,
|
||||
plugins: [
|
||||
// The plugin will run tests for the stories defined in your Storybook config
|
||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, '.storybook')
|
||||
})],
|
||||
test: {
|
||||
name: 'storybook',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright({}),
|
||||
instances: [{
|
||||
browser: 'chromium'
|
||||
}]
|
||||
},
|
||||
setupFiles: ['.storybook/vitest.setup.js']
|
||||
}
|
||||
}]
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
}
|
||||
plugins: [react()],
|
||||
base: '/',
|
||||
test: {
|
||||
projects: [{
|
||||
extends: true,
|
||||
plugins: [
|
||||
// The plugin will run tests for the stories defined in your Storybook config
|
||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, '.storybook')
|
||||
})],
|
||||
test: {
|
||||
name: 'storybook',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright({}),
|
||||
instances: [{
|
||||
browser: 'chromium'
|
||||
}]
|
||||
},
|
||||
setupFiles: ['.storybook/vitest.setup.js']
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||