mirror of
https://github.com/hanzoai/sign.git
synced 2026-10-21 01:21:51 +00:00
Six delete dialogs ask for a name typed exactly, as z.literal(name, {
errorMap: () => ({ message }) }). zod 4 took errorMap out of a schema's
params — it is `error` now — and ignores what it does not know, so since
the version sweep a wrong entry has shown zod's default, `Invalid input:
expected "delete acme"`, instead of "You must enter 'delete acme' to
proceed". The types caught it: the object literal no longer matched, and the
failed overload left the field's value typed as any literal, which the
input would not take.
The six build the field with createConfirmationSchema(name, message), which
spells it the zod 4 way. confirmation.test.ts checks the exact text passes
and near misses fail with the given message; with the errorMap spelling put
back, the second case fails on zod's default. zod 3 kept the message, so
this is the behaviour from before the sweep.
Co-authored-by: Hanzo Dev <[email protected]>
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { msg } from '@lingui/core/macro';
|
|
import { useLingui } from '@lingui/react';
|
|
import { Trans } from '@lingui/react/macro';
|
|
import { useForm } from 'react-hook-form';
|
|
import { useNavigate } from 'react-router';
|
|
import { z } from 'zod';
|
|
|
|
import { useCurrentOrganisation } from '@hanzo/esign-lib/client-only/providers/organisation';
|
|
import { useSession } from '@hanzo/esign-lib/client-only/providers/session';
|
|
import { AppError } from '@hanzo/esign-lib/errors/app-error';
|
|
import { createConfirmationSchema } from '@hanzo/esign-lib/types/confirmation';
|
|
import { useZapMutation } from '@hanzo/esign-trpc/zap/react';
|
|
import { Button } from '@hanzo/esign-ui/primitives/button';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@hanzo/esign-ui/primitives/dialog';
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from '@hanzo/esign-ui/primitives/form/form';
|
|
import { Input } from '@hanzo/esign-ui/primitives/input';
|
|
import { useToast } from '@hanzo/esign-ui/primitives/use-toast';
|
|
|
|
export type OrganisationDeleteDialogProps = {
|
|
trigger?: React.ReactNode;
|
|
};
|
|
|
|
export const OrganisationDeleteDialog = ({ trigger }: OrganisationDeleteDialogProps) => {
|
|
const navigate = useNavigate();
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const { _ } = useLingui();
|
|
const { toast } = useToast();
|
|
const { refreshSession } = useSession();
|
|
|
|
const organisation = useCurrentOrganisation();
|
|
|
|
const deleteMessage = _(msg`delete ${organisation.name}`);
|
|
|
|
const ZDeleteOrganisationFormSchema = z.object({
|
|
organisationName: createConfirmationSchema(
|
|
deleteMessage,
|
|
_(msg`You must enter '${deleteMessage}' to proceed`),
|
|
),
|
|
});
|
|
|
|
const form = useForm({
|
|
resolver: zodResolver(ZDeleteOrganisationFormSchema),
|
|
defaultValues: {
|
|
organisationName: '',
|
|
},
|
|
});
|
|
|
|
const { mutateAsync: deleteOrganisation } = useZapMutation<void, unknown>('organisation.delete');
|
|
|
|
const onFormSubmit = async () => {
|
|
try {
|
|
await deleteOrganisation({ organisationId: organisation.id });
|
|
|
|
toast({
|
|
title: _(msg`Success`),
|
|
description: _(msg`Your organisation has been successfully deleted.`),
|
|
duration: 5000,
|
|
});
|
|
|
|
await navigate('/settings/organisations');
|
|
await refreshSession();
|
|
|
|
setOpen(false);
|
|
} catch (err) {
|
|
const error = AppError.parseError(err);
|
|
console.error(error);
|
|
|
|
toast({
|
|
title: _(msg`An unknown error occurred`),
|
|
description: _(
|
|
msg`We encountered an unknown error while attempting to delete this organisation. Please try again later.`,
|
|
),
|
|
variant: 'destructive',
|
|
duration: 10000,
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
form.reset();
|
|
}
|
|
}, [open, form]);
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
|
<DialogTrigger asChild>
|
|
{trigger ?? (
|
|
<Button variant="destructive">
|
|
<Trans>Delete</Trans>
|
|
</Button>
|
|
)}
|
|
</DialogTrigger>
|
|
|
|
<DialogContent position="center">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
<Trans>Are you sure you wish to delete this organisation?</Trans>
|
|
</DialogTitle>
|
|
|
|
<DialogDescription>
|
|
<Trans>
|
|
You are about to delete <span className="font-semibold">{organisation.name}</span>.
|
|
All data related to this organisation such as teams, documents, and all other
|
|
resources will be deleted. This action is irreversible.
|
|
</Trans>
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
|
<fieldset
|
|
className="flex h-full flex-col space-y-4"
|
|
disabled={form.formState.isSubmitting}
|
|
>
|
|
<FormField
|
|
control={form.control}
|
|
name="organisationName"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>
|
|
<Trans>
|
|
Confirm by typing <span className="text-destructive">{deleteMessage}</span>
|
|
</Trans>
|
|
</FormLabel>
|
|
<FormControl>
|
|
<Input className="bg-background" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
|
<Trans>Cancel</Trans>
|
|
</Button>
|
|
|
|
<Button type="submit" variant="destructive" loading={form.formState.isSubmitting}>
|
|
<Trans>Delete</Trans>
|
|
</Button>
|
|
</DialogFooter>
|
|
</fieldset>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|