@@ -14,12 +14,17 @@ import {
1414 markChatThreadRead ,
1515 sendChatMessage ,
1616} from "@/components/ChatWindow/chatWindowController" ;
17- import { formatWeekday } from "@/utils/dateUtils" ;
17+ import { DEMO_CHAT_REFERENCE_TIME } from "@/data/demo/threads" ;
18+ import {
19+ CHAT_RENDER_TIME_ZONE ,
20+ formatWeekday ,
21+ getChatDateKey ,
22+ } from "@/utils/dateUtils" ;
1823
1924import { styled } from "next-yak" ;
2025import { useUnreadMessages } from "@/contexts/UnreadMessagesContext" ;
2126import { useInlineMutation } from "@/hooks/useInlineMutation" ;
22- import { useTranslations } from "next-intl" ;
27+ import { useLocale , useTranslations } from "next-intl" ;
2328import type {
2429 ChatListing ,
2530 ChatMessageRecord ,
@@ -30,15 +35,45 @@ import type {
3035import type { DemoListing } from "@/types/listing" ;
3136import type { FormSubmitEvent } from "@/types/events" ;
3237
33- type ChatWindowProps = {
38+ type SharedChatWindowProps = {
3439 isDrawer ?: boolean ;
3540 user : User | null ;
3641 listing : ChatListing | DemoListing ;
3742 existingThread ?: ChatThreadRecord | ChatThreadView | null ;
38- isDemo ?: boolean ;
3943} ;
4044
45+ type DemoChatWindowProps = SharedChatWindowProps & {
46+ isDemo : true ;
47+ referenceNow ?: never ;
48+ } ;
49+
50+ type NonDemoChatWindowProps = SharedChatWindowProps & {
51+ isDemo ?: false ;
52+ referenceNow : string ;
53+ } ;
54+
55+ type ChatWindowProps = DemoChatWindowProps | NonDemoChatWindowProps ;
56+
4157const DIRECTIONS_FOR_DEMO = [ "sent" , "received" ] as const ;
58+ type ChatRenderOptions = {
59+ locale : string ;
60+ now ?: string ;
61+ timeZone : string ;
62+ useRelativeDayLabels : boolean ;
63+ } ;
64+
65+ const defaultChatRenderOptions : ChatRenderOptions = {
66+ locale : "en" ,
67+ now : undefined ,
68+ timeZone : CHAT_RENDER_TIME_ZONE ,
69+ useRelativeDayLabels : false ,
70+ } ;
71+
72+ function getClientTimeZone ( ) {
73+ return (
74+ Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone ?? CHAT_RENDER_TIME_ZONE
75+ ) ;
76+ }
4277
4378const StyledChatWindow = styled . div `
4479 height: 100%;
@@ -115,8 +150,10 @@ const ChatWindow = memo(function ChatWindow({
115150 listing,
116151 existingThread = null ,
117152 isDemo = false ,
153+ referenceNow,
118154} : ChatWindowProps ) {
119155 const t = useTranslations ( ) ;
156+ const locale = useLocale ( ) ;
120157 const supabase = useMemo ( ( ) => ( isDemo ? null : createClient ( ) ) , [ isDemo ] ) ;
121158 const { setUnreadCount, markThreadAsRead } = useUnreadMessages ( ) ;
122159 const realListing = isDemo ? null : ( listing as ChatListing ) ;
@@ -130,6 +167,34 @@ const ChatWindow = memo(function ChatWindow({
130167 const [ messages , setMessages ] = useState < ChatMessageRecord [ ] > (
131168 getThreadMessages ( existingThread )
132169 ) ;
170+ const [ clientTimeZone , setClientTimeZone ] = useState < string | null > ( null ) ;
171+ const chatRenderOptions = useMemo < ChatRenderOptions > (
172+ ( ) =>
173+ isDemo
174+ ? {
175+ locale,
176+ now : DEMO_CHAT_REFERENCE_TIME ,
177+ timeZone : CHAT_RENDER_TIME_ZONE ,
178+ useRelativeDayLabels : clientTimeZone !== null ,
179+ }
180+ : {
181+ ...defaultChatRenderOptions ,
182+ locale,
183+ now : referenceNow ,
184+ timeZone : clientTimeZone ?? CHAT_RENDER_TIME_ZONE ,
185+ useRelativeDayLabels : clientTimeZone !== null ,
186+ } ,
187+ [ clientTimeZone , isDemo , locale , referenceNow ]
188+ ) ;
189+ const messageDateKeys = useMemo (
190+ ( ) =>
191+ messages . map ( ( chatMessage ) =>
192+ getChatDateKey ( chatMessage . created_at , {
193+ timeZone : chatRenderOptions . timeZone ,
194+ } )
195+ ) ,
196+ [ messages , chatRenderOptions . timeZone ]
197+ ) ;
133198
134199 function resolveChatErrorMessage ( errorMessage : string | null ) {
135200 if ( ! errorMessage ) {
@@ -150,6 +215,10 @@ const ChatWindow = memo(function ChatWindow({
150215 return errorMessage ;
151216 }
152217
218+ useEffect ( ( ) => {
219+ setClientTimeZone ( getClientTimeZone ( ) ) ;
220+ } , [ ] ) ;
221+
153222 useEffect ( ( ) => {
154223 setThreadId ( existingThread ?. id ?? null ) ;
155224 setMessages ( getThreadMessages ( existingThread ) ) ;
@@ -333,18 +402,26 @@ const ChatWindow = memo(function ChatWindow({
333402 ) }
334403
335404 { messages . map ( ( chatMessage , index ) => {
405+ const currentDateKey = messageDateKeys [ index ] ;
406+ const previousDateKey = messageDateKeys [ index - 1 ] ;
336407 const showDateHeader =
337- index === 0 ||
338- new Date ( chatMessage . created_at ) . toDateString ( ) !==
339- new Date ( messages [ index - 1 ] . created_at ) . toDateString ( ) ;
408+ index === 0 || currentDateKey !== previousDateKey ;
340409 const showInitiationHeader = index === 0 ;
341410
342411 return (
343412 < Day key = { isDemo ? index : chatMessage . id } >
344413 { showDateHeader || showInitiationHeader ? (
345414 < DayHeader >
346415 { showDateHeader && (
347- < h3 > { formatWeekday ( chatMessage . created_at ) } </ h3 >
416+ < h3 data-testid = "chat-day-label" >
417+ { formatWeekday ( chatMessage . created_at , {
418+ locale : chatRenderOptions . locale ,
419+ now : chatRenderOptions . now ,
420+ timeZone : chatRenderOptions . timeZone ,
421+ useRelativeDayLabels :
422+ chatRenderOptions . useRelativeDayLabels ,
423+ } ) }
424+ </ h3 >
348425 ) }
349426 { showInitiationHeader && (
350427 < p >
@@ -373,6 +450,8 @@ const ChatWindow = memo(function ChatWindow({
373450 : "received"
374451 }
375452 message = { chatMessage }
453+ locale = { chatRenderOptions . locale }
454+ timeZone = { chatRenderOptions . timeZone }
376455 />
377456 </ Day >
378457 ) ;
0 commit comments