diff --git a/next.config.js b/next.config.js index 4b9db8b..79ef6a5 100644 --- a/next.config.js +++ b/next.config.js @@ -12,7 +12,7 @@ const { i18n } = require('./next-i18next.config'); const nextConfig = { basePath: '/dashboard', // assetPrefix: '/dashboard/', - reactStrictMode: true, + reactStrictMode: false, i18n, publicRuntimeConfig: { diff --git a/package.json b/package.json index 87f05eb..7020654 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "6.6.0", "private": true, "scripts": { - "dev": "next dev -p 3000", + "dev": "next dev -p 3001", "build": "next build", "start": "next start -p 3000", "lint": "next lint" diff --git a/public/image/Frame-48098164.svg b/public/image/Frame-48098164.svg new file mode 100644 index 0000000..2aa3ee1 --- /dev/null +++ b/public/image/Frame-48098164.svg @@ -0,0 +1,2055 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/image/face-smile.svg b/public/image/face-smile.svg new file mode 100644 index 0000000..14d1e75 --- /dev/null +++ b/public/image/face-smile.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/image/icon-park-outline_link.svg b/public/image/icon-park-outline_link.svg new file mode 100644 index 0000000..5daa41c --- /dev/null +++ b/public/image/icon-park-outline_link.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/image/link-simple.svg b/public/image/link-simple.svg new file mode 100644 index 0000000..3b1feea --- /dev/null +++ b/public/image/link-simple.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/image/microphone-01.svg b/public/image/microphone-01.svg new file mode 100644 index 0000000..acdb138 --- /dev/null +++ b/public/image/microphone-01.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/image/send-01.svg b/public/image/send-01.svg new file mode 100644 index 0000000..2848199 --- /dev/null +++ b/public/image/send-01.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/chat/chat-input.tsx b/src/components/chat/chat-input.tsx index 5062d42..a766dae 100644 --- a/src/components/chat/chat-input.tsx +++ b/src/components/chat/chat-input.tsx @@ -1,13 +1,13 @@ import { PiTrash } from "react-icons/pi"; import Image from "next/image"; -import SendIcon from "public/assets/images/send-01.svg"; -import MicIcon from "public/assets/images/microphone-01.svg"; -import LinkIcon from "public/assets/images/link-simple.svg"; -import SmileIcon from "public/assets/images/face-smile.svg"; +import SendIcon from "public/image/send-01.svg"; +import MicIcon from "public/image/microphone-01.svg"; +import LinkIcon from "public/image/link-simple.svg"; +import SmileIcon from "public/image/face-smile.svg"; import { MdClose } from "react-icons/md"; import { useEffect, useState, useRef } from "react"; import { useWebSocket } from "@/contexts/WebSocket.context"; -import FileInput from "../ui/file-input"; +import FileInput from "./file-input"; import dynamic from "next/dynamic"; import EmojiPicker from "emoji-picker-react"; import { BsFillReplyFill } from "react-icons/bs"; diff --git a/src/components/chat/file-input.tsx b/src/components/chat/file-input.tsx new file mode 100644 index 0000000..d671614 --- /dev/null +++ b/src/components/chat/file-input.tsx @@ -0,0 +1,80 @@ +import { useWebSocket } from "@/contexts/WebSocket.context"; +import Image from "next/image"; +import FaPlus from "public/image/icon-park-outline_link.svg"; +import { useEffect, useState } from "react"; +import { toast } from "react-toastify"; // Import toast from react-toastify +import "react-toastify/dist/ReactToastify.css"; // Import CSS for toast + +// Define the allowed image, video, and audio extensions +const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp", "svg"]; +const VIDEO_EXTENSIONS = ["mp4", "mkv", "webm", "avi", "mov", "flv", "wmv"]; +const AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "flac", "aac", "m4a"]; + +const getFileType = (extension : string) => { + const ext = extension.toLowerCase(); + + // Check if it's an image + if (IMAGE_EXTENSIONS.includes(ext)) { + return "image"; + } + + // Check if it's a video + if (VIDEO_EXTENSIONS.includes(ext)) { + return "video"; + } + + // Check if it's an audio file + if (AUDIO_EXTENSIONS.includes(ext)) { + return "audio"; + } + + // Everything else is a generic file + return "file"; +}; + +const FileInput = () => { + const {setLoadingMessage} = useWebSocket() + const { sendFile } = useWebSocket(); + const [file, setFile] = useState(null); // To store the selected file + + const MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB in bytes + + const handleFileChange = async (event : any) => { + const selectedFile = event.target.files[0]; + + if (selectedFile) { + // Check file size + if (selectedFile.size > MAX_FILE_SIZE) { + toast.error("File is too large. Please select a file smaller than 300 MB."); + return; // Prevent further processing if the file is too large + } + selectedFile.status = "loading" + setLoadingMessage((prev) => [...prev, selectedFile]); + + } + }; + + // Send the file when it's available + + + return ( +
+ {/* Hidden file input */} + + {/* Label triggers the file input */} + +
+ ); +}; + +export default FileInput; diff --git a/src/components/chat/file-message.tsx b/src/components/chat/file-message.tsx index 93fd75f..7500e5c 100644 --- a/src/components/chat/file-message.tsx +++ b/src/components/chat/file-message.tsx @@ -19,6 +19,8 @@ const fileTypes = { }; const FileMessage = ({ file }) => { + console.log( "file Message",file); + const { sendFile } = useWebSocket(); const [percentage, setPercentage] = useState(1); const isLoading = file.status === "loading"; @@ -95,7 +97,9 @@ const FileMessage = ({ file }) => { }) .then((res) => { const fileType = getFileType(fileExtension); - sendFile({ ...res.data, type: fileType }); + console.log("file sent"); + + sendFile({ ...res, type: fileType }); }) .catch((err) => console.error(err)); } diff --git a/src/components/chat/message.tsx b/src/components/chat/message.tsx index 7980141..ed58bec 100644 --- a/src/components/chat/message.tsx +++ b/src/components/chat/message.tsx @@ -1,26 +1,26 @@ -import { useState, useEffect, useCallback, useRef } from "react"; -import { useRouter } from "next/router"; -import ContextMenu from "./contex-menu"; // Import the ContextMenu component -import Image from "next/image"; -import Seen from "public/image/quill_checkmark-double.svg"; -import UnSeen from "public/image/State=Send.svg"; -import Pending from "public/image/clock-fast-forward.png"; -import { useWebSocket } from "@/contexts/WebSocket.context"; -import Button from "../ui/button"; -import { Routes as ROUTES } from "@/config/routes"; +import { useState, useEffect, useCallback, useRef } from 'react'; +import { useRouter } from 'next/router'; +import ContextMenu from './contex-menu'; // Import the ContextMenu component +import Image from 'next/image'; +import Seen from 'public/image/quill_checkmark-double.svg'; +import UnSeen from 'public/image/State=Send.svg'; +import Pending from 'public/image/clock-fast-forward.png'; +import { useWebSocket } from '@/contexts/WebSocket.context'; +import Button from '../ui/button'; +import { Routes as ROUTES } from '@/config/routes'; -import { HttpClient as http } from "@/data/client/http-client"; -import FileMessage from "./file-message"; -import ImageMessage from "./image-message"; -import AudioMessage from "./audio-message"; +import { HttpClient as http } from '@/data/client/http-client'; +import FileMessage from './file-message'; +import ImageMessage from './image-message'; +import AudioMessage from './audio-message'; const Message = ({ msg }) => { - const isFileMessage = msg.status || msg?.mime_type === "file" ? true : false; + const isFileMessage = msg.status || msg?.mime_type === 'file' ? true : false; const isLoadingMessage = msg.status ? true : false; - const isAudioMessage = msg?.mime_type === "audio" ? true : false; + const isAudioMessage = msg?.mime_type === 'audio' ? true : false; const isImageMessage = - msg?.mime_type === "image" || msg?.mime_type === "video" ? true : false; - const isTextMessage = msg.mime_type === "text" ? true : false; + msg?.mime_type === 'image' || msg?.mime_type === 'video' ? true : false; + const isTextMessage = msg.mime_type === 'text' ? true : false; if (isLoadingMessage) { return ; } @@ -46,7 +46,7 @@ const Message = ({ msg }) => { const date = new Date(timestamp); let hours = date.getHours(); const minutes = date.getMinutes(); - const ampm = hours >= 12 ? "PM" : "AM"; + const ampm = hours >= 12 ? 'PM' : 'AM'; hours %= 12; hours = hours || 12; // Convert '0' to '12' @@ -55,16 +55,15 @@ const Message = ({ msg }) => { return `${hours}:${formattedMinutes} ${ampm}`; }, []); - // Handlers for context menu actions const handleCopy = useCallback(() => { navigator.clipboard - .writeText(typeof msg === "string" ? msg : msg.content) + .writeText(typeof msg === 'string' ? msg : msg.content) .then(() => { - console.log("Copied to clipboard"); + console.log('Copied to clipboard'); }) .catch((err) => { - console.error("Failed to copy: ", err); + console.error('Failed to copy: ', err); }); setShowMenu(false); }, [msg]); @@ -85,7 +84,7 @@ const Message = ({ msg }) => { e.preventDefault(); setShowMenu((prev) => !prev); }, - [setShowMenu] + [setShowMenu], ); // Close menu if clicked outside @@ -100,43 +99,43 @@ const Message = ({ msg }) => { setShowMenu(false); } }, - [menuRef, messageRef] + [menuRef, messageRef], ); // Handle escape key to close menu const handleKeyDown = useCallback( (e) => { - if (e.key === "Escape") { + if (e.key === 'Escape') { setShowMenu(false); } }, - [setShowMenu] + [setShowMenu], ); useEffect(() => { if (showMenu) { - document.addEventListener("mousedown", handleClickOutside); - document.addEventListener("keydown", handleKeyDown); + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleKeyDown); } else { - document.removeEventListener("mousedown", handleClickOutside); - document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleKeyDown); } // Cleanup on unmount return () => { - document.removeEventListener("mousedown", handleClickOutside); - document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleKeyDown); }; }, [showMenu, handleClickOutside, handleKeyDown]); - const isStringMessage = typeof msg === "string"; + const isStringMessage = typeof msg === 'string'; const messageContent = isStringMessage ? msg : msg.content; const messageKey = isStringMessage ? msg : msg.id; const messageTime = isStringMessage ? formatTime() : formatTime(msg.created_at); - const isUser = !isStringMessage && msg?.by_user?.account_type === "user"; + const isUser = !isStringMessage && msg?.by_user?.account_type === 'merchant'; const isSeen = !isStringMessage && msg?.is_read; const product = !isStringMessage && Object.keys(msg?.product_reply).length @@ -161,14 +160,14 @@ const Message = ({ msg }) => { key={messageKey} className={`rounded-lg border p-2 mb-2 max-w-[525px] flex flex-col whitespace-pre-line break-words relative ${ isUser - ? "self-end bg-[#323232] text-white" + ? 'self-end bg-[#323232] text-white' : isStringMessage - ? "self-end bg-[#323232] text-white" - : "self-start bg-white text-gray-800" + ? 'self-end bg-[#323232] text-white' + : 'self-start bg-white text-gray-800' }`} tabIndex={0} onKeyDown={(e) => { - if (e.key === "ContextMenu" || (e.shiftKey && e.key === "F10")) { + if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) { e.preventDefault(); setShowMenu(true); } @@ -184,28 +183,30 @@ const Message = ({ msg }) => { onDelete={handleDelete} menuRef={menuRef} isUser={isUser} - className={isUser ? "-right-2" : ""} + className={isUser ? '-right-2' : ''} /> {/* Render file message */} {product && (
-
+ {product?.get_first_image && ( product -
+ )} +

{