Implement delete confirmation

This commit is contained in:
2025-09-19 21:18:57 +03:00
Unverified
parent 76cb084675
commit b3aa8cebd5
4 changed files with 79 additions and 64 deletions
@@ -6,9 +6,10 @@ import type { UserProfile } from "../../../core/types";
import { UserProfileDialog } from "./UserProfileDialog";
import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu";
import { fetchUserProfile } from "../../../api/profileApi";
import { useState, type ReactNode } from "react";
import { useEffect, useState, type ReactNode } from "react";
import { delay } from "../../../utils/utils";
import { request } from "../../../core/websocket";
import { MaterialDialog } from "../core/Dialog";
interface ChatMessagesProps {
messages?: MessageType[];
@@ -35,7 +36,17 @@ export function ChatMessages({ messages: propMessages, children, isDm = false, o
position: { x: 0, y: 0 }
});
const handleProfileClick = async (username: string) => {
// Delete dialog
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [toBeDeleted, setToBeDeleted] = useState<number | null>(null);
useEffect(() => {
if (!deleteDialogOpen) {
setToBeDeleted(null);
}
}, [deleteDialogOpen]);
async function handleProfileClick(username: string) {
if (!user.authToken) return;
setIsLoadingProfile(true);
@@ -52,7 +63,7 @@ export function ChatMessages({ messages: propMessages, children, isDm = false, o
}
};
const handleContextMenu = (e: React.MouseEvent, message: MessageType) => {
function handleContextMenu(e: React.MouseEvent, message: MessageType) {
e.preventDefault();
setContextMenu({
isOpen: true,
@@ -61,28 +72,29 @@ export function ChatMessages({ messages: propMessages, children, isDm = false, o
});
};
const handleContextMenuOpenChange = (isOpen: boolean) => {
function handleContextMenuOpenChange(isOpen: boolean) {
setContextMenu(prev => ({
...prev,
isOpen
}));
};
const handleEdit = (message: MessageType) => {
function handleEdit(message: MessageType) {
if (onEditSelect) onEditSelect(message);
};
const handleReply = (message: MessageType) => {
function handleReply(message: MessageType) {
if (onReplySelect) onReplySelect(message);
};
const handleDelete = async (message: MessageType) => {
async function confirmDelete() {
if (toBeDeleted) {
if (!user.authToken) return;
try {
await request({
type: "deleteMessage",
data: { message_id: message.id },
data: { message_id: toBeDeleted },
credentials: {
scheme: "Bearer",
credentials: user.authToken
@@ -91,7 +103,15 @@ export function ChatMessages({ messages: propMessages, children, isDm = false, o
} catch (error) {
console.error("Failed to delete message:", error);
}
};
setDeleteDialogOpen(false);
}
}
async function handleDelete(message: MessageType) {
setToBeDeleted(message.id);
setDeleteDialogOpen(true);
}
return (
<>
@@ -121,6 +141,14 @@ export function ChatMessages({ messages: propMessages, children, isDm = false, o
userProfile={selectedUserProfile}
/>
<MaterialDialog
headline="Удалить сообщение?"
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}>
<mdui-button slot="action" variant="tonal" onClick={() => setDeleteDialogOpen(false)}>Отменить</mdui-button>
<mdui-button slot="action" variant="filled" onClick={confirmDelete}>Удалить</mdui-button>
</MaterialDialog>
{/* Context Menu */}
{contextMenu.message && (
<MessageContextMenu
@@ -125,6 +125,7 @@ export function MessageContextMenu({
}
break;
}
onOpenChange(false);
};
const handleClose = () => {
@@ -141,10 +142,7 @@ export function MessageContextMenu({
}, 200); // Match the animation duration from _animations.scss
};
// Inline edit handled by parent via onEdit
const content = (
return isOpen && (
<div
className={`context-menu ${animationClass}`}
style={{
@@ -154,8 +152,7 @@ export function MessageContextMenu({
left: calculatedPosition.x,
zIndex: 1000
}}
onClick={(e) => e.stopPropagation()}
>
onClick={(e) => e.stopPropagation()}>
<div className="context-menu-item" onClick={() => handleAction("reply")}>
<span className="material-symbols">reply</span>
Ответить
@@ -174,13 +171,4 @@ export function MessageContextMenu({
)}
</div>
)
// Don't render if not open
if (!isOpen) return null;
return (
<>
{isOpen ? content : null}
</>
);
}
+5 -3
View File
@@ -1,16 +1,18 @@
import type { Dialog as MduiDialog } from "mdui/components/dialog";
import React, { useEffect, useRef } from "react"
import { useEffect, type Ref } from "react"
import { createPortal } from "react-dom";
import { id } from "../../../utils/utils";
import useCombinedRefs from "../../hooks/useCombinedRefs";
export interface BaseDialogProps {
onOpenChange: (value: boolean) => void;
ref?: Ref<MduiDialog & HTMLElement>
}
export type FullDialogProps = React.ComponentPropsWithoutRef<"mdui-dialog"> & BaseDialogProps;
export function MaterialDialog(props: FullDialogProps) {
const dialogRef = useRef<MduiDialog>(null);
const [setDialogRef, dialogRef] = useCombinedRefs(props.ref);
useEffect(() => {
const dialog = dialogRef.current;
@@ -39,5 +41,5 @@ export function MaterialDialog(props: FullDialogProps) {
};
}, [dialogRef.current, props.open, props.onOpenChange]);
return createPortal(<mdui-dialog {...props} ref={dialogRef} />, id("root"));
return createPortal(<mdui-dialog {...props} ref={setDialogRef} />, id("root"));
}
+2 -5
View File
@@ -6,16 +6,13 @@ type PossibleRef<T> = Ref<T> | undefined;
export default function useCombinedRefs<T>(...refs: PossibleRef<T>[]): [RefCallback<T>, React.RefObject<T | null>] {
const targetRef = useRef<T | null>(null);
const setRefs = useCallback(
(node: T | null) => {
const setRefs = useCallback((node: T | null) => {
// Обновляем внутренний ref
targetRef.current = node;
// Обновляем все переданные refs
refs.forEach((ref) => {
if (!ref) {
return;
}
if (!ref) return;
if (typeof ref === 'function') {
// Если ref - это функция, вызываем её