mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-20 11:41:38 +10:00
refactor(v4.0.0-alpha): beginning of a new era
This commit is contained in:
112
apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx
Normal file
112
apps/client/src/pages/builder/sidebars/left/dialogs/awards.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { awardSchema, defaultAward } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = awardSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const AwardsDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultAward,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="awards" form={form} defaultValues={defaultAward}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="title"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="3rd Runner Up" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="awarder"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Awarder</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="TechCrunch Disrupt SF" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Aug 2019" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://techcrunch.com/events/disrupt-sf-2019" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,112 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { certificationSchema, defaultCertification } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = certificationSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const CertificationsDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultCertification,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="certifications" form={form} defaultValues={defaultCertification}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Web Developer Bootcamp" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="issuer"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Issuer</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Udemy" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Aug 2019" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://udemy.com/certificate/UC-..." />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,195 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { CustomSection, customSectionSchema, defaultCustomSection } from "@reactive-resume/schema";
|
||||
import {
|
||||
Badge,
|
||||
BadgeInput,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
Slider,
|
||||
} from "@reactive-resume/ui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
import { DialogName, useDialog } from "@/client/stores/dialog";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = customSectionSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const CustomSectionDialog = () => {
|
||||
const { payload } = useDialog<CustomSection>("custom");
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultCustomSection,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
if (!payload) return null;
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues>
|
||||
form={form}
|
||||
id={payload.id as DialogName}
|
||||
defaultValues={defaultCustomSection}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="description"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="level"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Level</FormLabel>
|
||||
<FormControl className="py-2">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Slider
|
||||
{...field}
|
||||
min={0}
|
||||
max={5}
|
||||
value={[field.value]}
|
||||
orientation="horizontal"
|
||||
onValueChange={(value) => field.onChange(value[0])}
|
||||
/>
|
||||
|
||||
<span className="text-base font-bold">{field.value}</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="keywords"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2 space-y-3">
|
||||
<FormItem>
|
||||
<FormLabel>Keywords</FormLabel>
|
||||
<FormControl>
|
||||
<BadgeInput {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
You can add multiple keywords by separating them with a comma.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-3">
|
||||
<AnimatePresence>
|
||||
{field.value.map((item, index) => (
|
||||
<motion.div
|
||||
layout
|
||||
key={item}
|
||||
initial={{ opacity: 0, y: -50 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { delay: index * 0.1 } }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
>
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
field.onChange(field.value.filter((v) => item !== v));
|
||||
}}
|
||||
>
|
||||
<span className="mr-1">{item}</span>
|
||||
<X size={12} weight="bold" />
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,140 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultEducation, educationSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = educationSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const EducationDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultEducation,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="education" form={form} defaultValues={defaultEducation}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="institution"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Institution</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Carnegie Mellon University" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="studyType"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Type of Study</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Bachelor's Degree" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="area"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Area of Study</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Computer Science" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="score"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Score</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="9.2 GPA" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Aug 2006 - Oct 2012" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://www.cmu.edu/" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,126 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultExperience, experienceSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = experienceSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const ExperienceDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultExperience,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="experience" form={form} defaultValues={defaultExperience}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="company"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Company</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Alphabet Inc." />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="position"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Position</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Chief Executive Officer" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Dec 2019 - Present" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="location"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Location</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="New York, NY" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://www.abc.xyz/" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { defaultInterest, interestSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
Badge,
|
||||
BadgeInput,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
} from "@reactive-resume/ui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
|
||||
const formSchema = interestSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const InterestsDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultInterest,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="interests" form={form} defaultValues={defaultInterest}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Video Games" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="keywords"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2 space-y-3">
|
||||
<FormItem>
|
||||
<FormLabel>Keywords</FormLabel>
|
||||
<FormControl>
|
||||
<BadgeInput {...field} placeholder="FIFA 23, Call of Duty, etc." />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
You can add multiple keywords by separating them with a comma.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-3">
|
||||
<AnimatePresence>
|
||||
{field.value.map((item, index) => (
|
||||
<motion.div
|
||||
layout
|
||||
key={item}
|
||||
initial={{ opacity: 0, y: -50 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { delay: index * 0.1 } }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
>
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
field.onChange(field.value.filter((v) => item !== v));
|
||||
}}
|
||||
>
|
||||
<span className="mr-1">{item}</span>
|
||||
<X size={12} weight="bold" />
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,85 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultLanguage, languageSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
Slider,
|
||||
} from "@reactive-resume/ui";
|
||||
import { getCEFRLevel } from "@reactive-resume/utils";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
|
||||
const formSchema = languageSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const LanguagesDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultLanguage,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="languages" form={form} defaultValues={defaultLanguage}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="German" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="fluency"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Fluency</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Native Speaker" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="fluencyLevel"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Fluency (CEFR)</FormLabel>
|
||||
<FormControl className="py-2">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Slider
|
||||
{...field}
|
||||
min={1}
|
||||
max={6}
|
||||
value={[field.value]}
|
||||
onValueChange={(value) => field.onChange(value[0])}
|
||||
/>
|
||||
|
||||
<span className="text-base font-bold">{getCEFRLevel(field.value)}</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
112
apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx
Normal file
112
apps/client/src/pages/builder/sidebars/left/dialogs/profiles.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultProfile, profileSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarImage,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = profileSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const ProfilesDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultProfile,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="profiles" form={form} defaultValues={defaultProfile}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="network"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Network</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="LinkedIn" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="username"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="johndoe" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>URL</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://linkedin.com/in/johndoe" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="icon"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel htmlFor="iconSlug">Icon</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Avatar className="h-8 w-8 bg-white">
|
||||
{field.value && (
|
||||
<AvatarImage
|
||||
className="p-1.5"
|
||||
src={`https://cdn.simpleicons.org/${field.value}`}
|
||||
/>
|
||||
)}
|
||||
</Avatar>
|
||||
<Input {...field} id="iconSlug" placeholder="linkedin" />
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription className="ml-10">
|
||||
Powered by{" "}
|
||||
<a
|
||||
href="https://simpleicons.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
className="font-medium"
|
||||
>
|
||||
Simple Icons
|
||||
</a>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
160
apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx
Normal file
160
apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { defaultProject, projectSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
Badge,
|
||||
BadgeInput,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = projectSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const ProjectsDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultProject,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="projects" form={form} defaultValues={defaultProject}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Reactive Resume" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="description"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Open Source Resume Builder" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Sep 2018 - Present" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://rxresu.me" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="keywords"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2 space-y-3">
|
||||
<FormItem>
|
||||
<FormLabel>Keywords</FormLabel>
|
||||
<FormControl>
|
||||
<BadgeInput {...field} placeholder="FIFA 23, Call of Duty, etc." />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
You can add multiple keywords by separating them with a comma.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-3">
|
||||
<AnimatePresence>
|
||||
{field.value.map((item, index) => (
|
||||
<motion.div
|
||||
layout
|
||||
key={item}
|
||||
initial={{ opacity: 0, y: -50 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { delay: index * 0.1 } }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
>
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
field.onChange(field.value.filter((v) => item !== v));
|
||||
}}
|
||||
>
|
||||
<span className="mr-1">{item}</span>
|
||||
<X size={12} weight="bold" />
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,112 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultPublication, publicationSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = publicationSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const PublicationsDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultPublication,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="publications" form={form} defaultValues={defaultPublication}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="The Great Gatsby" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="publisher"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Publisher</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Charles Scribner's Sons" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Release Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="April 10, 1925" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://books.google.com/..." />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,98 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultReference, referenceSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = referenceSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const ReferencesDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultReference,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="references" form={form} defaultValues={defaultReference}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Cosmo Kramer" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="description"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Neighbour" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://linkedin.com/in/cosmo.kramer" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
133
apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx
Normal file
133
apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { defaultSkill, skillSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
Badge,
|
||||
BadgeInput,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
Slider,
|
||||
} from "@reactive-resume/ui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
|
||||
const formSchema = skillSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const SkillsDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultSkill,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="skills" form={form} defaultValues={defaultSkill}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="name"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Content Management" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="description"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Advanced" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="level"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Level</FormLabel>
|
||||
<FormControl className="py-2">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Slider
|
||||
{...field}
|
||||
min={1}
|
||||
max={5}
|
||||
value={[field.value]}
|
||||
orientation="horizontal"
|
||||
onValueChange={(value) => field.onChange(value[0])}
|
||||
/>
|
||||
|
||||
<span className="text-base font-bold">{field.value}</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="keywords"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<div className="col-span-2 space-y-3">
|
||||
<FormItem>
|
||||
<FormLabel>Keywords</FormLabel>
|
||||
<FormControl>
|
||||
<BadgeInput {...field} placeholder="WordPress, Joomla, Webflow etc." />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
You can add multiple keywords by separating them with a comma.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-3">
|
||||
<AnimatePresence>
|
||||
{field.value.map((item, index) => (
|
||||
<motion.div
|
||||
layout
|
||||
key={item}
|
||||
initial={{ opacity: 0, y: -50 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { delay: index * 0.1 } }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
>
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
field.onChange(field.value.filter((v) => item !== v));
|
||||
}}
|
||||
>
|
||||
<span className="mr-1">{item}</span>
|
||||
<X size={12} weight="bold" />
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,126 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultVolunteer, volunteerSchema } from "@reactive-resume/schema";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
Input,
|
||||
RichInput,
|
||||
} from "@reactive-resume/ui";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AiActions } from "@/client/components/ai-actions";
|
||||
|
||||
import { SectionDialog } from "../sections/shared/section-dialog";
|
||||
import { URLInput } from "../sections/shared/url-input";
|
||||
|
||||
const formSchema = volunteerSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const VolunteerDialog = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: defaultVolunteer,
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionDialog<FormValues> id="volunteer" form={form} defaultValues={defaultVolunteer}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormField
|
||||
name="organization"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Organization</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Amnesty International" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="position"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Position</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Recruiter" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="date"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Dec 2016 - Aug 2017" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="location"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Location</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="New York, NY" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="url"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<URLInput {...field} placeholder="https://www.amnesty.org/" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="summary"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 sm:col-span-2">
|
||||
<FormLabel>Summary</FormLabel>
|
||||
<FormControl>
|
||||
<RichInput
|
||||
{...field}
|
||||
content={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
footer={(editor) => (
|
||||
<AiActions value={editor.getText()} onChange={editor.commands.setContent} />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SectionDialog>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user