mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
174e9f05b6
Implements admin ability to delete requests with soft delete, media file cleanup, and seeding-aware torrent management. Adds new API endpoint, frontend confirmation dialog, and request actions dropdown. Updates database schema with deletedAt and deletedBy fields, and ensures all queries filter out deleted requests. Documentation added for feature and user flow.
131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
/**
|
|
* Component: Confirm Dialog
|
|
* Documentation: documentation/frontend/components.md
|
|
*
|
|
* Reusable confirmation dialog for destructive actions
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { Fragment } from 'react';
|
|
|
|
export interface ConfirmDialogProps {
|
|
isOpen: boolean;
|
|
title: string;
|
|
message: string | React.ReactNode;
|
|
confirmLabel?: string;
|
|
cancelLabel?: string;
|
|
confirmVariant?: 'danger' | 'primary';
|
|
onConfirm: () => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
export function ConfirmDialog({
|
|
isOpen,
|
|
title,
|
|
message,
|
|
confirmLabel = 'Confirm',
|
|
cancelLabel = 'Cancel',
|
|
confirmVariant = 'danger',
|
|
onConfirm,
|
|
onCancel,
|
|
}: ConfirmDialogProps) {
|
|
if (!isOpen) return null;
|
|
|
|
const confirmButtonClasses =
|
|
confirmVariant === 'danger'
|
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
|
: 'bg-blue-600 hover:bg-blue-700 text-white';
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
|
{/* Backdrop */}
|
|
<div
|
|
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
|
|
onClick={onCancel}
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
{/* Dialog */}
|
|
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
|
|
<div className="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
|
<div className="bg-white dark:bg-gray-800 px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
|
|
<div className="sm:flex sm:items-start">
|
|
{/* Icon */}
|
|
<div
|
|
className={`mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full ${
|
|
confirmVariant === 'danger'
|
|
? 'bg-red-100 dark:bg-red-900'
|
|
: 'bg-blue-100 dark:bg-blue-900'
|
|
} sm:mx-0 sm:h-10 sm:w-10`}
|
|
>
|
|
<svg
|
|
className={`h-6 w-6 ${
|
|
confirmVariant === 'danger'
|
|
? 'text-red-600 dark:text-red-400'
|
|
: 'text-blue-600 dark:text-blue-400'
|
|
}`}
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
strokeWidth="1.5"
|
|
stroke="currentColor"
|
|
>
|
|
{confirmVariant === 'danger' ? (
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
|
|
/>
|
|
) : (
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
|
|
/>
|
|
)}
|
|
</svg>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left flex-1">
|
|
<h3 className="text-lg font-semibold leading-6 text-gray-900 dark:text-gray-100">
|
|
{title}
|
|
</h3>
|
|
<div className="mt-2">
|
|
{typeof message === 'string' ? (
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 whitespace-pre-line">
|
|
{message}
|
|
</p>
|
|
) : (
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
{message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="bg-gray-50 dark:bg-gray-900 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={onConfirm}
|
|
className={`inline-flex w-full justify-center rounded-lg px-4 py-2 text-sm font-semibold shadow-sm sm:w-auto transition-colors ${confirmButtonClasses}`}
|
|
>
|
|
{confirmLabel}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
className="mt-3 inline-flex w-full justify-center rounded-lg bg-white dark:bg-gray-700 px-4 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 sm:mt-0 sm:w-auto transition-colors"
|
|
>
|
|
{cancelLabel}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|