99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
import { Clock } from "lucide-react";
|
|
import { useShallow } from "zustand/react/shallow";
|
|
import { useFetchUser } from "~/hooks/use-fetch-user";
|
|
import type { Message } from "~/lib/api/types";
|
|
import { useUsersStore } from "~/stores/users-store";
|
|
import ChatMessageAttachment from "./chat-message-attachment";
|
|
import UserAvatar from "./user-avatar";
|
|
import UserContextMenu from "./user-context-menu";
|
|
|
|
interface ChatMessageProps {
|
|
message: Message;
|
|
}
|
|
|
|
export default function ChatMessage({ message }: ChatMessageProps) {
|
|
const { user } = useUsersStore(
|
|
useShallow((state) => ({
|
|
user: state.users[message.authorId],
|
|
})),
|
|
);
|
|
|
|
useFetchUser(message.authorId);
|
|
|
|
const formatMessageDate = (date: Date) => {
|
|
const now = new Date();
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
const messageDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
// Get localized time string
|
|
const timeString = date.toLocaleTimeString(undefined, {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
hour12: false,
|
|
});
|
|
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
|
|
if (messageDate.getTime() === today.getTime()) {
|
|
// Use Intl.RelativeTimeFormat for localized "Today"
|
|
const rtf = new Intl.RelativeTimeFormat(undefined, {
|
|
numeric: "auto",
|
|
});
|
|
return `${capitalize(rtf.format(0, "day"))}, ${timeString}`;
|
|
} else if (messageDate.getTime() === yesterday.getTime()) {
|
|
// Use Intl.RelativeTimeFormat for localized "Yesterday"
|
|
const rtf = new Intl.RelativeTimeFormat(undefined, {
|
|
numeric: "auto",
|
|
});
|
|
return `${capitalize(rtf.format(-1, "day"))}, ${timeString}`;
|
|
} else {
|
|
return date.toLocaleDateString(undefined, {
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
hour12: false,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="grid gap-x-2"
|
|
style={{
|
|
gridTemplateColumns: "auto 1fr",
|
|
gridTemplateRows: "auto 1fr",
|
|
}}
|
|
>
|
|
<div className="row-start-1 col-start-1 row-span-2 col-span-1">
|
|
<UserContextMenu userId={message.authorId}>
|
|
<UserAvatar user={user} />
|
|
</UserContextMenu>
|
|
</div>
|
|
<div className="row-start-1 col-start-2 row-span-1 col-span-1 flex items-center gap-2">
|
|
<span className="font-medium text-sm">
|
|
<UserContextMenu userId={message.authorId}>
|
|
<div>{user?.displayName || user?.username}</div>
|
|
</UserContextMenu>
|
|
</span>
|
|
<div className="flex items-center gap-0.5 text-xs text-muted-foreground whitespace-nowrap">
|
|
<Clock className="size-3" />
|
|
<span>{formatMessageDate(message.createdAt)}</span>
|
|
</div>
|
|
</div>
|
|
<div className="row-start-2 col-start-2 row-span-1 col-span-1">
|
|
<div className="wrap-break-word contain-inline-size">{message.content}</div>
|
|
<div className="grid gap-2 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 justify-start">
|
|
{message.attachments.map((file, i) => (
|
|
<div key={file.id}>
|
|
<ChatMessageAttachment file={file} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|