+++ /dev/null
-/**
- * @file Chat component for real-time messaging
- * @module Chat
- */
-
-import React, { useEffect, useRef, useState } from 'react';
-import axios from 'axios';
-import { API_BASE_URL } from '../../App';
-import AddIcon from '@mui/icons-material/Add';
-import {
- Button,
- Card,
- CardActions,
- CardContent,
- CardHeader,
- Grid,
- IconButton,
- InputAdornment,
- TextField
-} from '@mui/material';
-import ChatBubble from './ChatBubble';
-
-/**
- * @typedef {Object} User
- * @property {number} id
- * @property {string} first_name
- * @property {string} middle_name
- * @property {string} last_name
- */
-
-/**
- * Chat component
- * @param {Object} props - Component props
- * @param {Object|null} props.user - Current user
- * @param {Object} props.socket - Socket.io client instance
- * @returns {JSX.Element} Rendered component
- */
-const Chat = ( { user, socket } ) => {
- const [conversations, setConversations] = useState( [] );
- const [selectedConversation, setSelectedConversation] = useState( null );
- const [messages, setMessages] = useState( [] );
- const [content, setContent] = useState( '' );
- const [attachedFiles, setAttachedFiles] = useState( [] );
- const [users, setUsers] = useState( [] );
- const [groups, setGroups] = useState( [] );
- const [error, setError] = useState( null );
- const [isSidebarOpen, setIsSidebarOpen] = useState( true );
- const messagesEndRef = useRef( null );
- const fileRef = useRef( null );
-
- useEffect( () => {
- if (user) {
- const fetchData = async () => {
- try {
- const [usersRes, groupsRes, messagesRes] = await Promise.all( [
- axios.get( `${ API_BASE_URL }/user`, { withCredentials:true } ),
- axios.get( `${ API_BASE_URL }/message_group`, { withCredentials:true } ),
- axios.get( `${ API_BASE_URL }/message`, { withCredentials:true } )
- ] );
- setUsers( Array.isArray( usersRes.data ) ? usersRes.data.filter( u => u.id !== user.id ) : [] );
- setGroups( Array.isArray( groupsRes.data ) ? groupsRes.data : [] );
-
- // Group messages by recipient_id or group_id
- const messages = Array.isArray( messagesRes.data ) ? messagesRes.data : [];
- const convoMap = {};
- messages.forEach( msg => {
- if (msg.group_id) {
- // Group chat
- const key = `group_${ msg.group_id }`;
- if (!convoMap[key]) {
- convoMap[key] = {
- id:key,
- type:'group',
- targetId:msg.group_id,
- name:groupsRes.data.find( g => g.id === msg.group_id )?.name || 'Group',
- lastMessage:msg.content,
- lastMessageTime:msg.created_at
- };
- }
- } else {
- // Private chat: Use sorted user IDs to ensure single conversation
- const otherUserId = msg.sender_id === user.id ? msg.recipient_id : msg.sender_id;
- const key = `user_${ [user.id, otherUserId].sort( ( a, b ) => a - b ).join( '_' ) }`;
- if (!convoMap[key]) {
- convoMap[key] = {
- id:key,
- type:'user',
- targetId:otherUserId,
- name:usersRes.data.find( u => u.id === otherUserId )?.first_name || 'User',
- lastMessage:msg.content,
- lastMessageTime:msg.created_at
- };
- }
- }
- } );
- setConversations( Object.values( convoMap ).sort( ( a, b ) => new Date( b.lastMessageTime ) - new Date( a.lastMessageTime ) ) );
- setMessages( messages );
- } catch (err) {
- console.error( 'Chat data fetch error:', err );
- setError( 'Failed to load chat data' );
- }
- };
- fetchData().catch();
-
- socket.on( 'newMessage', msg => {
- setMessages( prev => (Array.isArray( prev ) ? [...prev, msg] : [msg]) );
- setConversations( prev => {
- let key, convo;
- if (msg.group_id) {
- key = `group_${ msg.group_id }`;
- convo = {
- id:key,
- type:'group',
- targetId:msg.group_id,
- name:groups.find( g => g.id === msg.group_id )?.name || 'Group',
- lastMessage:msg.content,
- lastMessageTime:msg.created_at
- };
- } else {
- const otherUserId = msg.sender_id === user.id ? msg.recipient_id : msg.sender_id;
- key = `user_${ [user.id, otherUserId].sort( ( a, b ) => a - b ).join( '_' ) }`;
- convo = {
- id:key,
- type:'user',
- targetId:otherUserId,
- name:users.find( u => u.id === otherUserId )?.first_name || 'User',
- lastMessage:msg.content,
- lastMessageTime:msg.created_at
- };
- }
- return [convo, ...prev.filter( c => c.id !== key )].sort( ( a, b ) => new Date( b.lastMessageTime ) - new Date( a.lastMessageTime ) );
- } );
- } );
-
- socket.on( 'newReaction', ( { messageId, reaction_data } ) => {
- setMessages( prev => {
- if (!Array.isArray( prev )) return prev;
- return prev.map( m => m.id === parseInt( messageId ) ? {
- ...m,
- reactions:[...(Array.isArray( m.reactions ) ? Array.from( new Set( m.reactions ) ) : []), reaction_data]
- } : m );
- } );
- } );
-
- return () => {
- socket.off( 'newMessage' );
- socket.off( 'newReaction' );
- };
- }
- }, [user] );
-
- useEffect( () => {
- messagesEndRef.current?.scrollIntoView( { behavior:'smooth' } );
- }, [messages] );
-
- /**
- * Select a conversation to view messages
- * @param {Object} convo - Conversation object
- */
- const selectConversation = ( convo ) => {
- setSelectedConversation( convo );
- setMessages(
- Array.isArray( messages )
- ? messages.filter( m =>
- (convo.type === 'group' && m.group_id === convo.targetId) ||
- (convo.type === 'user' && (m.recipient_id === convo.targetId || m.sender_id === convo.targetId))
- )
- : []
- );
- setIsSidebarOpen( false );
- };
-
- /**
- * Send a message with optional file attachments
- */
- const sendMessage = async () => {
- if (content.trim() || attachedFiles.length) {
- let fileIds = [];
- try {
- if (attachedFiles.length) {
- const formData = new FormData();
- attachedFiles.forEach( f => formData.append( 'files', f ) );
- formData.append( 'visibility', selectedConversation?.type === 'group' ? 'family' : selectedConversation?.type === 'user' ? 'specific' : 'family' );
- if (selectedConversation?.type === 'user') formData.append( 'specific_users', JSON.stringify( [selectedConversation.targetId] ) );
- const res = await axios.post( `${ API_BASE_URL }/file/create`, formData, {
- headers:{ 'Content-Type':'multipart/form-data' },
- withCredentials:true
- } );
- fileIds = Array.isArray( res.data ) ? res.data.map( f => f.id ) : [res.data.id];
- }
- socket.emit( 'chatMessage', {
- content,
- recipientId:selectedConversation?.type === 'user' ? selectedConversation.targetId : null,
- groupId:selectedConversation?.type === 'group' ? selectedConversation.targetId : null,
- fileIds
- } );
- setContent( '' );
- setAttachedFiles( [] );
- } catch (err) {
- console.error( 'Send message error:', err );
- setError( 'Failed to send message' );
- }
- }
- };
-
- if (!user) {
- return <div className="text-center pt-20">Please log in to access chat</div>;
- }
-
- if (error) {
- return <div className="text-center pt-20 text-red-500">{ error }</div>;
- }
-
- /**
- *
- * @returns {React.ReactNode[]}
- */
- const getConvos = () => (
- conversations.map( convo => (
- <div key={ convo.id }
- onClick={ () => selectConversation( convo ) }
- className={ `p-2 mb-2 rounded cursor-pointer ${ selectedConversation?.id === convo.id ? 'bg-blue-200' : 'hover:bg-gray-200' }` }>
- <p className="font-bold">{ convo.name } ({ convo.type === 'group' ? 'Group' : 'Private' })</p>
- <p className="text-sm text-gray-600 truncate">{ convo.lastMessage }</p>
- <p className="text-xs text-gray-500">{ new Date( convo.lastMessageTime ).toLocaleString() }</p>
- </div>
- ) )
- );
-
- /**
- *
- * @returns {React.ReactNode[]}
- */
- const getMessages = () => (
- messages.map( ( msg, i ) => (
- <Grid key={ i }
- size={ { xs:12 } }>
- <div className={ `mb-4 flex ${ msg.sender_id === user.id ? 'justify-end' : 'justify-start' }` }>
- <ChatBubble user={ user }
- msg={ msg }
- socket={ socket }
- setError={ setError }/>
- </div>
- </Grid>
- ) )
- );
-
- return (
- <Grid container
- className="chat-container"
- spacing={ 0 }>
- { isSidebarOpen && <Grid size={{ xs: 10, md: 6, lg: 3 }}>
- { getConvos() }
- </Grid> }
- <Grid size="grow">
- <Card className="chat-card">
- <CardHeader className="chat-header"
- title={
- <div className="flex justify-between items-center p-4 border-b">
- <h2 className="text-2xl">
- { selectedConversation ? `${ selectedConversation.name } (${ selectedConversation.type === 'group' ? 'Group' : 'Private' })` : 'Select a conversation' }
- </h2>
- <button onClick={ () => setIsSidebarOpen( prevState => !prevState ) }
- className="text-blue-600">☰
- </button>
- </div> }/>
- <CardContent className="chat-content">
- <div className="flex-1 overflow-y-auto p-4 bg-gray-50">
- { selectedConversation ? (
- messages.length === 0 ? (
- <p className="text-center text-gray-500">No messages in this conversation</p>
- ) : (
- <Grid container
- columnSpacing={ 0 }
- rowSpacing={ 2 }
- maxWidth="xxl">
- { getMessages() }
- </Grid>
- )
- ) : (
- <p className="text-center text-gray-500">Select a conversation to start chatting</p>
- ) }
- <div ref={ messagesEndRef }/>
- </div>
- </CardContent>
- <CardActions className="chat-actions">
- <input type="file"
- accept="image/!*,video/!*"
- multiple
- hidden
- ref={fileRef}
- onChange={ e => setAttachedFiles( Array.from( e.target.files ) ) }
- className="mb-2"/>
- { selectedConversation ? (
- <TextField value={ content }
- className='chat-input'
- onChange={ e => setContent( e.target.value ) }
- placeholder="Type a message"
- fullWidth
- slotProps={ {
- input:{
- startAdornment:
- <InputAdornment
- position="start">
- <Button
- variant='contained'
- onClick={fileRef?.current?.click}
- size="small"
- color="primary"><AddIcon/></Button>
- </InputAdornment>,
- endAdornment:
- <InputAdornment
- variant="filled"
- position="end">
- <Button
- variant="contained"
- color="primary"
- onClick={sendMessage}
- size="small">Send</Button>
- </InputAdornment>
- }
- } }/>
- /*<div className="p-4 border-t">
- <input type="file"
- accept="image/!*,video/!*"
- multiple
- onChange={ e => setAttachedFiles( Array.from( e.target.files ) ) }
- className="mb-2"/>
- <div className="flex">
- <input value={ content }
- onChange={ e => setContent( e.target.value ) }
- placeholder="Type a message"
- className="border p-2 flex-grow rounded-l"/>
- <button onClick={ sendMessage }
- className="bg-blue-500 text-white p-2 rounded-r">Send
- </button>
- </div>
- </div>*/
- ) : null }
- </CardActions>
- </Card>
- </Grid>
- </Grid>
- );
-};
-
-export default Chat;
\ No newline at end of file
--- /dev/null
+/**
+ * @file Chat component for real-time messaging
+ * @module Chat
+ */
+
+import React, { useEffect, useRef, useState } from 'react';
+import axios from 'axios';
+import { API_BASE_URL } from '../../../App';
+import AddIcon from '@mui/icons-material/Add';
+import {
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CardHeader,
+ Grid,
+ IconButton,
+ InputAdornment,
+ TextField,
+ Box,
+ CardMedia,
+ Chip,
+ LinearProgress
+} from '@mui/material';
+import ChatBubble from '../../components/ChatBubble';
+
+/**
+ * @typedef {Object} User
+ * @property {number} id
+ * @property {string} first_name
+ * @property {string} middle_name
+ * @property {string} last_name
+ */
+
+/**
+ * Chat component
+ * @param {Object} props - Component props
+ * @param {Object|null} props.user - Current user
+ * @param {Object} props.socket - Socket.io client instance
+ * @returns {JSX.Element} Rendered component
+ */
+const Chat = ( { user, socket, maxSizeMB = 10 } ) => {
+ const [conversations, setConversations] = useState( [] );
+ const [selectedConversation, setSelectedConversation] = useState( null );
+ const [messages, setMessages] = useState( [] );
+ const [content, setContent] = useState( '' );
+ const [attachedFiles, setAttachedFiles] = useState( [] );
+ const [users, setUsers] = useState( [] );
+ const [groups, setGroups] = useState( [] );
+ const [error, setError] = useState( null );
+ const [isSidebarOpen, setIsSidebarOpen] = useState( true );
+ const messagesEndRef = useRef( null );
+ const fileRef = useRef( null );
+
+ /**
+ * @type {[MediaFile[], React.Dispatch<React.SetStateAction<MediaFile[]>>]}
+ */
+ const [selectedMedia, setSelectedMedia] = useState([]);
+ /**
+ * @type {[string[], React.Dispatch<React.SetStateAction<string[]>>]}
+ */
+ const [previews, setPreviews] = useState([]);
+ /**
+ * @type {[number, React.Dispatch<React.SetStateAction<number>>]}
+ */
+ const [progress, setProgress] = useState(0);
+
+ useEffect( () => {
+ if (user) {
+ const fetchData = async () => {
+ try {
+ const [usersRes, groupsRes, messagesRes] = await Promise.all( [
+ axios.get( `${ API_BASE_URL }/user`, { withCredentials:true } ),
+ axios.get( `${ API_BASE_URL }/message_group`, { withCredentials:true } ),
+ axios.get( `${ API_BASE_URL }/message`, { withCredentials:true } )
+ ] );
+ setUsers( Array.isArray( usersRes.data ) ? usersRes.data.filter( u => u.id !== user.id ) : [] );
+ setGroups( Array.isArray( groupsRes.data ) ? groupsRes.data : [] );
+
+ // Group messages by recipient_id or group_id
+ const messages = Array.isArray( messagesRes.data ) ? messagesRes.data : [];
+ const convoMap = {};
+ messages.forEach( msg => {
+ if (msg.group_id) {
+ // Group chat
+ const key = `group_${ msg.group_id }`;
+ if (!convoMap[key]) {
+ convoMap[key] = {
+ id:key,
+ type:'group',
+ targetId:msg.group_id,
+ name:groupsRes.data.find( g => g.id === msg.group_id )?.name || 'Group',
+ lastMessage:msg.content,
+ lastMessageTime:msg.created_at
+ };
+ }
+ } else {
+ // Private chat: Use sorted user IDs to ensure single conversation
+ const otherUserId = msg.sender_id === user.id ? msg.recipient_id : msg.sender_id;
+ const key = `user_${ [user.id, otherUserId].sort( ( a, b ) => a - b ).join( '_' ) }`;
+ if (!convoMap[key]) {
+ convoMap[key] = {
+ id:key,
+ type:'user',
+ targetId:otherUserId,
+ name:usersRes.data.find( u => u.id === otherUserId )?.first_name || 'User',
+ lastMessage:msg.content,
+ lastMessageTime:msg.created_at
+ };
+ }
+ }
+ } );
+ setConversations( Object.values( convoMap ).sort( ( a, b ) => new Date( b.lastMessageTime ) - new Date( a.lastMessageTime ) ) );
+ setMessages( messages );
+ } catch (err) {
+ console.error( 'Chat data fetch error:', err );
+ setError( 'Failed to load chat data' );
+ }
+ };
+ fetchData().catch();
+
+ socket.on( 'newMessage', msg => {
+ setMessages( prev => (Array.isArray( prev ) ? [...prev, msg] : [msg]) );
+ setConversations( prev => {
+ let key, convo;
+ if (msg.group_id) {
+ key = `group_${ msg.group_id }`;
+ convo = {
+ id:key,
+ type:'group',
+ targetId:msg.group_id,
+ name:groups.find( g => g.id === msg.group_id )?.name || 'Group',
+ lastMessage:msg.content,
+ lastMessageTime:msg.created_at
+ };
+ } else {
+ const otherUserId = msg.sender_id === user.id ? msg.recipient_id : msg.sender_id;
+ key = `user_${ [user.id, otherUserId].sort( ( a, b ) => a - b ).join( '_' ) }`;
+ convo = {
+ id:key,
+ type:'user',
+ targetId:otherUserId,
+ name:users.find( u => u.id === otherUserId )?.first_name || 'User',
+ lastMessage:msg.content,
+ lastMessageTime:msg.created_at
+ };
+ }
+ return [convo, ...prev.filter( c => c.id !== key )].sort( ( a, b ) => new Date( b.lastMessageTime ) - new Date( a.lastMessageTime ) );
+ } );
+ } );
+
+ socket.on( 'newReaction', ( { messageId, reaction_data } ) => {
+ setMessages( prev => {
+ if (!Array.isArray( prev )) return prev;
+ return prev.map( m => m.id === parseInt( messageId ) ? {
+ ...m,
+ reactions:[...(Array.isArray( m.reactions ) ? Array.from( new Set( m.reactions ) ) : []), reaction_data]
+ } : m );
+ } );
+ } );
+
+ return () => {
+ socket.off( 'newMessage' );
+ socket.off( 'newReaction' );
+ };
+ }
+ }, [user] );
+
+ useEffect( () => {
+ messagesEndRef.current?.scrollIntoView( { behavior:'smooth' } );
+ }, [messages] );
+
+ /**
+ * Select a conversation to view messages
+ * @param {Object} convo - Conversation object
+ */
+ const selectConversation = ( convo ) => {
+ setSelectedConversation( convo );
+ setMessages(
+ Array.isArray( messages )
+ ? messages.filter( m =>
+ (convo.type === 'group' && m.group_id === convo.targetId) ||
+ (convo.type === 'user' && (m.recipient_id === convo.targetId || m.sender_id === convo.targetId))
+ )
+ : []
+ );
+ setIsSidebarOpen( false );
+ };
+
+ /**
+ * Send a message with optional file attachments
+ */
+ const sendMessage = async () => {
+ if (content.trim() || attachedFiles.length) {
+ let fileIds = [];
+ try {
+ if (attachedFiles.length) {
+ const formData = new FormData();
+ attachedFiles.forEach( f => formData.append( 'files', f ) );
+ formData.append( 'visibility', selectedConversation?.type === 'group' ? 'family' : selectedConversation?.type === 'user' ? 'specific' : 'family' );
+ if (selectedConversation?.type === 'user') formData.append( 'specific_users', JSON.stringify( [selectedConversation.targetId] ) );
+ const res = await axios.post( `${ API_BASE_URL }/file/create`, formData, {
+ headers:{ 'Content-Type':'multipart/form-data' },
+ withCredentials:true
+ } );
+ fileIds = Array.isArray( res.data ) ? res.data.map( f => f.id ) : [res.data.id];
+ }
+ socket.emit( 'chatMessage', {
+ content,
+ recipientId:selectedConversation?.type === 'user' ? selectedConversation.targetId : null,
+ groupId:selectedConversation?.type === 'group' ? selectedConversation.targetId : null,
+ fileIds
+ } );
+ setContent( '' );
+ setAttachedFiles( [] );
+ } catch (err) {
+ console.error( 'Send message error:', err );
+ setError( 'Failed to send message' );
+ }
+ }
+ };
+
+ if (!user) {
+ return <div className="text-center pt-20">Please log in to access chat</div>;
+ }
+
+ if (error) {
+ return <div className="text-center pt-20 text-red-500">{ error }</div>;
+ }
+
+ /**
+ *
+ * @returns {React.ReactNode[]}
+ */
+ const getConvos = () => (
+ conversations.map( convo => (
+ <div key={ convo.id }
+ onClick={ () => selectConversation( convo ) }
+ className={ `p-2 mb-2 rounded cursor-pointer ${ selectedConversation?.id === convo.id ? 'bg-blue-200' : 'hover:bg-gray-200' }` }>
+ <p className="font-bold">{ convo.name } ({ convo.type === 'group' ? 'Group' : 'Private' })</p>
+ <p className="text-sm text-gray-600 truncate">{ convo.lastMessage }</p>
+ <p className="text-xs text-gray-500">{ new Date( convo.lastMessageTime ).toLocaleString() }</p>
+ </div>
+ ) )
+ );
+
+ /**
+ *
+ * @returns {React.ReactNode[]}
+ */
+ const getMessages = () => (
+ messages.map( ( msg, i ) => (
+ <Grid key={ i }
+ size={ { xs:12 } }>
+ <div className={ `mb-4 flex ${ msg.sender_id === user.id ? 'justify-end' : 'justify-start' }` }>
+ <ChatBubble user={ user }
+ msg={ msg }
+ socket={ socket }
+ setError={ setError }/>
+ </div>
+ </Grid>
+ ) )
+ );
+
+ /**
+ * Handles file selection and validation
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ const handleFileChange = (event) => {
+ const files = Array.from(event.target.files);
+ const validMedia = files
+ .map((file) => ({
+ file,
+ type: file.type.startsWith('image/') ? 'image' : file.type.startsWith('video/') ? 'video' : null,
+ }))
+ .filter((media) => {
+ if (!media.type) {
+ alert('Please select image or video files only.');
+ return false;
+ }
+ if (media.file.size > maxSizeMB * 1024 * 1024) {
+ alert(`File size exceeds ${maxSizeMB}MB.`);
+ return false;
+ }
+ return true;
+ });
+
+ setSelectedMedia(validMedia);
+
+ // Generate previews
+ const newPreviews = validMedia.map((media) => URL.createObjectURL(media.file));
+ setPreviews(newPreviews);
+
+ // Trigger upload if provided
+ // if (onUpload && validMedia.length > 0) {
+ // uploadFiles(validMedia.map((media) => media.file));
+ // }
+ };
+
+ /**
+ * Removes a file from the selection
+ * @param {number} index
+ */
+ const removeFile = (index) => {
+ setSelectedMedia((prev) => prev.filter((_, i) => i !== index));
+ setPreviews((prev) => {
+ const newPreviews = [...prev];
+ URL.revokeObjectURL(newPreviews[index]); // Free memory
+ newPreviews.splice(index, 1);
+ return newPreviews;
+ });
+ };
+
+ return (
+ <Grid container
+ className="page-container"
+ spacing={ 0 }>
+ { isSidebarOpen && <Grid size={{ xs: 10, md: 6, lg: 3 }}>
+ { getConvos() }
+ </Grid> }
+ <Grid size="grow">
+ <Card className="chat-card">
+ <CardHeader className="chat-header"
+ title={
+ <div className="flex justify-between items-center p-4 border-b">
+ <h2 className="text-2xl">
+ { selectedConversation ? `${ selectedConversation.name } (${ selectedConversation.type === 'group' ? 'Group' : 'Private' })` : 'Select a conversation' }
+ </h2>
+ <button onClick={ () => setIsSidebarOpen( prevState => !prevState ) }
+ className="text-blue-600">☰
+ </button>
+ </div> }/>
+ <CardContent className="chat-content">
+ <div className="flex-1 overflow-y-auto p-4 bg-gray-50">
+ { selectedConversation ? (
+ messages.length === 0 ? (
+ <p className="text-center text-gray-500">No messages in this conversation</p>
+ ) : (
+ <Grid container
+ columnSpacing={ 0 }
+ rowSpacing={ 2 }
+ maxWidth="xxl">
+ { getMessages() }
+ </Grid>
+ )
+ ) : (
+ <p className="text-center text-gray-500">Select a conversation to start chatting</p>
+ ) }
+ <div ref={ messagesEndRef }/>
+ </div>
+
+ {/* Progress Bar */}
+ {progress > 0 && (
+ <LinearProgress variant="determinate" value={progress} sx={{ mt: 1 }} />
+ )}
+ { selectedConversation && previews.length > 0 && (
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2, width: '100vw' }}>
+ {previews.map((preview, index) => (
+ <Card key={index} sx={{ width: 150, position: 'relative' }}>
+ {selectedMedia[index].type === 'image' ? (
+ <CardMedia component="img" height="150" image={preview} alt="preview" />
+ ) : (
+ <CardMedia
+ component="video"
+ height="150"
+ src={preview}
+ controls
+ sx={{ objectFit: 'cover' }}
+ />
+ )}
+ <Chip
+ label="Remove"
+ size="small"
+ onClick={() => removeFile(index)}
+ sx={{ position: 'absolute', top: 4, right: 4 }}
+ />
+ </Card>
+ ))}
+ </Box>
+ ) }
+ </CardContent>
+ <CardActions className="chat-actions">
+ <input type="file"
+ accept="image/*,video/*"
+ multiple
+ hidden
+ ref={fileRef}
+ onChange={ e => handleFileChange(e) }
+ className="mb-2"/>
+ { selectedConversation ? (
+ <TextField value={ content }
+ className='chat-input'
+ onChange={ e => setContent( e.target.value ) }
+ placeholder="Type a message"
+ fullWidth
+ slotProps={ {
+ input:{
+ startAdornment:
+ <InputAdornment
+ position="start">
+ <Button
+ variant='contained'
+ onClick={ () => {
+ console.log(fileRef);
+ fileRef?.current?.click();
+ }}
+ size="small"
+ color="primary"><AddIcon/></Button>
+ </InputAdornment>,
+ endAdornment:
+ <InputAdornment
+ variant="filled"
+ position="end">
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={sendMessage}
+ size="small">Send</Button>
+ </InputAdornment>
+ }
+ } }/>
+
+ /*<div className="p-4 border-t">
+ <input type="file"
+ accept="image/!*,video/!*"
+ multiple
+ onChange={ e => setAttachedFiles( Array.from( e.target.files ) ) }
+ className="mb-2"/>
+ <div className="flex">
+ <input value={ content }
+ onChange={ e => setContent( e.target.value ) }
+ placeholder="Type a message"
+ className="border p-2 flex-grow rounded-l"/>
+ <button onClick={ sendMessage }
+ className="bg-blue-500 text-white p-2 rounded-r">Send
+ </button>
+ </div>
+ </div>*/
+ ) : null }
+ </CardActions>
+ </Card>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default Chat;
\ No newline at end of file