mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-19 05:54:06 +01:00
Refactor Apply page component to adjust left margin for company information section and simplify pitchDeckSchema validation
This commit is contained in:
parent
9675a1c491
commit
ec17305e16
@ -19,12 +19,59 @@ import { DualOptionSelector } from "@/components/dualSelector";
|
|||||||
import { MultipleOptionSelector } from "@/components/multipleSelector";
|
import { MultipleOptionSelector } from "@/components/multipleSelector";
|
||||||
|
|
||||||
export default function Apply() {
|
export default function Apply() {
|
||||||
|
const [industry, setIndustry] = useState<string[]>([]);
|
||||||
|
const [isInUS, setIsInUS] = useState("");
|
||||||
|
const [isForSale, setIsForSale] = useState("");
|
||||||
|
const [isGenerating, setIsGenerating] = useState("");
|
||||||
|
const [businessPitch, setBusinessPitch] = useState("text");
|
||||||
|
const [projectType, setProjectType] = useState<string[]>([]);
|
||||||
|
const [projectPitch, setProjectPitch] = useState("text");
|
||||||
|
const [applyProject, setApplyProject] = useState(false);
|
||||||
|
const [selectedImages, setSelectedImages] = useState<File[]>([]);
|
||||||
|
const [businessPitchFile, setBusinessPitchFile] = useState("");
|
||||||
const MAX_FILE_SIZE = 5000000;
|
const MAX_FILE_SIZE = 5000000;
|
||||||
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png"];
|
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png"];
|
||||||
const pitchDeckSchema = z.union([
|
const createPitchDeckSchema = (inputType: string) => {
|
||||||
z.string().url("Pitch deck must be a valid URL."),
|
if (inputType === "text") {
|
||||||
z.object({}),
|
return z
|
||||||
]);
|
.string()
|
||||||
|
.url("Pitch deck must be a valid URL.")
|
||||||
|
.refine((url) => url.endsWith(".md"), {
|
||||||
|
message: "Pitch deck URL must link to a markdown file (.md).",
|
||||||
|
});
|
||||||
|
} else if (inputType === "file") {
|
||||||
|
return z
|
||||||
|
.custom<File>(
|
||||||
|
(val) => {
|
||||||
|
// Confirm val is a File object
|
||||||
|
return val instanceof File; // Ensure it is a File instance
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Input must be a file.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine((file) => file.size < MAX_FILE_SIZE, {
|
||||||
|
message: "File can't be bigger than 5MB.",
|
||||||
|
})
|
||||||
|
.refine((file) => file.name.endsWith(".md"), {
|
||||||
|
message: "File must be a markdown file (.md).",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return z.any(); // avoid undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const projectLogoSchema = z
|
||||||
|
.custom<File>(
|
||||||
|
(val) => val && typeof val === "object" && "size" in val && "type" in val,
|
||||||
|
{ message: "Input must be a file." }
|
||||||
|
)
|
||||||
|
.refine((file) => file.size < MAX_FILE_SIZE, {
|
||||||
|
message: "File can't be bigger than 5MB.",
|
||||||
|
})
|
||||||
|
.refine((file) => ACCEPTED_IMAGE_TYPES.includes(file.type), {
|
||||||
|
message: "File format must be either jpg, jpeg, or png.",
|
||||||
|
});
|
||||||
|
|
||||||
const projectFormSchema = z.object({
|
const projectFormSchema = z.object({
|
||||||
projectName: z.string().min(5, {
|
projectName: z.string().min(5, {
|
||||||
message: "Project name must be at least 5 characters.",
|
message: "Project name must be at least 5 characters.",
|
||||||
@ -39,33 +86,26 @@ export default function Apply() {
|
|||||||
.min(10, {
|
.min(10, {
|
||||||
message: "Short description must be at least 10 characters.",
|
message: "Short description must be at least 10 characters.",
|
||||||
}),
|
}),
|
||||||
projectPitchDeck: pitchDeckSchema,
|
projectPitchDeck: createPitchDeckSchema(projectPitch),
|
||||||
projectLogo: z
|
projectLogo: projectLogoSchema,
|
||||||
.instanceof(File)
|
|
||||||
.refine((file) => ACCEPTED_IMAGE_TYPES.includes(file.type), {
|
|
||||||
message: "Only .jpg, .jpeg, and .png formats are supported.",
|
|
||||||
})
|
|
||||||
.refine((file) => file.size <= MAX_FILE_SIZE, {
|
|
||||||
message: "Max image size is 5MB.",
|
|
||||||
}),
|
|
||||||
|
|
||||||
projectPhotos: z
|
// projectPhotos: z
|
||||||
.array(
|
// .array(
|
||||||
z.object({
|
// z.object({
|
||||||
file: z
|
// file: z
|
||||||
.any()
|
// .any()
|
||||||
.refine(
|
// .refine(
|
||||||
(file) => file?.size <= MAX_FILE_SIZE,
|
// (file) => file?.size <= MAX_FILE_SIZE,
|
||||||
`Max image size is 5MB.`
|
// `Max image size is 5MB.`
|
||||||
)
|
// )
|
||||||
.refine(
|
// .refine(
|
||||||
(file) => ACCEPTED_IMAGE_TYPES.includes(file?.type),
|
// (file) => ACCEPTED_IMAGE_TYPES.includes(file?.type),
|
||||||
"Only .jpg, .jpeg, and .png formats are supported."
|
// "Only .jpg, .jpeg, and .png formats are supported."
|
||||||
),
|
// ),
|
||||||
})
|
// })
|
||||||
)
|
// )
|
||||||
.min(1, "You must upload at least one photo.")
|
// .min(1, "You must upload at least one photo.")
|
||||||
.max(10, "You can upload a maximum of 10 photos."),
|
// .max(10, "You can upload a maximum of 10 photos."),
|
||||||
minInvest: z
|
minInvest: z
|
||||||
.number({
|
.number({
|
||||||
required_error: "Minimum invesment must be a number.",
|
required_error: "Minimum invesment must be a number.",
|
||||||
@ -132,23 +172,12 @@ export default function Apply() {
|
|||||||
communitySize: z.string({
|
communitySize: z.string({
|
||||||
required_error: "Please select one of the option",
|
required_error: "Please select one of the option",
|
||||||
}),
|
}),
|
||||||
businessPitchDeck: pitchDeckSchema,
|
businessPitchDeck: createPitchDeckSchema(businessPitch),
|
||||||
});
|
});
|
||||||
let supabase = createSupabaseClient();
|
let supabase = createSupabaseClient();
|
||||||
|
|
||||||
const [industry, setIndustry] = useState<string[]>([]);
|
|
||||||
const [isInUS, setIsInUS] = useState("");
|
|
||||||
const [isForSale, setIsForSale] = useState("");
|
|
||||||
const [isGenerating, setIsGenerating] = useState("");
|
|
||||||
const [businessPitch, setBusinessPitch] = useState("");
|
|
||||||
const [projectType, setProjectType] = useState<string[]>([]);
|
|
||||||
const [projectPitch, setProjectPitch] = useState("");
|
|
||||||
const [applyProject, setApplyProject] = useState(false);
|
|
||||||
const [selectedImages, setSelectedImages] = useState<File[]>([]);
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
|
||||||
setValue: setValueBusiness,
|
setValue: setValueBusiness,
|
||||||
formState: { errors: errorsBusiness },
|
formState: { errors: errorsBusiness },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
@ -243,7 +272,7 @@ export default function Apply() {
|
|||||||
|
|
||||||
const fetchIndustry = async () => {
|
const fetchIndustry = async () => {
|
||||||
let { data: BusinessType, error } = await supabase
|
let { data: BusinessType, error } = await supabase
|
||||||
.from("BusinessType")
|
.from("business_type")
|
||||||
.select("value");
|
.select("value");
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -257,6 +286,9 @@ export default function Apply() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmitSingleForm = (data: any) => {
|
const onSubmitSingleForm = (data: any) => {
|
||||||
|
const pitchDeckSchema = createPitchDeckSchema(businessPitch); // Ensure you create the schema dynamically
|
||||||
|
pitchDeckSchema.parse(data.businessPitchDeck); // Validate the specific field
|
||||||
|
console.log("Valid form input:", data);
|
||||||
alert(JSON.stringify(data));
|
alert(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -278,7 +310,7 @@ export default function Apply() {
|
|||||||
};
|
};
|
||||||
const fetchProjectType = async () => {
|
const fetchProjectType = async () => {
|
||||||
let { data: ProjectType, error } = await supabase
|
let { data: ProjectType, error } = await supabase
|
||||||
.from("ProjectType")
|
.from("project_type")
|
||||||
.select("value");
|
.select("value");
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -503,9 +535,16 @@ export default function Apply() {
|
|||||||
: "https:// "
|
: "https:// "
|
||||||
}
|
}
|
||||||
accept={businessPitch === "file" ? ".md" : undefined}
|
accept={businessPitch === "file" ? ".md" : undefined}
|
||||||
{...register("businessPitchDeck", { required: true })}
|
{...(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">
|
<span className="text-[12px] text-neutral-500 self-center">
|
||||||
Your pitch deck and other application info will be used for{" "}
|
Your pitch deck and other application info will be used for{" "}
|
||||||
<br />
|
<br />
|
||||||
@ -519,10 +558,25 @@ export default function Apply() {
|
|||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{errorsBusiness.pitchDeck && (
|
{errorsBusiness.businessPitchDeck && (
|
||||||
<p className="text-red-500 text-sm">
|
<p className="text-red-500 text-sm">
|
||||||
{errorsBusiness.pitchDeck.message as string}
|
{errorsBusiness.businessPitchDeck.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<MultipleOptionSelector
|
<MultipleOptionSelector
|
||||||
@ -591,7 +645,6 @@ export default function Apply() {
|
|||||||
to begin your journey and unlock the necessary tools for raising
|
to begin your journey and unlock the necessary tools for raising
|
||||||
funds.
|
funds.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* project's name */}
|
{/* project's name */}
|
||||||
<div className="mt-10 space-y-5">
|
<div className="mt-10 space-y-5">
|
||||||
<Label htmlFor="projectName" className="font-bold text-lg">
|
<Label htmlFor="projectName" className="font-bold text-lg">
|
||||||
@ -611,7 +664,6 @@ export default function Apply() {
|
|||||||
{errorsProject.projectName.message as string}
|
{errorsProject.projectName.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* project type */}
|
{/* project type */}
|
||||||
<MultipleOptionSelector
|
<MultipleOptionSelector
|
||||||
header={<>Project type</>}
|
header={<>Project type</>}
|
||||||
@ -629,7 +681,6 @@ export default function Apply() {
|
|||||||
{errorsProject.projectType.message as string}
|
{errorsProject.projectType.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* short description */}
|
{/* short description */}
|
||||||
<div className="mt-10 space-y-5">
|
<div className="mt-10 space-y-5">
|
||||||
<Label htmlFor="shortDescription" className="font-bold text-lg">
|
<Label htmlFor="shortDescription" className="font-bold text-lg">
|
||||||
@ -652,7 +703,6 @@ export default function Apply() {
|
|||||||
{errorsProject.shortDescription.message as string}
|
{errorsProject.shortDescription.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pitch deck */}
|
{/* Pitch deck */}
|
||||||
<div className="mt-10 space-y-5">
|
<div className="mt-10 space-y-5">
|
||||||
<Label htmlFor="projectPitchDeck" className="font-bold text-lg">
|
<Label htmlFor="projectPitchDeck" className="font-bold text-lg">
|
||||||
@ -705,7 +755,6 @@ export default function Apply() {
|
|||||||
{errorsProject.projectPitchDeck.message as string}
|
{errorsProject.projectPitchDeck.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* project logo */}
|
{/* project logo */}
|
||||||
<div className="mt-10 space-y-5">
|
<div className="mt-10 space-y-5">
|
||||||
<Label
|
<Label
|
||||||
@ -720,7 +769,16 @@ export default function Apply() {
|
|||||||
id="projectLogo"
|
id="projectLogo"
|
||||||
className="w-96"
|
className="w-96"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
{...registerSecondForm("projectLogo", { required: true })}
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
registerSecondForm("projectLogo").onChange({
|
||||||
|
target: { name: "projectLogo", value: file },
|
||||||
|
});
|
||||||
|
// Set the file value directly in the form
|
||||||
|
registerSecondForm("projectLogo").onChange({
|
||||||
|
target: { name: "projectLogo", value: file },
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="text-[12px] text-neutral-500 self-center">
|
<span className="text-[12px] text-neutral-500 self-center">
|
||||||
Please upload the logo picture that best represents your
|
Please upload the logo picture that best represents your
|
||||||
@ -733,11 +791,10 @@ export default function Apply() {
|
|||||||
{errorsProject.projectLogo.message as string}
|
{errorsProject.projectLogo.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{/* Project Photos
|
||||||
{/* Project pictures */}
|
|
||||||
<div className="mt-10 space-y-5">
|
<div className="mt-10 space-y-5">
|
||||||
<Label
|
<Label
|
||||||
htmlFor="projectPicture"
|
htmlFor="projectPhotos"
|
||||||
className="font-bold text-lg mt-10"
|
className="font-bold text-lg mt-10"
|
||||||
>
|
>
|
||||||
Project pictures
|
Project pictures
|
||||||
@ -745,7 +802,7 @@ export default function Apply() {
|
|||||||
<div className="flex space-x-5">
|
<div className="flex space-x-5">
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
id="projectPicture"
|
id="projectPhotos"
|
||||||
multiple
|
multiple
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
className="w-96"
|
className="w-96"
|
||||||
@ -778,13 +835,12 @@ export default function Apply() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
{errorsProject.projectPhotos && (
|
{/* {errorsProject.projectPhotos && (
|
||||||
<p className="text-red-500 text-sm">
|
<p className="text-red-500 text-sm">
|
||||||
{errorsProject.projectPhotos.message as string}
|
{errorsProject.projectPhotos.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{/* Minimum Investment */}
|
{/* Minimum Investment */}
|
||||||
<div className="space-y-5 mt-10">
|
<div className="space-y-5 mt-10">
|
||||||
<Label htmlFor="minInvest" className="font-bold text-lg">
|
<Label htmlFor="minInvest" className="font-bold text-lg">
|
||||||
@ -810,7 +866,6 @@ export default function Apply() {
|
|||||||
{errorsProject.minInvest.message as string}
|
{errorsProject.minInvest.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Target Investment */}
|
{/* Target Investment */}
|
||||||
<div className="space-y-5 mt-10">
|
<div className="space-y-5 mt-10">
|
||||||
<Label htmlFor="targetInvest" className="font-bold text-lg">
|
<Label htmlFor="targetInvest" className="font-bold text-lg">
|
||||||
@ -837,7 +892,6 @@ export default function Apply() {
|
|||||||
{errorsProject.targetInvest.message as string}
|
{errorsProject.targetInvest.message as string}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Deadline */}
|
{/* Deadline */}
|
||||||
<div className="space-y-5 mt-10">
|
<div className="space-y-5 mt-10">
|
||||||
<Label htmlFor="deadline" className="font-bold text-lg">
|
<Label htmlFor="deadline" className="font-bold text-lg">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user