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

View File

@ -17,13 +17,14 @@ import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { DualOptionSelector } from "@/components/dualSelector"; import { DualOptionSelector } from "@/components/dualSelector";
import { MultipleOptionSelector } from "@/components/multipleSelector"; import { MultipleOptionSelector } from "@/components/multipleSelector";
import BusinessForm from "@/components/BusinessForm";
export default function Apply() { export default function Apply() {
const [industry, setIndustry] = useState<string[]>([]); const [industry, setIndustry] = useState<string[]>([]);
const [isInUS, setIsInUS] = useState(""); // const [isInUS, setIsInUS] = useState("");
const [isForSale, setIsForSale] = useState(""); // const [isForSale, setIsForSale] = useState("");
const [isGenerating, setIsGenerating] = useState(""); // const [isGenerating, setIsGenerating] = useState("");
const [businessPitch, setBusinessPitch] = useState("text"); // const [businessPitch, setBusinessPitch] = useState("text");
const [projectType, setProjectType] = useState<string[]>([]); const [projectType, setProjectType] = useState<string[]>([]);
const [projectPitch, setProjectPitch] = useState("text"); const [projectPitch, setProjectPitch] = useState("text");
const [applyProject, setApplyProject] = useState(false); const [applyProject, setApplyProject] = useState(false);
@ -134,58 +135,58 @@ export default function Apply() {
message: "Deadline must be in the future.", message: "Deadline must be in the future.",
}), }),
}); });
const businessFormSchema = z.object({ // const businessFormSchema = z.object({
companyName: z.string().min(5, { // companyName: z.string().min(5, {
message: "Company name must be at least 5 characters.", // message: "Company name must be at least 5 characters.",
}), // }),
industry: z.string({ // industry: z.string({
required_error: "Please select one of the option", // required_error: "Please select one of the option",
}), // }),
isInUS: z // isInUS: z
.string({ // .string({
required_error: "Please select either 'Yes' or 'No'.", // required_error: "Please select either 'Yes' or 'No'.",
}) // })
.transform((val: string) => val.toLowerCase()) // .transform((val: string) => val.toLowerCase())
.refine((val: string) => val === "yes" || val === "no", { // .refine((val: string) => val === "yes" || val === "no", {
message: "Please select either 'Yes' or 'No'.", // message: "Please select either 'Yes' or 'No'.",
}), // }),
isForSale: z // isForSale: z
.string({ // .string({
required_error: "Please select either 'Yes' or 'No'.", // required_error: "Please select either 'Yes' or 'No'.",
}) // })
.transform((val: string) => val.toLowerCase()) // .transform((val: string) => val.toLowerCase())
.refine((val: string) => val === "yes" || val === "no", { // .refine((val: string) => val === "yes" || val === "no", {
message: "Please select either 'Yes' or 'No'.", // message: "Please select either 'Yes' or 'No'.",
}), // }),
isGenerating: z // isGenerating: z
.string({ // .string({
required_error: "Please select either 'Yes' or 'No'.", // required_error: "Please select either 'Yes' or 'No'.",
}) // })
.transform((val: string) => val.toLowerCase()) // .transform((val: string) => val.toLowerCase())
.refine((val: string) => val === "yes" || val === "no", { // .refine((val: string) => val === "yes" || val === "no", {
message: "Please select either 'Yes' or 'No'.", // message: "Please select either 'Yes' or 'No'.",
}), // }),
totalRaised: z // totalRaised: z
.number({ // .number({
required_error: "Total raised must be a number.", // required_error: "Total raised must be a number.",
invalid_type_error: "Total raised must be a valid number.", // invalid_type_error: "Total raised must be a valid number.",
}) // })
.positive() // .positive()
.max(9999999999, "Total raised must be a realistic amount."), // .max(9999999999, "Total raised must be a realistic amount."),
communitySize: z.string({ // communitySize: z.string({
required_error: "Please select one of the option", // required_error: "Please select one of the option",
}), // }),
businessPitchDeck: createPitchDeckSchema(businessPitch), // businessPitchDeck: createPitchDeckSchema(businessPitch),
}); // });
let supabase = createSupabaseClient(); let supabase = createSupabaseClient();
const { // const {
register, // register,
handleSubmit, // handleSubmit,
setValue: setValueBusiness, // setValue: setValueBusiness,
formState: { errors: errorsBusiness }, // formState: { errors: errorsBusiness },
} = useForm({ // } = useForm({
resolver: zodResolver(businessFormSchema), // resolver: zodResolver(businessFormSchema),
}); // });
const { const {
register: registerSecondForm, register: registerSecondForm,
handleSubmit: handleSecondSubmit, handleSubmit: handleSecondSubmit,
@ -205,12 +206,12 @@ export default function Apply() {
"100K+", "100K+",
]; ];
useEffect(() => { // useEffect(() => {
register("industry"); // register("industry");
register("isInUS"); // register("isInUS");
register("isForSale"); // register("isForSale");
register("isGenerating"); // register("isGenerating");
}, [register]); // }, [register]);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) { if (event.target.files) {
@ -264,26 +265,26 @@ export default function Apply() {
); );
return transformedData; return transformedData;
}; };
const handleBusinessPitchChange = (type: string) => { // const handleBusinessPitchChange = (type: string) => {
setBusinessPitch(type); // setBusinessPitch(type);
// clear out old data // // clear out old data
setValueBusiness("pitchDeck", ""); // setValueBusiness("pitchDeck", "");
}; // };
const handleBusinessFieldChange = (fieldName: string, value: any) => { // const handleBusinessFieldChange = (fieldName: string, value: any) => {
switch (fieldName) { // switch (fieldName) {
case "isInUS": // case "isInUS":
setIsInUS(value); // setIsInUS(value);
break; // break;
case "isForSale": // case "isForSale":
setIsForSale(value); // setIsForSale(value);
break; // break;
case "isGenerating": // case "isGenerating":
setIsGenerating(value); // setIsGenerating(value);
break; // break;
} // }
setValueBusiness(fieldName, value); // setValueBusiness(fieldName, value);
}; // };
const handleProjectFieldChange = (fieldName: string, value: any) => { const handleProjectFieldChange = (fieldName: string, value: any) => {
switch (fieldName) { switch (fieldName) {
} }
@ -306,8 +307,8 @@ export default function Apply() {
}; };
const onSubmitSingleForm = (data: any) => { const onSubmitSingleForm = (data: any) => {
const pitchDeckSchema = createPitchDeckSchema(businessPitch); // const pitchDeckSchema = createPitchDeckSchema(businessPitch);
pitchDeckSchema.parse(data.businessPitchDeck); // pitchDeckSchema.parse(data.businessPitchDeck);
console.log("Valid form input:", data); console.log("Valid form input:", data);
alert(JSON.stringify(data)); alert(JSON.stringify(data));
}; };
@ -371,293 +372,31 @@ export default function Apply() {
</div> </div>
</div> </div>
{/* form */} {/* form */}
<form action="" onSubmit={handleSubmit(handleSubmitForms)}> {/* <form action="" onSubmit={handleSubmit(handleSubmitForms)}> */}
<div className="grid grid-flow-row auto-rows-max w-3/4 ml-1/2 lg:ml-[10%]"> <BusinessForm onSubmit={onSubmitSingleForm} industry={industry} />
<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.
</p>
{/* company name */}
<div className="ml-96 mt-5 space-y-10">
<div className="mt-10 space-y-5">
<Label htmlFor="companyName" className="font-bold text-lg">
Company name
</Label>
<div className="flex space-x-5">
<Input
type="text"
id="companyName"
className="w-96"
{...register("companyName")}
/>
<span className="text-[12px] text-neutral-500 self-center">
This should be the name your company uses on your <br />
website and in the market.
</span>
</div>
{errorsBusiness.companyName && (
<p className="text-red-500 text-sm">
{errorsBusiness.companyName && (
<p className="text-red-500 text-sm">
{errorsBusiness.companyName.message as string}
</p>
)}
</p>
)}
</div>
{/* industry */}
<MultipleOptionSelector
header={<>Industry</>}
fieldName="industry"
choices={industry}
handleFunction={handleBusinessFieldChange}
description={
<>Choose the industry that best aligns with your business.</>
}
placeholder="Select an industry"
selectLabel="Industry"
/>
{errorsBusiness.industry && (
<p className="text-red-500 text-sm">
{errorsBusiness.industry.message as string}
</p>
)}
{/* How much money has your company raised to date? */}
<div className="space-y-5">
<Label htmlFor="totalRaised" className="font-bold text-lg">
How much money has your company <br /> raised to date?
</Label>
<div className="flex space-x-5">
<Input
type="number"
id="totalRaised"
className="w-96"
placeholder="$ 1,000,000"
{...register("totalRaised", {
valueAsNumber: true,
})}
/>
<span className="text-[12px] text-neutral-500 self-center">
The sum total of past financing, including angel or venture{" "}
<br />
capital, loans, grants, or token sales.
</span>
</div>
{errorsBusiness.totalRaised && (
<p className="text-red-500 text-sm">
{errorsBusiness.totalRaised.message as string}
</p>
)}
</div>
{/* Is your company incorporated in the United States? */}
<DualOptionSelector
label={
<>
Is your company incorporated in the <br />
United States?
</>
}
name="isInUS"
choice1="Yes"
choice2="No"
handleFunction={handleBusinessFieldChange}
description={
<>
Only companies that are incorporated or formed in the US are{" "}
<br />
eligible to raise via Reg CF. If your company is incorporated{" "}
<br />
outside the US, we still encourage you to apply.
</>
}
value={isInUS}
/>
{errorsBusiness.isInUS && (
<p className="text-red-500 text-sm">
{errorsBusiness.isInUS.message as string}
</p>
)}
{/* Is your product available (for sale) in market? */}
<DualOptionSelector
label={
<>
Is your product available (for sale) <br />
in market?
</>
}
name="isForSale"
choice1="Yes"
choice2="No"
handleFunction={handleBusinessFieldChange}
description={
<>
Only check this box if customers can access, use, or buy your{" "}
<br />
product today.
</>
}
value={isForSale}
/>
{errorsBusiness.isForSale && (
<p className="text-red-500 text-sm">
{errorsBusiness.isForSale.message as string}
</p>
)}
{/* Is your company generating revenue?*/}
<DualOptionSelector
label={<>Is your company generating revenue?</>}
name="isGenerating"
choice1="Yes"
choice2="No"
handleFunction={handleBusinessFieldChange}
description={
<>
Only check this box if your company is making money. <br />
Please elaborate on revenue and other traction below.
</>
}
value={isGenerating}
/>
{errorsBusiness.isGenerating && (
<p className="text-red-500 text-sm">
{errorsBusiness.isGenerating.message as string}
</p>
)}
{/* Pitch deck */}
<div className="space-y-5">
<Label htmlFor="pitchDeck" className="font-bold text-lg">
Pitch deck
</Label>
<div className="flex space-x-2 w-96">
<Button
type="button"
variant={businessPitch === "text" ? "default" : "outline"}
onClick={() => handleBusinessPitchChange("text")}
className="w-32 h-12 text-base"
>
Paste URL
</Button>
<Button
type="button"
variant={businessPitch === "file" ? "default" : "outline"}
onClick={() => handleBusinessPitchChange("file")}
className="w-32 h-12 text-base"
>
Upload a file
</Button>
</div>
<div className="flex space-x-5">
<Input
type={businessPitch === "file" ? "file" : "text"}
id="pitchDeck"
className="w-96"
placeholder={
businessPitch === "file"
? "Upload your Markdown file"
: "https:// "
}
accept={businessPitch === "file" ? ".md" : undefined}
// if text use normal register
{...(businessPitch === "text"
? register("businessPitchDeck", { required: true })
: {
onChange: (e) => {
const file = e.target.files?.[0];
setValueBusiness("businessPitchDeck", file);
setBusinessPitchFile(file?.name || "");
},
})}
/>
<span className="text-[12px] text-neutral-500 self-center">
Your pitch deck and other application info will be used for{" "}
<br />
internal purposes only. <br />
Please make sure this document is publicly accessible. This
can <br />
be a DocSend, Box, Dropbox, Google Drive or other link.
<br />
<p className="text-red-500">
** support only markdown(.md) format
</p>
</span>
</div>
{/* box to show file name */}
{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>
{errorsBusiness.businessPitchDeck && (
<p className="text-red-500 text-sm">
{errorsBusiness.businessPitchDeck.message as string}
</p>
)}
<MultipleOptionSelector
header={
<>
What's the rough size of your <br /> community?
</>
}
fieldName="communitySize"
choices={communitySize}
handleFunction={handleBusinessFieldChange}
description={
<>
{" "}
Include your email list, social media following (i.e.
Instagram, <br /> Discord, Facebook, Twitter, TikTok). Wed
like to understand the <br /> rough size of your current
audience.
</>
}
placeholder="Select"
selectLabel="Select"
/>
{errorsBusiness.communitySize && (
<p className="text-red-500 text-sm">
{errorsBusiness.communitySize.message as string}
</p>
)}
<div className="flex space-x-5"> <div className="flex space-x-5">
<Switch <Switch onCheckedChange={() => setApplyProject(!applyProject)}></Switch>
onCheckedChange={() => setApplyProject(!applyProject)}
></Switch>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span className="text-[12px] text-neutral-500 self-center cursor-pointer"> <span className="text-[12px] text-neutral-500 self-center cursor-pointer">
Would you like to apply for your first fundraising project Would you like to apply for your first fundraising project as
as well? well?
</span> </span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p className="text-[11px]"> <p className="text-[11px]">
Toggling this option allows you to begin your first Toggling this option allows you to begin your first project,{" "}
project, <br /> which is crucial for unlocking the tools <br /> which is crucial for unlocking the tools necessary to
necessary to raise funds. raise funds.
</p> </p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</div> </div>
</div> {/* </div>
</div> </div> */}
{/* apply first project */} {/* apply first project */}
{applyProject && ( {applyProject && (
@ -693,17 +432,15 @@ export default function Apply() {
</p> </p>
)} )}
{/* project type */} {/* project type */}
<MultipleOptionSelector {/* <MultipleOptionSelector
header={<>Project type</>} header={<>Project type</>}
fieldName="projectType" fieldName="projectType"
choices={projectType} choices={projectType}
handleFunction={handleProjectFieldChange} // handleFunction={handleProjectFieldChange}
description={ description={<>Please specify the primary purpose of the funds</>}
<>Please specify the primary purpose of the funds</>
}
placeholder="Select a Project type" placeholder="Select a Project type"
selectLabel="Project type" selectLabel="Project type"
/> /> */}
{errorsProject.projectType && ( {errorsProject.projectType && (
<p className="text-red-500 text-sm"> <p className="text-red-500 text-sm">
{errorsProject.projectType.message as string} {errorsProject.projectType.message as string}
@ -807,10 +544,7 @@ export default function Apply() {
)} )}
{/* project logo */} {/* project logo */}
<div className="mt-10 space-y-5"> <div className="mt-10 space-y-5">
<Label <Label htmlFor="projectLogo" className="font-bold text-lg mt-10">
htmlFor="projectLogo"
className="font-bold text-lg mt-10"
>
Project logo Project logo
</Label> </Label>
<div className="flex space-x-5"> <div className="flex space-x-5">
@ -857,8 +591,7 @@ export default function Apply() {
})} })}
/> />
<span className="text-[12px] text-neutral-500 self-center"> <span className="text-[12px] text-neutral-500 self-center">
Feel free to upload any additional images that provide{" "} Feel free to upload any additional images that provide <br />
<br />
further insight into your project. further insight into your project.
</span> </span>
</div> </div>
@ -951,8 +684,7 @@ export default function Apply() {
/> />
<span className="text-[12px] text-neutral-500 self-center"> <span className="text-[12px] text-neutral-500 self-center">
What is the deadline for your fundraising project? Setting{" "} What is the deadline for your fundraising project? Setting{" "}
<br /> a clear timeline can help motivate potential <br /> a clear timeline can help motivate potential investors.
investors.
</span> </span>
</div> </div>
</div> </div>
@ -973,7 +705,7 @@ export default function Apply() {
Submit application Submit application
</Button> </Button>
</center> </center>
</form> {/* </form> */}
</div> </div>
); );
} }

