Workflow-App/Workflow/components/DeleteConfirmModal.tsx
sjcoetzeesj-coder 11d0178ab1 feat: commit Workflow app source; move CIPC credentials to env
Adds the full Next.js app (app/, components/, lib/, scripts/, assets/,
docs/, Dockerfile, compose, configs) to version control.

Security:
- CIPC BizPortal customer-code logins (Login 1 + Login 3) now read
  CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE /
  CIPC_LOGIN3_PASSWORD from .env.local instead of source.
- BizPortal personal login in apply-bizportal route reads
  BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD.
- Added .env.example as the committed template.

.gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright
storage_state.json (live CIPC cookies), scratch dirs, client PDFs and
debug screenshots.
2026-10-18 15:21:45 +02:00

109 lines
4.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
// components/DeleteConfirmModal.tsx
// Generic delete confirmation modal.
// Accepts any PocketBase record and calls DELETE on confirm.
// On success: invalidates list cache so the table refreshes.
import { useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { pbDelete } from "@/lib/api";
interface Props {
/** "ysterbaard_directors" | "ysterbaard_companies" */
collection: "ysterbaard_directors" | "ysterbaard_companies";
/** The record to delete — used only for display (id + name fields) */
record: { id: string; surname?: string; first_names?: string; enterprise_name?: string; [key: string]: unknown };
open: boolean;
onClose: () => void;
}
export default function DeleteConfirmModal({ collection, record, open, onClose }: Props) {
const qc = useQueryClient();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
if (!open) return null;
const isDirector = collection === "ysterbaard_directors";
const displayName = isDirector
? `${record.surname ?? ""}, ${record.first_names ?? ""}`.trim()
: (record.enterprise_name as string) ?? record.id;
const handleConfirm = async () => {
setError(null);
setLoading(true);
try {
await pbDelete({ collection, id: record.id });
// Match the actual query keys used by the list pages
// ("companies" / "directors"), not just "pb-list".
await qc.invalidateQueries({ queryKey: ["pb-list", collection] });
await qc.invalidateQueries({ queryKey: ["companies"] });
await qc.invalidateQueries({ queryKey: ["directors"] });
await qc.invalidateQueries({ queryKey: ["company", record.id] });
await qc.invalidateQueries({ queryKey: ["director", record.id] });
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : "Delete failed. Please try again.");
} finally {
setLoading(false);
}
};
return (
<div
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
role="dialog"
aria-modal="true"
aria-label={`Delete ${isDirector ? "director" : "company"}`}
>
<div className="bg-surface border border-border rounded-xl shadow-2xl w-full max-w-sm">
{/* Header */}
<div className="px-6 pt-6 pb-4">
<div className="flex items-start justify-between">
<div>
<h2 className="text-lg font-bold text-fg">Delete {isDirector ? "director" : "company"}?</h2>
<p className="text-sm text-muted mt-1">
This will permanently delete <strong className="text-fg">{displayName}</strong> from PocketBase. This action cannot be undone.
</p>
</div>
<button
onClick={onClose}
className="ml-4 text-muted hover:text-fg text-xl leading-none p-1 rounded transition-colors"
aria-label="Close"
>
×
</button>
</div>
</div>
{/* Body */}
<div className="px-6 pb-6">
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3 text-sm text-red-400 mb-4">
{error}
</div>
)}
<div className="flex gap-3">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2.5 rounded-lg border border-border bg-surface-warm text-sm font-semibold text-fg hover:bg-surface transition-colors"
>
Cancel
</button>
<button
type="button"
onClick={handleConfirm}
disabled={loading}
className="flex-1 px-4 py-2.5 rounded-lg bg-red-600 text-white text-sm font-bold hover:bg-red-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
>
{loading ? "Deleting..." : "Yes, delete"}
</button>
</div>
</div>
</div>
</div>
);
}