diff --git a/package-lock.json b/package-lock.json index 9fab789..b20175e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "redux-thunk": "^3.1.0", "sass": "^1.77.5", "sass-loader": "^14.2.1", + "socket.io-client": "^4.7.5", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "style-loader": "^4.0.0", @@ -3890,6 +3891,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@storybook/addon-actions": { "version": "8.1.10", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.1.10.tgz", @@ -11169,6 +11176,51 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", @@ -20711,6 +20763,80 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -23514,10 +23640,10 @@ } }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", - "dev": true, + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -23549,6 +23675,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": 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/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index d018ebc..78a705c 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "redux-thunk": "^3.1.0", "sass": "^1.77.5", "sass-loader": "^14.2.1", + "socket.io-client": "^4.7.5", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "style-loader": "^4.0.0", diff --git a/src/components/AvailableProduct/product.tsx b/src/components/AvailableProduct/product.tsx index 070ebaa..7468b2b 100644 --- a/src/components/AvailableProduct/product.tsx +++ b/src/components/AvailableProduct/product.tsx @@ -116,10 +116,10 @@ const Product: React.FC = ({

{name}

{price} $

-

+ {/*

Category: {category} -

+

*/}
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss new file mode 100644 index 0000000..89bcf56 --- /dev/null +++ b/src/components/chat/chat.scss @@ -0,0 +1,276 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + scroll-behavior: smooth; +} + +.display{ + font-style: italic; + align-items: center; + justify-content: center; + text-align: center; + padding-top: 70px; + font-weight: bold; +} + +.all { + display: grid; + place-items: center; + overflow: hidden; + height: 400px; + width: 100%; +} +.chat{ + display: block; + font-size: 12px; + color: #dcdcdc; + text-align: left; +} + +.chat-container { + border: 8px solid #0F1848; + border-radius: 24px; + overflow: hidden; + height: 100%; + width: 100%; + max-width: 500px; +} + +.send-button { + height: 48px; + font-size: 16px; + border: none; + padding: 0px 20px; + outline: none; + background-color: #0F1848; + cursor: pointer; +} + +.send-button:hover { + background-color: #CC5612; + color: rgb(17, 16, 16); +} + +.message-left > p > span, +.message-right > p > span { + display: block; + font-style: italic; + font-size: 12px; + margin-top: 4px; +} + +.message-input { + flex-grow: 1; + height: 48px; + font-size: 16px; + border: none; + outline: none; + padding: 0 12px; + background-color: #ebe9ed; +} + +.time { + font-size: 12px; + color: #080807; + margin-bottom: 10px; + align-items: end; + right: 0; +} + +.titlez { + color: #0F1848; + font-size: 20px; + background-color: #c45718; + padding: 10px 0; + font-weight: bold; + border: 2px solid #0F1848; + font-family: "Inter", sans-serif; + align-items: center; + text-align: center; + justify-content: center; +} + +.message-left, +.message-right { + list-style: none; + padding: 8px 12px; + margin: 5px; + max-width: 250px; + font-size: 12px; + word-wrap: break-word; +} + +.message-right { + border-radius: 7px 7px 0px 7px; + align-items: flex-start; + background-color: #4071b9; + color: #0b0b0b; + box-shadow: 2px 2px 4px #dcdcdc; +} + +.message-left { + border-radius: 10px 10px 10px 0px; + align-self: flex-end; + background-color: #c45718; + box-shadow: 2px 2px 4px #dcdcdc; + color: #060606; +} + +.typing-indicator { + color: green; + font-weight: bold; + font-style: italic; +} + +.username { + font-weight: bold; + display: block; + color: #0F1848; + text-align: left; + +} + +.message { + display: flex; + flex-direction: column; + background-color: #dfdbdb; + width: 100%; + height: 300px; + overflow-y: scroll; + overflow-x: hidden; +} + +/* Webkit browsers (Chrome, Safari) */ +.message::-webkit-scrollbar { + width: 8px; +} + +.msg-form { + display: flex; + justify-content: space-between; + height: 100%; +} + +.message-container { + display: flex; + flex-direction: column; + background-color: #dfdbdb; + width: 100%; + height: auto; +} + +/* Responsive Styles */ +@media screen and (max-width: 768px) { + .all{ + height: 400px; + margin-left: 45px; + bottom: 56px; + padding-left: 15px; + // width: 600px; + + } + .chat-container { + width: 500px; + background-color: #CC5612; + height: 900px; + + } + + .message { + width: 100%; + height: 250px; + } + + .message-container { + width: 100%; + height: auto; + } + + .titlez { + font-size: 18px; + padding: 8px 0; + } + + + .message-input { + font-size: 14px; + height: 700px; + } + + .message-left, + .message-right { + max-width: 100%; + font-size: 14px; + } +} + +@media screen and (max-width: 480px) { + .all{ + height: auto; + + } + .chat-container { + width: 90%; + height: auto; + border-radius: 12px; + + } + + .message { + width: 100%; + height: 200px; + } + + .message-container { + width: 100%; + height: auto; + + } + + .titlez { + font-size: 16px; + padding: 6px 0; + } + + .send-button { + width: 50%; + font-size: 12px; + padding: 0; + } + + .message-input { + font-size: 12px; + height: 50px; + width: 50%; + } + + .message-left, + .message-right { + max-width: 100%; + font-size: 12px; + } +} +.send-button { + position: relative; + + .loading-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top: 2px solid #fff; + border-radius: 50%; + animation: spin 1s linear infinite; + display: inline-block; + margin-left: 5px; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +} \ No newline at end of file diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx new file mode 100644 index 0000000..53742f6 --- /dev/null +++ b/src/components/chat/chat.tsx @@ -0,0 +1,189 @@ +import { ReactNode, useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { decodeToken } from "react-jwt"; +import socket from "./socket"; +import "./chat.scss"; +import { fetchMessages } from "../../redux/slices/chatSlice"; +import { AppDispatch, RootState } from "../../redux/store"; +import { format, isToday, isYesterday, parseISO } from "date-fns"; + +interface DecodedToken { + userId: string; + role: string; +} + +interface Message { + timestamp: string; + userId: string; + username: string; + message: string; +} + +interface TypingUser { + userId: string; + username: string; +} + +function Chat() { + const dispatch = useDispatch(); + const { messages, loading, error } = useSelector( + (state: RootState) => state.chat + ); + const [message, setMessage] = useState(''); + const [typing, setTyping] = useState(null); + const [userId, setUserId] = useState(null); + const [sending, setSending] = useState(false); + + useEffect(() => { + // Decode the user ID from the token + const token = localStorage.getItem("token"); + if (token) { + const decodedToken = decodeToken(token); + if (decodedToken) { + setUserId(decodedToken.userId); + } + } + + // Dispatch the fetchMessages action to get messages from the backend + dispatch(fetchMessages()); + }, [dispatch]); + + useEffect(() => { + // Socket event listeners + socket.on("newMessage", (message: Message) => { + dispatch({ type: "chat/addMessage", payload: message }); + setSending(false); + resetMessage(); + console.log("message received", message); + }); + + socket.on("typing", (typingUser: TypingUser) => { + setTyping(typingUser); + setTimeout(() => setTyping(null), 2000); + }); + + socket.on("Left", (userLeft: TypingUser) => { + setTyping(userLeft); + setTimeout(() => setTyping(null), 1000); + }); + + return () => { + // Clean up socket event listeners + socket.off("newMessage"); + socket.off("typing"); + socket.off("Left"); + }; + }, [dispatch]); + + const resetMessage = () => { + setMessage(""); + }; + + const SendMessage = (e: React.FormEvent) => { + e.preventDefault(); + if (message.trim()) { + setSending(true); + socket.emit("message", message, () => { + resetMessage(); + setSending(false); + }); + } + }; + + const handleMessage = (e: React.ChangeEvent) => { + setMessage(e.target.value); + socket.emit("typing"); + }; + + const isValidString = (value: any) => { + return typeof value === "string" && value.trim() !== ""; + }; + + const formatTimestamp = (timestamp: string) => { + try { + const date = parseISO(timestamp); + if (isToday(date)) { + return `Today at ${format(date, "HH:mm")}`; + } else if (isYesterday(date)) { + return `Yesterday at ${format(date, "HH:mm")}`; + } else { + return format(date, "dd/MM/yyyy HH:mm"); + } + } catch (error) { + console.error("Invalid timestamp:", timestamp); + return "Invalid date"; + } + }; + + return ( +
+
+
CHAT
+
+ {loading ? ( +

Loading messages...

+ ) : error ? ( +

No messages yet

+ ) : ( + messages.map((messageObj: Message, index: number) => ( +
+ {userId === messageObj.userId ? ( +
+

+ {isValidString(messageObj?.message) + ? messageObj.message + : "No message"} + + sent {formatTimestamp(messageObj.timestamp)} + +

+
+ ) : ( +
+

+ + {isValidString(messageObj?.username) + ? messageObj.username + : "Unknown"} + + {isValidString(messageObj?.message) + ? messageObj.message + : "No message"} + + sent {formatTimestamp(messageObj.timestamp)} + +

+
+ )} +
+ )) + )} + {typing && userId !== typing.userId && ( +
+

{isValidString(typing?.username) ? typing.username : "Someone"} is typing...

+
+ )} +
+
+ + +
+
+
+ ); +} + +export default Chat; \ No newline at end of file diff --git a/src/components/chat/socket.ts b/src/components/chat/socket.ts new file mode 100644 index 0000000..c6e3323 --- /dev/null +++ b/src/components/chat/socket.ts @@ -0,0 +1,17 @@ +import {io} from "socket.io-client"; + + +// const url = "http://localhost:8000" +const BACKEND_URL = process.env.REACT_APP_BACKEND_URL; + +const token = localStorage.getItem('token'); + +const socketIo = io(BACKEND_URL || '', { + auth: { + + password: '123', + token: token + } +}); + +export default socketIo; \ No newline at end of file diff --git a/src/components/chatButton/button.tsx b/src/components/chatButton/button.tsx new file mode 100644 index 0000000..d7495dd --- /dev/null +++ b/src/components/chatButton/button.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import './floatingButton.scss'; +import ChatModal from '../chatModal/chatModal'; +import Chat from '../chat/chat'; +import { toast } from "react-toastify"; + + +const isAuthenticated = () => { + const token = localStorage.getItem("token"); + return !!token; +}; + +const FloatingButton: React.FC = () => { + const [isModalVisible, setIsModalVisible] = useState(false); + + const openModal = (e: { preventDefault: () => void }) => { + e.preventDefault(); + + if (isAuthenticated()) { + setIsModalVisible(true); + } else { + + toast.error("You need to be logged in to access the chat."); + } + }; + + const closeModal = () => { + setIsModalVisible(false); + }; + + return ( +
+ + + {isModalVisible && ( + + + + )} + +
+ ); +}; + +export default FloatingButton; \ No newline at end of file diff --git a/src/components/chatButton/floatingButton.scss b/src/components/chatButton/floatingButton.scss new file mode 100644 index 0000000..5472c82 --- /dev/null +++ b/src/components/chatButton/floatingButton.scss @@ -0,0 +1,21 @@ +.floating-button { + position: fixed; + bottom: 20px; + right: 20px; + width: 60px; + height: 60px; + border-radius: 50%; + background-color: #0F1848; + color: white; + border: none; + font-size: 24px; + cursor: pointer; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + display: flex; + align-items: center; + justify-content: center; +} + +// .floating-button:hover { +// background-color: #0056b3; +// } \ No newline at end of file diff --git a/src/components/chatModal/chatModal.tsx b/src/components/chatModal/chatModal.tsx new file mode 100644 index 0000000..cfbdfc6 --- /dev/null +++ b/src/components/chatModal/chatModal.tsx @@ -0,0 +1,22 @@ +import React, { ReactNode } from 'react'; +import './chatmodalStyles.scss'; + +interface ModalProps { + children: ReactNode; + onClose: () => void; +} +const ChatModal: React.FC = ({ children, onClose }) => { + return ( +
+
+
+ + cancel +
+ {children} +
+
+ ); +}; + +export default ChatModal; \ No newline at end of file diff --git a/src/components/chatModal/chatmodalStyles.scss b/src/components/chatModal/chatmodalStyles.scss new file mode 100644 index 0000000..25613ed --- /dev/null +++ b/src/components/chatModal/chatmodalStyles.scss @@ -0,0 +1,80 @@ +.chat-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: end; + justify-content: end; + z-index: 1000; + +} +.cancel{ + background: none; + font-size: 20px; + cursor: pointer; + color: rgb(244, 235, 235); + font-weight: bold; + margin-bottom: 900px; + // position: fixed; + top: 80%; + left: 70%; + width: 45px; + justify-content: center; + align-items: center; +} + +.chat-modal-content { + padding-top: 40px; + // padding: 20px; + height: 60px; + border-radius: 5px; + position: relative; + width: fit-content; + +} +.chat-modal-close-button img{ + width: 30px; + height: 30px; + background-color: #0F1848; + border-radius: 5px; + + +} + +.chat-modal-close-button { + position: absolute; + top: 50px; + right: 10px; + padding: 5px; + border: none; + font-size: 20px; + cursor: pointer; + color: rgb(11, 11, 11); + font-weight: bold; +}:hover{ + color: black; +} +@media screen and (max-width: 768px) { + .chat-modal-content { + width: 95%; + height: 350px; + } + .chat-modal-close-button{ + top: 30px; + right: 20px; + padding: 3px; + font-size: 16px; + } + + + +} +@media only screen and (min-width: 768px) { + .chat-modal-content { + width: 30%; + height: 450px; + } + +} \ No newline at end of file diff --git a/src/redux/slices/availableProductSlice.ts b/src/redux/slices/availableProductSlice.ts index d384370..2e66355 100644 --- a/src/redux/slices/availableProductSlice.ts +++ b/src/redux/slices/availableProductSlice.ts @@ -24,7 +24,7 @@ export const fetchAvailableProducts = createAsyncThunk( let url = `${BACKEND_URL}/api/products/available?page=${page}`; if (searchKeyword) { - url = `${BACKEND_URL}/api/products/${encodeURIComponent(searchKeyword)}?searchKeyword=${encodeURIComponent(searchKeyword)}&page=${page}`; + url = `${BACKEND_URL}/api/products/search/${encodeURIComponent(searchKeyword)}?searchKeyword=${encodeURIComponent(searchKeyword)}&page=${page}`; } else { url = `${BACKEND_URL}/api/products/available?page=${page}`; } diff --git a/src/redux/slices/chatSlice.ts b/src/redux/slices/chatSlice.ts new file mode 100644 index 0000000..54e79d2 --- /dev/null +++ b/src/redux/slices/chatSlice.ts @@ -0,0 +1,70 @@ +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { toast } from 'react-toastify'; +import { boolean } from 'yup'; + + +interface ChatState { + messages: any[]; + loading: boolean; + error: string | null; + +} + +const initialState: ChatState = { + messages: [], + loading: false, + error: null, + + +}; + +export const fetchMessages = createAsyncThunk( + 'chat/fetchMessages', + async () => { + const token = localStorage.getItem('token'); + if (!token) { + toast.error("Login first"); + throw new Error('Bearer token is not available'); + } + const BACKEND_URL = process.env.REACT_APP_BACKEND_URL; + const response = await axios.get(`${BACKEND_URL}/api/chats`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + console.log('here are our messages',response.data); + + return response.data; + } +); + +const chatSlice = createSlice({ + name: 'chat', + initialState, + reducers: { + addMessage: (state, action) => { + state.messages.push(action.payload); + }, + }, + extraReducers: (builder) => { + builder + .addCase(fetchMessages.pending, (state) => { + state.loading = true; + state.error = null; + + }) + .addCase(fetchMessages.fulfilled, (state, action) => { + state.loading = false; + state.messages = action.payload; + }) + .addCase(fetchMessages.rejected, (state, action) => { + state.loading = false; + state.error = action.error.message || 'Failed to fetch messages'; + }); + }, +}); + +export const { addMessage } = chatSlice.actions; + +export default chatSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index 286f908..bd50b96 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -7,7 +7,7 @@ import productsReducer from './slices/availableProductSlice'; import cartSlice from "./slices/cartSlice"; import navbarSlice from "./slices/navbarSlice"; import otpReducer from './slices/otpSlice'; // Add this line - +import chatReducer from './slices/chatSlice'; import userReducer from './slices/userSlices'; import singleItemOrderReducer from "./slices/singleItemOrderSlice"; import productReviewReducer from './slices/productSlice' @@ -27,7 +27,8 @@ const rootReducer = combineReducers({ otp: otpReducer, cartCheckout: cartCheckoutReducer, singleItemOrder: singleItemOrderReducer, - productReviews: productReviewReducer + productReviews: productReviewReducer, + chat: chatReducer }); diff --git a/src/views/Home.tsx b/src/views/Home.tsx index 3d0da5d..b15f189 100644 --- a/src/views/Home.tsx +++ b/src/views/Home.tsx @@ -13,6 +13,12 @@ import Toast from '../components/Toast/Toast'; import { useLocation } from 'react-router-dom'; import { fetchAvailableProducts } from '../redux/slices/availableProductSlice'; +import "../styles/Home.scss"; + +import FakeProduct from "../components/cart/fakeproduct"; +import Chat from "../components/chat/chat"; +import FloatingButton from "../components/chatButton/button"; + const Home: React.FC = () => { const location = useLocation(); const dispatch: AppDispatch = useDispatch(); @@ -31,7 +37,7 @@ const Home: React.FC = () => { useEffect(() => { const state = location.state as { from?: { pathname: string } }; const previousRoute = state?.from?.pathname; - if (previousRoute === '/login') { + if (previousRoute === "/login") { setShowToast(true); } }, [location, isSuccessfully, isSucceeded]); @@ -94,10 +100,13 @@ const Home: React.FC = () => {
-
+
-
+ + + + ); };