@@ -21,7 +21,15 @@ import {
2121 DialogTitle ,
2222} from "@/components/ui/dialog" ;
2323import { Heart , Send , Clock , MoreVertical , Pencil , Trash2 , X , Check } from "lucide-react" ;
24- import { fetchComments , postComment , toggleCommentLike , updateComment , deleteComment , type Comment } from "../actions" ;
24+ import {
25+ fetchComments ,
26+ postComment ,
27+ toggleCommentLike ,
28+ updateComment ,
29+ deleteComment ,
30+ fetchCommentLikes ,
31+ type Comment
32+ } from "../actions" ;
2533import { Question , fetchQuestions } from "../actions" ;
2634
2735interface CommentSectionProps {
@@ -95,86 +103,87 @@ const CommentItem = memo(
95103
96104 const handleDelete = ( ) => {
97105 onDelete ( comment . id ) ;
98- // Use setTimeout to ensure modal opens before dropdown closes
99106 setTimeout ( ( ) => {
100107 setDropdownOpen ( false ) ;
101108 } , 0 ) ;
102109 } ;
103110
104111 return (
105- < div className = "flex gap-3" >
106- < div className = "h-10 w-10 mt-3 flex-shrink-0 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700" >
112+ < div className = "flex gap-2 sm:gap-3" >
113+ { /* Avatar - Responsive sizing */ }
114+ < div className = "h-8 w-8 sm:h-10 sm:w-10 mt-2 sm:mt-3 flex-shrink-0 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700" >
107115 { comment . author . image_url ? (
108116 < Image
109117 src = { comment . author . image_url }
110118 alt = { comment . author . name }
111- width = { 32 }
112- height = { 32 }
119+ width = { 40 }
120+ height = { 40 }
113121 className = "h-full w-full object-cover"
114122 />
115123 ) : (
116124 < DefaultAvatar size = { 32 } />
117125 ) }
118126 </ div >
119127
120- < div className = "flex-1 rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800" >
121- < div className = "mb-2 flex items-start justify-between" >
122- < div className = "flex items-center gap-2" >
123- < div >
124- < span className = "text-sm font-medium text-gray-900 dark:text-white" >
125- { comment . author . name }
126- </ span >
127- < div className = "flex items-center gap-1 text-xs text-gray-600 dark:text-white" >
128- < Clock className = "h-3 w-3" />
129- { comment . created_at === comment . updated_at ? (
130- < TimeAgo date = { comment . created_at } />
131- ) : (
132- < span className = "flex items-center gap-1" >
133- < span > edited</ span >
134- < TimeAgo date = { comment . updated_at } />
135- </ span >
136- ) }
137- </ div >
128+ { /* Comment content */ }
129+ < div className = "flex-1 min-w-0 rounded-lg border border-gray-200 bg-gray-50 p-2.5 sm:p-3 dark:border-gray-700 dark:bg-gray-800" >
130+ < div className = "mb-2 flex items-start justify-between gap-2" >
131+ < div className = "flex-1 min-w-0" >
132+ < span className = "text-xs sm:text-sm font-medium text-gray-900 dark:text-white block truncate" >
133+ { comment . author . name }
134+ </ span >
135+ < div className = "flex items-center gap-1 text-xs text-gray-600 dark:text-white" >
136+ < Clock className = "h-3 w-3 flex-shrink-0" />
137+ { comment . created_at === comment . updated_at ? (
138+ < TimeAgo date = { comment . created_at } />
139+ ) : (
140+ < span className = "flex items-center gap-1" >
141+ < span > edited</ span >
142+ < TimeAgo date = { comment . updated_at } />
143+ </ span >
144+ ) }
138145 </ div >
139146 </ div >
140- < div className = "flex items-center gap-1" >
147+
148+ { /* Action buttons - Responsive */ }
149+ < div className = "flex items-center gap-0.5 sm:gap-1 flex-shrink-0" >
141150 < Button
142151 variant = "ghost"
143152 size = "sm"
144153 onClick = { ( ) => onLike ( comment . id ) }
145- className = { `flex items-center gap-1 px-2 py-1 ${
154+ className = { `flex items-center gap-1 px-1.5 sm:px- 2 py-1 h-auto ${
146155 isLiked
147156 ? "text-red-600 hover:text-red-700"
148157 : "text-gray-600 hover:text-gray-700 dark:text-white dark:hover:text-gray-200"
149158 } `}
150159 >
151- < Heart className = { `h-5 w-5 ${ isLiked ? "fill-current" : "" } ` } />
152- < span className = "text-sm" > { comment . likes } </ span >
160+ < Heart className = { `h-4 w-4 sm:h-5 sm: w-5 ${ isLiked ? "fill-current" : "" } ` } />
161+ < span className = "text-xs sm:text- sm" > { comment . likes } </ span >
153162 </ Button >
154163
155164 { ! isEditing && (
156165 < DropdownMenu open = { dropdownOpen } onOpenChange = { setDropdownOpen } >
157166 < DropdownMenuTrigger asChild >
158167 < Button
159- disabled = { ! isOwner }
160168 variant = "ghost"
169+ disabled = { ! isOwner }
161170 size = "sm"
162- className = "px-2 py-1 text-gray-600 hover:text-gray-700 dark:text-white dark:hover:text-gray-200"
171+ className = "px-1.5 sm:px- 2 py-1 h-auto text-gray-600 hover:text-gray-700 dark:text-white dark:hover:text-gray-200"
163172 >
164- < MoreVertical className = "h-5 w-5" />
173+ < MoreVertical className = "h-4 w-4 sm:h-5 sm: w-5" />
165174 </ Button >
166175 </ DropdownMenuTrigger >
167- < DropdownMenuContent align = "end" >
168- < DropdownMenuItem onClick = { handleStartEdit } >
169- < Pencil className = "mr-2 h-4 w-4" />
176+ < DropdownMenuContent align = "end" className = "w-32 sm:w-auto" >
177+ < DropdownMenuItem onClick = { handleStartEdit } className = "text-xs sm:text-sm" >
178+ < Pencil className = "mr-2 h-3 w-3 sm:h-4 sm: w-4" />
170179 Edit
171180 </ DropdownMenuItem >
172181 < DropdownMenuItem
173182 onClick = { handleDelete }
174183 onSelect = { ( e ) => e . preventDefault ( ) }
175- className = "text-red-600 focus:text-red-600 dark:text-red-400 dark:focus:text-red-400"
184+ className = "text-xs sm:text-sm text- red-600 focus:text-red-600 dark:text-red-400 dark:focus:text-red-400"
176185 >
177- < Trash2 className = "mr-2 h-4 w-4" />
186+ < Trash2 className = "mr-2 h-3 w-3 sm:h-4 sm: w-4" />
178187 Delete
179188 </ DropdownMenuItem >
180189 </ DropdownMenuContent >
@@ -189,31 +198,32 @@ const CommentItem = memo(
189198 value = { editContent }
190199 onChange = { ( e ) => setEditContent ( e . target . value ) }
191200 rows = { 3 }
192- className = "resize-none border-gray-300 bg-white text-gray-900 placeholder-gray-500 focus:border-customBlue-500 focus:ring-customBlue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-500 dark:focus:border-customBlue-400 dark:focus:ring-customBlue-400"
201+ className = "resize-none border-gray-300 bg-white text-sm sm:text-base text- gray-900 placeholder-gray-500 focus:border-customBlue-500 focus:ring-customBlue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-500 dark:focus:border-customBlue-400 dark:focus:ring-customBlue-400"
193202 autoFocus
194203 />
195204 < div className = "flex justify-end gap-2" >
196205 < Button
197206 variant = "ghost"
198207 size = "sm"
199208 onClick = { handleCancelEdit }
200- className = "text-gray-600 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-200"
209+ className = "text-xs sm:text-sm h-8 sm:h-9 text- gray-600 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-200"
201210 >
202- < X className = "mr-1 h-4 w-4" />
211+ < X className = "mr-1 h-3 w-3 sm:h-4 sm: w-4" />
203212 Cancel
204213 </ Button >
205214 < Button
206215 size = "sm"
207216 onClick = { handleSaveEdit }
208217 disabled = { ! editContent . trim ( ) || editContent === comment . content }
218+ className = "text-xs sm:text-sm h-8 sm:h-9"
209219 >
210- < Check className = "mr-1 h-4 w-4" />
220+ < Check className = "mr-1 h-3 w-3 sm:h-4 sm: w-4" />
211221 Save
212222 </ Button >
213223 </ div >
214224 </ div >
215225 ) : (
216- < p className = "text-sm leading-relaxed text-gray-700 dark:text-white" >
226+ < p className = "text-xs sm:text-sm leading-relaxed text-gray-700 dark:text-white break-words " >
217227 { comment . content }
218228 </ p >
219229 ) }
@@ -240,8 +250,13 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
240250 const loadComments = async ( ) => {
241251 setIsLoading ( true ) ;
242252 try {
243- const fetchedComments = await fetchComments ( questionId ) ;
253+ const [ fetchedComments , likedCommentIds ] = await Promise . all ( [
254+ fetchComments ( questionId ) ,
255+ fetchCommentLikes ( loggedIn . id , questionId )
256+ ] ) ;
257+
244258 setComments ( fetchedComments ) ;
259+ setLikedComments ( new Set ( likedCommentIds ) ) ;
245260 } catch ( error ) {
246261 console . error ( "Error loading comments:" , error ) ;
247262 } finally {
@@ -289,6 +304,7 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
289304 const wasLiked = likedComments . has ( commentId ) ;
290305 const newLikedComments = new Set ( likedComments ) ;
291306
307+ // Optimistic update
292308 if ( wasLiked ) {
293309 newLikedComments . delete ( commentId ) ;
294310 setComments ( ( prev ) =>
@@ -301,12 +317,13 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
301317 ) ;
302318 }
303319
304- setLikedComments ( new Set ( newLikedComments ) ) ;
320+ setLikedComments ( newLikedComments ) ;
305321
306322 try {
307- const result = await toggleCommentLike ( commentId , ! wasLiked ) ;
323+ const result = await toggleCommentLike ( commentId , loggedIn . id ) ;
308324
309325 if ( ! result . success ) {
326+ // Revert on failure
310327 if ( wasLiked ) {
311328 newLikedComments . add ( commentId ) ;
312329 setComments ( ( prev ) =>
@@ -318,22 +335,34 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
318335 prev . map ( ( c ) => ( c . id === commentId ? { ...c , likes : c . likes - 1 } : c ) )
319336 ) ;
320337 }
321- setLikedComments ( new Set ( newLikedComments ) ) ;
338+ setLikedComments ( newLikedComments ) ;
339+ console . error ( "Failed to toggle like:" , result . error ) ;
322340 }
323341 } catch ( error ) {
342+ // Revert on error
343+ if ( wasLiked ) {
344+ newLikedComments . add ( commentId ) ;
345+ setComments ( ( prev ) =>
346+ prev . map ( ( c ) => ( c . id === commentId ? { ...c , likes : c . likes + 1 } : c ) )
347+ ) ;
348+ } else {
349+ newLikedComments . delete ( commentId ) ;
350+ setComments ( ( prev ) =>
351+ prev . map ( ( c ) => ( c . id === commentId ? { ...c , likes : c . likes - 1 } : c ) )
352+ ) ;
353+ }
354+ setLikedComments ( newLikedComments ) ;
324355 console . error ( "Error toggling like:" , error ) ;
325356 }
326357 } ;
327358
328359 const handleEditComment = async ( commentId : string , newContent : string ) => {
329- // Store original comment for potential revert
330360 const originalComment = comments . find ( c => c . id === commentId ) ;
331361 if ( ! originalComment ) return ;
332362
333363 const originalContent = originalComment . content ;
334364 const originalUpdatedAt = originalComment . updated_at ;
335365
336- // Optimistically update the UI with current timestamp
337366 const newUpdatedAt = new Date ( ) . toISOString ( ) ;
338367 setComments ( prev =>
339368 prev . map ( c => c . id === commentId
@@ -349,7 +378,6 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
349378 } ) ;
350379
351380 if ( ! result . success ) {
352- // Revert on failure
353381 setComments ( prev =>
354382 prev . map ( c => c . id === commentId
355383 ? { ...c , content : originalContent , updated_at : originalUpdatedAt }
@@ -358,14 +386,12 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
358386 ) ;
359387 alert ( "Failed to update comment. Please try again." ) ;
360388 } else if ( result . comment ) {
361- // Update with actual server response
362389 setComments ( prev =>
363390 prev . map ( c => c . id === commentId ? result . comment ! : c )
364391 ) ;
365392 }
366393 } catch ( error ) {
367394 console . error ( "Error editing comment:" , error ) ;
368- // Revert on error
369395 setComments ( prev =>
370396 prev . map ( c => c . id === commentId
371397 ? { ...c , content : originalContent , updated_at : originalUpdatedAt }
@@ -390,10 +416,8 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
390416 const result = await deleteComment ( commentToDelete ) ;
391417
392418 if ( result . success ) {
393- // Remove comment from UI
394419 setComments ( prev => prev . filter ( c => c . id !== commentToDelete ) ) ;
395420
396- // Update question count
397421 fetchQuestions ( )
398422 . then ( ( result ) => {
399423 setQuestions ( result ) ;
@@ -416,13 +440,13 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
416440 } ;
417441
418442 const cancelDelete = ( ) => {
419- if ( isDeleting ) return ; // Prevent closing while deleting
443+ if ( isDeleting ) return ;
420444 setDeleteModalOpen ( false ) ;
421445 setCommentToDelete ( null ) ;
422446 } ;
423447
424448 const handleDialogChange = ( open : boolean ) => {
425- if ( ! open && isDeleting ) return ; // Prevent closing while deleting
449+ if ( ! open && isDeleting ) return ;
426450 setDeleteModalOpen ( open ) ;
427451 if ( ! open ) {
428452 setCommentToDelete ( null ) ;
@@ -433,36 +457,37 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
433457 return (
434458 < div className = "rounded-lg border border-gray-200 bg-gray-50 p-4 text-center dark:border-gray-700 dark:bg-gray-800" >
435459 < div className = "mb-3 flex justify-center" >
436- < div className = "h-8 w-8 animate-spin rounded-full border-2 border-gray-200 border-t-customBlue-500 dark:border-gray-700 dark:border-t-customBlue-400" > </ div >
460+ < div className = "h-6 w-6 sm:h-8 sm: w-8 animate-spin rounded-full border-2 border-gray-200 border-t-customBlue-500 dark:border-gray-700 dark:border-t-customBlue-400" > </ div >
437461 </ div >
438- < p className = "text-sm text-gray-600 dark:text-white" > Loading comments...</ p >
462+ < p className = "text-xs sm:text- sm text-gray-600 dark:text-white" > Loading comments...</ p >
439463 </ div >
440464 ) ;
441465 }
442466
443467 return (
444- < div className = "space-y-4" >
468+ < div className = "space-y-3 sm:space-y- 4" >
445469 { /* Delete Confirmation Modal */ }
446470 < Dialog open = { deleteModalOpen } onOpenChange = { handleDialogChange } >
447- < DialogContent className = "sm: max-w-[425px]" >
471+ < DialogContent className = "w-[90vw] max-w-[425px] rounded-lg " >
448472 < DialogHeader >
449- < DialogTitle > Delete Comment</ DialogTitle >
450- < DialogDescription >
473+ < DialogTitle className = "text-base sm:text-lg" > Delete Comment</ DialogTitle >
474+ < DialogDescription className = "text-xs sm:text-sm" >
451475 Are you sure you want to delete this comment? This action cannot be undone.
452476 </ DialogDescription >
453477 </ DialogHeader >
454- < DialogFooter >
478+ < DialogFooter className = "gap-2 sm:gap-0" >
455479 < Button
456480 variant = "ghost"
457481 onClick = { cancelDelete }
458482 disabled = { isDeleting }
483+ className = "text-xs sm:text-sm h-9"
459484 >
460485 Cancel
461486 </ Button >
462487 < Button
463488 onClick = { confirmDelete }
464489 disabled = { isDeleting }
465- className = "bg-red-600 text-white hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600"
490+ className = "text-xs sm:text-sm h-9 bg-red-600 text-white hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600"
466491 >
467492 { isDeleting ? "Deleting..." : "Delete" }
468493 </ Button >
@@ -472,7 +497,7 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
472497
473498 { /* Comments List */ }
474499 { comments . length > 0 && (
475- < div className = "space-y-3" >
500+ < div className = "space-y-2.5 sm:space-y- 3" >
476501 { comments . map ( ( comment ) => (
477502 < CommentItem
478503 key = { comment . id }
@@ -488,31 +513,32 @@ export default function CommentSection({ questionId, loggedIn, setQuestions }: C
488513 ) }
489514
490515 { /* Add Comment Form */ }
491- < form onSubmit = { handleSubmitComment } className = "space-y-3" >
516+ < form onSubmit = { handleSubmitComment } className = "space-y-2 sm:space-y- 3" >
492517 < Textarea
493518 placeholder = "Write a helpful comment..."
494519 value = { newComment }
495520 onChange = { ( e ) => setNewComment ( e . target . value ) }
496521 disabled = { isSubmitting }
497522 rows = { 3 }
498- className = "resize-none border-gray-300 bg-gray-50 text-gray-900 placeholder-gray-500 focus:border-customBlue-500 focus:ring-customBlue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500 dark:focus:border-customBlue-400 dark:focus:ring-customBlue-400"
523+ className = "resize-none text-sm sm:text-base border-gray-300 bg-gray-50 text-gray-900 placeholder-gray-500 focus:border-customBlue-500 focus:ring-customBlue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500 dark:focus:border-customBlue-400 dark:focus:ring-customBlue-400"
499524 />
500525
501526 < div className = "flex justify-end" >
502527 < Button
503528 type = "submit"
504529 size = "sm"
505530 disabled = { isSubmitting || ! newComment . trim ( ) }
531+ className = "text-xs sm:text-sm h-9"
506532 >
507- < Send className = "mr-2 h-5 w-5 " />
533+ < Send className = "mr-1.5 sm:mr- 2 h-4 w-4 " />
508534 { isSubmitting ? "Posting..." : "Post Comment" }
509535 </ Button >
510536 </ div >
511537 </ form >
512538
513539 { comments . length === 0 && (
514540 < div className = "rounded-lg border border-gray-200 bg-gray-50 p-4 text-center dark:border-gray-700 dark:bg-gray-800" >
515- < p className = "text-sm text-gray-600 dark:text-white" >
541+ < p className = "text-xs sm:text- sm text-gray-600 dark:text-white" >
516542 No comments yet. Be the first to help!
517543 </ p >
518544 </ div >
0 commit comments