mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-15 15:26:59 +10:00
Feature: Create a new useFormBlocker hook to block the user from closing a dialog or navigating away from a page if the form is dirty (#2654)
* feat: add useFormBlocker hook for dialog dirty state protection * feat: add useFormBlocker hook for dialog dirty state protection - Create useFormBlocker hook that blocks dialog closing when forms have unsaved changes - Use onPointerDownOutside and onEscapeKeyDown to intercept close attempts - Show confirmation dialog with Leave/Stay options using useConfirm - Integrate with CreateResumeDialog, UpdateResumeDialog, and DuplicateResumeDialog - All strings are translatable via Lingui * Feature: Create a new `useFormBlocker` hook to block the user from closing a dialog or navigating away from a page if the form is dirty.
This commit is contained in:
@@ -14,6 +14,7 @@ import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTit
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { authClient } from "@/integrations/auth/client";
|
||||
import { type DialogProps, useDialogStore } from "../store";
|
||||
|
||||
@@ -45,6 +46,8 @@ const CreateApiKeyForm = ({ setApiKey }: CreateApiKeyFormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = async (values: FormValues) => {
|
||||
const toastId = toast.loading(t`Creating your API key...`);
|
||||
|
||||
@@ -63,7 +66,7 @@ const CreateApiKeyForm = ({ setApiKey }: CreateApiKeyFormProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { authClient } from "@/integrations/auth/client";
|
||||
import { type DialogProps, useDialogStore } from "../store";
|
||||
|
||||
@@ -41,6 +42,8 @@ export function ChangePasswordDialog(_: DialogProps<"auth.change-password">) {
|
||||
},
|
||||
});
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
const toastId = toast.loading(t`Updating your password...`);
|
||||
|
||||
@@ -60,7 +63,7 @@ export function ChangePasswordDialog(_: DialogProps<"auth.change-password">) {
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PasswordIcon />
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { authClient } from "@/integrations/auth/client";
|
||||
import { type DialogProps, useDialogStore } from "../store";
|
||||
|
||||
@@ -32,6 +33,8 @@ export function DisableTwoFactorDialog(_: DialogProps<"auth.two-factor.disable">
|
||||
},
|
||||
});
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
const toastId = toast.loading(t`Disabling two-factor authentication...`);
|
||||
|
||||
@@ -49,7 +52,7 @@ export function DisableTwoFactorDialog(_: DialogProps<"auth.two-factor.disable">
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<LockOpenIcon />
|
||||
|
||||
@@ -16,6 +16,7 @@ import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTit
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { authClient } from "@/integrations/auth/client";
|
||||
import { type DialogProps, useDialogStore } from "../store";
|
||||
|
||||
@@ -55,6 +56,17 @@ export function EnableTwoFactorDialog(_: DialogProps<"auth.two-factor.enable">)
|
||||
},
|
||||
});
|
||||
|
||||
const enableFormState = enableForm.formState;
|
||||
const verifyFormState = verifyForm.formState;
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(enableForm, {
|
||||
shouldBlock: () => {
|
||||
if (step === "enable") return enableFormState.isDirty && !enableFormState.isSubmitting;
|
||||
if (step === "verify") return verifyFormState.isDirty && !verifyFormState.isSubmitting;
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
const onEnableSubmit = async (values: EnableFormValues) => {
|
||||
const toastId = toast.loading(t`Enabling two-factor authentication...`);
|
||||
|
||||
@@ -136,7 +148,7 @@ export function EnableTwoFactorDialog(_: DialogProps<"auth.two-factor.enable">)
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogContent className="max-w-md" {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{match(step)
|
||||
@@ -257,7 +269,7 @@ export function EnableTwoFactorDialog(_: DialogProps<"auth.two-factor.enable">)
|
||||
/>
|
||||
|
||||
<DialogFooter className="gap-x-2">
|
||||
<Button type="button" variant="outline" onClick={closeDialog}>
|
||||
<Button type="button" variant="outline" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTit
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { useAIStore } from "@/integrations/ai/store";
|
||||
import { JSONResumeImporter } from "@/integrations/import/json-resume";
|
||||
import { ReactiveResumeJSONImporter } from "@/integrations/import/reactive-resume-json";
|
||||
@@ -98,6 +99,8 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) {
|
||||
form.setValue("file", file, { shouldDirty: true });
|
||||
};
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = async (values: FormValues) => {
|
||||
if (values.type === "") return;
|
||||
|
||||
@@ -182,7 +185,7 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) {
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<DownloadSimpleIcon />
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { authClient } from "@/integrations/auth/client";
|
||||
import { orpc, type RouterInput } from "@/integrations/orpc/client";
|
||||
import { generateId, generateRandomName, slugify } from "@/utils/string";
|
||||
@@ -56,6 +57,8 @@ export function CreateResumeDialog(_: DialogProps<"resume.create">) {
|
||||
form.setValue("slug", slugify(name), { shouldDirty: true });
|
||||
}, [form, name]);
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
const toastId = toast.loading(t`Creating your resume...`);
|
||||
|
||||
@@ -100,7 +103,7 @@ export function CreateResumeDialog(_: DialogProps<"resume.create">) {
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -165,6 +168,8 @@ export function UpdateResumeDialog({ data }: DialogProps<"resume.update">) {
|
||||
form.setValue("slug", slugify(name), { shouldDirty: true });
|
||||
}, [form, name]);
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
const toastId = toast.loading(t`Updating your resume...`);
|
||||
|
||||
@@ -185,7 +190,7 @@ export function UpdateResumeDialog({ data }: DialogProps<"resume.update">) {
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -234,6 +239,8 @@ export function DuplicateResumeDialog({ data }: DialogProps<"resume.duplicate">)
|
||||
form.setValue("slug", slugify(name), { shouldDirty: true });
|
||||
}, [form, name]);
|
||||
|
||||
const { blockEvents } = useFormBlocker(form);
|
||||
|
||||
const onSubmit = (values: FormValues) => {
|
||||
const toastId = toast.loading(t`Duplicating your resume...`);
|
||||
|
||||
@@ -253,7 +260,7 @@ export function DuplicateResumeDialog({ data }: DialogProps<"resume.duplicate">)
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { awardItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -48,8 +49,10 @@ export function CreateAwardDialog({ data }: DialogProps<"resume.sections.awards.
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -63,7 +66,7 @@ export function CreateAwardDialog({ data }: DialogProps<"resume.sections.awards.
|
||||
<AwardForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -109,8 +112,10 @@ export function UpdateAwardDialog({ data }: DialogProps<"resume.sections.awards.
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -124,7 +129,7 @@ export function UpdateAwardDialog({ data }: DialogProps<"resume.sections.awards.
|
||||
<AwardForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { certificationItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -48,8 +49,10 @@ export function CreateCertificationDialog({ data }: DialogProps<"resume.sections
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -63,7 +66,7 @@ export function CreateCertificationDialog({ data }: DialogProps<"resume.sections
|
||||
<CertificationForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -109,8 +112,10 @@ export function UpdateCertificationDialog({ data }: DialogProps<"resume.sections
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -124,7 +129,7 @@ export function UpdateCertificationDialog({ data }: DialogProps<"resume.sections
|
||||
<CertificationForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { customSectionSchema, type SectionType } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -59,8 +60,10 @@ export function CreateCustomSectionDialog({ data }: DialogProps<"resume.sections
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -74,7 +77,7 @@ export function CreateCustomSectionDialog({ data }: DialogProps<"resume.sections
|
||||
<CustomSectionForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -113,8 +116,10 @@ export function UpdateCustomSectionDialog({ data }: DialogProps<"resume.sections
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -128,7 +133,7 @@ export function UpdateCustomSectionDialog({ data }: DialogProps<"resume.sections
|
||||
<CustomSectionForm isUpdate />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { educationItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -51,8 +52,10 @@ export function CreateEducationDialog({ data }: DialogProps<"resume.sections.edu
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -66,7 +69,7 @@ export function CreateEducationDialog({ data }: DialogProps<"resume.sections.edu
|
||||
<EducationForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -115,8 +118,10 @@ export function UpdateEducationDialog({ data }: DialogProps<"resume.sections.edu
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -130,7 +135,7 @@ export function UpdateEducationDialog({ data }: DialogProps<"resume.sections.edu
|
||||
<EducationForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { experienceItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -49,8 +50,10 @@ export function CreateExperienceDialog({ data }: DialogProps<"resume.sections.ex
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -64,7 +67,7 @@ export function CreateExperienceDialog({ data }: DialogProps<"resume.sections.ex
|
||||
<ExperienceForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -111,8 +114,10 @@ export function UpdateExperienceDialog({ data }: DialogProps<"resume.sections.ex
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -126,7 +131,7 @@ export function UpdateExperienceDialog({ data }: DialogProps<"resume.sections.ex
|
||||
<ExperienceForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { interestItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
@@ -48,8 +49,10 @@ export function CreateInterestDialog({ data }: DialogProps<"resume.sections.inte
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -63,7 +66,7 @@ export function CreateInterestDialog({ data }: DialogProps<"resume.sections.inte
|
||||
<InterestForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -107,8 +110,10 @@ export function UpdateInterestDialog({ data }: DialogProps<"resume.sections.inte
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -122,7 +127,7 @@ export function UpdateInterestDialog({ data }: DialogProps<"resume.sections.inte
|
||||
<InterestForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { languageItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -46,8 +47,10 @@ export function CreateLanguageDialog({ data }: DialogProps<"resume.sections.lang
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -61,7 +64,7 @@ export function CreateLanguageDialog({ data }: DialogProps<"resume.sections.lang
|
||||
<LanguageForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -105,8 +108,10 @@ export function UpdateLanguageDialog({ data }: DialogProps<"resume.sections.lang
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -120,7 +125,7 @@ export function UpdateLanguageDialog({ data }: DialogProps<"resume.sections.lang
|
||||
<LanguageForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group";
|
||||
import { type DialogProps, useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { profileItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
@@ -49,8 +50,10 @@ export function CreateProfileDialog({ data }: DialogProps<"resume.sections.profi
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -64,7 +67,7 @@ export function CreateProfileDialog({ data }: DialogProps<"resume.sections.profi
|
||||
<ProfileForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -109,8 +112,10 @@ export function UpdateProfileDialog({ data }: DialogProps<"resume.sections.profi
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -124,7 +129,7 @@ export function UpdateProfileDialog({ data }: DialogProps<"resume.sections.profi
|
||||
<ProfileForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { projectItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -47,8 +48,10 @@ export function CreateProjectDialog({ data }: DialogProps<"resume.sections.proje
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -62,7 +65,7 @@ export function CreateProjectDialog({ data }: DialogProps<"resume.sections.proje
|
||||
<ProjectForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -107,8 +110,10 @@ export function UpdateProjectDialog({ data }: DialogProps<"resume.sections.proje
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -122,7 +127,7 @@ export function UpdateProjectDialog({ data }: DialogProps<"resume.sections.proje
|
||||
<ProjectForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { publicationItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -48,8 +49,10 @@ export function CreatePublicationDialog({ data }: DialogProps<"resume.sections.p
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -63,7 +66,7 @@ export function CreatePublicationDialog({ data }: DialogProps<"resume.sections.p
|
||||
<PublicationForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -109,8 +112,10 @@ export function UpdatePublicationDialog({ data }: DialogProps<"resume.sections.p
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -124,7 +129,7 @@ export function UpdatePublicationDialog({ data }: DialogProps<"resume.sections.p
|
||||
<PublicationForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { referenceItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -48,8 +49,10 @@ export function CreateReferenceDialog({ data }: DialogProps<"resume.sections.ref
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -63,7 +66,7 @@ export function CreateReferenceDialog({ data }: DialogProps<"resume.sections.ref
|
||||
<ReferenceForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -109,8 +112,10 @@ export function UpdateReferenceDialog({ data }: DialogProps<"resume.sections.ref
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -124,7 +129,7 @@ export function UpdateReferenceDialog({ data }: DialogProps<"resume.sections.ref
|
||||
<ReferenceForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { skillItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
import { cn } from "@/utils/style";
|
||||
@@ -52,8 +53,10 @@ export function CreateSkillDialog({ data }: DialogProps<"resume.sections.skills.
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -67,7 +70,7 @@ export function CreateSkillDialog({ data }: DialogProps<"resume.sections.skills.
|
||||
<SkillForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -113,8 +116,10 @@ export function UpdateSkillDialog({ data }: DialogProps<"resume.sections.skills.
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -128,7 +133,7 @@ export function UpdateSkillDialog({ data }: DialogProps<"resume.sections.skills.
|
||||
<SkillForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { volunteerItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
@@ -48,8 +49,10 @@ export function CreateVolunteerDialog({ data }: DialogProps<"resume.sections.vol
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
@@ -63,7 +66,7 @@ export function CreateVolunteerDialog({ data }: DialogProps<"resume.sections.vol
|
||||
<VolunteerForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -109,8 +112,10 @@ export function UpdateVolunteerDialog({ data }: DialogProps<"resume.sections.vol
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
@@ -124,7 +129,7 @@ export function UpdateVolunteerDialog({ data }: DialogProps<"resume.sections.vol
|
||||
<VolunteerForm />
|
||||
|
||||
<DialogFooter className="sm:col-span-full">
|
||||
<Button variant="ghost" onClick={closeDialog}>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user