Deletion verification modal
This commit is contained in:
Binary file not shown.
83
ui/src/components/ConfirmDialog.tsx
Normal file
83
ui/src/components/ConfirmDialog.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
type ConfirmDialogProps = {
|
||||
open: boolean;
|
||||
title: string;
|
||||
description?: string;
|
||||
contextText?: string;
|
||||
confirmLabel: string;
|
||||
cancelLabel: string;
|
||||
busy?: boolean;
|
||||
busyLabel?: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
export default function ConfirmDialog({
|
||||
open,
|
||||
title,
|
||||
description,
|
||||
contextText,
|
||||
confirmLabel,
|
||||
cancelLabel,
|
||||
busy = false,
|
||||
busyLabel,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: ConfirmDialogProps) {
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && !busy) {
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [open, busy, onCancel]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-[#2B170A]/60 px-4"
|
||||
onClick={() => {
|
||||
if (!busy) onCancel();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
className="w-full max-w-md rounded-xl border border-[#C99763] bg-[#F5D1A9] p-5 shadow-xl"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-[#4C250E]">{title}</h3>
|
||||
{description && <p className="mt-2 text-sm text-[#70421E]">{description}</p>}
|
||||
{contextText && <p className="mt-1 text-sm font-medium text-[#4C250E]">{contextText}</p>}
|
||||
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
disabled={busy}
|
||||
className="rounded-md border border-[#A56C38] bg-[#EED5B8] px-3 py-1.5 text-sm text-[#70421E] hover:bg-[#E3A977] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{cancelLabel}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onConfirm}
|
||||
disabled={busy}
|
||||
className="rounded-md border border-[#70421E] bg-[#8E4F24] px-3 py-1.5 text-sm text-[#FFF7EE] hover:bg-[#70421E] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{busy ? busyLabel ?? confirmLabel : confirmLabel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
updateLokOpenHours,
|
||||
type LokOpenHours,
|
||||
} from "~/api";
|
||||
import ConfirmDialog from "~/components/ConfirmDialog";
|
||||
import { useT } from "~/i18n";
|
||||
import { openHoursAtom, toastsAtom, type Toast } from "~/state/appState";
|
||||
|
||||
@@ -38,6 +39,7 @@ export default function OpenHoursForm() {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editingVersionId, setEditingVersionId] = useState("");
|
||||
const [deletingId, setDeletingId] = useState("");
|
||||
const [confirmDeleteVersion, setConfirmDeleteVersion] = useState<LokOpenHours | null>(null);
|
||||
const [activatingId, setActivatingId] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [form, setForm] = useState<FormState>(EMPTY_FORM);
|
||||
@@ -217,6 +219,21 @@ export default function OpenHoursForm() {
|
||||
}
|
||||
};
|
||||
|
||||
const askDeleteConfirmation = (version: LokOpenHours) => {
|
||||
setConfirmDeleteVersion(version);
|
||||
};
|
||||
|
||||
const cancelDeleteConfirmation = () => {
|
||||
if (deletingId) return;
|
||||
setConfirmDeleteVersion(null);
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!confirmDeleteVersion) return;
|
||||
await onDelete(confirmDeleteVersion);
|
||||
setConfirmDeleteVersion(null);
|
||||
};
|
||||
|
||||
const onSetActive = async (version: LokOpenHours) => {
|
||||
if (version.isActive) return;
|
||||
|
||||
@@ -328,7 +345,7 @@ export default function OpenHoursForm() {
|
||||
disabled={active || deleting}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
void onDelete(version);
|
||||
askDeleteConfirmation(version);
|
||||
}}
|
||||
className={`rounded-md border border-[#8E4F24] bg-[#EED5B8] px-3 py-1.5 text-sm text-[#70421E] disabled:cursor-not-allowed disabled:opacity-50 ${!active ? "hover:bg-[#E3A977]" : ""
|
||||
}`}
|
||||
@@ -416,6 +433,21 @@ export default function OpenHoursForm() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(confirmDeleteVersion)}
|
||||
title={t("home.openHours.deleteConfirmTitle")}
|
||||
description={t("home.openHours.deleteConfirmMessage")}
|
||||
contextText={confirmDeleteVersion?.name || t("home.openHours.latest")}
|
||||
cancelLabel={t("home.openHours.cancel")}
|
||||
confirmLabel={t("home.openHours.deleteConfirmAction")}
|
||||
busy={Boolean(deletingId)}
|
||||
busyLabel={t("home.openHours.deleting")}
|
||||
onCancel={cancelDeleteConfirmation}
|
||||
onConfirm={() => {
|
||||
void confirmDelete();
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ const translations = {
|
||||
"home.openHours.saved": "New version saved",
|
||||
"home.openHours.updated": "Version updated",
|
||||
"home.openHours.deleted": "Version deleted",
|
||||
"home.openHours.deleting": "Deleting...",
|
||||
"home.openHours.deleteConfirmTitle": "Delete version?",
|
||||
"home.openHours.deleteConfirmMessage": "This action cannot be undone.",
|
||||
"home.openHours.deleteConfirmAction": "Delete version",
|
||||
"about.title": "About",
|
||||
"about.description":
|
||||
"Livonsaaren Tietokonepaja is a local project providing IT services for our dear archipelago.",
|
||||
@@ -125,6 +129,10 @@ const translations = {
|
||||
"home.openHours.saved": "Uusi versio tallennettu",
|
||||
"home.openHours.updated": "Versio päivitetty",
|
||||
"home.openHours.deleted": "Versio poistettu",
|
||||
"home.openHours.deleting": "Poistetaan...",
|
||||
"home.openHours.deleteConfirmTitle": "Poistetaanko versio?",
|
||||
"home.openHours.deleteConfirmMessage": "Tätä toimintoa ei voi perua.",
|
||||
"home.openHours.deleteConfirmAction": "Poista versio",
|
||||
"about.title": "Tietoja",
|
||||
"about.description":
|
||||
"Livonsaaren Tietokonepaja on paikallisprojekti, joka tuottaa IT-palveluita rakkaalle lähisaaristollemme.",
|
||||
@@ -207,6 +215,11 @@ const translations = {
|
||||
"home.openHours.saved": "Nová verzia uložená",
|
||||
"home.openHours.updated": "Verzia bola aktualizovaná",
|
||||
"home.openHours.deleted": "Verzia bola odstránená",
|
||||
"home.openHours.deleting": "Odstraňuje sa...",
|
||||
"home.openHours.deleteConfirmTitle": "Odstrániť verziu?",
|
||||
"home.openHours.deleteConfirmMessage":
|
||||
"Túto akciu nie je možné vrátiť späť.",
|
||||
"home.openHours.deleteConfirmAction": "Odstrániť verziu",
|
||||
"about.title": "O aplikácii",
|
||||
"about.description":
|
||||
"Livonsaaren Tietokonepaja je lokálny projekt poskytujúci IT služby pre naše milované súostrovie.",
|
||||
|
||||
Reference in New Issue
Block a user