View File

@ -1,64 +1,97 @@
import { useState } from "react"; import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { DualOptionSelector } from "@/components/dualSelector"; import { DualOptionSelector } from "@/components/dualSelector";
import { MultipleOptionSelector } from "@/components/multipleSelector"; import { MultipleOptionSelector } from "@/components/multipleSelector";
import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; import {
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; 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 { Input } from "@/components/ui/input";
import { format } from "path";
import { businessFormSchema } from "@/types/schemas/application.schema"; import { businessFormSchema } from "@/types/schemas/application.schema";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
type businessSchema = z.infer<typeof businessFormSchema>; type businessSchema = z.infer<typeof businessFormSchema>;
const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> }) => { interface BusinessFormProps {
const communitySize = ["N/A", "0-5K", "5-10K", "10-20K", "20-50K", "50-100K", "100K+"]; industry: string[];
}
const form = useForm<z.infer<typeof businessFormSchema>>({ 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), resolver: zodResolver(businessFormSchema),
defaultValues: {}, defaultValues: {},
}); });
const [isInUS, setIsInUS] = useState("");
const handleBusinessFieldChange = (fieldName: string, value: any) => { const [isForSale, setIsForSale] = useState("");
switch (fieldName) { const [isGenerating, setIsGenerating] = useState("");
case "isInUS": const [businessPitch, setBusinessPitch] = useState("text");
setIsInUS(value); const [businessPitchFile, setBusinessPitchFile] = useState("");
break; // const handleBusinessFieldChange = (fieldName: string, value: any) => {
case "isForSale": // switch (fieldName) {
setIsForSale(value); // case "isInUS":
break; // setIsInUS(value);
case "isGenerating": // break;
setIsGenerating(value); // case "isForSale":
break; // setIsForSale(value);
} // break;
setValueBusiness(fieldName, value); // case "isGenerating":
}; // setIsGenerating(value);
// break;
// }
// setValueBusiness(fieldName, value);
// };
return ( return (
<Form {...form}> <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%]"> <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> <h1 className="text-3xl font-bold mt-10 ml-96">About your company</h1>
<p className="ml-96 mt-5 text-neutral-500"> <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> </p>
{/* Company Name */} {/* Company Name */}
<FormField <FormField
control={form.control} control={form.control}
name="companyName" name="companyName"
render={({ field }) => ( render={({ field }: { field: any }) => (
<FormItem> <FormItem>
<FormLabel>Company name</FormLabel> <FormLabel>Company name</FormLabel>
<FormControl> <FormControl>
<Input placeholder="Your company name" {...field} /> <Input placeholder="Your company name" {...field} />
</FormControl> </FormControl>
<FormDescription> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -69,7 +102,7 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<FormField <FormField
control={form.control} control={form.control}
name="industry" name="industry"
render={({ field }) => ( render={({ field }: { field: any }) => (
<FormItem> <FormItem>
<FormLabel>Industry</FormLabel> <FormLabel>Industry</FormLabel>
<FormControl> <FormControl>
@ -77,11 +110,16 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
header={<>Industry</>} header={<>Industry</>}
fieldName="industry" fieldName="industry"
choices={industry} choices={industry}
handleFunction={handleBusinessFieldChange} handleFunction={(selectedValues: string) => {
description={<>Choose the industry that best aligns with your business.</>} field.onChange(selectedValues);
}}
description={
<>
Choose the industry that best aligns with your business.
</>
}
placeholder="Select an industry" placeholder="Select an industry"
selectLabel="Industry" selectLabel="Industry"
{...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -95,12 +133,19 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
name="totalRaised" name="totalRaised"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>How much money has your company raised to date?</FormLabel> <FormLabel>
How much money has your company raised to date?
</FormLabel>
<FormControl> <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> </FormControl>
<FormDescription> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -113,19 +158,28 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
name="isInUS" name="isInUS"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Is your company incorporated in the United States?</FormLabel> <FormLabel>
Is your company incorporated in the United States?
</FormLabel>
<FormControl> <FormControl>
<DualOptionSelector <DualOptionSelector
label={<>Is your company incorporated in the United States?</>}
name="isInUS" name="isInUS"
label={
<>Is your company incorporated in the United States?</>
}
choice1="Yes" choice1="Yes"
choice2="No" choice2="No"
handleFunction={handleBusinessFieldChange} handleFunction={(selectedValues: string) => {
setIsInUS;
field.onChange(selectedValues);
}}
description={ 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} value={field.value}
{...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -139,17 +193,26 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
name="isForSale" name="isForSale"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Is your product available (for sale) in market?</FormLabel> <FormLabel>
Is your product available (for sale) in market?
</FormLabel>
<FormControl> <FormControl>
<DualOptionSelector <DualOptionSelector
label={<>Is your product available (for sale) in market?</>}
name="isForSale" name="isForSale"
value={field.value}
label={<>Is your product available (for sale) in market?</>}
choice1="Yes" choice1="Yes"
choice2="No" choice2="No"
handleFunction={handleBusinessFieldChange} handleFunction={(selectedValues: string) => {
description={<>Only check this box if customers can access, use, or buy your product today.</>} setIsForSale;
value={isForSale} field.onChange(selectedValues);
{...field} }}
description={
<>
Only check this box if customers can access, use, or buy
your product today.
</>
}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -166,16 +229,21 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<FormLabel>Is your company generating revenue?</FormLabel> <FormLabel>Is your company generating revenue?</FormLabel>
<FormControl> <FormControl>
<DualOptionSelector <DualOptionSelector
label={<>Is your company generating revenue?</>}
name="isGenerating" name="isGenerating"
label={<>Is your company generating revenue?</>}
choice1="Yes" choice1="Yes"
choice2="No" choice2="No"
handleFunction={handleBusinessFieldChange} value={field.value}
handleFunction={(selectedValues: string) => {
setIsGenerating;
field.onChange(selectedValues);
}}
description={ 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> </FormControl>
<FormMessage /> <FormMessage />
@ -196,22 +264,39 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
type="button" type="button"
variant={businessPitch === "text" ? "default" : "outline"} variant={businessPitch === "text" ? "default" : "outline"}
onClick={() => setBusinessPitch("text")} onClick={() => setBusinessPitch("text")}
className="w-32 h-12 text-base"> className="w-32 h-12 text-base"
>
Paste URL Paste URL
</Button> </Button>
<Button <Button
type="button" type="button"
variant={businessPitch === "file" ? "default" : "outline"} variant={businessPitch === "file" ? "default" : "outline"}
onClick={() => setBusinessPitch("file")} onClick={() => setBusinessPitch("file")}
className="w-32 h-12 text-base"> className="w-32 h-12 text-base"
>
Upload a file Upload a file
</Button> </Button>
</div>
<Input <Input
type={businessPitch === "file" ? "file" : "text"} type={businessPitch === "file" ? "file" : "text"}
placeholder={businessPitch === "file" ? "Upload your Markdown file" : "https:// "} placeholder={
businessPitch === "file"
? "Upload your Markdown file"
: "https:// "
}
accept={businessPitch === "file" ? ".md" : undefined} accept={businessPitch === "file" ? ".md" : undefined}
{...field} // onChange={(e) => {
// if (businessPitch === "file") {
// setValueBusiness(
// "businessPitchDeck",
// e.target.files?.[0] || ""
// );
// } else {
// field.onChange(e);
// }
// }}
value={
businessPitch === "file" ? "" : (field.value as string)
}
/> />
{businessPitchFile && ( {businessPitchFile && (
<div className="flex justify-between items-center border p-2 rounded w-96 text-sm text-foreground"> <div className="flex justify-between items-center border p-2 rounded w-96 text-sm text-foreground">
@ -219,13 +304,15 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<Button <Button
className="ml-4" className="ml-4"
onClick={() => { onClick={() => {
setValueBusiness("businessPitchDeck", null); // setValueBusiness("businessPitchDeck", null);
setBusinessPitchFile(""); setBusinessPitchFile("");
}}> }}
>
Remove Remove
</Button> </Button>
</div> </div>
)} )}
</div>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -244,13 +331,17 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
header={<>What's the rough size of your community?</>} header={<>What's the rough size of your community?</>}
fieldName="communitySize" fieldName="communitySize"
choices={communitySize} choices={communitySize}
handleFunction={handleBusinessFieldChange} handleFunction={(selectedValues: string) => {
field.onChange(selectedValues);
}}
description={ 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" placeholder="Select"
selectLabel="Select" selectLabel="Select"
{...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -259,25 +350,30 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
/> />
{/* Apply for First Fundraising Project */} {/* Apply for First Fundraising Project */}
<FormField {/* <FormField
control={form.control} control={form.control}
name="applyProject" name="applyProject"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<div className="flex space-x-5"> <div className="flex space-x-5">
<Switch onCheckedChange={() => setApplyProject(!applyProject)} {...field} /> <Switch
onCheckedChange={() => setApplyProject(!applyProject)}
{...field}
/>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span className="text-[12px] text-neutral-500 self-center cursor-pointer"> <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> </span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p className="text-[11px]"> <p className="text-[11px]">
Toggling this option allows you to begin your first project, which is crucial for unlocking Toggling this option allows you to begin your first
fundraising tools. project, which is crucial for unlocking fundraising
tools.
</p> </p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
@ -287,10 +383,10 @@ const BusinessForm = ({ onSubmit }: { onSubmit: SubmitHandler<businessSchema> })
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> /> */}
{/* Submit Button */} {/* 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 Continue
</Button> </Button>
</div> </div>

View File

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

View File

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