teqo-backup/src/components/campaign/RemoveSupporterDataButton.tsx
Francisco Ignácio Fontoura Solla 9fdbae05d7 Add C6 scale/DRY for supporters: bulk import, KPI aggregate, shared shells.
Phase 1 UX: StrictCombobox municipality in SupporterForm, AlertDialog in
RemoveSupporterDataButton. Phase 2 DRY: campaignListUrl + CampaignListPagination
+ campaignFormFields + mapCampaignFormActionError shared across nuclei,
supporters and action plans. Phase 3: single SQL aggregate for supporter list
KPIs via drizzle in the Payload txn. Phase 4: bulk drizzle insert for CSV import
inside the existing Payload txn with ON CONFLICT DO NOTHING, plus a fail-closed
skipContactPhoneInvariant context flag. Phase 5: HMAC-signed single-use import
token backed by a new supporterImportBatch staging collection so the full ok-row
set never crosses the Server Action boundary twice.
2026-07-18 22:17:31 -03:00

69 lines
2.7 KiB
TypeScript

'use client'
import { useActionState } from 'react'
import type { SupporterRemoveFormState } from '@/app/(campaign)/campanha/(app)/apoiadores/[id]/formActions'
import { removeSupporterDataFormAction } from '@/app/(campaign)/campanha/(app)/apoiadores/[id]/formActions'
import { Alert, AlertDescription } from '@/components/ui/Alert'
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/AlertDialog'
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/Spinner'
export const RemoveSupporterDataButton = ({ supporterId }: { supporterId: number }) => {
const [state, formAction, pending] = useActionState<
SupporterRemoveFormState,
FormData
>(removeSupporterDataFormAction, {})
return (
<section className="rounded-[6px] border border-destructive/30 bg-destructive/5 p-4">
<h2 className="font-medium text-destructive">Remover meus dados</h2>
<p className="mt-1 text-sm text-muted-foreground">
Exercício do direito de eliminação (LGPD art. 18). Remove o cadastro de apoiador e anonimiza
o contato quando não houver outros vínculos.
</p>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button type="button" variant="destructive" className="mt-4 min-h-11">
Remover dados
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Remover os dados deste apoiador?</AlertDialogTitle>
<AlertDialogDescription>
Esta ação não pode ser desfeita. O cadastro de apoiador será removido e o contato
anonimizado quando não houver outros vínculos.
</AlertDialogDescription>
</AlertDialogHeader>
{state.message ? (
<Alert variant="destructive" aria-live="polite">
<AlertDescription>{state.message}</AlertDescription>
</Alert>
) : null}
<form action={formAction}>
<input type="hidden" name="id" value={supporterId} />
<AlertDialogFooter>
<AlertDialogCancel type="button" className="min-h-11" disabled={pending}>
Cancelar
</AlertDialogCancel>
<Button type="submit" variant="destructive" className="min-h-11" disabled={pending}>
{pending ? <Spinner data-icon="inline-start" /> : null}
{pending ? 'Removendo…' : 'Confirmar remoção'}
</Button>
</AlertDialogFooter>
</form>
</AlertDialogContent>
</AlertDialog>
</section>
)
}