Files
loyaly-merchant/src/features/customers/components/EraseCustomerDialog.tsx
2026-09-25 16:29:41 +05:30

89 lines
3.0 KiB
TypeScript

'use client';
import {useState} from 'react';
import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
import {VStack, HStack} from '@astryxdesign/core/Layout';
import {TextInput} from '@astryxdesign/core/TextInput';
import {Button} from '@astryxdesign/core/Button';
import {Banner} from '@astryxdesign/core/Banner';
import {customerRepository} from '@/features/customers/repositories/customerRepository';
import type {Customer} from '@/features/customers/types/customer';
/**
* Erase a customer — the right-to-be-forgotten path. Manager and above.
*
* Irreversible, so it asks for the customer number to be typed. What it does
* is said plainly: the face and photo are destroyed; the visits stay as
* anonymous footfall; the consent record stays, revoked.
*
* A failure is shown and never softened. The platform's 502 means the photo
* could not be deleted and NOTHING was erased — telling the merchant otherwise
* would leave them believing data is gone that is still there.
*/
export function EraseCustomerDialog({
customer,
onClose,
onErased,
}: {
customer: Customer;
onClose: () => void;
onErased: () => void;
}) {
const expected = customer.ref ?? customer.id;
const [confirm, setConfirm] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
async function submit() {
setBusy(true);
setError(null);
const res = await customerRepository.erase(customer.id);
setBusy(false);
if (!res.ok) {
setError(res.message ?? 'Nothing was erased. Please try again.');
return;
}
onErased();
}
return (
<Dialog
isOpen
onOpenChange={(open) => (open ? undefined : onClose())}
purpose="required"
width={460}
aria-label="Erase customer"
>
<VStack gap={4} width="100%">
<DialogHeader
title={`Erase ${customer.label}?`}
onOpenChange={(open) => (open ? undefined : onClose())}
/>
<Banner
status="error"
title="This cannot be undone"
description="Their face template and photo are deleted permanently, and their name and contact details are removed. Their visits stay as anonymous footfall, and the record of their consent stays, marked withdrawn. The cameras will not re-enrol them as a new customer."
/>
{error ? <Banner status="error" title={error} /> : null}
<TextInput
label="Type the customer number to confirm"
value={confirm}
onChange={setConfirm}
placeholder={expected}
description={`Exactly: ${expected}`}
/>
<HStack gap={2} hAlign="end">
<Button variant="secondary" onClick={onClose} label="Cancel" />
<Button
variant="destructive"
onClick={() => void submit()}
isDisabled={confirm.trim() !== expected || busy}
isLoading={busy}
label="Erase customer"
/>
</HStack>
</VStack>
</Dialog>
);
}