dashboard message request page is blank, please complete view message hi

This commit is contained in:
Indigo Tang 2025-06-10 06:07:33 +00:00
parent a2b1eae0fe
commit a1be73d2e6
3 changed files with 268 additions and 0 deletions

View File

@ -0,0 +1,242 @@
'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>
);
}

View File

@ -260,4 +260,17 @@ export default {
'dashboard.messages.view_conversation_button': 'View Conversation', 'dashboard.messages.view_conversation_button': 'View Conversation',
'dashboard.messages.message_date_time': '{date} at {time}', 'dashboard.messages.message_date_time': '{date} at {time}',
'dashboard.messages.go_find_toys': 'Find Toys to Rent', 'dashboard.messages.go_find_toys': 'Find Toys to Rent',
'message_detail.title': 'Conversation about {toyName}',
'message_detail.back_to_messages': 'Back to Messages',
'message_detail.no_conversation_found': 'Conversation Not Found',
'message_detail.no_conversation_description': 'The conversation you are looking for does not exist.',
'message_detail.type_your_message': 'Type your message here...',
'message_detail.send_button': 'Send',
'message_detail.sending_button': 'Sending...',
'message_detail.message_sent_toast': 'Message sent (mock)',
'message_detail.empty_message_toast': 'Cannot send an empty message.',
'message_detail.message_from_you': 'You',
'message_detail.message_from_name': 'From {name}',
} as const; } as const;

View File

@ -260,4 +260,17 @@ export default {
'dashboard.messages.view_conversation_button': '查看對話', 'dashboard.messages.view_conversation_button': '查看對話',
'dashboard.messages.message_date_time': '{date} {time}', 'dashboard.messages.message_date_time': '{date} {time}',
'dashboard.messages.go_find_toys': '尋找可租借的玩具', 'dashboard.messages.go_find_toys': '尋找可租借的玩具',
'message_detail.title': '關於 {toyName} 的對話',
'message_detail.back_to_messages': '返回訊息列表',
'message_detail.no_conversation_found': '找不到對話',
'message_detail.no_conversation_description': '您尋找的對話不存在。',
'message_detail.type_your_message': '在此輸入您的訊息...',
'message_detail.send_button': '傳送',
'message_detail.sending_button': '傳送中...',
'message_detail.message_sent_toast': '訊息已傳送(模擬)',
'message_detail.empty_message_toast': '無法傳送空訊息。',
'message_detail.message_from_you': '您',
'message_detail.message_from_name': '來自 {name}',
} as const; } as const;