Refactor dualSelector and multipleSelector components

This commit is contained in:
THIS ONE IS A LITTLE BIT TRICKY KRUB 2024-10-16 22:18:22 +07:00
parent 01afc6c6fb
commit 25a9f37b19
6 changed files with 1618 additions and 740 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,97 @@
import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { DualOptionSelector } from "@/components/dualSelector";
import { MultipleOptionSelector } from "@/components/multipleSelector";
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import {
Tooltip,
TooltipProvider,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { format } from "path";
import { businessFormSchema } from "@/types/schemas/application.schema";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
type businessSchema = z.infer<typeof businessFormSchema>;
const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> }) => {
const communitySize = ["N/A", "0-5K", "5-10K", "10-20K", "20-50K", "50-100K", "100K+"];
const form = useForm<z.infer<typeof businessFormSchema>>({
interface BusinessFormProps {
industry: string[];
}
const handleSubmit = (values: z.infer<typeof businessFormSchema>) => {
console.table(values);
};
const BusinessForm = ({
onSubmit,
industry,
}: BusinessFormProps & { onSubmit: SubmitHandler<businessSchema> }) => {
const communitySize = [
"N/A",
"0-5K",
"5-10K",
"10-20K",
"20-50K",
"50-100K",
"100K+",
];
const form = useForm<businessSchema>({
resolver: zodResolver(businessFormSchema),
defaultValues: {},
});
const handleBusinessFieldChange = (fieldName: string, value: any) => {
switch (fieldName) {
case "isInUS":
setIsInUS(value);
break;
case "isForSale":
setIsForSale(value);
break;
case "isGenerating":
setIsGenerating(value);
break;
}
setValueBusiness(fieldName, value);
};
const [isInUS, setIsInUS] = useState("");
const [isForSale, setIsForSale] = useState("");
const [isGenerating, setIsGenerating] = useState("");
const [businessPitch, setBusinessPitch] = useState("text");
const [businessPitchFile, setBusinessPitchFile] = useState("");
// const handleBusinessFieldChange = (fieldName: string, value: any) => {
// switch (fieldName) {
// case "isInUS":
// setIsInUS(value);
// break;
// case "isForSale":
// setIsForSale(value);
// break;
// case "isGenerating":
// setIsGenerating(value);
// break;
// }
// setValueBusiness(fieldName, value);
// };
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-8">
<div className="grid grid-flow-row auto-rows-max w-3/4 ml-1/2 lg:ml-[10%]">
<h1 className="text-3xl font-bold mt-10 ml-96">About your company</h1>
<p className="ml-96 mt-5 text-neutral-500">
<span className="text-red-500 font-bold">**</span>All requested information in this section is required.
<span className="text-red-500 font-bold">**</span>All requested
information in this section is required.
</p>
{/* Company Name */}
<FormField
control={form.control}
name="companyName"
render={({ field }) => (
render={({ field }: { field: any }) => (
<FormItem>
<FormLabel>Company name</FormLabel>
<FormControl>
<Input placeholder="Your company name" {...field} />
</FormControl>
<FormDescription>
This should be the name your company uses on your website and in the market.
This should be the name your company uses on your website and
in the market.
</FormDescription>
<FormMessage />
</FormItem>
@ -69,7 +102,7 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<FormField
control={form.control}
name="industry"
render={({ field }) => (
render={({ field }: { field: any }) => (
<FormItem>
<FormLabel>Industry</FormLabel>
<FormControl>
@ -77,11 +110,16 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
header={<>Industry</>}
fieldName="industry"
choices={industry}
handleFunction={handleBusinessFieldChange}
description={<>Choose the industry that best aligns with your business.</>}
handleFunction={(selectedValues: string) => {
field.onChange(selectedValues);
}}
description={
<>
Choose the industry that best aligns with your business.
</>
}
placeholder="Select an industry"
selectLabel="Industry"
{...field}
/>
</FormControl>
<FormMessage />
@ -95,12 +133,19 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
name="totalRaised"
render={({ field }) => (
<FormItem>
<FormLabel>How much money has your company raised to date?</FormLabel>
<FormLabel>
How much money has your company raised to date?
</FormLabel>
<FormControl>
<Input type="number" placeholder="$ 1,000,000" {...field} />
<Input
type="number"
placeholder="$ 1,000,000"
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormDescription>
The sum total of past financing, including angel or venture capital, loans, grants, or token sales.
The sum total of past financing, including angel or venture
capital, loans, grants, or token sales.
</FormDescription>
<FormMessage />
</FormItem>
@ -113,19 +158,28 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
name="isInUS"
render={({ field }) => (
<FormItem>
<FormLabel>Is your company incorporated in the United States?</FormLabel>
<FormLabel>
Is your company incorporated in the United States?
</FormLabel>
<FormControl>
<DualOptionSelector
label={<>Is your company incorporated in the United States?</>}
name="isInUS"
label={
<>Is your company incorporated in the United States?</>
}
choice1="Yes"
choice2="No"
handleFunction={handleBusinessFieldChange}
handleFunction={(selectedValues: string) => {
setIsInUS;
field.onChange(selectedValues);
}}
description={
<>Only companies that are incorporated or formed in the US are eligible to raise via Reg CF.</>
<>
Only companies that are incorporated or formed in the US
are eligible to raise via Reg CF.
</>
}
value={isInUS}
{...field}
value={field.value}
/>
</FormControl>
<FormMessage />
@ -139,17 +193,26 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
name="isForSale"
render={({ field }) => (
<FormItem>
<FormLabel>Is your product available (for sale) in market?</FormLabel>
<FormLabel>
Is your product available (for sale) in market?
</FormLabel>
<FormControl>
<DualOptionSelector
label={<>Is your product available (for sale) in market?</>}
name="isForSale"
value={field.value}
label={<>Is your product available (for sale) in market?</>}
choice1="Yes"
choice2="No"
handleFunction={handleBusinessFieldChange}
description={<>Only check this box if customers can access, use, or buy your product today.</>}
value={isForSale}
{...field}
handleFunction={(selectedValues: string) => {
setIsForSale;
field.onChange(selectedValues);
}}
description={
<>
Only check this box if customers can access, use, or buy
your product today.
</>
}
/>
</FormControl>
<FormMessage />
@ -166,16 +229,21 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<FormLabel>Is your company generating revenue?</FormLabel>
<FormControl>
<DualOptionSelector
label={<>Is your company generating revenue?</>}
name="isGenerating"
label={<>Is your company generating revenue?</>}
choice1="Yes"
choice2="No"
handleFunction={handleBusinessFieldChange}
value={field.value}
handleFunction={(selectedValues: string) => {
setIsGenerating;
field.onChange(selectedValues);
}}
description={
<>Only check this box if your company is making money. Please elaborate on revenue below.</>
<>
Only check this box if your company is making money.
Please elaborate on revenue below.
</>
}
value={isGenerating}
{...field}
/>
</FormControl>
<FormMessage />
@ -196,36 +264,55 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
type="button"
variant={businessPitch === "text" ? "default" : "outline"}
onClick={() => setBusinessPitch("text")}
className="w-32 h-12 text-base">
className="w-32 h-12 text-base"
>
Paste URL
</Button>
<Button
type="button"
variant={businessPitch === "file" ? "default" : "outline"}
onClick={() => setBusinessPitch("file")}
className="w-32 h-12 text-base">
className="w-32 h-12 text-base"
>
Upload a file
</Button>
<Input
type={businessPitch === "file" ? "file" : "text"}
placeholder={
businessPitch === "file"
? "Upload your Markdown file"
: "https:// "
}
accept={businessPitch === "file" ? ".md" : undefined}
// onChange={(e) => {
// if (businessPitch === "file") {
// setValueBusiness(
// "businessPitchDeck",
// e.target.files?.[0] || ""
// );
// } else {
// field.onChange(e);
// }
// }}
value={
businessPitch === "file" ? "" : (field.value as string)
}
/>
{businessPitchFile && (
<div className="flex justify-between items-center border p-2 rounded w-96 text-sm text-foreground">
<span>1. {businessPitchFile}</span>
<Button
className="ml-4"
onClick={() => {
// setValueBusiness("businessPitchDeck", null);
setBusinessPitchFile("");
}}
>
Remove
</Button>
</div>
)}
</div>
<Input
type={businessPitch === "file" ? "file" : "text"}
placeholder={businessPitch === "file" ? "Upload your Markdown file" : "https:// "}
accept={businessPitch === "file" ? ".md" : undefined}
{...field}
/>
{businessPitchFile && (
<div className="flex justify-between items-center border p-2 rounded w-96 text-sm text-foreground">
<span>1. {businessPitchFile}</span>
<Button
className="ml-4"
onClick={() => {
setValueBusiness("businessPitchDeck", null);
setBusinessPitchFile("");
}}>
Remove
</Button>
</div>
)}
</FormControl>
<FormMessage />
</FormItem>
@ -244,13 +331,17 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
header={<>What's the rough size of your community?</>}
fieldName="communitySize"
choices={communitySize}
handleFunction={handleBusinessFieldChange}
handleFunction={(selectedValues: string) => {
field.onChange(selectedValues);
}}
description={
<>Include your email list, social media following (e.g., Instagram, Discord, Twitter).</>
<>
Include your email list, social media following (e.g.,
Instagram, Discord, Twitter).
</>
}
placeholder="Select"
selectLabel="Select"
{...field}
/>
</FormControl>
<FormMessage />
@ -259,25 +350,30 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
/>
{/* Apply for First Fundraising Project */}
<FormField
{/* <FormField
control={form.control}
name="applyProject"
render={({ field }) => (
<FormItem>
<FormControl>
<div className="flex space-x-5">
<Switch onCheckedChange={() => setApplyProject(!applyProject)} {...field} />
<Switch
onCheckedChange={() => setApplyProject(!applyProject)}
{...field}
/>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="text-[12px] text-neutral-500 self-center cursor-pointer">
Would you like to apply for your first fundraising project as well?
Would you like to apply for your first fundraising
project as well?
</span>
</TooltipTrigger>
<TooltipContent>
<p className="text-[11px]">
Toggling this option allows you to begin your first project, which is crucial for unlocking
fundraising tools.
Toggling this option allows you to begin your first
project, which is crucial for unlocking fundraising
tools.
</p>
</TooltipContent>
</Tooltip>
@ -287,10 +383,10 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<FormMessage />
</FormItem>
)}
/>
/> */}
{/* Submit Button */}
<Button type="submit" onClick={handleSubmit(onSubmit)} className="w-52 ml-[45%] my-10">
<Button type="submit" className="w-52 ml-[45%] my-10">
Continue
</Button>
</div>

View File

@ -23,7 +23,7 @@ export function DualOptionSelector(props: SelectorInterface) {
<Button
type="button"
variant={props.value === props.choice1 ? "default" : "outline"}
onClick={() => props.handleFunction(props.name, props.choice1)}
onClick={() => props.handleFunction(props.choice1)}
className="w-20 h-12 text-base"
>
{props.choice1}
@ -31,7 +31,7 @@ export function DualOptionSelector(props: SelectorInterface) {
<Button
type="button"
variant={props.value === props.choice2 ? "default" : "outline"}
onClick={() => props.handleFunction(props.name, props.choice2)}
onClick={() => props.handleFunction(props.choice2)}
className="w-20 h-12 text-base"
>
{props.choice2}

View File

@ -10,17 +10,17 @@ import {
} from "@/components/ui/select";
import { ReactElement } from "react";
interface MultipleOptionSelector {
interface MultipleOptionSelectorProps {
header: ReactElement;
fieldName: string;
choices: string[];
handleFunction: Function;
handleFunction: Function | null;
description: ReactElement;
placeholder: string;
selectLabel: string;
}
export function MultipleOptionSelector(props: MultipleOptionSelector) {
export function MultipleOptionSelector(props: MultipleOptionSelectorProps) {
return (
<div className="mt-10 space-y-5">
<Label htmlFor={props.fieldName} className="font-bold text-lg mt-10">
@ -29,8 +29,10 @@ export function MultipleOptionSelector(props: MultipleOptionSelector) {
<div className="flex space-x-5">
<Select
onValueChange={(value) => {
props.handleFunction(props.fieldName, value);
// console.log(value, props.fieldName);
if (props.handleFunction) {
props.handleFunction(props.fieldName, value);
// console.log(value, props.fieldName);
}
}}
>
<SelectTrigger className="w-96">

178
src/components/ui/form.tsx Normal file
View File

@ -0,0 +1,178 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}