diff --git a/components/chat/ChatItem.tsx b/components/chat/ChatItem.tsx index 9e6e0e4..7b64196 100644 --- a/components/chat/ChatItem.tsx +++ b/components/chat/ChatItem.tsx @@ -1,15 +1,16 @@ /** * @file components/chat/ChatItem.tsx - * @description Single message row component supporting inline editing, deletion, and user association details. + * @description Single message row component supporting editing and deletion functionality. */ "use client"; -import type { Message, Member, User } from "@/db/schema"; -import { UserAvatar } from "../ui/UserAvatar"; import { useState } from "react"; import { useRouter } from "next/navigation"; -import { Check, Pencil, Trash2, X } from "lucide-react"; +import type { Message, Member, User } from "@/db/schema"; +import { UserAvatar } from "../ui/UserAvatar"; +import { ChatItemActions } from "./ChatItemActions"; +import { ChatItemEdit } from "./ChatItemEdit"; /** * Composite message type extending base database Message with populated member and user relation. @@ -18,7 +19,7 @@ import { Check, Pencil, Trash2, X } from "lucide-react"; * @property {string} id - The unique identifier of the message. * @property {string} content - The text content of the message. * @property {string} createdAt - The timestamp when the message was created. - * @property {string} [updatedAt] - The timestamp when the message was last updated. + * @property {string | null} [updatedAt] - The timestamp when the message was last updated. * @property {Member & { user: User }} member - The associated member and user relational data. */ export type MessageWithMember = Message & { @@ -28,7 +29,7 @@ export type MessageWithMember = Message & { }; /** - * Props for the ChatItem component. + * Properties for the ChatItem component. * * @interface ChatItemProps * @property {MessageWithMember} message - The message object containing member and user relational data. @@ -40,8 +41,7 @@ interface ChatItemProps { } /** - * Renders an individual chat message row displaying user avatar, sender name, timestamp, - * edited indicator, and inline editing or deletion capabilities. + * Renders an individual chat message row supporting message editing, deletion, and author details. * * @async * @param {ChatItemProps} props - The component props. @@ -73,11 +73,11 @@ export function ChatItem({ message, currentUserId }: ChatItemProps) { ); /** - * Handles the asynchronous deletion of the chat message via API. + * Handles the asynchronous deletion of the chat message. * * @async * @function handleDelete - * @returns {Promise} Resolves when the deletion completes or fails. + * @returns {Promise} Resolves when the deletion process completes or fails. */ const handleDelete = async () => { if (isDeleting) return; @@ -88,10 +88,7 @@ export function ChatItem({ message, currentUserId }: ChatItemProps) { method: "DELETE", }); - if (!response.ok) { - throw new Error("Failed to delete message"); - } - + if (!response.ok) throw new Error("Failed to delete message"); router.refresh(); } catch (error) { console.error("Error deleting the message:", error); @@ -101,11 +98,11 @@ export function ChatItem({ message, currentUserId }: ChatItemProps) { }; /** - * Handles the asynchronous update of the chat message content via API. + * Handles the asynchronous update of the chat message content. * * @async * @function handleEdit - * @returns {Promise} Resolves when the message update completes or fails. + * @returns {Promise} Resolves when the update process completes or fails. */ const handleEdit = async () => { if (!content.trim() || isLoading) return; @@ -118,10 +115,7 @@ export function ChatItem({ message, currentUserId }: ChatItemProps) { body: JSON.stringify({ content }), }); - if (!response.ok) { - throw new Error("Failed to update message"); - } - + if (!response.ok) throw new Error("Failed to update message"); setIsEditing(false); router.refresh(); } catch (error) { @@ -131,23 +125,6 @@ export function ChatItem({ message, currentUserId }: ChatItemProps) { } }; - /** - * Handles keyboard events during inline editing (Enter to save, Escape to cancel). - * - * @function handleKeyDown - * @param {React.KeyboardEvent} e - The keyboard event object. - * @returns {void} - */ - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - e.preventDefault(); - handleEdit(); - } else if (e.key === "Escape") { - setIsEditing(false); - setContent(message.content); - } - }; - return (
@@ -164,61 +141,26 @@ export function ChatItem({ message, currentUserId }: ChatItemProps) { )}
- {/* Action Buttons */} {isOwner && !isEditing && ( -
- - -
+ setIsEditing(true)} + onDelete={handleDelete} + isDeleting={isDeleting} + /> )} - {/* Inline Edit Input vs. Regular Content */} {isEditing ? ( -
- setContent(e.target.value)} - onKeyDown={handleKeyDown} - disabled={isLoading} - className="w-full bg-background border border-surface rounded px-2 py-1 text-sm text-foreground outline-none focus:ring-1 focus:ring-accent" - autoFocus - /> - - -
+ { + setIsEditing(false); + setContent(message.content); + }} + isLoading={isLoading} + /> ) : (

{message.content} diff --git a/components/chat/ChatItemActions.tsx b/components/chat/ChatItemActions.tsx new file mode 100644 index 0000000..ad60888 --- /dev/null +++ b/components/chat/ChatItemActions.tsx @@ -0,0 +1,59 @@ +/** + * @file components/chat/ChatItemActions.tsx + * @description Action buttons component for editing or deleting chat messages on hover. + */ + +"use client"; + +import { Pencil, Trash2 } from "lucide-react"; + +/** + * Properties for the ChatItemActions component. + * + * @interface ChatItemActionsProps + * @property {() => void} onEdit - Callback function triggered when the edit button is clicked. + * @property {() => void} onDelete - Callback function triggered when the delete button is clicked. + * @property {boolean} isDeleting - Flag indicating whether a deletion operation is currently in progress. + */ +interface ChatItemActionsProps { + onEdit: () => void; + onDelete: () => void; + isDeleting: boolean; +} + +/** + * Renders action buttons (edit and delete) for a chat message item on hover. + * + * @param {ChatItemActionsProps} props - The component props. + * @param {() => void} props.onEdit - Callback function triggered when the edit button is clicked. + * @param {() => void} props.onDelete - Callback function triggered when the delete button is clicked. + * @param {boolean} props.isDeleting - Flag indicating whether a deletion operation is currently in progress. + * @returns {JSX.Element} The rendered chat item action buttons. + */ +export function ChatItemActions({ + onEdit, + onDelete, + isDeleting, +}: ChatItemActionsProps) { + return ( +

+ + +
+ ); +} diff --git a/components/chat/ChatItemEdit.tsx b/components/chat/ChatItemEdit.tsx new file mode 100644 index 0000000..8872590 --- /dev/null +++ b/components/chat/ChatItemEdit.tsx @@ -0,0 +1,90 @@ +/** + * @file components/chat/ChatItemEdit.tsx + * @description Component allowing users to edit an existing chat message inline with keyboard support. + */ + +"use client"; + +import { Check, X } from "lucide-react"; + +/** + * Properties for the ChatItemEdit component. + * + * @interface ChatItemEditProps + * @property {string} content - The current text content of the message being edited. + * @property {function} setContent - Callback function to update the message content state. + * @property {function} onSave - Callback function invoked to save the edited message. + * @property {function} onCancel - Callback function invoked to cancel the editing process. + * @property {boolean} isLoading - Flag indicating whether a save operation is currently in progress. + */ +interface ChatItemEditProps { + content: string; + setContent: (value: string) => void; + onSave: () => void; + onCancel: () => void; + isLoading: boolean; +} + +/** + * Renders an inline text input field with save and cancel buttons for editing chat messages. + * + * @param {ChatItemEditProps} props - The component props. + * @param {string} props.content - The current text content of the message being edited. + * @param {function} props.setContent - Callback function to update the message content state. + * @param {function} props.onSave - Callback function invoked to save the edited message. + * @param {function} props.onCancel - Callback function invoked to cancel the editing process. + * @param {boolean} props.isLoading - Flag indicating whether a save operation is currently in progress. + * @returns {JSX.Element} The rendered inline message editing component. + */ +export function ChatItemEdit({ + content, + setContent, + onSave, + onCancel, + isLoading, +}: ChatItemEditProps) { + /** + * Handles keyboard events within the input field for quick actions (Enter to save, Escape to cancel). + * + * @function handleKeyDown + * @param {React.KeyboardEvent} e - The keyboard event object. + * @returns {void} + */ + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + onSave(); + } else if (e.key === "Escape") { + onCancel(); + } + }; + + return ( +
+ setContent(e.target.value)} + onKeyDown={handleKeyDown} + disabled={isLoading} + className="w-full bg-background border border-surface rounded px-2 py-1 text-sm text-foreground outline-none focus:ring-1 focus:ring-accent" + autoFocus + /> + + +
+ ); +}