From c91f5f3dfbc60b7041663e29a6c417f37a016140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luc=C3=A0s?= Date: Mon, 3 Apr 2023 16:35:25 +0200 Subject: [PATCH] Send messages with sockets :heart: Took 3 hours 51 minutes --- package-lock.json | 253 +++++++++++++++++- package.json | 5 +- ...421d8378e4fd27374c7f334_1680519696140.webp | Bin 0 -> 9240 bytes ...421d8378e4fd27374c7f334_1680519702295.webp | Bin 0 -> 10180 bytes ...421d8378e4fd27374c7f334_1680519705034.webp | Bin 0 -> 10706 bytes src/components/ModalModifyImages.jsx | 204 -------------- src/components/form/FormMessage.tsx | 27 +- .../layout/dashboard/left_panel/LeftPanel.jsx | 22 +- src/pages/admin/settings.tsx | 91 +++++++ src/pages/api/socket/chat/[id].ts | 42 +++ src/pages/chat/[id].tsx | 59 +++- src/pages/dashboard.tsx | 2 +- src/pages/userProfile.tsx | 38 ++- 13 files changed, 501 insertions(+), 242 deletions(-) create mode 100644 public/ImageUsers/6421d8378e4fd27374c7f334_1680519696140.webp create mode 100644 public/ImageUsers/6421d8378e4fd27374c7f334_1680519702295.webp create mode 100644 public/ImageUsers/6421d8378e4fd27374c7f334_1680519705034.webp delete mode 100644 src/components/ModalModifyImages.jsx create mode 100644 src/pages/admin/settings.tsx create mode 100644 src/pages/api/socket/chat/[id].ts diff --git a/package-lock.json b/package-lock.json index b89902c..7f8e739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.10.6", "@premieroctet/next-crud": "^2.2.0", "@prisma/client": "^4.11.0", + "@reduxjs/toolkit": "^1.9.3", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "bcrypt": "^5.1.0", @@ -27,7 +28,9 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.43.5", - "react-icons": "^4.8.0" + "react-icons": "^4.8.0", + "socket.io": "^4.6.1", + "socket.io-client": "^4.6.1" }, "devDependencies": { "@types/bcrypt": "^5.0.0", @@ -2129,11 +2132,39 @@ "integrity": "sha512-KV+IrVh8LdwBfJjDGdbZZDg+KwQ6b4Ee7/hLiTpL8ZHLI2IdGOufRxvmZSTuio28wfAporSA+N0YdzAdx43gWA==", "optional": true }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", + "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@swc/helpers": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", @@ -2184,6 +2215,19 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cross-spawn": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz", @@ -2229,8 +2273,7 @@ "node_modules/@types/node": { "version": "18.15.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.1.tgz", - "integrity": "sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==", - "devOptional": true + "integrity": "sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -2388,6 +2431,18 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -2794,6 +2849,14 @@ ], "optional": true }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", @@ -3154,6 +3217,18 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "optional": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3434,6 +3509,54 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", + "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enhanced-resolve": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", @@ -4815,6 +4938,15 @@ "node": ">=10" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5704,6 +5836,14 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/new-github-issue-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/new-github-issue-url/-/new-github-issue-url-0.2.1.tgz", @@ -6903,6 +7043,22 @@ "node": ">=10" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -6947,6 +7103,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -7162,6 +7323,56 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", + "integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.4.0", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7875,6 +8086,14 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -7981,6 +8200,34 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index d33a693..565d1ad 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/styled": "^11.10.6", "@premieroctet/next-crud": "^2.2.0", "@prisma/client": "^4.11.0", + "@reduxjs/toolkit": "^1.9.3", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "bcrypt": "^5.1.0", @@ -28,7 +29,9 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.43.5", - "react-icons": "^4.8.0" + "react-icons": "^4.8.0", + "socket.io": "^4.6.1", + "socket.io-client": "^4.6.1" }, "devDependencies": { "@types/bcrypt": "^5.0.0", diff --git a/public/ImageUsers/6421d8378e4fd27374c7f334_1680519696140.webp b/public/ImageUsers/6421d8378e4fd27374c7f334_1680519696140.webp new file mode 100644 index 0000000000000000000000000000000000000000..d0dd90ff791ba96d8556f0ba39e586533f973730 GIT binary patch literal 9240 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 literal 0 HcmV?d00001 diff --git a/src/components/ModalModifyImages.jsx b/src/components/ModalModifyImages.jsx deleted file mode 100644 index f31de9a..0000000 --- a/src/components/ModalModifyImages.jsx +++ /dev/null @@ -1,204 +0,0 @@ -import { - Button, - Flex, - Grid, - GridItem, - Image, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - useDisclosure, - useToast, -} from "@chakra-ui/react"; -import { useState } from "react"; -import { RiEditBoxLine } from "react-icons/ri"; - -export default function ModalModifyImages(props) { - const { isOpen, onOpen, onClose } = useDisclosure(); - const { images, user, userData, setUserData } = props; - const [listImage, setlistImage] = useState(images); - const toast = useToast(); - - const uploadImage = async (file) => { - const body = new FormData(); - body.append("file", file); - const imagePostOptions = { - method: "POST", - body, - }; - - const imagePatchOptions = { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - images: [...listImage, `imageUsers/${file.name}`], - }), - }; - - fetch(`/api/file/uploadFile`, imagePostOptions) - .then((res) => { - fetch(`/api/users/${user.id}`, imagePatchOptions) - .then((res) => { - toast({ - title: `Ajout d'image effectué`, - status: "success", - isClosable: true, - }); - setlistImage([...listImage, `imageUsers/${file.name}`]); - // router.reload(); - }) - .catch(() => { - setIsLoading(false); - toast({ - title: `Erreur lors de l'ajout des images`, - status: "error", - isClosable: true, - }); - }); - }) - .catch((err) => { - toast({ - title: `Erreur lors de l'ajout des images`, - status: "error", - isClosable: true, - }); - console.log(err); - }); - }; - - const deleteImage = async (fileName) => { - let newListImage = listImage; - const index = newListImage.indexOf(fileName); - - if (index > -1) { - newListImage.splice(index, 1); - setlistImage([...newListImage]); - } - - const imageDeleteOptions = { - method: "DELETE", - body: JSON.stringify({ fileName: fileName.split("/").pop() }), - }; - - fetch(`/api/file/deleteFile`, imageDeleteOptions).then((res) => { - const imagePatchOptions = { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - images: [...listImage], - }), - }; - fetch(`/api/users/${user.id}`, imagePatchOptions) - .then((res) => { - toast({ - title: `Suppression d'image effectué`, - status: "success", - isClosable: true, - }); - }) - .catch(() => { - toast({ - title: `Erreur lors de la suppression des images`, - status: "error", - isClosable: true, - }); - }); - }); - }; - - return ( - <> - - - - - - Modification des images - - - - {listImage.map((image, index) => ( - - - - - - - ))} - {listImage.length < 5 ? ( - - { - const date = new Date(); - const time = date.getTime(); - - const file = target.files[0]; - const extension = file.name.split(".").pop(); - const newFile = new File( - [file], - `${user.id}_${time}.${extension}`, - { - type: file.type, - } - ); - uploadImage(newFile); - }} - > - - ) : ( - <> - )} - - - - - - - - - - ); -} diff --git a/src/components/form/FormMessage.tsx b/src/components/form/FormMessage.tsx index 67bd3f5..f1493d1 100644 --- a/src/components/form/FormMessage.tsx +++ b/src/components/form/FormMessage.tsx @@ -1,27 +1,32 @@ import {Button, Flex, Input} from '@chakra-ui/react'; import {useState} from 'react'; import {User} from '@prisma/client'; +import {io, Socket} from 'socket.io-client'; +import Message from '@/components/chat/Message'; type Props = { user: User, - chatId: Props + chatId: Props, + socket: any } export default function FormMessage(props: Props) { - const {user, chatId} = props; + const {user, socket} = props; const [text, setText] = useState(""); const handleSubmit = () => { if (text !== "") { - fetch("/api/messages", { - method: "POST", - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - text, - 'User': {"connect": {'id': user.id}}, - 'Chat': {"connect": {'id': chatId}} - }) - }).then(res => res.json()).then(res => console.log(res)).catch(err => console.error(err)) + socket.emit("createdMessage", {text, sender: user.id}) + + // fetch("/api/messages", { + // method: "POST", + // headers: {'Content-Type': 'application/json'}, + // body: JSON.stringify({ + // text, + // 'User': {"connect": {'id': user.id}}, + // 'Chat': {"connect": {'id': chatId}} + // }) + // }).then(res => res.json()).then(res => console.log(res)).catch(err => console.error(err)) } } diff --git a/src/components/layout/dashboard/left_panel/LeftPanel.jsx b/src/components/layout/dashboard/left_panel/LeftPanel.jsx index 433725f..533357d 100644 --- a/src/components/layout/dashboard/left_panel/LeftPanel.jsx +++ b/src/components/layout/dashboard/left_panel/LeftPanel.jsx @@ -12,6 +12,11 @@ export default function LeftPanel(props) { const router = useRouter(); const { user } = props; + const formateDate = (dateString) => { + var options = { year: "numeric", month: "long", day: "numeric" }; + return new Date(dateString).toLocaleDateString([], options); + }; + return ( - - - {user.firstName} {user.lastName} - - - "{user.aPropos}" + + + + {user.firstName} {user.lastName} + + + {formateDate(user.birthdate)} + + + + "{user.bio}" diff --git a/src/pages/admin/settings.tsx b/src/pages/admin/settings.tsx new file mode 100644 index 0000000..d993ca5 --- /dev/null +++ b/src/pages/admin/settings.tsx @@ -0,0 +1,91 @@ +import { + Box, + Button, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Input, + useToast, + } from '@chakra-ui/react'; +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/router'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + + export default function settings() { + + const router = useRouter(); + const toast = useToast(); + + const [isLoading, setIsLoading] = useState(false); + + const { + handleSubmit, + register, + formState: { errors, isSubmitting }, + } = useForm() + + const [userData, setUserData] = useState({}); + + const { data: session, status } = useSession(); + if (status === "unauthenticated") router.push("/login"); + + if (status === "authenticated") { + const { user } = session as unknown as Session; + + if (user.role !== "ADMIN") router.push("/login"); + + const savePassion = (passion: any) => { + const options = { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(passion), + }; + + fetch(`/api/passions`, options) + .then((res) => { + setIsLoading(false); + toast({ + position:'top', + title: `Passion ajoutée`, + status: "success", + isClosable: true, + }); + }) + .catch((err) => { + setIsLoading(false); + toast({ + title: `Erreur lors de l'ajout`, + position :'top', + status: "error", + isClosable: true, + }); + }); + } + + return ( + <> + + + Passion + + + {errors.name && errors.name.message} + + + + + + ); + } + } + \ No newline at end of file diff --git a/src/pages/api/socket/chat/[id].ts b/src/pages/api/socket/chat/[id].ts new file mode 100644 index 0000000..4eb0ce5 --- /dev/null +++ b/src/pages/api/socket/chat/[id].ts @@ -0,0 +1,42 @@ +import {Server} from 'socket.io'; +import prismaClient from '@/lib/prismaClient'; + +export default async function SocketHandler(req: any, res: any) { + const {id: chatId} = req.query; + + // It means that socket server was already initialised + if (res.socket.server.io) { + console.log('Already set up'); + res.end(); + return; + } + + const io = new Server(res.socket.server); + res.socket.server.io = io; + + // Define actions inside + io.on('connection', async (socket) => { + console.log(socket.id); + + await prismaClient.chat.findFirst({ + where: {id: chatId}, + include: {Message: true}, + }).then(chat => { + // @ts-ignore + return socket.emit('allOldMessages', chat.Message); + }); + + socket.on('createdMessage', async (msgInput) => { + await prismaClient.message.create({ + data: { + text: msgInput.text, + User: {connect: {id: msgInput.sender}}, + Chat: {connect: {id: chatId}}, + }, + }).then((newMessage) => socket.emit('newIncomingMessage', newMessage)); + }); + }); + + console.log('Setting up socket'); + res.end(); +} \ No newline at end of file diff --git a/src/pages/chat/[id].tsx b/src/pages/chat/[id].tsx index c3d0ad0..7e6f24b 100644 --- a/src/pages/chat/[id].tsx +++ b/src/pages/chat/[id].tsx @@ -1,36 +1,73 @@ -import {Container, Flex, Input, Text} from '@chakra-ui/react'; +import {Button, Container, Flex, Input, Text} from '@chakra-ui/react'; import Head from 'next/head'; import {websiteName} from '@/lib/constants'; import {useSession} from 'next-auth/react'; import {useRouter} from 'next/router'; -import {useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import MessageList from '@/components/chat/MessageList'; -import FormMessage from '@/components/form/FormMessage'; + +import {io, Socket} from 'socket.io-client'; +import {Message} from '@prisma/client'; export default function ChatId() { const router = useRouter(); const {data: session, status} = useSession(); - const {id} = router.query; - const [messages, setMessages] = useState([]) + const {id: chatId} = router.query; + + const [messages, setMessages] = useState([]); + const [text, setText] = useState(''); + const [socket, setSocket] = useState() useEffect(() => { - if (status === "authenticated") - fetch(`/api/messages?where={"ChatID": "${id}"}`) - .then(res => res.json()) - .then(msgs => setMessages(msgs)) + if (status === 'authenticated') socketInitializer() }, [status]); + + const socketInitializer = async () => { + await fetch(`/api/socket/chat/${chatId}`) + const soc = io() + + soc.on('connect', () => { + console.log('connected') + }) + + soc.on("allOldMessages", (allOldMessages: Message[]) => { + console.log("allOldMessages", allOldMessages); + setMessages(allOldMessages); + }) + + soc.on("newIncomingMessage", (newIncomingMessage: Message) => { + console.log("newIncomingMessage", newIncomingMessage); + console.log("messages", messages); + setMessages(messages => ([...messages, newIncomingMessage])); + }); + + setSocket(soc); + } + if (status === 'loading') return Loading...; - if (session && messages) + const handleSubmit = () => { + if (text !== "" && socket) { + // @ts-ignore + socket.emit("createdMessage", {text, sender: session.user.id}) + setText(""); + } + } + + if (session && session.user && messages) return ( <> {websiteName} - + + + setText(evt.target.value) } value={text} /> + + ); diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx index 09b9b7a..79c566a 100644 --- a/src/pages/dashboard.tsx +++ b/src/pages/dashboard.tsx @@ -41,7 +41,7 @@ export default function Dashboard() { minH={"100vh"} > - + diff --git a/src/pages/userProfile.tsx b/src/pages/userProfile.tsx index 3a9cf66..aa18ccd 100644 --- a/src/pages/userProfile.tsx +++ b/src/pages/userProfile.tsx @@ -25,7 +25,9 @@ import { useToast, } from "@chakra-ui/react"; -import ModalModifyImages from "@/components/ModalModifyImages"; +import ModalModifyImages from "@/components/layout/user_profile/ModalModifyImages"; +import ModalChoosePassion from "@/components/layout/user_profile/ModalChoosePassion"; +import ProfileBadgeList from "@/components/layout/user_profile/ProfileBadgeList"; import { useState } from "react"; import { useForm, Controller } from "react-hook-form"; @@ -33,6 +35,8 @@ import { useForm, Controller } from "react-hook-form"; export default function UserProfile() { const router = useRouter(); const toast = useToast(); + + const [isLoading, setIsLoading] = useState(false); const { @@ -82,6 +86,7 @@ export default function UserProfile() { .then((res) => { setIsLoading(false); toast({ + position:'top', title: `Modifications effectuées`, status: "success", isClosable: true, @@ -92,6 +97,7 @@ export default function UserProfile() { setIsLoading(false); toast({ title: `Erreur lors de l'envoi des modifications`, + position :'top', status: "error", isClosable: true, }); @@ -299,6 +305,24 @@ export default function UserProfile() { + + + + Centre d'intéret : + + ( + <> + + + + )} + /> + + + @@ -316,9 +340,6 @@ export default function UserProfile() { defaultValue={ user.gender === null ? Gender.UNKNOWN : user.gender } - // onChange={(value) => { - // setUserData({ ...userData, gender: value }); - // }} > @@ -372,7 +393,7 @@ export default function UserProfile() { */} -
+
+