11"use client" ;
22
3- import { CommentPresentation } from "@/backend/models/domain-models" ;
3+ import {
4+ CommentPresentation ,
5+ IServerFile ,
6+ } from "@/backend/models/domain-models" ;
47import * as commentActions from "@/backend/services/comment.action" ;
58import { useTranslation } from "@/i18n/use-translation" ;
69import { cn , formattedTime , getAvatarPlaceholder } from "@/lib/utils" ;
@@ -34,6 +37,7 @@ import {
3437import { Button } from "./ui/button" ;
3538import { Skeleton } from "./ui/skeleton" ;
3639import { Textarea } from "./ui/textarea" ;
40+ import getFileUrl from "@/utils/getFileUrl" ;
3741
3842const Context = React . createContext <
3943 { mutatingId ?: string ; setMutatingId : ( id ?: string ) => void } | undefined
@@ -55,7 +59,7 @@ export const useCommentSection = () => {
5559 const context = React . useContext ( Context ) ;
5660 if ( ! context ) {
5761 throw new Error (
58- "useCommentSectionContext must be used within a CommentSectionProvider"
62+ "useCommentSectionContext must be used within a CommentSectionProvider" ,
5963 ) ;
6064 }
6165 return context ;
@@ -127,13 +131,14 @@ export const CommentSection = (props: {
127131 name : session ?. user ?. name || "Temp User" ,
128132 username : session ?. user ?. username || "tempuser" ,
129133 email : session ?. user ?. email || "tempuser@example.com" ,
134+ profile_photo : session ?. user ?. profile_photo ?? null ,
130135 } ,
131136 replies : [ ] ,
132137 created_at : new Date ( ) ,
133138 } satisfies CommentPresentation ,
134139 ...prev ,
135140 ] ;
136- }
141+ } ,
137142 ) ;
138143 return { oldComments } ;
139144 } ,
@@ -163,7 +168,7 @@ export const CommentSection = (props: {
163168 < section
164169 className = { cn (
165170 "max-w-2xl mx-auto w-full px-3 py-4 sm:px-4" ,
166- props . className
171+ props . className ,
167172 ) }
168173 aria-label = { _t ( "Comments" ) }
169174 >
@@ -207,7 +212,9 @@ export const CommentSection = (props: {
207212 strokeWidth = { 1.25 }
208213 aria-hidden
209214 />
210- < p className = "text-xs font-medium text-foreground" > { _t ( "No comments yet" ) } </ p >
215+ < p className = "text-xs font-medium text-foreground" >
216+ { _t ( "No comments yet" ) }
217+ </ p >
211218 < p className = "mt-0.5 max-w-sm text-xs text-muted-foreground" >
212219 { _t ( "Be the first to share your thoughts." ) }
213220 </ p >
@@ -265,15 +272,15 @@ const CommentEditor = (props: {
265272 < div
266273 className = { cn (
267274 "rounded-md bg-muted/40 px-0 transition-colors focus-within:bg-muted/55 dark:bg-muted/30 dark:focus-within:bg-muted/45" ,
268- isCompact ? "py-1.5" : "py-2"
275+ isCompact ? "py-1.5" : "py-2" ,
269276 ) }
270277 >
271278 < Textarea
272279 placeholder = { props . placeholder }
273280 ref = { inputRef }
274281 className = { cn (
275282 "w-full resize-y border-0 bg-transparent px-3 py-2 text-sm leading-snug shadow-none placeholder:text-muted-foreground/80 focus-visible:ring-0" ,
276- isCompact ? "min-h-[56px]" : "min-h-[72px]"
283+ isCompact ? "min-h-[56px]" : "min-h-[72px]" ,
277284 ) }
278285 required
279286 disabled = { props . isLoading }
@@ -319,13 +326,13 @@ const CommentItem = (props: {
319326 const [ editDraft , setEditDraft ] = useState ( props . comment . body ?? "" ) ;
320327 const { mutatingId, setMutatingId } = useCommentSection ( ) ;
321328 const [ replies , setReplies ] = useImmer < CommentPresentation [ ] > (
322- props . comment . replies ?? [ ]
329+ props . comment . replies ?? [ ] ,
323330 ) ;
324331
325332 const isOwner = Boolean (
326333 session ?. user ?. id &&
327- props . comment . author ?. id &&
328- session . user . id === props . comment . author . id
334+ props . comment . author ?. id &&
335+ session . user . id === props . comment . author . id ,
329336 ) ;
330337
331338 const level = useMemo ( ( ) => props . comment . level ?? 0 , [ props . comment ] ) ;
@@ -336,12 +343,15 @@ const CommentItem = (props: {
336343 commentActions . updateMyComment ( { id : props . comment . id , body } ) ,
337344 onSuccess : ( res ) => {
338345 if ( res && "success" in res && res . success ) {
339- void queryClient . invalidateQueries ( { queryKey : [ ...props . listQueryKey ] } ) ;
346+ void queryClient . invalidateQueries ( {
347+ queryKey : [ ...props . listQueryKey ] ,
348+ } ) ;
340349 setIsEditing ( false ) ;
341350 toast . success ( _t ( "Comment updated" ) ) ;
342351 return ;
343352 }
344- const err = res && "error" in res ? res . error : _t ( "Something went wrong" ) ;
353+ const err =
354+ res && "error" in res ? res . error : _t ( "Something went wrong" ) ;
345355 toast . error ( err ) ;
346356 } ,
347357 } ) ;
@@ -350,11 +360,14 @@ const CommentItem = (props: {
350360 mutationFn : ( ) => commentActions . deleteMyComment ( { id : props . comment . id } ) ,
351361 onSuccess : ( res ) => {
352362 if ( res && "success" in res && res . success ) {
353- void queryClient . invalidateQueries ( { queryKey : [ ...props . listQueryKey ] } ) ;
363+ void queryClient . invalidateQueries ( {
364+ queryKey : [ ...props . listQueryKey ] ,
365+ } ) ;
354366 toast . success ( _t ( "Comment deleted" ) ) ;
355367 return ;
356368 }
357- const err = res && "error" in res ? res . error : _t ( "Something went wrong" ) ;
369+ const err =
370+ res && "error" in res ? res . error : _t ( "Something went wrong" ) ;
358371 toast . error ( err ) ;
359372 } ,
360373 } ) ;
@@ -383,6 +396,7 @@ const CommentItem = (props: {
383396 name : session ?. user ?. name || "Temp User" ,
384397 username : session ?. user ?. username || "tempuser" ,
385398 email : session ?. user ?. email || "tempuser@example.com" ,
399+ profile_photo : session ?. user ?. profile_photo ?? null ,
386400 } ,
387401 replies : [ ] ,
388402 created_at : new Date ( ) ,
@@ -401,7 +415,7 @@ const CommentItem = (props: {
401415 data-comment-id = { props . comment . id }
402416 className = { cn (
403417 "group/comment" ,
404- level > 0 && "ml-0.5 border-l border-muted-foreground/20 pl-3"
418+ level > 0 && "ml-0.5 border-l border-muted-foreground/20 pl-3" ,
405419 ) }
406420 >
407421 < article className = "py-1 pr-0" >
@@ -412,11 +426,17 @@ const CommentItem = (props: {
412426 className = "shrink-0 pt-0.5 rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
413427 aria-label = { `@${ username } ` }
414428 >
415- < CommentAvatar label = { authorLabel } />
429+ < CommentAvatar
430+ label = { authorLabel }
431+ author = { props . comment . author }
432+ />
416433 </ Link >
417434 ) : (
418435 < div className = "shrink-0 pt-0.5" aria-hidden >
419- < CommentAvatar label = { authorLabel } />
436+ < CommentAvatar
437+ label = { authorLabel }
438+ author = { props . comment . author }
439+ />
420440 </ div >
421441 ) }
422442
@@ -427,12 +447,14 @@ const CommentItem = (props: {
427447 onClick = { ( ) => setIsCollapsed ( ! isCollapsed ) }
428448 className = "inline-flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
429449 aria-expanded = { ! isCollapsed }
430- aria-label = { isCollapsed ? _t ( "Expand comment" ) : _t ( "Collapse comment" ) }
450+ aria-label = {
451+ isCollapsed ? _t ( "Expand comment" ) : _t ( "Collapse comment" )
452+ }
431453 >
432454 < ChevronDown
433455 className = { cn (
434456 "size-3.5 transition-transform duration-300 ease-out motion-reduce:transition-none" ,
435- isCollapsed && "-rotate-90"
457+ isCollapsed && "-rotate-90" ,
436458 ) }
437459 aria-hidden
438460 />
@@ -446,7 +468,9 @@ const CommentItem = (props: {
446468 @{ username }
447469 </ Link >
448470 ) : (
449- < span className = "text-xs font-medium text-muted-foreground" > —</ span >
471+ < span className = "text-xs font-medium text-muted-foreground" >
472+ —
473+ </ span >
450474 ) }
451475 < span className = "text-muted-foreground" aria-hidden >
452476 ·
@@ -469,14 +493,16 @@ const CommentItem = (props: {
469493 < div
470494 className = { cn (
471495 "grid transition-[grid-template-rows] duration-300 ease-out motion-reduce:transition-none motion-reduce:duration-0" ,
472- isCollapsed ? "grid-rows-[0fr]" : "grid-rows-[1fr]"
496+ isCollapsed ? "grid-rows-[0fr]" : "grid-rows-[1fr]" ,
473497 ) }
474498 >
475499 < div className = "min-h-0 overflow-hidden" >
476500 < div
477501 className = { cn (
478502 "transition-opacity duration-300 ease-out motion-reduce:transition-none motion-reduce:duration-0" ,
479- isCollapsed ? "pointer-events-none opacity-0" : "opacity-100"
503+ isCollapsed
504+ ? "pointer-events-none opacity-0"
505+ : "opacity-100" ,
480506 ) }
481507 aria-hidden = { isCollapsed }
482508 >
@@ -502,7 +528,10 @@ const CommentItem = (props: {
502528 disabled = { updateMutation . isPending }
503529 >
504530 { updateMutation . isPending ? (
505- < Loader2 className = "size-3 animate-spin" aria-hidden />
531+ < Loader2
532+ className = "size-3 animate-spin"
533+ aria-hidden
534+ />
506535 ) : null }
507536 { _t ( "Save" ) }
508537 </ Button >
@@ -571,13 +600,15 @@ const CommentItem = (props: {
571600 < AlertDialogDescription >
572601 { ( replies ?. length ?? 0 ) > 0
573602 ? _t (
574- "This will remove this comment and all nested replies under it."
603+ "This will remove this comment and all nested replies under it." ,
575604 )
576605 : _t ( "This cannot be undone." ) }
577606 </ AlertDialogDescription >
578607 </ AlertDialogHeader >
579608 < AlertDialogFooter >
580- < AlertDialogCancel > { _t ( "Cancel" ) } </ AlertDialogCancel >
609+ < AlertDialogCancel >
610+ { _t ( "Cancel" ) }
611+ </ AlertDialogCancel >
581612 < AlertDialogAction
582613 onClick = { ( ) => deleteMutation . mutate ( ) }
583614 className = "bg-destructive text-destructive-foreground hover:bg-destructive/90"
@@ -597,7 +628,10 @@ const CommentItem = (props: {
597628 className = "h-7 px-1.5 text-xs text-muted-foreground hover:text-foreground"
598629 onClick = { ( ) => setShowReplyBox ( ! showReplyBox ) }
599630 >
600- < MessageSquare className = "size-3 mr-0.5" aria-hidden />
631+ < MessageSquare
632+ className = "size-3 mr-0.5"
633+ aria-hidden
634+ />
601635 { _t ( "Reply" ) }
602636 </ Button >
603637 ) }
@@ -660,14 +694,22 @@ function commentAuthorLabel(c: CommentPresentation) {
660694 return "User" ;
661695}
662696
663- function CommentAvatar ( { label } : { label : string } ) {
697+ function CommentAvatar ( {
698+ label,
699+ author,
700+ } : {
701+ label : string ;
702+ author ?: CommentPresentation [ "author" ] ;
703+ } ) {
704+ const fromStructured = author ?. profile_photo
705+ ? getFileUrl ( author . profile_photo )
706+ : "" ;
707+
708+ const src = fromStructured || getAvatarPlaceholder ( label ) ;
709+
664710 return (
665711 < Avatar className = "size-7" >
666- < AvatarImage
667- src = { getAvatarPlaceholder ( label ) }
668- alt = ""
669- className = "object-cover"
670- />
712+ < AvatarImage src = { src } alt = "" className = "object-cover" />
671713 < AvatarFallback className = "text-[10px] font-medium" >
672714 { label . slice ( 0 , 2 ) . toUpperCase ( ) }
673715 </ AvatarFallback >
0 commit comments