source: frontend/src/components/CommentSection.tsx

main
Last change on this file was 700e2f9, checked in by 186079 <matej.milevski@…>, 5 days ago

Init

  • Property mode set to 100644
File size: 7.1 KB
RevLine 
[700e2f9]1import { type Component, createSignal, For, Show } from "solid-js";
2import { formatDateTime } from "@/utils";
3import { useAuth } from "@/context/AuthContext";
4import type { Comment } from "@/api/blog";
5
6interface CommentSectionProps {
7 comments: Comment[];
8 commentsCount: number;
9 onAddComment: (content: string) => Promise<void>;
10 onUpdateComment?: (commentId: number, content: string) => Promise<void>;
11 onDeleteComment?: (commentId: number) => Promise<void>;
12}
13
14const CommentSection: Component<CommentSectionProps> = (props) => {
15 const { user } = useAuth();
16 const [newComment, setNewComment] = createSignal("");
17 const [editingCommentId, setEditingCommentId] = createSignal<number | null>(
18 null,
19 );
20 const [editContent, setEditContent] = createSignal("");
21
22 const handleSubmit = async (e: Event) => {
23 e.preventDefault();
24 if (!newComment().trim()) return;
25
26 await props.onAddComment(newComment());
27 setNewComment("");
28 };
29
30 const startEdit = (comment: Comment) => {
31 setEditingCommentId(comment.idComment);
32 setEditContent(comment.content);
33 };
34
35 const cancelEdit = () => {
36 setEditingCommentId(null);
37 setEditContent("");
38 };
39
40 const saveEdit = async (commentId: number) => {
41 if (props.onUpdateComment && editContent().trim()) {
42 await props.onUpdateComment(commentId, editContent());
43 setEditingCommentId(null);
44 setEditContent("");
45 }
46 };
47
48 const handleDelete = async (commentId: number) => {
49 if (
50 props.onDeleteComment &&
51 confirm("Are you sure you want to delete this comment?")
52 ) {
53 await props.onDeleteComment(commentId);
54 }
55 };
56
57 const isCommentOwner = (comment: Comment) =>
58 user()?.userId === comment.patientId;
59
60 const sortedComments = () => {
61 if (!props.comments) return [];
62
63 return [...props.comments].sort(
64 (a, b) =>
65 new Date(b.dateOfComment).getTime() -
66 new Date(a.dateOfComment).getTime(),
67 );
68 };
69
70 return (
71 <div class="space-y-4">
72 <h3 class="text-xl font-semibold text-gray-900 mb-4">Comments</h3>
73
74 <form onSubmit={handleSubmit} class="mb-6">
75 <textarea
76 rows={3}
77 class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
78 placeholder="Write a comment..."
79 value={newComment()}
80 onInput={(e) => setNewComment(e.currentTarget.value)}
81 />
82 <button
83 type="submit"
84 disabled={!newComment().trim()}
85 class="mt-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-gray-400 transition-colors cursor-pointer"
86 >
87 Post Comment
88 </button>
89 </form>
90
91 <Show when={props.commentsCount === 0}>
92 <p class="text-gray-500 text-center py-4">
93 No comments yet. Be the first to comment!
94 </p>
95 </Show>
96
97 <Show when={(props.comments?.length || 0) > 0}>
98 <div class="space-y-4">
99 <For each={sortedComments()}>
100 {(comment) => (
101 <div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
102 <div class="flex justify-between items-start mb-2">
103 <span class="font-semibold text-gray-900">
104 {comment.patientName}
105 </span>
106 <div class="flex items-center gap-2">
107 <span class="text-xs text-gray-500">
108 {formatDateTime(comment.dateOfComment)}
109 </span>
110 <Show
111 when={
112 isCommentOwner(comment) &&
113 editingCommentId() !== comment.idComment
114 }
115 >
116 <button
117 onClick={() => startEdit(comment)}
118 class="text-gray-400 hover:text-blue-600 transition-colors cursor-pointer"
119 title="Edit comment"
120 >
121 <svg
122 class="w-4 h-4"
123 fill="none"
124 stroke="currentColor"
125 viewBox="0 0 24 24"
126 >
127 <path
128 stroke-linecap="round"
129 stroke-linejoin="round"
130 stroke-width="2"
131 d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
132 />
133 </svg>
134 </button>
135 <button
136 onClick={() => handleDelete(comment.idComment)}
137 class="text-gray-400 hover:text-red-600 transition-colors cursor-pointer"
138 title="Delete comment"
139 >
140 <svg
141 class="w-4 h-4"
142 fill="none"
143 stroke="currentColor"
144 viewBox="0 0 24 24"
145 >
146 <path
147 stroke-linecap="round"
148 stroke-linejoin="round"
149 stroke-width="2"
150 d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
151 />
152 </svg>
153 </button>
154 </Show>
155 </div>
156 </div>
157 <Show
158 when={editingCommentId() !== comment.idComment}
159 fallback={
160 <div class="space-y-2">
161 <textarea
162 value={editContent()}
163 onInput={(e) => setEditContent(e.currentTarget.value)}
164 class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
165 rows={3}
166 />
167 <div class="flex gap-2">
168 <button
169 onClick={() => saveEdit(comment.idComment)}
170 class="px-3 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors cursor-pointer"
171 >
172 Save
173 </button>
174 <button
175 onClick={cancelEdit}
176 class="px-3 py-1 text-sm bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors cursor-pointer"
177 >
178 Cancel
179 </button>
180 </div>
181 </div>
182 }
183 >
184 <p class="text-gray-700 whitespace-pre-wrap">
185 {comment.content}
186 </p>
187 </Show>
188 </div>
189 )}
190 </For>
191 </div>
192 </Show>
193 </div>
194 );
195};
196
197export default CommentSection;
Note: See TracBrowser for help on using the repository browser.