mirror of
https://github.com/hanzoai/sign.git
synced 2026-11-02 23:26:15 +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]>
166 lines
4.9 KiB
TypeScript
166 lines
4.9 KiB
TypeScript
import { useState } from 'react';
|
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { Trans } from '@lingui/react/macro';
|
|
import { useForm } from 'react-hook-form';
|
|
import { z } from 'zod';
|
|
|
|
import { useCurrentOrganisation } from '@hanzo/esign-lib/client-only/providers/organisation';
|
|
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 OrganisationEmailDomainDeleteDialogProps = {
|
|
emailDomainId: string;
|
|
emailDomain: string;
|
|
trigger?: React.ReactNode;
|
|
};
|
|
|
|
export const OrganisationEmailDomainDeleteDialog = ({
|
|
trigger,
|
|
emailDomainId,
|
|
emailDomain,
|
|
}: OrganisationEmailDomainDeleteDialogProps) => {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const { t } = useLingui();
|
|
const { toast } = useToast();
|
|
|
|
const organisation = useCurrentOrganisation();
|
|
|
|
const deleteMessage = t`delete ${emailDomain}`;
|
|
|
|
const ZDeleteEmailDomainFormSchema = z.object({
|
|
confirmText: createConfirmationSchema(
|
|
deleteMessage,
|
|
t`You must type '${deleteMessage}' to confirm`,
|
|
),
|
|
});
|
|
|
|
const form = useForm<z.infer<typeof ZDeleteEmailDomainFormSchema>>({
|
|
resolver: zodResolver(ZDeleteEmailDomainFormSchema),
|
|
defaultValues: {
|
|
confirmText: '',
|
|
},
|
|
});
|
|
|
|
const { mutateAsync: deleteEmailDomain, isPending: isDeleting } = useZapMutation<void, unknown>(
|
|
'organisation.emailDomain.delete',
|
|
{
|
|
onSuccess: () => {
|
|
toast({
|
|
title: t`Success`,
|
|
description: t`You have successfully removed this email domain from the organisation.`,
|
|
duration: 5000,
|
|
});
|
|
|
|
setOpen(false);
|
|
},
|
|
onError: () => {
|
|
toast({
|
|
title: t`An unknown error occurred`,
|
|
description: t`We encountered an unknown error while attempting to remove this email domain. Please try again later.`,
|
|
variant: 'destructive',
|
|
duration: 10000,
|
|
});
|
|
},
|
|
},
|
|
);
|
|
|
|
const onFormSubmit = async () => {
|
|
await deleteEmailDomain({
|
|
emailDomainId,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
|
|
<DialogTrigger asChild>
|
|
{trigger ?? (
|
|
<Button variant="secondary">
|
|
<Trans>Delete email domain</Trans>
|
|
</Button>
|
|
)}
|
|
</DialogTrigger>
|
|
|
|
<DialogContent position="center">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
<Trans>Are you sure?</Trans>
|
|
</DialogTitle>
|
|
|
|
<DialogDescription className="mt-4">
|
|
<Trans>
|
|
You are about to remove the email domain{' '}
|
|
<span className="font-semibold">{emailDomain}</span> from{' '}
|
|
<span className="font-semibold">{organisation.name}</span>. All emails associated with
|
|
this domain will be deleted.
|
|
</Trans>
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
|
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="confirmText"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>
|
|
<Trans>
|
|
Confirm by typing{' '}
|
|
<span className="font-sm text-destructive font-semibold">
|
|
{deleteMessage}
|
|
</span>
|
|
</Trans>
|
|
</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder={deleteMessage} {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
|
<Trans>Cancel</Trans>
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
type="submit"
|
|
disabled={!form.formState.isValid}
|
|
loading={form.formState.isSubmitting}
|
|
>
|
|
<Trans>Delete</Trans>
|
|
</Button>
|
|
</DialogFooter>
|
|
</fieldset>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|