96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { cn } from "@/lib/utils";
|
||
|
|
import { Avatar } from "@/components/ui/avatar";
|
||
|
|
import { useSession } from "next-auth/react";
|
||
|
|
|
||
|
|
export interface ChatMessageViewProps {
|
||
|
|
message: {
|
||
|
|
id: string;
|
||
|
|
body: string;
|
||
|
|
authorUserId: string;
|
||
|
|
timestamp: string | Date;
|
||
|
|
sequence: number;
|
||
|
|
parentMessageId?: string;
|
||
|
|
editState?: "editing" | "saved";
|
||
|
|
};
|
||
|
|
author: {
|
||
|
|
id: string;
|
||
|
|
name: string;
|
||
|
|
avatarUrl?: string;
|
||
|
|
};
|
||
|
|
isEditing?: boolean;
|
||
|
|
onEdit?: (messageId: string) => void;
|
||
|
|
onDelete?: (messageId: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ChatMessageView({
|
||
|
|
message,
|
||
|
|
author,
|
||
|
|
isEditing = false,
|
||
|
|
onEdit,
|
||
|
|
onDelete,
|
||
|
|
}: ChatMessageViewProps) {
|
||
|
|
const { data: session } = useSession();
|
||
|
|
const currentUserId = session?.user?.id;
|
||
|
|
const isAuthor = message.authorUserId === currentUserId;
|
||
|
|
const isEditable = isAuthor && !isEditing;
|
||
|
|
|
||
|
|
const avatarSrc = author.avatarUrl || `/default-avatar.png`;
|
||
|
|
const displayName = author.name || "Unknown User";
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
"flex items-start space-x-3 py-2",
|
||
|
|
"border-b border-sidebar-border",
|
||
|
|
isEditing && "bg-accent/50",
|
||
|
|
message.editState === "editing" && "bg-yellow-50"
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<Avatar className="w-8 h-8 rounded-full flex-shrink-0 mt-0.5">
|
||
|
|
<img
|
||
|
|
src={avatarSrc}
|
||
|
|
alt={displayName}
|
||
|
|
className="w-full h-full rounded-full"
|
||
|
|
/>
|
||
|
|
</Avatar>
|
||
|
|
<div className="flex-1 min-w-0">
|
||
|
|
<div className="flex items-center space-x-2">
|
||
|
|
<span className="font-medium text-sm">{displayName}</span>
|
||
|
|
<span className="text-xs text-muted-foreground">
|
||
|
|
{new Date(message.timestamp).toLocaleTimeString([], {
|
||
|
|
hour: "2-digit",
|
||
|
|
minute: "2-digit",
|
||
|
|
})}
|
||
|
|
</span>
|
||
|
|
{message.editState === "editing" && (
|
||
|
|
<span className="text-xs text-yellow-600 font-medium">Editing...</span>
|
||
|
|
)}
|
||
|
|
{message.editState === "saved" && (
|
||
|
|
<span className="text-xs text-green-600 font-medium">Edited</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<div className="mt-1 whitespace-pre-wrap break-words text-sm">
|
||
|
|
{message.body}
|
||
|
|
</div>
|
||
|
|
{isEditable && (
|
||
|
|
<div className="flex items-center space-x-2 mt-2">
|
||
|
|
<button
|
||
|
|
onClick={() => onEdit?.(message.id)}
|
||
|
|
className="text-xs text-indigo-600 hover:text-indigo-800 font-medium"
|
||
|
|
>
|
||
|
|
Edit
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={() => onDelete?.(message.id)}
|
||
|
|
className="text-xs text-red-600 hover:text-red-800 font-medium"
|
||
|
|
>
|
||
|
|
Delete
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|