242 lines
10 KiB
TypeScript
242 lines
10 KiB
TypeScript
|
|
'use client';
|
|
|
|
import { useState, useEffect, useRef } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import { mockRentalRequests, mockToys } from '@/lib/mockData'; // Assuming mockToys has owner avatars
|
|
import type { RentalRequest, MessageEntry, Toy } from '@/types';
|
|
import { useI18n, useCurrentLocale } from '@/locales/client';
|
|
import { ArrowLeft, Send, Loader2, AlertTriangle, ToyBrick } from 'lucide-react';
|
|
import { useToast } from '@/hooks/use-toast';
|
|
import { format } from 'date-fns';
|
|
|
|
// Assume current user is 'user1' for mock purposes
|
|
const currentUserId = 'user1';
|
|
const currentUserProfiles: Record<string, { name: string; avatarInitial: string; avatarUrl?: string }> = {
|
|
'user1': { name: 'Alice Wonderland', avatarInitial: 'AW', avatarUrl: 'https://placehold.co/40x40.png?text=AW' }, // Logged in user
|
|
'user2': { name: 'Bob The Builder', avatarInitial: 'BT', avatarUrl: 'https://placehold.co/40x40.png?text=BT' },
|
|
'user3': { name: 'Carol Danvers', avatarInitial: 'CD', avatarUrl: 'https://placehold.co/40x40.png?text=CD' },
|
|
'user4': { name: 'Charlie Brown', avatarInitial: 'CB', avatarUrl: 'https://placehold.co/40x40.png?text=CB' },
|
|
'user5': { name: 'Diana Prince', avatarInitial: 'DP', avatarUrl: 'https://placehold.co/40x40.png?text=DP' },
|
|
'user6': { name: 'Edward Nigma', avatarInitial: 'EN', avatarUrl: 'https://placehold.co/40x40.png?text=EN' },
|
|
};
|
|
|
|
|
|
// Helper function to get the other participant in a conversation
|
|
const getOtherParticipant = (request: RentalRequest, currentUserId: string) => {
|
|
if (request.toy.ownerId === currentUserId) {
|
|
return { id: request.requesterId, name: request.requesterName };
|
|
}
|
|
return { id: request.toy.ownerId, name: request.toy.ownerName };
|
|
};
|
|
|
|
export default function MessageDetailPage({ params }: { params: { id: string } }) {
|
|
const router = useRouter();
|
|
const locale = useCurrentLocale();
|
|
const t = useI18n();
|
|
const { toast } = useToast();
|
|
|
|
const [request, setRequest] = useState<RentalRequest | null | undefined>(undefined); // undefined for loading state
|
|
const [newMessage, setNewMessage] = useState('');
|
|
const [isSending, setIsSending] = useState(false);
|
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const foundRequest = mockRentalRequests.find(r => r.id === params.id);
|
|
setRequest(foundRequest || null);
|
|
}, [params.id]);
|
|
|
|
useEffect(() => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [request?.messages]);
|
|
|
|
|
|
const handleSendMessage = async () => {
|
|
if (!newMessage.trim()) {
|
|
toast({ title: t('message_detail.empty_message_toast'), variant: 'destructive' });
|
|
return;
|
|
}
|
|
if (!request) return;
|
|
|
|
setIsSending(true);
|
|
|
|
// Mock sending message
|
|
await new Promise(resolve => setTimeout(resolve, 700));
|
|
|
|
const currentSenderProfile = currentUserProfiles[currentUserId];
|
|
const newMsgEntry: MessageEntry = {
|
|
id: `msg-${Date.now()}`,
|
|
senderId: currentUserId,
|
|
senderName: currentSenderProfile?.name || 'Current User',
|
|
text: newMessage.trim(),
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
setRequest(prevRequest => {
|
|
if (!prevRequest) return null;
|
|
// This is a mock update. In a real app, this would re-fetch or update via state management.
|
|
const updatedMessages = [...(prevRequest.messages || []), newMsgEntry];
|
|
// Find the request in global mock data and update it (for mock persistence across navigation)
|
|
const globalRequestIndex = mockRentalRequests.findIndex(r => r.id === prevRequest.id);
|
|
if (globalRequestIndex > -1) {
|
|
mockRentalRequests[globalRequestIndex].messages = updatedMessages;
|
|
}
|
|
return { ...prevRequest, messages: updatedMessages };
|
|
});
|
|
|
|
setNewMessage('');
|
|
setIsSending(false);
|
|
toast({ title: t('message_detail.message_sent_toast') });
|
|
};
|
|
|
|
if (request === undefined) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center h-[calc(100vh-10rem)]">
|
|
<Loader2 className="h-12 w-12 animate-spin text-primary mb-4" />
|
|
<p className="text-muted-foreground">{t('dashboard.layout.loading')}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!request) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center h-[calc(100vh-10rem)] text-center p-4">
|
|
<AlertTriangle className="h-16 w-16 text-destructive mb-6" />
|
|
<h1 className="text-2xl font-bold mb-3">{t('message_detail.no_conversation_found')}</h1>
|
|
<p className="text-muted-foreground mb-8 max-w-md">{t('message_detail.no_conversation_description')}</p>
|
|
<Link href={`/${locale}/dashboard/messages`} passHref>
|
|
<Button variant="outline">
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
{t('message_detail.back_to_messages')}
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const otherParticipant = getOtherParticipant(request, currentUserId);
|
|
const otherParticipantProfile = currentUserProfiles[otherParticipant.id] || { name: otherParticipant.name, avatarInitial: otherParticipant.name.charAt(0).toUpperCase() };
|
|
const currentSenderProfile = currentUserProfiles[currentUserId] || { name: "You", avatarInitial: "U" };
|
|
|
|
|
|
return (
|
|
<div className="flex flex-col h-[calc(100vh-8rem)] max-w-3xl mx-auto"> {/* Adjust height as needed */}
|
|
<Card className="flex-1 flex flex-col shadow-lg">
|
|
<CardHeader className="border-b">
|
|
<div className="flex items-center justify-between">
|
|
<Link href={`/${locale}/dashboard/messages`} className="flex items-center text-sm text-primary hover:underline">
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
{t('message_detail.back_to_messages')}
|
|
</Link>
|
|
</div>
|
|
<div className="flex items-center gap-3 mt-2">
|
|
<Link href={`/${locale}/toys/${request.toy.id}`} className="flex items-center gap-3 group">
|
|
<Image
|
|
src={request.toy.images[0] || 'https://placehold.co/60x60.png'}
|
|
alt={request.toy.name}
|
|
width={60}
|
|
height={60}
|
|
className="rounded-md object-cover border group-hover:opacity-80 transition-opacity"
|
|
data-ai-hint={request.toy.category.toLowerCase()}
|
|
/>
|
|
<div>
|
|
<CardTitle className="text-xl font-headline group-hover:text-primary transition-colors">
|
|
{t('message_detail.title', { toyName: request.toy.name })}
|
|
</CardTitle>
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('dashboard.messages.conversation_with', { name: otherParticipant.name })}
|
|
</p>
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<ScrollArea className="flex-1 p-6 space-y-4 bg-muted/20">
|
|
{request.messages && request.messages.length > 0 ? (
|
|
request.messages.map(msg => {
|
|
const isCurrentUser = msg.senderId === currentUserId;
|
|
const senderProfile = isCurrentUser ? currentSenderProfile : (currentUserProfiles[msg.senderId] || {name: msg.senderName, avatarInitial: msg.senderName.charAt(0).toUpperCase()});
|
|
|
|
return (
|
|
<div key={msg.id} className={`flex items-end gap-2 ${isCurrentUser ? 'justify-end' : ''}`}>
|
|
{!isCurrentUser && (
|
|
<Avatar className="h-8 w-8">
|
|
<AvatarImage src={senderProfile.avatarUrl} alt={senderProfile.name} data-ai-hint="user avatar"/>
|
|
<AvatarFallback>{senderProfile.avatarInitial}</AvatarFallback>
|
|
</Avatar>
|
|
)}
|
|
<div
|
|
className={`max-w-[70%] p-3 rounded-lg shadow-sm ${
|
|
isCurrentUser
|
|
? 'bg-primary text-primary-foreground'
|
|
: 'bg-card border'
|
|
}`}
|
|
>
|
|
<p className="text-sm whitespace-pre-line">{msg.text}</p>
|
|
<p className={`text-xs mt-1 ${isCurrentUser ? 'text-primary-foreground/70 text-right' : 'text-muted-foreground text-left'}`}>
|
|
{format(new Date(msg.timestamp), 'MMM d, HH:mm')}
|
|
</p>
|
|
</div>
|
|
{isCurrentUser && (
|
|
<Avatar className="h-8 w-8">
|
|
<AvatarImage src={senderProfile.avatarUrl} alt={senderProfile.name} data-ai-hint="user avatar"/>
|
|
<AvatarFallback>{senderProfile.avatarInitial}</AvatarFallback>
|
|
</Avatar>
|
|
)}
|
|
</div>
|
|
);
|
|
})
|
|
) : (
|
|
<div className="text-center text-muted-foreground py-8">
|
|
<ToyBrick className="h-12 w-12 mx-auto mb-3"/>
|
|
<p>{t('dashboard.messages.no_messages_description')}</p>
|
|
</div>
|
|
)}
|
|
<div ref={messagesEndRef} />
|
|
</ScrollArea>
|
|
|
|
<CardFooter className="p-4 border-t">
|
|
<form
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
handleSendMessage();
|
|
}}
|
|
className="flex w-full items-start gap-2"
|
|
>
|
|
<Textarea
|
|
placeholder={t('message_detail.type_your_message')}
|
|
value={newMessage}
|
|
onChange={(e) => setNewMessage(e.target.value)}
|
|
rows={1}
|
|
className="min-h-[40px] max-h-[120px] flex-1 resize-none transition-height duration-150 ease-out focus-visible:ring-primary"
|
|
disabled={isSending}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSendMessage();
|
|
}
|
|
}}
|
|
/>
|
|
<Button type="submit" disabled={isSending || !newMessage.trim()} className="h-10">
|
|
{isSending ? (
|
|
<Loader2 className="h-5 w-5 animate-spin" />
|
|
) : (
|
|
<Send className="h-5 w-5" />
|
|
)}
|
|
<span className="sr-only">{isSending ? t('message_detail.sending_button') : t('message_detail.send_button')}</span>
|
|
</Button>
|
|
</form>
|
|
</CardFooter>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|