Merge pull request #3 from NabilOuldHamou/features/front
features/front
This commit is contained in:
commit
4ad1da144e
19 changed files with 1366 additions and 4 deletions
|
@ -36,6 +36,9 @@
|
|||
"vite": "^5.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-svelte": "^0.462.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"svelte-radix": "^2.0.1"
|
||||
"@prisma/client": "^5.22.0",
|
||||
"@types/node": "^22.10.1",
|
||||
"argon2": "^0.41.1",
|
||||
|
|
147
pnpm-lock.yaml
generated
147
pnpm-lock.yaml
generated
|
@ -8,6 +8,12 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
lucide-svelte:
|
||||
specifier: ^0.462.0
|
||||
version: 0.462.0(svelte@5.2.7)
|
||||
multer:
|
||||
specifier: ^1.4.5-lts.1
|
||||
version: 1.4.5-lts.1
|
||||
'@prisma/client':
|
||||
specifier: ^5.22.0
|
||||
version: 5.22.0(prisma@5.22.0)
|
||||
|
@ -664,6 +670,9 @@ packages:
|
|||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
append-field@1.0.0:
|
||||
resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
|
||||
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
|
@ -719,6 +728,13 @@ packages:
|
|||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
||||
callsites@3.1.0:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -779,10 +795,17 @@ packages:
|
|||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
concat-stream@1.6.2:
|
||||
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
|
||||
engines: {'0': node >= 0.8}
|
||||
|
||||
cookie@0.6.0:
|
||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -1053,6 +1076,7 @@ packages:
|
|||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
|
||||
|
@ -1083,6 +1107,9 @@ packages:
|
|||
is-reference@3.0.3:
|
||||
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
||||
|
||||
|
||||
isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
is-stream@2.0.1:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -1155,6 +1182,11 @@ packages:
|
|||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lucide-svelte@0.462.0:
|
||||
resolution: {integrity: sha512-BTY44UyXEhlakuPMS4w7NayKhDYARzmhEv3E2YchTiNZ/aySS0F2ktPscTlXRgSZ9xwqoozhnhO1oKhm/nnmqg==}
|
||||
peerDependencies:
|
||||
svelte: ^3 || ^4 || ^5.0.0-next.42
|
||||
|
||||
magic-string@0.30.13:
|
||||
resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==}
|
||||
|
||||
|
@ -1163,6 +1195,10 @@ packages:
|
|||
peerDependencies:
|
||||
svelte: ^3.56.0 || ^4.0.0 || ^5.0.0-next.120
|
||||
|
||||
media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
merge2@1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -1171,6 +1207,14 @@ packages:
|
|||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
|
@ -1178,10 +1222,17 @@ packages:
|
|||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
mkdirp@0.5.6:
|
||||
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
|
||||
hasBin: true
|
||||
|
||||
mri@1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -1193,6 +1244,10 @@ packages:
|
|||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
multer@1.4.5-lts.1:
|
||||
resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
|
@ -1435,6 +1490,9 @@ packages:
|
|||
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -1445,6 +1503,8 @@ packages:
|
|||
read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -1484,6 +1544,8 @@ packages:
|
|||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
|
@ -1522,6 +1584,9 @@ packages:
|
|||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
streamsearch@1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
stack-trace@0.0.10:
|
||||
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
||||
|
||||
|
@ -1533,6 +1598,8 @@ packages:
|
|||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
|
@ -1646,6 +1713,13 @@ packages:
|
|||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
typedarray@0.0.6:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
|
||||
typescript-eslint@8.15.0:
|
||||
resolution: {integrity: sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
@ -1746,6 +1820,9 @@ packages:
|
|||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
|
@ -2257,6 +2334,8 @@ snapshots:
|
|||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
append-field@1.0.0: {}
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argon2@0.41.1:
|
||||
|
@ -2314,6 +2393,12 @@ snapshots:
|
|||
node-releases: 2.0.18
|
||||
update-browserslist-db: 1.1.1(browserslist@4.24.2)
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
busboy@1.6.0:
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
camelcase-css@2.0.1: {}
|
||||
|
@ -2376,8 +2461,17 @@ snapshots:
|
|||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
concat-stream@1.6.2:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
inherits: 2.0.4
|
||||
readable-stream: 2.3.8
|
||||
typedarray: 0.0.6
|
||||
|
||||
cookie@0.6.0: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
@ -2688,6 +2782,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
isarray@1.0.0: {}
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
@ -2750,6 +2845,10 @@ snapshots:
|
|||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lucide-svelte@0.462.0(svelte@5.2.7):
|
||||
dependencies:
|
||||
svelte: 5.2.7
|
||||
|
||||
magic-string@0.30.13:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
@ -2762,6 +2861,8 @@ snapshots:
|
|||
svelte: 5.2.7
|
||||
vfile-message: 2.0.4
|
||||
|
||||
media-typer@0.3.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
|
@ -2769,6 +2870,12 @@ snapshots:
|
|||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
|
@ -2777,14 +2884,30 @@ snapshots:
|
|||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
mkdirp@0.5.6:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
|
||||
mri@1.2.0: {}
|
||||
|
||||
mrmime@2.0.0: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
multer@1.4.5-lts.1:
|
||||
dependencies:
|
||||
append-field: 1.0.0
|
||||
busboy: 1.6.0
|
||||
concat-stream: 1.6.2
|
||||
mkdirp: 0.5.6
|
||||
object-assign: 4.1.1
|
||||
type-is: 1.6.18
|
||||
xtend: 4.0.2
|
||||
|
||||
mz@2.7.0:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
@ -2934,6 +3057,8 @@ snapshots:
|
|||
|
||||
prismjs@1.29.0: {}
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
@ -2942,10 +3067,17 @@ snapshots:
|
|||
dependencies:
|
||||
pify: 2.3.0
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
core-util-is: 1.0.3
|
||||
inherits: 2.0.4
|
||||
isarray: 1.0.0
|
||||
process-nextick-args: 2.0.1
|
||||
safe-buffer: 5.1.2
|
||||
string_decoder: 1.1.1
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readdirp@3.6.0:
|
||||
|
@ -3005,6 +3137,7 @@ snapshots:
|
|||
dependencies:
|
||||
mri: 1.2.0
|
||||
|
||||
safe-buffer@5.1.2: {}
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-stable-stringify@2.5.0: {}
|
||||
|
@ -3033,6 +3166,7 @@ snapshots:
|
|||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
stack-trace@0.0.10: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
|
@ -3047,6 +3181,9 @@ snapshots:
|
|||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
@ -3190,6 +3327,13 @@ snapshots:
|
|||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
typedarray@0.0.6: {}
|
||||
|
||||
typescript-eslint@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)
|
||||
|
@ -3277,6 +3421,7 @@ snapshots:
|
|||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
xtend@4.0.2: {}
|
||||
yallist@4.0.0: {}
|
||||
|
||||
yaml@1.10.2: {}
|
||||
|
|
66
src/lib/components/ui/Alert.svelte
Normal file
66
src/lib/components/ui/Alert.svelte
Normal file
|
@ -0,0 +1,66 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { fade, fly } from 'svelte/transition'; // Importer fade et fly
|
||||
|
||||
export let message: string = ""; // Le message d'alerte
|
||||
export let onClose: () => void = () => {}; // Fonction de fermeture de l'alerte
|
||||
export let duration: number = 5000;
|
||||
|
||||
export let show = false;
|
||||
|
||||
// Fonction pour fermer l'alerte
|
||||
const closeAlert = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// Gestion du timeout pour fermer l'alerte après un certain délai
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
$: {
|
||||
if (show) {
|
||||
timeout = setTimeout(() => {
|
||||
closeAlert(); // Fermer l'alerte après la durée
|
||||
}, duration);
|
||||
} else {
|
||||
clearTimeout(timeout); // Si l'alerte est fermée, on annule le timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Nettoyage du timeout si le composant est démonté avant la fin du délai
|
||||
onDestroy(() => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<div class="alert fixed top-4 right-4 bg-blue-500 text-white px-6 py-3 rounded-lg shadow-lg"
|
||||
in:fly={{ y: -20, opacity: 0, duration: 300 }}
|
||||
out:fly={{ y: -20, opacity: 0, duration: 300 }}>
|
||||
|
||||
<span>{message}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* Styles de l'alerte */
|
||||
.alert {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background-color: #3182ce;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
19
src/lib/components/ui/ChatItem.svelte
Normal file
19
src/lib/components/ui/ChatItem.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let title: string; // Nom ou titre du chat
|
||||
export let lastMessage: string; // Dernier message affiché
|
||||
export let time: string; // Heure du dernier message
|
||||
</script>
|
||||
|
||||
<div class="chat-item p-4 border rounded-md hover:bg-gray-100 cursor-pointer flex justify-between items-center">
|
||||
<div>
|
||||
<p class="font-semibold text-lg">{title}</p>
|
||||
<p class="text-sm text-gray-500 truncate">{lastMessage}</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400">{time}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chat-item {
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
</style>
|
105
src/lib/components/ui/ChoosePicture.svelte
Normal file
105
src/lib/components/ui/ChoosePicture.svelte
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script lang="ts">
|
||||
export let profilePicture: File | null = null;
|
||||
const defaultImage = '/profile-default.svg'; // Remplacez par votre image par défaut
|
||||
|
||||
// Gérer le changement de fichier sélectionné
|
||||
const handleFileChange = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files?.length) {
|
||||
profilePicture = input.files[0];
|
||||
}
|
||||
};
|
||||
|
||||
// Supprimer l'image
|
||||
const handleDelete = () => {
|
||||
profilePicture = null;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-upload-btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background-color: #3182ce;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-upload-btn:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
.file-upload-btn:active {
|
||||
background-color: #1e40af;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
margin-top: 20px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%; /* Arrondir l'image en cercle */
|
||||
border: 4px solid #3182ce; /* Bordure autour de l'image */
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.action-buttons button:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Conteneur principal -->
|
||||
<div class="container">
|
||||
<!-- Image de profil ou image par défaut -->
|
||||
<img
|
||||
src={profilePicture ? URL.createObjectURL(profilePicture) : defaultImage}
|
||||
alt="Image de profil"
|
||||
class="image-preview mb-10"
|
||||
/>
|
||||
|
||||
<!-- Sélectionner une image -->
|
||||
<label for="profilePicture" class="file-upload-btn">
|
||||
Sélectionner une image
|
||||
<input
|
||||
type="file"
|
||||
id="profilePicture"
|
||||
class="file-input"
|
||||
accept="image/*"
|
||||
on:change={handleFileChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="action-buttons">
|
||||
<!-- Bouton Supprimer l'image -->
|
||||
<button
|
||||
type="button"
|
||||
class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600"
|
||||
on:click={handleDelete}
|
||||
disabled={!profilePicture}
|
||||
>
|
||||
Supprimer l'image
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
73
src/lib/components/ui/CreateChat.svelte
Normal file
73
src/lib/components/ui/CreateChat.svelte
Normal file
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Alert from "$lib/components/ui/Alert.svelte"; // Importer le composant Alert
|
||||
|
||||
export let show = false;
|
||||
|
||||
export let onClose: () => void; // Callback pour fermer le composant
|
||||
|
||||
let showAlert = false;
|
||||
let alertMessage = "";
|
||||
|
||||
let chatName = "";
|
||||
|
||||
const createChat = () => {
|
||||
if (chatName.trim()) {
|
||||
alertMessage = `Le chat "${chatName}" a été créé avec succès.`;
|
||||
showAlert = true;
|
||||
chatName = ""; // Réinitialiser
|
||||
onClose?.(); // Fermer le composant après création
|
||||
} else {
|
||||
alertMessage = "Veuillez entrer un nom pour le chat.";
|
||||
showAlert = true;
|
||||
}
|
||||
};
|
||||
|
||||
const closeAlert = () => {
|
||||
showAlert = false;
|
||||
};
|
||||
|
||||
// Fonction pour détecter le clic en dehors
|
||||
let createChatRef: HTMLElement | null = null;
|
||||
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<div class="fixed inset-0 flex justify-center items-center bg-black bg-opacity-50 z-50" on:click={onClose}>
|
||||
<div
|
||||
class="bg-white border border-gray-300 rounded-lg p-8 w-96"
|
||||
bind:this={createChatRef}
|
||||
on:click|stopPropagation
|
||||
>
|
||||
<h1 class="text-2xl font-bold mb-6 text-center">Créer un nouveau chat</h1>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={chatName}
|
||||
placeholder="Nom du chat..."
|
||||
class="w-full border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring focus:border-blue-500 mb-4"
|
||||
/>
|
||||
<button
|
||||
on:click={createChat}
|
||||
class="bg-blue-500 text-white w-full px-4 py-2 rounded-lg hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
Créer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Alert show={showAlert} message={alertMessage} onClose={closeAlert} />
|
||||
|
||||
<style>
|
||||
.fixed {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
71
src/lib/components/ui/ProfileCard.svelte
Normal file
71
src/lib/components/ui/ProfileCard.svelte
Normal file
|
@ -0,0 +1,71 @@
|
|||
<script>
|
||||
export let user = {
|
||||
pseudo: '',
|
||||
prenom: '',
|
||||
nom: '',
|
||||
description: '',
|
||||
profilePictureUrl: '', // Ajouter l'URL de l'image de profil
|
||||
}; // Infos utilisateur
|
||||
export let show = false; // Contrôle si la carte est visible
|
||||
export let onClose = () => {
|
||||
}; // Fonction pour fermer la carte
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<div class="overlay" role="dialog" aria-labelledby="profile-card-title" on:click={onClose}>
|
||||
<div class="profile-card" on:click|stopPropagation>
|
||||
<div class="profile-header">
|
||||
<!-- Image de profil -->
|
||||
<img src={user.profilePictureUrl} alt="Profile" class="profile-image" />
|
||||
<h2 id="profile-card-title" class="profile-name">{user.pseudo}</h2>
|
||||
</div>
|
||||
<p>{user.prenom} {user.nom}</p>
|
||||
<p>{user.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.7); /* Fond noir avec opacité */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
|
||||
text-align: center;
|
||||
width: 400px; /* Taille de la carte ajustée */
|
||||
max-width: 90%; /* Limite la largeur */
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%; /* Rendre l'image ronde */
|
||||
object-fit: cover; /* Pour que l'image remplisse bien le cercle */
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
34
src/lib/components/ui/ProfileInfo.svelte
Normal file
34
src/lib/components/ui/ProfileInfo.svelte
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
export let user = { pseudo: '', prenom: '', nom: '', description: '' }; // Infos utilisateur
|
||||
export let position = 'right'; // Position de la photo de profil: 'left' ou 'right'
|
||||
</script>
|
||||
|
||||
<div class="user-info" style="left: {position === 'left' ? 'auto' : '0'}; right: {position === 'right' ? '0' : 'auto'};">
|
||||
<h3>{user.pseudo}</h3>
|
||||
<p>{user.prenom} {user.nom}</p>
|
||||
<p>{user.description}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.user-info {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
max-width: 200px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.user-info h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.user-info p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
16
src/lib/components/ui/Search.svelte
Normal file
16
src/lib/components/ui/Search.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { Search } from "lucide-svelte"; // Icône de recherche depuis Lucide Svelte
|
||||
|
||||
export let placeholder: string = "Rechercher..."; // Texte par défaut pour l'input
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
class="w-full pl-10 pr-4 py-2 border rounded-md focus:outline-none focus:ring focus:border-blue-300"
|
||||
/>
|
||||
</div>
|
||||
|
123
src/routes/api/canal/[id]/+page.server.ts
Normal file
123
src/routes/api/canal/[id]/+page.server.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import { json } from '@sveltejs/kit';
|
||||
import prisma from '$lib/prismaClient';
|
||||
import redisClient from '$lib/redisClient'; // Assurez-vous d'importer le client Redis
|
||||
|
||||
// Récupérer les informations du canal et le dernier message (avec cache Redis)
|
||||
export async function GET({ params }) {
|
||||
const canalId = parseInt(params.id);
|
||||
|
||||
// Clé cache pour les informations du canal et le dernier message
|
||||
const canalCacheKey = `canal:${canalId}:info`;
|
||||
|
||||
try {
|
||||
// Vérifier si les informations du canal et le dernier message sont dans le cache Redis
|
||||
const cachedCanalData = await redisClient.get(canalCacheKey);
|
||||
if (cachedCanalData) {
|
||||
console.log('✅ Cache hit pour les informations du canal et le dernier message');
|
||||
return json(JSON.parse(cachedCanalData));
|
||||
}
|
||||
|
||||
console.log('❌ Cache miss');
|
||||
// Si non, récupérer les informations du canal et le dernier message depuis Prisma
|
||||
const canal = await prisma.canal.findUnique({
|
||||
where: { id: canalId },
|
||||
include: {
|
||||
users: true, // Inclut les utilisateurs associés au canal
|
||||
},
|
||||
});
|
||||
|
||||
if (!canal) {
|
||||
return json({ error: 'Canal non trouvé' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Récupérer le dernier message
|
||||
const lastMessage = await prisma.message.findFirst({
|
||||
where: { canalId },
|
||||
include: {
|
||||
user: { select: { id: true, pseudo: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }, // Trie par date décroissante, donc le dernier message est récupéré en premier
|
||||
});
|
||||
|
||||
// Créer un objet combiné pour le canal et le dernier message
|
||||
const canalData = {
|
||||
canal,
|
||||
lastMessage, // Inclure uniquement le dernier message
|
||||
};
|
||||
|
||||
// Mettre en cache les informations du canal et le dernier message pendant 5 minutes
|
||||
await redisClient.set(canalCacheKey, JSON.stringify(canalData), 'EX', 300); // Cache pendant 5 minutes
|
||||
|
||||
console.log('❌ Cache miss - Mise en cache des résultats');
|
||||
return json(canalData);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la récupération du canal ou du dernier message' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer un canal et invalider le cache associé
|
||||
export async function DELETE({ params }) {
|
||||
const canalId = parseInt(params.id);
|
||||
|
||||
try {
|
||||
// Supprimer le canal de la base de données
|
||||
await prisma.canal.delete({
|
||||
where: { id: canalId },
|
||||
});
|
||||
|
||||
// Invalider le cache
|
||||
await redisClient.del(`canal:${canalId}:info`);
|
||||
|
||||
return json({ message: 'Canal supprimé avec succès' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la suppression du canal' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Modifier un canal
|
||||
export async function PUT({ params, request }) {
|
||||
const canalId = parseInt(params.id);
|
||||
const { nom, domaine } = await request.json(); // On suppose que ce sont les champs à mettre à jour
|
||||
|
||||
// Clé cache pour les informations du canal et le dernier message
|
||||
const canalCacheKey = `canal:${canalId}:info`;
|
||||
|
||||
try {
|
||||
// Mettre à jour les informations du canal dans la base de données
|
||||
const updatedCanal = await prisma.canal.update({
|
||||
where: { id: canalId },
|
||||
data: {
|
||||
nom, // Nom du canal
|
||||
domaine, // Domaine du canal
|
||||
},
|
||||
include: {
|
||||
users: true, // Inclut les utilisateurs associés au canal
|
||||
},
|
||||
});
|
||||
|
||||
// Récupérer le dernier message associé au canal après mise à jour
|
||||
const lastMessage = await prisma.message.findFirst({
|
||||
where: { canalId },
|
||||
include: {
|
||||
user: { select: { id: true, pseudo: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
// Créer un objet combiné pour les nouvelles informations du canal et le dernier message
|
||||
const canalData = {
|
||||
canal: updatedCanal,
|
||||
lastMessage, // Inclure uniquement le dernier message
|
||||
};
|
||||
|
||||
// Mettre en cache les nouvelles informations pendant 5 minutes
|
||||
await redisClient.set(canalCacheKey, JSON.stringify(canalData), 'EX', 60 * 5); // Cache pendant 5 minutes
|
||||
|
||||
return json(canalData);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la mise à jour du canal' }, { status: 500 });
|
||||
}
|
||||
}
|
149
src/routes/api/canal/[id]/messages/+page.server.ts
Normal file
149
src/routes/api/canal/[id]/messages/+page.server.ts
Normal file
|
@ -0,0 +1,149 @@
|
|||
import { json } from '@sveltejs/kit';
|
||||
import prisma from '$lib/prismaClient';
|
||||
import redisClient from '$lib/redisClient'; // Assure-toi d'importer ton client Redis
|
||||
|
||||
export async function GET({ params, url }) {
|
||||
const canalId = parseInt(params.id);
|
||||
|
||||
// Gestion de la pagination avec des paramètres optionnels `page` et `limit`
|
||||
const page = parseInt(url.searchParams.get('page')) || 1;
|
||||
const limit = parseInt(url.searchParams.get('limit')) || 10;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Générer une clé cache Redis unique en fonction du canal et des paramètres de pagination
|
||||
const cacheKey = `canal:${canalId}:messages:page:${page}:limit:${limit}`;
|
||||
|
||||
try {
|
||||
// 1. Vérifier si les messages sont déjà dans le cache Redis
|
||||
const cachedMessages = await redisClient.get(cacheKey);
|
||||
if (cachedMessages) {
|
||||
console.log('✅ Cache hit');
|
||||
return json(JSON.parse(cachedMessages)); // Si les données sont en cache, les retourner
|
||||
}
|
||||
|
||||
// 2. Si les messages ne sont pas en cache, récupérer depuis la base de données
|
||||
const messages = await prisma.message.findMany({
|
||||
where: { canalId },
|
||||
include: {
|
||||
user: {
|
||||
select: { id: true, pseudo: true }, // Inclut uniquement l’ID et le pseudo de l’utilisateur
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc', // Trie par date croissante
|
||||
},
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
|
||||
// 3. Compter le nombre total de messages pour la pagination
|
||||
const totalMessages = await prisma.message.count({
|
||||
where: { canalId },
|
||||
});
|
||||
|
||||
const response = {
|
||||
messages,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
totalMessages,
|
||||
totalPages: Math.ceil(totalMessages / limit),
|
||||
},
|
||||
};
|
||||
|
||||
// 4. Mettre en cache les messages avec une expiration (par exemple 5 minutes)
|
||||
await redisClient.set(cacheKey, JSON.stringify(response), 'EX', 60 * 5); // Cache pendant 5 minutes
|
||||
|
||||
console.log('❌ Cache miss - Mise en cache des résultats');
|
||||
return json(response); // Retourner les données récupérées
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la récupération des messages' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST({ params, request }) {
|
||||
const canalId = parseInt(params.id);
|
||||
const { userId, text } = await request.json();
|
||||
|
||||
try {
|
||||
// Créer un nouveau message dans la base de données
|
||||
const newMessage = await prisma.message.create({
|
||||
data: {
|
||||
userId,
|
||||
canalId,
|
||||
text,
|
||||
},
|
||||
include: { user: { select: { id: true, pseudo: true } } },
|
||||
});
|
||||
|
||||
updateCaches(); // Mettre à jour les caches après la création d’un nouveau message
|
||||
|
||||
return json(newMessage, { status: 201 });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la création du message' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE({ params }) {
|
||||
const messageId = parseInt(params.id);
|
||||
|
||||
try {
|
||||
// Supprimer le message de la base de données
|
||||
await prisma.message.delete({
|
||||
where: { id: messageId },
|
||||
});
|
||||
|
||||
updateCaches(); // Mettre à jour les caches après la suppression d’un message
|
||||
|
||||
return json({ message: 'Message supprimé avec succès' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la suppression du message' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction pour mettre à jour tous les caches des messages
|
||||
function updateCaches(canalId) {
|
||||
// Mettre à jour tous les caches
|
||||
// Mettre à jour toutes les pages dans le cache
|
||||
let page : number = 1;
|
||||
let limit : number = 10;
|
||||
let offset : number = (page - 1) * limit;
|
||||
while (true) {
|
||||
const cacheKey = `canal:${canalId}:messages:page:${page}:limit:${limit}`;
|
||||
const cachedMessages = await redisClient.get(cacheKey);
|
||||
if (!cachedMessages) {
|
||||
break;
|
||||
}
|
||||
const totalMessages = await prisma.message.count({
|
||||
where: { canalId },
|
||||
});
|
||||
const messages = await prisma.message.findMany({
|
||||
where: { canalId },
|
||||
include: {
|
||||
user: {
|
||||
select: { id: true, pseudo: true },
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
},
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
const response = {
|
||||
messages,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
totalMessages,
|
||||
totalPages: Math.ceil(totalMessages / limit),
|
||||
},
|
||||
};
|
||||
await redisClient.set(cacheKey, JSON.stringify(response), 'EX', 60 * 5);
|
||||
page++;
|
||||
offset = (page - 1) * limit;
|
||||
}
|
||||
}
|
47
src/routes/api/canals/+page.server.ts
Normal file
47
src/routes/api/canals/+page.server.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { json } from '@sveltejs/kit';
|
||||
import prisma from '$lib/prismaClient';
|
||||
import redisClient from '$lib/redisClient';
|
||||
|
||||
// GET: Liste tous les canaux
|
||||
export async function GET() {
|
||||
try {
|
||||
const cachedCanaux = await redisClient.get('canaux');
|
||||
if (cachedCanaux) {
|
||||
console.log('✅ Cache hit');
|
||||
return json(JSON.parse(cachedCanaux));
|
||||
}
|
||||
|
||||
console.log('❌ Cache miss');
|
||||
const canaux = await prisma.canal.findMany({
|
||||
include: { users: true, messages: true }, // Inclut les relations
|
||||
});
|
||||
|
||||
await redisClient.set('canaux', JSON.stringify(canaux), { EX: 600 }); // Met en cache
|
||||
return json(canaux);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur serveur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST({ request }) {
|
||||
const { nom, domaine, userIds } = await request.json();
|
||||
|
||||
try {
|
||||
const canal = await prisma.canal.create({
|
||||
data: {
|
||||
nom,
|
||||
domaine,
|
||||
users: {
|
||||
connect: userIds.map((id) => ({ id })), // Associe des utilisateurs au canal
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return json(canal, { status: 201 });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la création du canal' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
200
src/routes/api/user/[id]/+page.server.ts
Normal file
200
src/routes/api/user/[id]/+page.server.ts
Normal file
|
@ -0,0 +1,200 @@
|
|||
import { json } from '@sveltejs/kit';
|
||||
import redisClient from '$lib/redisClient';
|
||||
import prisma from '$lib/prismaClient';
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const destinationDir = '/uploads';
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, `.${destinationDir}'); // Dossier où les images sont stockées`
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, `${Date.now()}-${file.originalname}`);
|
||||
},
|
||||
fileFilter(req, file, cb) {
|
||||
const fileExtension = path.extname(file.originalname).toLowerCase();
|
||||
if (fileExtension !== '.jpg' && fileExtension !== '.jpeg' && fileExtension !== '.png') {
|
||||
return cb(new Error('Seules les images JPG, JPEG et PNG sont autorisées.'));
|
||||
}
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({ storage });
|
||||
|
||||
export async function GET({ params }) {
|
||||
const userId = params.id;
|
||||
|
||||
try {
|
||||
// Vérifier si l'utilisateur est dans le cache Redis
|
||||
const cachedUser = await redisClient.get(`user:${userId}`);
|
||||
if (cachedUser) {
|
||||
console.log('✅ Cache hit');
|
||||
return json(JSON.parse(cachedUser));
|
||||
}
|
||||
|
||||
console.log('❌ Cache miss');
|
||||
// Si non, récupérer depuis MongoDB via Prisma
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: parseInt(userId) },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Mettre l'utilisateur en cache
|
||||
await redisClient.set(`user:${userId}`, JSON.stringify(user), { EX: 3600 });
|
||||
|
||||
return json(user);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur serveur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un utilisateur avec PUT
|
||||
export async function PUT({ params, request }) {
|
||||
const userId = parseInt(params.id);
|
||||
|
||||
const cachedUser = await redisClient.get(`user:${userId}`);
|
||||
// Récupérer l'utilisateur à partir de la base de données
|
||||
let existingUser;
|
||||
|
||||
if (cachedUser) {
|
||||
console.log('✅ Cache hit');
|
||||
// Si l'utilisateur est dans le cache, on le parse
|
||||
existingUser = JSON.parse(cachedUser);
|
||||
} else {
|
||||
// Si l'utilisateur n'est pas dans le cache, on le récupère de la base de données
|
||||
console.log('❌ Cache miss');
|
||||
existingUser = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Utilisation de multer pour récupérer l'image (si présente)
|
||||
return new Promise((resolve, reject) => {
|
||||
upload.single('profilePicture')(request.raw, request.raw, async (err) => {
|
||||
if (err) {
|
||||
console.error('Erreur de téléchargement:', err);
|
||||
return reject(json({ error: 'Erreur lors du téléchargement du fichier' }, { status: 500 }));
|
||||
}
|
||||
|
||||
// Extraire les autres données (pseudo, nom, etc.) du body de la requête
|
||||
const { pseudo, nom, prenom, email, password } = await request.json();
|
||||
|
||||
let updatedUserData = {
|
||||
pseudo,
|
||||
nom,
|
||||
prenom,
|
||||
email,
|
||||
password, // Assurez-vous de bien sécuriser les mots de passe
|
||||
};
|
||||
|
||||
// Si une nouvelle image est envoyée
|
||||
if (request.file) {
|
||||
// Vérifiez si l'utilisateur a déjà une image
|
||||
if (existingUser.profilePictureUrl) {
|
||||
// Supprimer l'ancienne image
|
||||
const oldImagePath = `.${destinationDir}/${path.basename(existingUser.profilePictureUrl)}`;
|
||||
if (fs.existsSync(oldImagePath)) {
|
||||
fs.unlinkSync(oldImagePath); // Suppression du fichier
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter la nouvelle image à la base de données
|
||||
updatedUserData.profilePictureUrl = `${destinationDir}/${request.file.filename}`;
|
||||
} else if (!request.file && existingUser.profilePictureUrl) {
|
||||
// Si aucune image n'est envoyée, supprimer l'image actuelle
|
||||
const oldImagePath = `.${destinationDir}/${path.basename(existingUser.profilePictureUrl)}`;
|
||||
if (fs.existsSync(oldImagePath)) {
|
||||
fs.unlinkSync(oldImagePath); // Supprimer l'ancienne image
|
||||
}
|
||||
|
||||
// Mettre à jour l'URL de l'image en null
|
||||
updatedUserData.profilePictureUrl = null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Mettre à jour l'utilisateur dans la base de données
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: updatedUserData,
|
||||
});
|
||||
|
||||
// Mettre à jour l'utilisateur dans le cache Redis
|
||||
await redisClient.set(`user:${userId}`, JSON.stringify(updatedUser), 'EX', 3600); // Cache pendant 1 heure (3600 secondes)
|
||||
|
||||
// Réponse avec l'utilisateur mis à jour
|
||||
return resolve(json(updatedUser));
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour de l\'utilisateur:', error);
|
||||
return reject(json({ error: 'Erreur lors de la mise à jour de l\'utilisateur' }, { status: 500 }));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function DELETE({ params }) {
|
||||
const userId = parseInt(params.id);
|
||||
|
||||
try {
|
||||
// Vérifier si l'utilisateur est dans le cache Redis
|
||||
const cachedUser = await redisClient.get(`user:${userId}`);
|
||||
let userToDelete;
|
||||
|
||||
if (cachedUser) {
|
||||
console.log('✅ Cache hit');
|
||||
// Si l'utilisateur est dans le cache, on le parse
|
||||
userToDelete = JSON.parse(cachedUser);
|
||||
} else {
|
||||
// Si l'utilisateur n'est pas dans le cache, on le récupère de la base de données
|
||||
console.log('❌ Cache miss');
|
||||
userToDelete = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!userToDelete) {
|
||||
return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Mettre l'utilisateur dans le cache Redis
|
||||
await redisClient.set(`user:${userId}`, JSON.stringify(userToDelete), { EX: 3600 }); // Cache pendant 1 heure
|
||||
}
|
||||
|
||||
// Si l'utilisateur a une image de profil, la supprimer
|
||||
if (userToDelete.profilePictureUrl) {
|
||||
// Calculer le chemin du fichier à supprimer
|
||||
const imagePath = `.${destinationDir}/${path.basename(userToDelete.profilePictureUrl)}`;
|
||||
if (fs.existsSync(imagePath)) {
|
||||
fs.unlinkSync(imagePath); // Supprimer le fichier image
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer l'utilisateur de la base de données
|
||||
await prisma.user.delete({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
// Supprimer l'utilisateur du cache Redis
|
||||
await redisClient.del(`user:${userId}`);
|
||||
|
||||
// Réponse après suppression réussie
|
||||
return json({ message: 'Utilisateur et image supprimés avec succès' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur lors de la suppression de l’utilisateur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
87
src/routes/api/users/+page.server.ts
Normal file
87
src/routes/api/users/+page.server.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
// src/routes/api/users/+server.js
|
||||
import { json } from '@sveltejs/kit';
|
||||
import redisClient from '$lib/redisClient';
|
||||
import prisma from '$lib/prismaClient';
|
||||
import multer from 'multer';
|
||||
|
||||
const destinationDir = '/uploads';
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, `.${destinationDir}'); // Dossier où les images sont stockées`
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, `${Date.now()}-${file.originalname}`);
|
||||
},
|
||||
fileFilter(req, file, cb) {
|
||||
const fileExtension = path.extname(file.originalname).toLowerCase();
|
||||
if (fileExtension !== '.jpg' && fileExtension !== '.jpeg' && fileExtension !== '.png') {
|
||||
return cb(new Error('Seules les images JPG, JPEG et PNG sont autorisées.'));
|
||||
}
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Vérifier si les utilisateurs sont dans le cache Redis
|
||||
const cachedUsers = await redisClient.get('users');
|
||||
if (cachedUsers) {
|
||||
console.log('✅ Cache hit');
|
||||
return json(JSON.parse(cachedUsers));
|
||||
}
|
||||
|
||||
console.log('❌ Cache miss');
|
||||
// Sinon, récupérer les utilisateurs depuis MongoDB
|
||||
const users = await prisma.user.findMany();
|
||||
|
||||
// Mettre les utilisateurs en cache
|
||||
await redisClient.set('users', JSON.stringify(users), { EX: 600 });
|
||||
|
||||
return json(users);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return json({ error: 'Erreur serveur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST({ request }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Utilisation de multer pour récupérer le fichier
|
||||
upload.single('profilePicture')(request.raw, request.raw, async (err) => {
|
||||
if (err) {
|
||||
console.error('Erreur de téléchargement:', err);
|
||||
return reject(json({ error: 'Erreur lors du téléchargement du fichier' }, { status: 500 }));
|
||||
}
|
||||
|
||||
// Récupérer les données du formulaire (sans le fichier)
|
||||
const { pseudo, nom, prenom, email, password } = await request.json();
|
||||
|
||||
// L'URL de l'image sera le chemin relatif à partir du dossier uploads
|
||||
const imageUrl = request.file ? `${destinationDir}/${request.file.filename}` : null;
|
||||
|
||||
try {
|
||||
// Créer un nouvel utilisateur avec l'URL de l'image
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
pseudo,
|
||||
nom,
|
||||
prenom,
|
||||
email,
|
||||
password,
|
||||
profilePictureUrl: imageUrl, // Stocker l'URL de l'image
|
||||
},
|
||||
});
|
||||
|
||||
// Mettre l'utilisateur dans le cache Redis
|
||||
await redisClient.set(`user:${user.id}`, JSON.stringify(user), { EX: 3600 });
|
||||
|
||||
// Réponse avec les données de l'utilisateur
|
||||
return resolve(json(user, { status: 201 }));
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la création de l\'utilisateur:', error);
|
||||
return reject(json({ error: 'Erreur lors de la création de l\'utilisateur' }, { status: 500 }));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
<script></script>
|
||||
|
||||
Salut
|
83
src/routes/chats/+page.svelte
Normal file
83
src/routes/chats/+page.svelte
Normal file
|
@ -0,0 +1,83 @@
|
|||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import Plus from "svelte-radix/Plus.svelte"; // Icône pour "Ajouter"
|
||||
import Search from "$lib/components/ui/Search.svelte";
|
||||
import ChatItem from "$lib/components/ui/ChatItem.svelte";
|
||||
import ProfileCard from "$lib/components/ui/ProfileCard.svelte"; // Importer le composant ProfileCard
|
||||
import CreateChat from "$lib/components/ui/CreateChat.svelte"; // Importer le composant CreateChat
|
||||
|
||||
let showProfileCard = false; // État pour afficher ou masquer le ProfileCard
|
||||
let showCreateChat = false; // État pour afficher ou masquer CreateChat
|
||||
let user = {
|
||||
pseudo: 'JohnDoe',
|
||||
prenom: 'John',
|
||||
nom: 'Doe',
|
||||
description: 'Développeur passionné',
|
||||
profilePictureUrl: 'path/to/profile-picture.jpg', // URL de l'image de profil
|
||||
};
|
||||
|
||||
function openProfileCard() {
|
||||
console.log('openProfileCard');
|
||||
showProfileCard = true; // Inverser l'état pour afficher/masquer le ProfilCard
|
||||
}
|
||||
|
||||
function closeProfileCard() {
|
||||
console.log('closeProfileCard');
|
||||
showProfileCard = false; // Inverser l'état pour afficher/masquer le ProfilCard
|
||||
}
|
||||
|
||||
function openCreateChat() {
|
||||
console.log('openCreateChat');
|
||||
showCreateChat = true; // Afficher le composant CreateChat
|
||||
}
|
||||
|
||||
function closeCreateChat() {
|
||||
console.log('closeCreateChat');
|
||||
showCreateChat = false; // Fermer le composant CreateChat
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col gap-5 p-5">
|
||||
<div class="flex justify-between items-center">
|
||||
<!-- Bouton Profile avec l'image de l'utilisateur -->
|
||||
<Button
|
||||
size="default"
|
||||
class="flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white"
|
||||
on:click={openProfileCard}
|
||||
>
|
||||
<img src={user.profilePictureUrl} alt="Profile" class="h-8 w-8 rounded-full" />
|
||||
Profile
|
||||
</Button>
|
||||
|
||||
<div class="flex items-center gap-2 w-full mr-6 ml-6">
|
||||
<div class="relative w-full">
|
||||
<Search class="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bouton Nouveau Chat -->
|
||||
<Button
|
||||
size="default"
|
||||
class="flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white"
|
||||
on:click={openCreateChat}
|
||||
>
|
||||
<Plus class="h-5 w-5" />
|
||||
Nouveau Chat
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 overflow-y-auto">
|
||||
<ChatItem title="Discussion avec Yanax" lastMessage="Salut les amis !" time="12:34" />
|
||||
<ChatItem title="Discussion avec Luxray" lastMessage="Salut Yanax" time="12:30" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<CreateChat show={showCreateChat} onClose={closeCreateChat} />
|
||||
<ProfileCard {user} show={showProfileCard} onClose={closeProfileCard} />
|
||||
|
||||
<style>
|
||||
.h-full {
|
||||
height: 100%;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
</style>
|
140
src/routes/user/edit/+page.svelte
Normal file
140
src/routes/user/edit/+page.svelte
Normal file
|
@ -0,0 +1,140 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import ChoosePicture from "$lib/components/ui/ChoosePicture.svelte"; // Import du composant
|
||||
|
||||
let pseudo = '';
|
||||
let firstName = '';
|
||||
let lastName = '';
|
||||
let email = '';
|
||||
let profilePicture: File | null = null;
|
||||
|
||||
let message = '';
|
||||
let showMessage = false;
|
||||
|
||||
// Fonction pour valider l'email
|
||||
const validateEmail = (email: string) => {
|
||||
const re = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
return re.test(email);
|
||||
};
|
||||
|
||||
// Fonction de soumission du formulaire
|
||||
const handleSubmit = () => {
|
||||
if (!pseudo || !firstName || !lastName || !email) {
|
||||
message = 'Veuillez remplir tous les champs.';
|
||||
showMessage = true;
|
||||
} else if (!validateEmail(email)) {
|
||||
message = 'L\'email est invalide.';
|
||||
showMessage = true;
|
||||
} else {
|
||||
// Vous pouvez ici envoyer les données à un serveur via une API
|
||||
message = 'Informations mises à jour avec succès!';
|
||||
showMessage = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Fonction pour gérer le téléchargement de l'image de profil
|
||||
const handleFileChange = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files?.length) {
|
||||
profilePicture = input.files[0];
|
||||
}
|
||||
};
|
||||
|
||||
// Simulation de données au chargement
|
||||
onMount(() => {
|
||||
pseudo = '';
|
||||
firstName = '';
|
||||
lastName = '';
|
||||
email = '';
|
||||
profilePicture = null; // ou une valeur par défaut si vous en avez une
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center min-h-screen bg-gray-100 mg-10">
|
||||
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-lg">
|
||||
<h2 class="text-2xl font-semibold text-center mb-6">Modifier les informations du compte</h2>
|
||||
|
||||
<!-- Message d'alerte -->
|
||||
{#if showMessage}
|
||||
<div class="bg-blue-500 text-white p-3 rounded-lg text-center mb-4">
|
||||
{message}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Formulaire de modification du profil -->
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="mb-4">
|
||||
<label for="pseudo" class="block text-sm font-semibold text-gray-700">Pseudo</label>
|
||||
<input
|
||||
type="text"
|
||||
id="pseudo"
|
||||
bind:value={pseudo}
|
||||
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Votre pseudo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="firstName" class="block text-sm font-semibold text-gray-700">Prénom</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
bind:value={firstName}
|
||||
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Votre prénom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="lastName" class="block text-sm font-semibold text-gray-700">Nom</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
bind:value={lastName}
|
||||
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Votre nom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="email" class="block text-sm font-semibold text-gray-700">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
bind:value={email}
|
||||
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Votre email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Intégration du composant de photo de profil -->
|
||||
<div class="mb-4">
|
||||
<ChoosePicture profilePicture={profilePicture} onFileChange={handleFileChange} />
|
||||
{#if profilePicture}
|
||||
<div class="mt-2 text-sm text-gray-600">Image sélectionnée : {profilePicture.name}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
Mettre à jour
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Supprimez le sélecteur body si non utilisé */
|
||||
/* body {
|
||||
background-color: #f8fafc;
|
||||
font-family: sans-serif;
|
||||
} */
|
||||
|
||||
input, button {
|
||||
font-family: inherit;
|
||||
}
|
||||
</style>
|
4
static/profile-default.svg
Normal file
4
static/profile-default.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#494c4e" d="M9 0a9 9 0 0 0-9 9 8.654 8.654 0 0 0 .05.92 9 9 0 0 0 17.9 0A8.654 8.654 0 0 0 18 9a9 9 0 0 0-9-9zm5.42 13.42c-.01 0-.06.08-.07.08a6.975 6.975 0 0 1-10.7 0c-.01 0-.06-.08-.07-.08a.512.512 0 0 1-.09-.27.522.522 0 0 1 .34-.48c.74-.25 1.45-.49 1.65-.54a.16.16 0 0 1 .03-.13.49.49 0 0 1 .43-.36l1.27-.1a2.077 2.077 0 0 0-.19-.79v-.01a2.814 2.814 0 0 0-.45-.78 3.83 3.83 0 0 1-.79-2.38A3.38 3.38 0 0 1 8.88 4h.24a3.38 3.38 0 0 1 3.1 3.58 3.83 3.83 0 0 1-.79 2.38 2.814 2.814 0 0 0-.45.78v.01a2.077 2.077 0 0 0-.19.79l1.27.1a.49.49 0 0 1 .43.36.16.16 0 0 1 .03.13c.2.05.91.29 1.65.54a.49.49 0 0 1 .25.75z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 843 B |
Loading…
Add table
Reference in a new issue