1+ import { useState } from 'react' ;
2+ import { Comment } from '../../types/Comment' ;
3+ import { useAuth } from '../../contexts/AuthContext' ;
4+ import { updateComment , deleteComment } from '../../services/commentService' ;
5+ import { formatDistanceToNow } from 'date-fns' ;
6+ import { PinIcon , EditIcon , TrashIcon } from 'lucide-react' ;
7+
8+ interface CommentItemProps {
9+ comment : Comment ;
10+ noteId : string ;
11+ onPin : ( commentId : string , isPinned : boolean ) => Promise < void > ;
12+ isPinnable : boolean ;
13+ }
14+
15+ export default function CommentItem ( {
16+ comment,
17+ noteId,
18+ onPin,
19+ isPinnable
20+ } : CommentItemProps ) {
21+ const { currentUser } = useAuth ( ) ;
22+ const [ isEditing , setIsEditing ] = useState ( false ) ;
23+ const [ editedContent , setEditedContent ] = useState ( comment . content ) ;
24+ const [ isUpdating , setIsUpdating ] = useState ( false ) ;
25+
26+ const canEdit = currentUser && currentUser . uid === comment . createdBy . uid ;
27+
28+ // Format the timestamp safely
29+ const formatTimeAgo = ( date : Date | null ) => {
30+ if ( ! date || isNaN ( date . getTime ( ) ) ) {
31+ return 'Just now' ;
32+ }
33+
34+ try {
35+ return formatDistanceToNow ( date , { addSuffix : true } ) ;
36+ } catch ( error ) {
37+ console . error ( 'Date formatting error:' , error ) ;
38+ return 'Recently' ;
39+ }
40+ } ;
41+
42+ const handleUpdate = async ( ) => {
43+ if ( ! editedContent . trim ( ) || isUpdating ) return ;
44+
45+ setIsUpdating ( true ) ;
46+ try {
47+ await updateComment ( noteId , comment . id , editedContent ) ;
48+ setIsEditing ( false ) ;
49+ } catch ( error ) {
50+ console . error ( 'Error updating comment:' , error ) ;
51+ } finally {
52+ setIsUpdating ( false ) ;
53+ }
54+ } ;
55+
56+ const handleDelete = async ( ) => {
57+ if ( window . confirm ( 'Are you sure you want to delete this comment?' ) ) {
58+ try {
59+ await deleteComment ( noteId , comment . id ) ;
60+ } catch ( error ) {
61+ console . error ( 'Error deleting comment:' , error ) ;
62+ }
63+ }
64+ } ;
65+
66+ const handlePin = async ( ) => {
67+ try {
68+ await onPin ( comment . id , ! comment . isPinned ) ;
69+ } catch ( error ) {
70+ console . error ( 'Error pinning comment:' , error ) ;
71+ }
72+ } ;
73+
74+ return (
75+ < div className = { `mb-3 p-3 rounded-md ${ comment . isPinned ? 'bg-primary/5 border border-primary/20' : 'bg-card hover:bg-muted' } ` } >
76+ < div className = "flex justify-between items-start" >
77+ < div className = "flex items-center" >
78+ < div className = "font-medium text-sm" >
79+ { comment . createdBy . displayName || 'Anonymous' }
80+ </ div >
81+ < span className = "mx-1 text-xs text-muted-foreground" > •</ span >
82+ < div className = "text-xs text-muted-foreground" >
83+ { formatTimeAgo ( comment . createdAt ) }
84+ </ div >
85+ </ div >
86+
87+ < div className = "flex space-x-1" >
88+ { isPinnable && (
89+ < button
90+ onClick = { handlePin }
91+ className = { `p-1 rounded-full ${ comment . isPinned ? 'text-primary bg-primary/10' : 'text-muted-foreground hover:text-foreground hover:bg-muted' } ` }
92+ title = { comment . isPinned ? 'Unpin comment' : 'Pin comment' }
93+ >
94+ < PinIcon className = "h-3.5 w-3.5" />
95+ </ button >
96+ ) }
97+
98+ { canEdit && (
99+ < >
100+ < button
101+ onClick = { ( ) => setIsEditing ( true ) }
102+ className = "p-1 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted"
103+ title = "Edit comment"
104+ disabled = { isEditing }
105+ >
106+ < EditIcon className = "h-3.5 w-3.5" />
107+ </ button >
108+
109+ < button
110+ onClick = { handleDelete }
111+ className = "p-1 rounded-full text-muted-foreground hover:text-destructive hover:bg-muted"
112+ title = "Delete comment"
113+ >
114+ < TrashIcon className = "h-3.5 w-3.5" />
115+ </ button >
116+ </ >
117+ ) }
118+ </ div >
119+ </ div >
120+
121+ < div className = "mt-1" >
122+ { isEditing ? (
123+ < div >
124+ < textarea
125+ value = { editedContent }
126+ onChange = { ( e ) => setEditedContent ( e . target . value ) }
127+ className = "w-full p-2 text-sm border rounded-md bg-card"
128+ rows = { 2 }
129+ />
130+ < div className = "flex justify-end space-x-2 mt-2" >
131+ < button
132+ onClick = { ( ) => setIsEditing ( false ) }
133+ className = "px-2 py-1 text-xs"
134+ >
135+ Cancel
136+ </ button >
137+ < button
138+ onClick = { handleUpdate }
139+ className = "px-2 py-1 bg-primary text-primary-foreground rounded text-xs"
140+ disabled = { ! editedContent . trim ( ) || isUpdating }
141+ >
142+ { isUpdating ? 'Saving...' : 'Save' }
143+ </ button >
144+ </ div >
145+ </ div >
146+ ) : (
147+ < p className = "text-sm whitespace-pre-wrap" > { comment . content } </ p >
148+ ) }
149+ </ div >
150+ </ div >
151+ ) ;
152+ }
0 commit comments