mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-26 09:54:43 +10:00
Feature: Implement Cover Letters as a custom section type (#2659)
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { PencilSimpleLineIcon, PlusIcon } from "@phosphor-icons/react";
|
||||
import { useForm, useFormContext } from "react-hook-form";
|
||||
import type z from "zod";
|
||||
import { RichInput } from "@/components/input/rich-input";
|
||||
import { useResumeStore } from "@/components/resume/store/resume";
|
||||
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 type { DialogProps } from "@/dialogs/store";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { useFormBlocker } from "@/hooks/use-form-blocker";
|
||||
import { coverLetterItemSchema } from "@/schema/resume/data";
|
||||
import { generateId } from "@/utils/string";
|
||||
|
||||
const formSchema = coverLetterItemSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export function CreateCoverLetterDialog({ data }: DialogProps<"resume.sections.cover-letter.create">) {
|
||||
const closeDialog = useDialogStore((state) => state.closeDialog);
|
||||
const updateResumeData = useResumeStore((state) => state.updateResumeData);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: generateId(),
|
||||
hidden: data?.item?.hidden ?? false,
|
||||
recipient: data?.item?.recipient ?? "",
|
||||
content: data?.item?.content ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (formData: FormValues) => {
|
||||
updateResumeData((draft) => {
|
||||
if (data?.customSectionId) {
|
||||
const section = draft.customSections.find((s) => s.id === data.customSectionId);
|
||||
if (section) section.items.push(formData);
|
||||
}
|
||||
});
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PlusIcon />
|
||||
<Trans>Create a new cover letter</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form className="grid gap-4" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<CoverLetterForm />
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
<Trans>Create</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
export function UpdateCoverLetterDialog({ data }: DialogProps<"resume.sections.cover-letter.update">) {
|
||||
const closeDialog = useDialogStore((state) => state.closeDialog);
|
||||
const updateResumeData = useResumeStore((state) => state.updateResumeData);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data.item.id,
|
||||
hidden: data.item.hidden,
|
||||
recipient: data.item.recipient,
|
||||
content: data.item.content,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (formData: FormValues) => {
|
||||
updateResumeData((draft) => {
|
||||
if (data?.customSectionId) {
|
||||
const section = draft.customSections.find((s) => s.id === data.customSectionId);
|
||||
if (!section) return;
|
||||
const index = section.items.findIndex((item) => item.id === formData.id);
|
||||
if (index !== -1) section.items[index] = formData;
|
||||
}
|
||||
});
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const { blockEvents, requestClose } = useFormBlocker(form);
|
||||
|
||||
return (
|
||||
<DialogContent {...blockEvents}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-x-2">
|
||||
<PencilSimpleLineIcon />
|
||||
<Trans>Update an existing cover letter</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form className="grid gap-4" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<CoverLetterForm />
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={requestClose}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
<Trans>Save Changes</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
function CoverLetterForm() {
|
||||
const form = useFormContext<FormValues>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="recipient"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>Recipient</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput {...field} value={field.value} onChange={field.onChange} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>Content</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput {...field} value={field.value} onChange={field.onChange} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -32,6 +32,7 @@ const SECTION_TYPE_OPTIONS: { value: CustomSectionType; label: string }[] = [
|
||||
{ value: "publications", label: "Publications" },
|
||||
{ value: "volunteer", label: "Volunteer" },
|
||||
{ value: "references", label: "References" },
|
||||
{ value: "cover-letter", label: "Cover Letter" },
|
||||
];
|
||||
|
||||
export function CreateCustomSectionDialog({ data }: DialogProps<"resume.sections.custom.create">) {
|
||||
|
||||
Reference in New Issue
Block a user