From 01afc6c6fb6898181ba45718e7f35a7fd1bab65d Mon Sep 17 00:00:00 2001 From: THIS ONE IS A LITTLE BIT TRICKY KRUB Date: Wed, 16 Oct 2024 20:11:49 +0700 Subject: [PATCH] Refactor application schema to add validation for project and business forms --- .../schemas => components}/BusinessForm.tsx | 0 src/types/schemas/application.schema.ts | 142 ++++++++++++++++++ 2 files changed, 142 insertions(+) rename src/{types/schemas => components}/BusinessForm.tsx (100%) create mode 100644 src/types/schemas/application.schema.ts diff --git a/src/types/schemas/BusinessForm.tsx b/src/components/BusinessForm.tsx similarity index 100% rename from src/types/schemas/BusinessForm.tsx rename to src/components/BusinessForm.tsx diff --git a/src/types/schemas/application.schema.ts b/src/types/schemas/application.schema.ts new file mode 100644 index 0000000..72985a3 --- /dev/null +++ b/src/types/schemas/application.schema.ts @@ -0,0 +1,142 @@ +import { z } from "zod"; + +const MAX_FILE_SIZE = 500000; +const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png"]; + +const imageSchema = z.custom( + (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({ + projectName: z.string().min(5, { + message: "Project name must be at least 5 characters.", + }), + projectType: z.string({ + required_error: "Please select one of the option", + }), + shortDescription: z.string({ + required_error: "Please provide a brief description for your project", + }).min(10, { + message: "Short description must be at least 10 characters.", + }), + projectPitchDeck: z.union([ + 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).", + }, + ), + z.custom( + (val) => val instanceof File, + { + 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).", + }), + ]), + projectLogo: imageSchema, + projectPhotos: z.custom( + (value) => { + if (value instanceof FileList || Array.isArray(value)) { + if (value.length === 1) { + return false; + } + return Array.from(value).every((item) => item instanceof File); + } + return false; + }, + { + message: + "Must be a FileList or an array of File objects with at least one file.", + }, + ), + minInvest: z.number({ + required_error: "Minimum investment must be a number.", + invalid_type_error: "Minimum investment must be a valid number.", + }).positive().max( + 9999999999, + "Minimum investment must be a realistic amount.", + ), + targetInvest: z.number({ + required_error: "Target investment must be a number.", + invalid_type_error: "Target investment must be a valid number.", + }).positive().max( + 9999999999, + "Target investment must be a realistic amount.", + ), + deadline: z.string().min(1, "Deadline is required.") + .refine((value) => !isNaN(Date.parse(value)), { + message: "Invalid date-time format.", + }) + .transform((value) => new Date(value)) + .refine((date) => date > new Date(), { + message: "Deadline must be in the future.", + }), +}); + +const businessFormSchema = z.object({ + companyName: z.string().min(5, { + message: "Company name must be at least 5 characters.", + }), + industry: z.string({ + required_error: "Please select one of the option", + }), + isInUS: z.string({ + required_error: "Please select either 'Yes' or 'No'.", + }) + .transform((val) => val.toLowerCase()) + .refine((val) => val === "yes" || val === "no", { + message: "Please select either 'Yes' or 'No'.", + }), + isForSale: z.string({ + required_error: "Please select either 'Yes' or 'No'.", + }) + .transform((val) => val.toLowerCase()) + .refine((val) => val === "yes" || val === "no", { + message: "Please select either 'Yes' or 'No'.", + }), + isGenerating: z.string({ + required_error: "Please select either 'Yes' or 'No'.", + }) + .transform((val) => val.toLowerCase()) + .refine((val) => val === "yes" || val === "no", { + message: "Please select either 'Yes' or 'No'.", + }), + totalRaised: z.number({ + required_error: "Total raised must be a number.", + invalid_type_error: "Total raised must be a valid number.", + }).positive().max(9999999999, "Total raised must be a realistic amount."), + communitySize: z.string({ + required_error: "Please select one of the option", + }), + businessPitchDeck: z.union([ + 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).", + }, + ), + z.custom( + (val) => val instanceof File, + { + 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).", + }), + ]), +}); + +export { businessFormSchema, projectFormSchema };