Skip to content

Commit

Permalink
build: create new user form
Browse files Browse the repository at this point in the history
  • Loading branch information
Glen Miracle authored and Glen Miracle committed Jul 10, 2024
1 parent 04cd156 commit 779baf4
Show file tree
Hide file tree
Showing 16 changed files with 1,819 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useForm } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ContributorInfoForm } from "../contributor-info-form";
import { PlusIcon } from "@radix-ui/react-icons";

export function AddContributor() {
const [open, setOpen] = React.useState(false);
Expand All @@ -34,11 +35,14 @@ export function AddContributor() {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="bg-teal-900">Add Contributor</Button>
<Button className="bg-teal-900 flex gap-2">
<PlusIcon className="size-6 font-bold" />
Add Contributor
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogTitle>Create New Contributor</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,26 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import CustomFormField, {
FormFieldType,
} from "@/components/forms/CustomFormField";
import { Phone } from "lucide-react";
import { isValidPhoneNumber } from "react-phone-number-input";
import { toast } from "@/components/ui/use-toast";

// form schema
const formSchema = z.object({
firstName: z.string().min(2, { message: "FirstName is required " }).max(50),
lastName: z.string().min(2, { message: "LastName is required" }).max(50),
phoneNumber: z.number().min(20, { message: "Phone number is out of bound" }),
address: z.string().min(20),
phone: z
.string()
.refine(isValidPhoneNumber, { message: "Invalid phone number" }),
address: z.string().min(5, { message: "Address is required" }),
date: z
.date({ required_error: "Date is required" })
.refine((date) => date <= new Date(), {
message: "Date must be in the past",
}),
});

export function ContributorInfoForm() {
Expand All @@ -29,11 +42,20 @@ export function ContributorInfoForm() {
defaultValues: {
firstName: "",
lastName: "",
phone: "",
address: "",
},
});

function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values);
function onSubmit(data: z.infer<typeof formSchema>) {
toast({
title: "New User Values collected:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
});
}

return (
Expand All @@ -42,52 +64,47 @@ export function ContributorInfoForm() {
className={"grid items-start gap-4"}
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
<CustomFormField
fieldType={FormFieldType.INPUT}
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input id="firstName" {...field} />
</FormControl>
<FormMessage>
{form.formState.errors.username?.message}
</FormMessage>
</FormItem>
)}
label="First Name"
placeholder="John"
/>
<FormField

<CustomFormField
fieldType={FormFieldType.INPUT}
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input id="lastName" {...field} />
</FormControl>
<FormMessage>
{form.formState.errors.username?.message}
</FormMessage>
</FormItem>
)}
label="Last name"
placeholder="Doe"
/>
<FormField

<CustomFormField
fieldType={FormFieldType.INPUT}
control={form.control}
name="address"
label="Address"
placeholder="Kigali"
/>

<CustomFormField
fieldType={FormFieldType.PHONE_INPUT}
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input id="username" {...field} />
</FormControl>
<FormMessage>
{form.formState.errors.username?.message}
</FormMessage>
</FormItem>
)}
name="phone"
label="Phone number"
placeholder="(555) 123-4567"
/>
<Button type="submit">Save changes</Button>

<CustomFormField
fieldType={FormFieldType.DATE_PICKER}
control={form.control}
name="date"
label="Date of Birth"
dateFormat="MM/dd/yyyy - h:mm aa"
/>

<Button type="submit">Create User</Button>
</form>
</Form>
);
Expand Down
184 changes: 184 additions & 0 deletions app/components/forms/CustomFormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/* eslint-disable no-unused-vars */
import { E164Number } from "libphonenumber-js/core";
import ReactDatePicker from "react-datepicker";
import { Control } from "react-hook-form";

import { Checkbox } from "@/components/ui/checkbox";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { CalendarIcon, LucideIcon } from "lucide-react";
import { Phone, BoxSelect, Keyboard } from "lucide-react";
import { PhoneInput } from "./atoms/phone-input";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { Button } from "../ui/button";
import { cn } from "@/lib/styles";
import { format } from "date-fns";
import { useState } from "react";
import { Calendar } from "../ui/calendar";

export enum FormFieldType {
INPUT = "input",
TEXTAREA = "textarea",
PHONE_INPUT = "phoneInput",
CHECKBOX = "checkbox",
DATE_PICKER = "datePicker",
SELECT = "select",
SKELETON = "skeleton",
}

interface CustomProps {
control: Control<any>;
name: string;
label?: string;
placeholder?: string;
icon?: LucideIcon; //TODO: change this to a lucide type
iconAlt?: string;
disabled?: boolean;
dateFormat?: string;
showTimeSelect?: boolean;
children?: React.ReactNode;
renderSkeleton?: (field: any) => React.ReactNode;
fieldType: FormFieldType;
}

const RenderInput = ({ field, props }: { field: any; props: CustomProps }) => {
const [date, setDate] = useState<Date | null>(null);
switch (props.fieldType) {
case FormFieldType.INPUT:
return (
<div className="flex rounded-md border border-dark-500">
{props.icon && <props.icon className="ml-2 s-4" />}
<FormControl>
<Input
placeholder={props.placeholder}
{...field}
className="shad-input border-0"
/>
</FormControl>
</div>
);
case FormFieldType.TEXTAREA:
return (
<FormControl>
<Textarea
placeholder={props.placeholder}
{...field}
className="shad-textArea"
disabled={props.disabled}
/>
</FormControl>
);
case FormFieldType.PHONE_INPUT:
return (
<FormControl>
<PhoneInput value={field.value} onChange={field.onChange} />
</FormControl>
);
case FormFieldType.CHECKBOX:
return (
<FormControl>
<div className="flex items-center gap-4">
<Checkbox
id={props.name}
checked={field.value}
onCheckedChange={field.onChange}
/>
<label htmlFor={props.name} className="checkbox-label">
{props.label}
</label>
</div>
</FormControl>
);
case FormFieldType.DATE_PICKER:
return (
<div className="flex rounded-md border border-dark-500 bg-dark-400">
{props.icon && <props.icon className="ml-2 h-4 w-4" />}
<FormControl>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground",
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value}
captionLayout="dropdown-buttons"
onSelect={(selectedDate) => {
setDate(selectedDate);
field.onChange(selectedDate);
}}
initialFocus
fromYear={1999}
toYear={2023}
/>
</PopoverContent>
</Popover>
</FormControl>
</div>
);
case FormFieldType.SELECT:
return (
<FormControl>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger className="shad-select-trigger">
<SelectValue placeholder={props.placeholder} />
</SelectTrigger>
</FormControl>
<SelectContent className="shad-select-content">
{props.children}
</SelectContent>
</Select>
</FormControl>
);
case FormFieldType.SKELETON:
return props.renderSkeleton ? props.renderSkeleton(field) : null;
default:
return null;
}
};

const CustomFormField = (props: CustomProps) => {
const { control, name, label } = props;

return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem className="flex-1">
{props.fieldType !== FormFieldType.CHECKBOX && label && (
<FormLabel className="shad-input-label">{label}</FormLabel>
)}
<RenderInput field={field} props={props} />

<FormMessage className="shad-error" />
</FormItem>
)}
/>
);
};

export default CustomFormField;
Loading

0 comments on commit 779baf4

Please sign in to comment.