| 1 | import { type Component, createSignal, createEffect, Show } from "solid-js";
|
|---|
| 2 | import CommentSection from "@/components/CommentSection";
|
|---|
| 3 | import { formatDate } from "@/utils";
|
|---|
| 4 | import { useAuth } from "@/context/AuthContext";
|
|---|
| 5 | import type { BlogPost } from "@/api/blog";
|
|---|
| 6 |
|
|---|
| 7 | interface BlogModalProps {
|
|---|
| 8 | blog: BlogPost;
|
|---|
| 9 | onClose: () => void;
|
|---|
| 10 | onLike: (blogId: number) => void;
|
|---|
| 11 | onAddComment: (content: string) => Promise<void>;
|
|---|
| 12 | onUpdateBlog?: (
|
|---|
| 13 | blogId: number,
|
|---|
| 14 | title: string,
|
|---|
| 15 | content: string,
|
|---|
| 16 | ) => Promise<void>;
|
|---|
| 17 | onDeleteBlog?: (blogId: number) => Promise<void>;
|
|---|
| 18 | onUpdateComment?: (commentId: number, content: string) => Promise<void>;
|
|---|
| 19 | onDeleteComment?: (commentId: number) => Promise<void>;
|
|---|
| 20 | initialEditMode?: boolean;
|
|---|
| 21 | }
|
|---|
| 22 |
|
|---|
| 23 | const BlogModal: Component<BlogModalProps> = (props) => {
|
|---|
| 24 | const { user } = useAuth();
|
|---|
| 25 | const [isEditing, setIsEditing] = createSignal(false);
|
|---|
| 26 | const [editTitle, setEditTitle] = createSignal("");
|
|---|
| 27 | const [editContent, setEditContent] = createSignal("");
|
|---|
| 28 |
|
|---|
| 29 | const isOwner = () => user()?.userId === props.blog?.patientId;
|
|---|
| 30 |
|
|---|
| 31 | createEffect(() => {
|
|---|
| 32 | if (props.initialEditMode && props.blog) {
|
|---|
| 33 | setEditTitle(props.blog.title || "");
|
|---|
| 34 | setEditContent(props.blog.content || "");
|
|---|
| 35 | setIsEditing(true);
|
|---|
| 36 | }
|
|---|
| 37 | });
|
|---|
| 38 |
|
|---|
| 39 | const startEdit = () => {
|
|---|
| 40 | setEditTitle(props.blog?.title || "");
|
|---|
| 41 | setEditContent(props.blog?.content || "");
|
|---|
| 42 | setIsEditing(true);
|
|---|
| 43 | };
|
|---|
| 44 |
|
|---|
| 45 | const cancelEdit = () => {
|
|---|
| 46 | setIsEditing(false);
|
|---|
| 47 | };
|
|---|
| 48 |
|
|---|
| 49 | const saveEdit = async () => {
|
|---|
| 50 | if (props.onUpdateBlog && props.blog) {
|
|---|
| 51 | await props.onUpdateBlog(props.blog.idBlog, editTitle(), editContent());
|
|---|
| 52 | setIsEditing(false);
|
|---|
| 53 | }
|
|---|
| 54 | };
|
|---|
| 55 |
|
|---|
| 56 | const handleDelete = async () => {
|
|---|
| 57 | if (
|
|---|
| 58 | props.onDeleteBlog &&
|
|---|
| 59 | props.blog &&
|
|---|
| 60 | confirm("Are you sure you want to delete this blog post?")
|
|---|
| 61 | ) {
|
|---|
| 62 | await props.onDeleteBlog(props.blog.idBlog);
|
|---|
| 63 | }
|
|---|
| 64 | };
|
|---|
| 65 |
|
|---|
| 66 | return (
|
|---|
| 67 | <div
|
|---|
| 68 | class="fixed inset-0 backdrop-blur-sm bg-gray-900/20 flex items-center justify-center p-4 z-50"
|
|---|
| 69 | onClick={props.onClose}
|
|---|
| 70 | >
|
|---|
| 71 | <div
|
|---|
| 72 | class="bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto"
|
|---|
| 73 | onClick={(e) => e.stopPropagation()}
|
|---|
| 74 | >
|
|---|
| 75 | <div class="sticky top-0 bg-white border-b border-gray-200 p-6">
|
|---|
| 76 | <div class="flex justify-between items-start">
|
|---|
| 77 | <div class="flex-1">
|
|---|
| 78 | <Show
|
|---|
| 79 | when={!isEditing()}
|
|---|
| 80 | fallback={
|
|---|
| 81 | <div class="space-y-3">
|
|---|
| 82 | <input
|
|---|
| 83 | type="text"
|
|---|
| 84 | value={editTitle()}
|
|---|
| 85 | onInput={(e) => setEditTitle(e.currentTarget.value)}
|
|---|
| 86 | class="w-full text-3xl font-bold text-gray-900 border-b-2 border-blue-500 focus:outline-none"
|
|---|
| 87 | placeholder="Title"
|
|---|
| 88 | />
|
|---|
| 89 | </div>
|
|---|
| 90 | }
|
|---|
| 91 | >
|
|---|
| 92 | <h2 class="text-3xl font-bold text-gray-900 mb-2">
|
|---|
| 93 | {props.blog?.title}
|
|---|
| 94 | </h2>
|
|---|
| 95 | </Show>
|
|---|
| 96 | <div class="flex items-center gap-2 text-sm text-gray-600 mt-2">
|
|---|
| 97 | <span class="font-medium">{props.blog?.patientName}</span>
|
|---|
| 98 | <span>•</span>
|
|---|
| 99 | <span>{formatDate(props.blog?.dateOfPost || "")}</span>
|
|---|
| 100 | </div>
|
|---|
| 101 | </div>
|
|---|
| 102 | <div class="flex items-center gap-2 ml-4">
|
|---|
| 103 | <Show when={isOwner() && !isEditing()}>
|
|---|
| 104 | <button
|
|---|
| 105 | onClick={startEdit}
|
|---|
| 106 | class="text-gray-400 hover:text-blue-600 transition-colors cursor-pointer"
|
|---|
| 107 | title="Edit blog"
|
|---|
| 108 | >
|
|---|
| 109 | <svg
|
|---|
| 110 | class="w-5 h-5"
|
|---|
| 111 | fill="none"
|
|---|
| 112 | stroke="currentColor"
|
|---|
| 113 | viewBox="0 0 24 24"
|
|---|
| 114 | >
|
|---|
| 115 | <path
|
|---|
| 116 | stroke-linecap="round"
|
|---|
| 117 | stroke-linejoin="round"
|
|---|
| 118 | stroke-width="2"
|
|---|
| 119 | 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"
|
|---|
| 120 | />
|
|---|
| 121 | </svg>
|
|---|
| 122 | </button>
|
|---|
| 123 | <button
|
|---|
| 124 | onClick={handleDelete}
|
|---|
| 125 | class="text-gray-400 hover:text-red-600 transition-colors cursor-pointer"
|
|---|
| 126 | title="Delete blog"
|
|---|
| 127 | >
|
|---|
| 128 | <svg
|
|---|
| 129 | class="w-5 h-5"
|
|---|
| 130 | fill="none"
|
|---|
| 131 | stroke="currentColor"
|
|---|
| 132 | viewBox="0 0 24 24"
|
|---|
| 133 | >
|
|---|
| 134 | <path
|
|---|
| 135 | stroke-linecap="round"
|
|---|
| 136 | stroke-linejoin="round"
|
|---|
| 137 | stroke-width="2"
|
|---|
| 138 | 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"
|
|---|
| 139 | />
|
|---|
| 140 | </svg>
|
|---|
| 141 | </button>
|
|---|
| 142 | </Show>
|
|---|
| 143 | <button
|
|---|
| 144 | onClick={props.onClose}
|
|---|
| 145 | class="text-gray-400 hover:text-gray-600 transition-colors cursor-pointer"
|
|---|
| 146 | >
|
|---|
| 147 | <svg
|
|---|
| 148 | class="w-6 h-6"
|
|---|
| 149 | fill="none"
|
|---|
| 150 | stroke="currentColor"
|
|---|
| 151 | viewBox="0 0 24 24"
|
|---|
| 152 | >
|
|---|
| 153 | <path
|
|---|
| 154 | stroke-linecap="round"
|
|---|
| 155 | stroke-linejoin="round"
|
|---|
| 156 | stroke-width="2"
|
|---|
| 157 | d="M6 18L18 6M6 6l12 12"
|
|---|
| 158 | />
|
|---|
| 159 | </svg>
|
|---|
| 160 | </button>
|
|---|
| 161 | </div>
|
|---|
| 162 | </div>
|
|---|
| 163 | </div>
|
|---|
| 164 |
|
|---|
| 165 | <div class="p-6">
|
|---|
| 166 | <Show
|
|---|
| 167 | when={!isEditing()}
|
|---|
| 168 | fallback={
|
|---|
| 169 | <div class="space-y-4 mb-6">
|
|---|
| 170 | <textarea
|
|---|
| 171 | value={editContent()}
|
|---|
| 172 | onInput={(e) => setEditContent(e.currentTarget.value)}
|
|---|
| 173 | class="w-full min-h-50 p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|---|
| 174 | placeholder="Write your blog content..."
|
|---|
| 175 | />
|
|---|
| 176 | <div class="flex gap-2">
|
|---|
| 177 | <button
|
|---|
| 178 | onClick={saveEdit}
|
|---|
| 179 | class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors cursor-pointer"
|
|---|
| 180 | >
|
|---|
| 181 | Save Changes
|
|---|
| 182 | </button>
|
|---|
| 183 | <button
|
|---|
| 184 | onClick={cancelEdit}
|
|---|
| 185 | class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors cursor-pointer"
|
|---|
| 186 | >
|
|---|
| 187 | Cancel
|
|---|
| 188 | </button>
|
|---|
| 189 | </div>
|
|---|
| 190 | </div>
|
|---|
| 191 | }
|
|---|
| 192 | >
|
|---|
| 193 | <p class="text-gray-700 text-lg mb-6 whitespace-pre-wrap">
|
|---|
| 194 | {props.blog?.content}
|
|---|
| 195 | </p>
|
|---|
| 196 | </Show>
|
|---|
| 197 |
|
|---|
| 198 | <div class="flex items-center gap-4 pb-6 mb-6 border-b border-gray-200">
|
|---|
| 199 | <button
|
|---|
| 200 | onClick={() => {
|
|---|
| 201 | if (props.blog) {
|
|---|
| 202 | props.onLike(props.blog.idBlog);
|
|---|
| 203 | }
|
|---|
| 204 | }}
|
|---|
| 205 | class={`flex items-center gap-2 px-3 py-1 rounded-md transition-colors cursor-pointer ${
|
|---|
| 206 | props.blog?.likedByCurrentUser
|
|---|
| 207 | ? "bg-blue-100 text-blue-700"
|
|---|
| 208 | : "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
|---|
| 209 | }`}
|
|---|
| 210 | >
|
|---|
| 211 | <svg
|
|---|
| 212 | class="w-5 h-5"
|
|---|
| 213 | fill={props.blog?.likedByCurrentUser ? "currentColor" : "none"}
|
|---|
| 214 | stroke="currentColor"
|
|---|
| 215 | viewBox="0 0 24 24"
|
|---|
| 216 | >
|
|---|
| 217 | <path
|
|---|
| 218 | stroke-linecap="round"
|
|---|
| 219 | stroke-linejoin="round"
|
|---|
| 220 | stroke-width="2"
|
|---|
| 221 | d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
|---|
| 222 | />
|
|---|
| 223 | </svg>
|
|---|
| 224 | <span class="font-medium">{props.blog?.likesCount}</span>
|
|---|
| 225 | </button>
|
|---|
| 226 | </div>
|
|---|
| 227 |
|
|---|
| 228 | <CommentSection
|
|---|
| 229 | comments={props.blog?.comments || []}
|
|---|
| 230 | commentsCount={props.blog?.commentsCount || 0}
|
|---|
| 231 | onAddComment={props.onAddComment}
|
|---|
| 232 | onUpdateComment={props.onUpdateComment}
|
|---|
| 233 | onDeleteComment={props.onDeleteComment}
|
|---|
| 234 | />
|
|---|
| 235 | </div>
|
|---|
| 236 | </div>
|
|---|
| 237 | </div>
|
|---|
| 238 | );
|
|---|
| 239 | };
|
|---|
| 240 |
|
|---|
| 241 | export default BlogModal;
|
|---|