89 lines
3.0 KiB
TypeScript
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>
|
|
);
|
|
}
|