2026-01-18 23:58:25 +05:30
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import Image from 'next/image';
|
2026-02-08 22:44:06 +05:30
|
|
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
2026-02-21 22:11:42 +05:30
|
|
|
import data from '@emoji-mart/data';
|
|
|
|
|
import Picker from '@emoji-mart/react';
|
|
|
|
|
import { GiphyFetch } from '@giphy/js-fetch-api';
|
|
|
|
|
import { Grid } from '@giphy/react-components';
|
|
|
|
|
|
|
|
|
|
const gf = new GiphyFetch(process.env.NEXT_PUBLIC_GIPHY_API_KEY || '');
|
|
|
|
|
|
2026-03-08 12:06:07 +05:30
|
|
|
|
2026-02-21 22:11:42 +05:30
|
|
|
interface AttachedFile {
|
|
|
|
|
file: File;
|
|
|
|
|
preview?: string; // Object URL for image preview
|
|
|
|
|
messageType: 'IMAGE' | 'FILE';
|
|
|
|
|
}
|
2026-01-18 23:58:25 +05:30
|
|
|
|
|
|
|
|
interface MessageInputProps {
|
|
|
|
|
onSend?: (message: string) => void;
|
2026-02-21 22:11:42 +05:30
|
|
|
onSendGif?: (gifUrl: string) => void;
|
|
|
|
|
onSendFile?: (file: File, messageType: 'IMAGE' | 'FILE') => void;
|
2026-02-08 22:44:06 +05:30
|
|
|
onTypingStart?: () => void;
|
|
|
|
|
onTypingStop?: () => void;
|
2026-02-21 22:11:42 +05:30
|
|
|
isUploading?: boolean;
|
2026-01-18 23:58:25 +05:30
|
|
|
}
|
|
|
|
|
|
2026-02-21 22:11:42 +05:30
|
|
|
export function MessageInput({ onSend, onSendGif, onSendFile, onTypingStart, onTypingStop, isUploading }: MessageInputProps) {
|
2026-01-18 23:58:25 +05:30
|
|
|
const [message, setMessage] = useState('');
|
2026-02-21 22:11:42 +05:30
|
|
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
|
|
|
|
const [showGifPicker, setShowGifPicker] = useState(false);
|
|
|
|
|
const [gifSearchQuery, setGifSearchQuery] = useState('');
|
|
|
|
|
const [attachedFile, setAttachedFile] = useState<AttachedFile | null>(null);
|
2026-02-08 22:44:06 +05:30
|
|
|
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
|
const isTypingRef = useRef(false);
|
2026-02-21 22:11:42 +05:30
|
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
|
const emojiPickerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const gifPickerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const emojiButtonRef = useRef<HTMLButtonElement>(null);
|
|
|
|
|
const gifButtonRef = useRef<HTMLButtonElement>(null);
|
|
|
|
|
const imageInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
2026-02-08 22:44:06 +05:30
|
|
|
|
|
|
|
|
// Handle typing indicator
|
|
|
|
|
const handleTyping = useCallback(() => {
|
|
|
|
|
if (!isTypingRef.current && onTypingStart) {
|
|
|
|
|
isTypingRef.current = true;
|
|
|
|
|
onTypingStart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typingTimeoutRef.current) {
|
|
|
|
|
clearTimeout(typingTimeoutRef.current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typingTimeoutRef.current = setTimeout(() => {
|
|
|
|
|
if (isTypingRef.current && onTypingStop) {
|
|
|
|
|
isTypingRef.current = false;
|
|
|
|
|
onTypingStop();
|
|
|
|
|
}
|
|
|
|
|
}, 2000);
|
|
|
|
|
}, [onTypingStart, onTypingStop]);
|
|
|
|
|
|
|
|
|
|
// Cleanup on unmount
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
if (typingTimeoutRef.current) {
|
|
|
|
|
clearTimeout(typingTimeoutRef.current);
|
|
|
|
|
}
|
|
|
|
|
if (isTypingRef.current && onTypingStop) {
|
|
|
|
|
onTypingStop();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [onTypingStop]);
|
2026-01-18 23:58:25 +05:30
|
|
|
|
2026-02-21 22:11:42 +05:30
|
|
|
// Close pickers on click outside
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
|
|
|
if (
|
|
|
|
|
showEmojiPicker &&
|
|
|
|
|
emojiPickerRef.current &&
|
|
|
|
|
!emojiPickerRef.current.contains(e.target as Node) &&
|
|
|
|
|
emojiButtonRef.current &&
|
|
|
|
|
!emojiButtonRef.current.contains(e.target as Node)
|
|
|
|
|
) {
|
|
|
|
|
setShowEmojiPicker(false);
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
showGifPicker &&
|
|
|
|
|
gifPickerRef.current &&
|
|
|
|
|
!gifPickerRef.current.contains(e.target as Node) &&
|
|
|
|
|
gifButtonRef.current &&
|
|
|
|
|
!gifButtonRef.current.contains(e.target as Node)
|
|
|
|
|
) {
|
|
|
|
|
setShowGifPicker(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
|
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
|
|
|
}, [showEmojiPicker, showGifPicker]);
|
|
|
|
|
|
|
|
|
|
// Clean up object URL on unmount or when file changes
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
if (attachedFile?.preview) {
|
|
|
|
|
URL.revokeObjectURL(attachedFile.preview);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [attachedFile]);
|
|
|
|
|
|
|
|
|
|
const handleImageSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const file = e.target.files?.[0];
|
|
|
|
|
if (!file) return;
|
|
|
|
|
const preview = URL.createObjectURL(file);
|
|
|
|
|
setAttachedFile({ file, preview, messageType: 'IMAGE' });
|
|
|
|
|
// Reset input so same file can be selected again
|
|
|
|
|
e.target.value = '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const file = e.target.files?.[0];
|
|
|
|
|
if (!file) return;
|
|
|
|
|
setAttachedFile({ file, messageType: 'FILE' });
|
|
|
|
|
e.target.value = '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeAttachment = () => {
|
|
|
|
|
if (attachedFile?.preview) {
|
|
|
|
|
URL.revokeObjectURL(attachedFile.preview);
|
|
|
|
|
}
|
|
|
|
|
setAttachedFile(null);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatFileSize = (bytes: number): string => {
|
|
|
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
|
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
|
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-18 23:58:25 +05:30
|
|
|
const handleSend = () => {
|
2026-02-21 22:11:42 +05:30
|
|
|
// Send attached file
|
|
|
|
|
if (attachedFile && onSendFile) {
|
|
|
|
|
if (typingTimeoutRef.current) {
|
|
|
|
|
clearTimeout(typingTimeoutRef.current);
|
|
|
|
|
}
|
|
|
|
|
if (isTypingRef.current && onTypingStop) {
|
|
|
|
|
isTypingRef.current = false;
|
|
|
|
|
onTypingStop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onSendFile(attachedFile.file, attachedFile.messageType);
|
|
|
|
|
removeAttachment();
|
|
|
|
|
setMessage('');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send text message
|
2026-01-18 23:58:25 +05:30
|
|
|
if (message.trim() && onSend) {
|
2026-02-08 22:44:06 +05:30
|
|
|
if (typingTimeoutRef.current) {
|
|
|
|
|
clearTimeout(typingTimeoutRef.current);
|
|
|
|
|
}
|
|
|
|
|
if (isTypingRef.current && onTypingStop) {
|
|
|
|
|
isTypingRef.current = false;
|
|
|
|
|
onTypingStop();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 23:58:25 +05:30
|
|
|
onSend(message.trim());
|
|
|
|
|
setMessage('');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
|
|
e.preventDefault();
|
2026-02-21 22:11:42 +05:30
|
|
|
if (!isUploading) handleSend();
|
2026-01-18 23:58:25 +05:30
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 22:44:06 +05:30
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
|
|
|
setMessage(e.target.value);
|
|
|
|
|
handleTyping();
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-21 22:11:42 +05:30
|
|
|
const handleEmojiSelect = (emoji: { native: string }) => {
|
|
|
|
|
const textarea = textareaRef.current;
|
|
|
|
|
if (textarea) {
|
|
|
|
|
const start = textarea.selectionStart;
|
|
|
|
|
const end = textarea.selectionEnd;
|
|
|
|
|
const newMessage = message.slice(0, start) + emoji.native + message.slice(end);
|
|
|
|
|
setMessage(newMessage);
|
|
|
|
|
// Restore cursor position after emoji
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
textarea.selectionStart = textarea.selectionEnd = start + emoji.native.length;
|
|
|
|
|
textarea.focus();
|
|
|
|
|
}, 0);
|
|
|
|
|
} else {
|
|
|
|
|
setMessage((prev) => prev + emoji.native);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleGifSelect = (gif: { images: { original: { url: string } } }, e: React.SyntheticEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const gifUrl = gif.images.original.url;
|
|
|
|
|
if (onSendGif) {
|
|
|
|
|
onSendGif(gifUrl);
|
|
|
|
|
}
|
|
|
|
|
setShowGifPicker(false);
|
|
|
|
|
setGifSearchQuery('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleEmojiPicker = () => {
|
|
|
|
|
setShowEmojiPicker((prev) => !prev);
|
|
|
|
|
setShowGifPicker(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleGifPicker = () => {
|
|
|
|
|
setShowGifPicker((prev) => !prev);
|
|
|
|
|
setShowEmojiPicker(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchGifs = useCallback(
|
|
|
|
|
(offset: number) => {
|
|
|
|
|
if (gifSearchQuery.trim()) {
|
|
|
|
|
return gf.search(gifSearchQuery, { offset, limit: 10 });
|
|
|
|
|
}
|
|
|
|
|
return gf.trending({ offset, limit: 10 });
|
|
|
|
|
},
|
|
|
|
|
[gifSearchQuery]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const canSend = message.trim() || attachedFile;
|
|
|
|
|
|
2026-01-18 23:58:25 +05:30
|
|
|
return (
|
|
|
|
|
<div className="border border-[#00293d]/10 rounded-[20px] bg-white p-4">
|
2026-02-21 22:11:42 +05:30
|
|
|
{/* Hidden file inputs */}
|
|
|
|
|
<input
|
|
|
|
|
ref={imageInputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
accept="image/jpeg,image/png,image/webp,image/gif"
|
|
|
|
|
onChange={handleImageSelect}
|
|
|
|
|
className="hidden"
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
|
|
|
onChange={handleFileSelect}
|
|
|
|
|
className="hidden"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Attachment preview area */}
|
|
|
|
|
{attachedFile && (
|
|
|
|
|
<div className="border border-[#00293d]/10 rounded-[15px] p-3 mb-3 bg-[#00293d]/[0.02]">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
{attachedFile.messageType === 'IMAGE' && attachedFile.preview ? (
|
|
|
|
|
<img
|
|
|
|
|
src={attachedFile.preview}
|
|
|
|
|
alt="Preview"
|
|
|
|
|
className="w-[80px] h-[80px] object-cover rounded-[10px]"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="w-[50px] h-[50px] bg-[#e58625]/10 rounded-[10px] flex items-center justify-center flex-shrink-0">
|
|
|
|
|
<Image
|
|
|
|
|
src="/assets/icons/attachment-icon.svg"
|
|
|
|
|
alt="File"
|
|
|
|
|
width={22}
|
|
|
|
|
height={12}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] truncate">
|
|
|
|
|
{attachedFile.file.name}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="font-serif font-normal text-[12px] leading-[16px] text-[#00293D]/50">
|
|
|
|
|
{formatFileSize(attachedFile.file.size)}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={removeAttachment}
|
|
|
|
|
className="p-1 hover:bg-gray-100 rounded-full transition-colors cursor-pointer flex-shrink-0"
|
|
|
|
|
title="Remove attachment"
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src="/assets/icons/close-icon.svg"
|
|
|
|
|
alt="Remove"
|
|
|
|
|
width={20}
|
|
|
|
|
height={20}
|
|
|
|
|
className="opacity-50"
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-01-18 23:58:25 +05:30
|
|
|
{/* Text input area */}
|
|
|
|
|
<div className="border border-[#00293d]/10 rounded-[20px] p-4 mb-3">
|
|
|
|
|
<textarea
|
2026-02-21 22:11:42 +05:30
|
|
|
ref={textareaRef}
|
2026-01-18 23:58:25 +05:30
|
|
|
value={message}
|
2026-02-08 22:44:06 +05:30
|
|
|
onChange={handleChange}
|
2026-01-18 23:58:25 +05:30
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
|
placeholder="Write a Message........"
|
|
|
|
|
className="w-full min-h-[120px] resize-none font-serif font-normal text-[14px] leading-[19px] text-[#00293D] placeholder-[#00293D]/50 focus:outline-none"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Action bar */}
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
{/* Attachment buttons */}
|
|
|
|
|
<div className="flex items-center gap-3">
|
2026-02-21 22:11:42 +05:30
|
|
|
<button
|
|
|
|
|
onClick={() => imageInputRef.current?.click()}
|
|
|
|
|
className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
|
|
|
|
|
title="Attach image"
|
|
|
|
|
>
|
2026-01-18 23:58:25 +05:30
|
|
|
<Image
|
|
|
|
|
src="/assets/icons/gallery-icon.svg"
|
|
|
|
|
alt="Add image"
|
|
|
|
|
width={24}
|
|
|
|
|
height={24}
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
2026-02-21 22:11:42 +05:30
|
|
|
<button
|
|
|
|
|
onClick={() => fileInputRef.current?.click()}
|
|
|
|
|
className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
|
|
|
|
|
title="Attach file"
|
|
|
|
|
>
|
2026-01-18 23:58:25 +05:30
|
|
|
<Image
|
|
|
|
|
src="/assets/icons/chain-icon.svg"
|
2026-02-21 22:11:42 +05:30
|
|
|
alt="Attach file"
|
2026-01-18 23:58:25 +05:30
|
|
|
width={24}
|
|
|
|
|
height={24}
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
2026-02-21 22:11:42 +05:30
|
|
|
|
|
|
|
|
{/* GIF button with picker */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<button
|
|
|
|
|
ref={gifButtonRef}
|
|
|
|
|
onClick={toggleGifPicker}
|
|
|
|
|
className={`p-1.5 rounded-lg transition-colors cursor-pointer ${showGifPicker ? 'bg-[#e58625]/20' : 'hover:bg-gray-100'}`}
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src="/assets/icons/gif-icon.svg"
|
|
|
|
|
alt="Add GIF"
|
|
|
|
|
width={24}
|
|
|
|
|
height={24}
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
|
|
|
|
{showGifPicker && (
|
|
|
|
|
<div
|
|
|
|
|
ref={gifPickerRef}
|
|
|
|
|
className="absolute bottom-full left-0 mb-2 z-50 bg-white border border-[#00293d]/10 rounded-[15px] shadow-lg overflow-hidden"
|
|
|
|
|
style={{ width: '360px', height: '420px' }}
|
|
|
|
|
>
|
|
|
|
|
<div className="p-3 border-b border-[#00293d]/10">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={gifSearchQuery}
|
|
|
|
|
onChange={(e) => setGifSearchQuery(e.target.value)}
|
|
|
|
|
placeholder="Search GIFs..."
|
|
|
|
|
className="w-full px-3 py-2 border border-[#00293d]/10 rounded-[10px] font-serif text-[14px] text-[#00293D] placeholder-[#00293D]/50 focus:outline-none focus:border-[#e58625]"
|
|
|
|
|
autoFocus
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="overflow-y-auto" style={{ height: 'calc(100% - 56px)' }}>
|
|
|
|
|
<Grid
|
|
|
|
|
key={gifSearchQuery}
|
|
|
|
|
fetchGifs={fetchGifs}
|
|
|
|
|
width={356}
|
|
|
|
|
columns={2}
|
|
|
|
|
gutter={6}
|
|
|
|
|
noLink
|
|
|
|
|
onGifClick={handleGifSelect}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="absolute bottom-2 right-3 bg-white/80 rounded px-1.5 py-0.5">
|
|
|
|
|
<span className="font-serif text-[10px] text-[#00293D]/40">Powered by GIPHY</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Emoji button with picker */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<button
|
|
|
|
|
ref={emojiButtonRef}
|
|
|
|
|
onClick={toggleEmojiPicker}
|
|
|
|
|
className={`p-1.5 rounded-lg transition-colors cursor-pointer ${showEmojiPicker ? 'bg-[#e58625]/20' : 'hover:bg-gray-100'}`}
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src="/assets/icons/emoji-icon.svg"
|
|
|
|
|
alt="Add emoji"
|
|
|
|
|
width={24}
|
|
|
|
|
height={24}
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
|
|
|
|
{showEmojiPicker && (
|
|
|
|
|
<div ref={emojiPickerRef} className="absolute bottom-full left-0 mb-2 z-50">
|
|
|
|
|
<Picker
|
|
|
|
|
data={data}
|
|
|
|
|
onEmojiSelect={handleEmojiSelect}
|
|
|
|
|
theme="light"
|
|
|
|
|
previewPosition="none"
|
|
|
|
|
skinTonePosition="search"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-01-18 23:58:25 +05:30
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Send button and more options */}
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleSend}
|
2026-02-21 22:11:42 +05:30
|
|
|
disabled={!canSend || isUploading}
|
2026-02-08 22:44:06 +05:30
|
|
|
className={`px-4 py-1.5 font-serif font-normal text-[14px] leading-[19px] rounded-[15px] transition-colors cursor-pointer ${
|
2026-02-21 22:11:42 +05:30
|
|
|
canSend && !isUploading
|
2026-02-08 22:44:06 +05:30
|
|
|
? 'bg-[#e58625] text-[#00293d] hover:bg-[#d47720]'
|
|
|
|
|
: 'bg-[#e58625]/50 text-[#00293d]/50 cursor-not-allowed'
|
|
|
|
|
}`}
|
2026-01-18 23:58:25 +05:30
|
|
|
>
|
2026-02-21 22:11:42 +05:30
|
|
|
{isUploading ? 'Uploading...' : 'Send'}
|
2026-01-18 23:58:25 +05:30
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|