Add shared ConfirmDialog; upgrade wordfilter with client actions and toasts.
Local Build and Deploy / deploy (push) Successful in 1m6s
Local Build and Deploy / deploy (push) Successful in 1m6s
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
1039044e48
commit
a2ca9b3c23
9 files changed
+366
-149
No files matched your search
@@ -4,35 +4,49 @@ import { revalidatePath } from "next/cache";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import {
|
||||
actionError,
|
||||
actionOk,
|
||||
type ActionResult,
|
||||
} from "@/lib/safe-action-shared";
|
||||
import { reloadWordFilter } from "@/lib/services/moderation";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
|
||||
export async function addWord(formData: FormData): Promise<void> {
|
||||
export async function addWord(input: {
|
||||
word: string;
|
||||
}): Promise<ActionResult<{ id: string }>> {
|
||||
await requirePermission(PERMS.WORDFILTER_EDIT);
|
||||
const word = String(formData.get("word") ?? "")
|
||||
const word = String(input.word ?? "")
|
||||
.normalize("NFC")
|
||||
.trim()
|
||||
.slice(0, 255);
|
||||
if (!word) return;
|
||||
if (!word) return actionError("Word is required");
|
||||
|
||||
try {
|
||||
await prisma.websiteWordfilter.create({ data: { word } });
|
||||
const created = await prisma.websiteWordfilter.create({ data: { word } });
|
||||
reloadWordFilter();
|
||||
await rcon.updateWordFilter();
|
||||
revalidatePath("/admin/wordfilter");
|
||||
return actionOk({ id: String(created.id) });
|
||||
} catch {
|
||||
// ignore (e.g. duplicate word) — page re-renders current state
|
||||
return actionError("Could not add word (it may already exist)");
|
||||
}
|
||||
revalidatePath("/admin/wordfilter");
|
||||
}
|
||||
|
||||
export async function deleteWord(formData: FormData): Promise<void> {
|
||||
export async function deleteWord(input: {
|
||||
id: string;
|
||||
}): Promise<ActionResult> {
|
||||
await requirePermission(PERMS.WORDFILTER_EDIT);
|
||||
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||
if (!raw) return;
|
||||
const raw = String(input.id ?? "").normalize("NFC");
|
||||
if (!raw) return actionError("Missing word id");
|
||||
|
||||
try {
|
||||
await prisma.websiteWordfilter.delete({ where: { id: BigInt(raw) } });
|
||||
reloadWordFilter();
|
||||
await rcon.updateWordFilter();
|
||||
revalidatePath("/admin/wordfilter");
|
||||
return actionOk();
|
||||
} catch {
|
||||
// ignore (e.g. already removed)
|
||||
return actionError("Could not remove word");
|
||||
}
|
||||
revalidatePath("/admin/wordfilter");
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRef, useState } from "react";
|
||||
import { deleteFavicon, saveFavicon } from "@/actions/save-favicon";
|
||||
import { useConfirmDialog } from "@/components/admin/confirm-dialog";
|
||||
import { FaviconGenerator } from "./favicon-generator";
|
||||
|
||||
export function FaviconForm({ currentUrl }: { currentUrl: string | null }) {
|
||||
const t = useTranslations("pages.admin.favicon");
|
||||
const { confirm, dialog: confirmDialog } = useConfirmDialog();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [preview, setPreview] = useState<string | null>(currentUrl);
|
||||
@@ -33,7 +35,13 @@ export function FaviconForm({ currentUrl }: { currentUrl: string | null }) {
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(t("confirmDelete"))) return;
|
||||
const ok = await confirm({
|
||||
title: t("delete"),
|
||||
description: t("confirmDelete"),
|
||||
confirmLabel: t("delete"),
|
||||
cancelLabel: t("cancel"),
|
||||
});
|
||||
if (!ok) return;
|
||||
setDeleting(true);
|
||||
setMessage(null);
|
||||
const result = await deleteFavicon();
|
||||
@@ -66,6 +74,7 @@ export function FaviconForm({ currentUrl }: { currentUrl: string | null }) {
|
||||
|
||||
return (
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
{confirmDialog}
|
||||
<div className="admin-card">
|
||||
<h3 className="text-sm font-extrabold text-[var(--admin-text)] mb-4">
|
||||
{t("uploadLabel")}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -10,6 +10,7 @@ import {
|
||||
updatePrefix,
|
||||
updatePrefixSettings,
|
||||
} from "@/actions/prefixes";
|
||||
import { useConfirmDialog } from "@/components/admin/confirm-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -315,6 +316,7 @@ function PrefixDialog({
|
||||
|
||||
export function PrefixesClient({ canEdit }: { canEdit: boolean }) {
|
||||
const { run, isPending } = useServerAction();
|
||||
const { confirm, dialog: confirmDialog } = useConfirmDialog();
|
||||
const [prefixes, setPrefixes] = useState<UserPrefix[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -370,7 +372,13 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) {
|
||||
}, [fetchBlacklist, fetchSettings]);
|
||||
|
||||
const handleDeletePrefix = async (id: number) => {
|
||||
if (!confirm("Are you sure you want to delete this prefix?")) return;
|
||||
const ok = await confirm({
|
||||
title: "Delete prefix",
|
||||
description: "Are you sure you want to delete this prefix?",
|
||||
confirmLabel: "Delete",
|
||||
cancelLabel: "Cancel",
|
||||
});
|
||||
if (!ok) return;
|
||||
run(() => deletePrefixAction({ id }), {
|
||||
successMessage: "Prefix deleted!",
|
||||
onSuccess: () => {
|
||||
@@ -463,6 +471,7 @@ export function PrefixesClient({ canEdit }: { canEdit: boolean }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{confirmDialog}
|
||||
<PrefixDialog
|
||||
editPrefix={editPrefix}
|
||||
isOpen={dialogOpen}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1943,10 +1943,18 @@
|
||||
"wordfilter": {
|
||||
"title": "Word Filter",
|
||||
"subtitle": "Manage filtered words and phrases in the hotel",
|
||||
"filteredWords": "Filtered words",
|
||||
"addWord": "Add word",
|
||||
"addWords": "Add words",
|
||||
"words": "Words",
|
||||
"noWords": "No filtered words yet",
|
||||
"delete": "Delete",
|
||||
"confirmDeleteTitle": "Remove word",
|
||||
"confirmDelete": "Remove \"{word}\" from the filter?",
|
||||
"added": "Word added to filter",
|
||||
"deleted": "Word removed from filter",
|
||||
"addError": "Could not add word (it may already exist)",
|
||||
"deleteError": "Could not remove word",
|
||||
"colId": "ID",
|
||||
"colWord": "Word",
|
||||
"colAddedBy": "Added by",
|
||||
@@ -2217,6 +2225,9 @@
|
||||
"uploading": "Uploading…",
|
||||
"delete": "Remove",
|
||||
"deleting": "Removing…",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Favicon saved successfully!",
|
||||
"deleted": "Favicon removed successfully!",
|
||||
"error": "Something went wrong",
|
||||
"confirmDelete": "Remove the favicon? The site will no longer show a custom favicon.",
|
||||
"generator": {
|
||||
|
||||
@@ -1790,10 +1790,18 @@
|
||||
"wordfilter": {
|
||||
"title": "Filtro parole",
|
||||
"subtitle": "Gestisci parole e frasi filtrate nell'hotel",
|
||||
"filteredWords": "Parole filtrate",
|
||||
"addWord": "Aggiungi parola",
|
||||
"addWords": "Aggiungi parole",
|
||||
"words": "Parole",
|
||||
"noWords": "Ancora nessuna parola filtrata",
|
||||
"delete": "Elimina",
|
||||
"confirmDeleteTitle": "Rimuovi parola",
|
||||
"confirmDelete": "Rimuovere \"{word}\" dal filtro?",
|
||||
"added": "Parola aggiunta al filtro",
|
||||
"deleted": "Parola rimossa dal filtro",
|
||||
"addError": "Impossibile aggiungere la parola (potrebbe già esistere)",
|
||||
"deleteError": "Impossibile rimuovere la parola",
|
||||
"colId": "ID",
|
||||
"colWord": "Parola",
|
||||
"colAddedBy": "Aggiunto da",
|
||||
@@ -2193,6 +2201,7 @@
|
||||
"uploading": "Uploading…",
|
||||
"delete": "Remove",
|
||||
"deleting": "Removing…",
|
||||
"cancel": "Annulla",
|
||||
"saved": "Favicon saved successfully!",
|
||||
"deleted": "Favicon removed successfully!",
|
||||
"error": "Something went wrong",
|
||||
|
||||
Reference in new issue
Block a user