+
{projectData?.project_description || "No pitch available."}
diff --git a/src/app/(user)/profile/page.tsx b/src/app/(user)/profile/[uid]/page.tsx
similarity index 90%
rename from src/app/(user)/profile/page.tsx
rename to src/app/(user)/profile/[uid]/page.tsx
index ceecf73..b45fd9f 100644
--- a/src/app/(user)/profile/page.tsx
+++ b/src/app/(user)/profile/[uid]/page.tsx
@@ -1,5 +1,3 @@
-// components/ProfilePage.tsx
-
import React from "react";
import Image from "next/image";
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
@@ -10,22 +8,11 @@ import ReactMarkdown from "react-markdown";
interface Profile extends Tables<"Profiles"> {}
-export default async function ProfilePage() {
+export default async function ProfilePage({ params }: { params: { uid: string } }) {
const supabase = createSupabaseClient();
+ const uid = params.uid;
- const {
- data: { user },
- } = await supabase.auth.getUser();
-
- if (!user) {
- return (
-
- );
- }
-
- const { data: profileData, error } = await getUserProfile(supabase, user.id);
+ const { data: profileData, error } = await getUserProfile(supabase, uid);
if (error) {
return (
diff --git a/src/app/api/dealApi.ts b/src/app/api/dealApi.ts
new file mode 100644
index 0000000..31b2094
--- /dev/null
+++ b/src/app/api/dealApi.ts
@@ -0,0 +1,44 @@
+import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
+import { getCurrentUserID } from "./userApi";
+
+export type Deal = {
+ deal_amount: number;
+ created_time: Date;
+ investor_id: string;
+};
+
+export async function getDealList() {
+ const supabase = createSupabaseClient();
+ const { data: dealData, error } = await supabase
+ .from('business')
+ .select(`
+ id,
+ project (
+ id,
+ investment_deal (
+ deal_amount,
+ created_time,
+ investor_id
+ )
+ )
+ `)
+ .eq('user_id', await getCurrentUserID())
+ .single();
+
+ if (error || !dealData) {
+ alert(JSON.stringify(error));
+ console.error('Error fetching deal list:', error);
+ } else {
+ const dealList = dealData.project[0].investment_deal;
+
+ if (!dealList.length) {
+ alert("No data available");
+ return; // Exit early if there's no data
+ }
+
+ // Sort the dealList by created_time in descending order
+ const byCreatedTimeDesc = (a: Deal, b: Deal) =>
+ new Date(b.created_time).getTime() - new Date(a.created_time).getTime();
+ return dealList.sort(byCreatedTimeDesc);
+ }
+};
\ No newline at end of file
diff --git a/src/app/api/generalApi.ts b/src/app/api/generalApi.ts
new file mode 100644
index 0000000..aa7c50b
--- /dev/null
+++ b/src/app/api/generalApi.ts
@@ -0,0 +1,100 @@
+import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
+import Swal from "sweetalert2";
+
+const supabase = createSupabaseClient();
+
+async function checkFolderExists(bucketName: string, filePath: string) {
+ const { data, error } = await supabase.storage
+ .from(bucketName)
+ .list(filePath);
+
+ if (error) {
+ console.error(`Error checking for folder: ${error.message}`);
+ }
+ return { folderData: data, folderError: error };
+}
+
+async function clearFolder(
+ bucketName: string,
+ folderData: any[],
+ filePath: string
+) {
+ const errors: string[] = [];
+
+ for (const fileItem of folderData) {
+ const { error } = await supabase.storage
+ .from(bucketName)
+ .remove([`${filePath}/${fileItem.name}`]);
+
+ if (error) {
+ errors.push(`Error removing file (${fileItem.name}): ${error.message}`);
+ }
+ }
+
+ return errors;
+}
+
+async function uploadToFolder(
+ bucketName: string,
+ filePath: string,
+ file: File
+) {
+ const { data, error } = await supabase.storage
+ .from(bucketName)
+ .upload(filePath, file, { upsert: true });
+
+ if (error) {
+ console.error(`Error uploading file: ${error.message}`);
+ }
+ return { uploadData: data, uploadError: error };
+}
+
+export async function uploadFile(
+ file: File,
+ bucketName: string,
+ filePath: string
+) {
+ const errorMessages: string[] = [];
+
+ // check if the folder exists
+ const { folderData, folderError } = await checkFolderExists(
+ bucketName,
+ filePath
+ );
+ if (folderError) {
+ errorMessages.push(`Error checking for folder: ${folderError.message}`);
+ }
+
+ // clear the folder if it exists
+ if (folderData && folderData.length > 0) {
+ const clearErrors = await clearFolder(bucketName, folderData, filePath);
+ errorMessages.push(...clearErrors);
+ }
+
+ // upload the new file if there were no previous errors
+ let uploadData = null;
+ if (errorMessages.length === 0) {
+ const { uploadData: data, uploadError } = await uploadToFolder(
+ bucketName,
+ filePath,
+ file
+ );
+ uploadData = data;
+
+ if (uploadError) {
+ errorMessages.push(`Error uploading file: ${uploadError.message}`);
+ }
+ }
+
+ if (errorMessages.length > 0) {
+ Swal.fire({
+ icon: "error",
+ title: "Errors occurred",
+ html: errorMessages.join("
"),
+ confirmButtonColor: "red",
+ });
+ return { success: false, errors: errorMessages, data: null };
+ }
+
+ return { success: true, errors: null, data: uploadData };
+}
diff --git a/src/app/api/userApi.ts b/src/app/api/userApi.ts
new file mode 100644
index 0000000..d3648d9
--- /dev/null
+++ b/src/app/api/userApi.ts
@@ -0,0 +1,13 @@
+import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
+
+export async function getCurrentUserID() {
+ const supabase = createSupabaseClient();
+ const { data: { user }, error } = await supabase.auth.getUser();
+
+ if (error || !user) {
+ console.error('Error fetching user:', error);
+ return;
+ }
+
+ return user.id;
+}
\ No newline at end of file
diff --git a/src/app/business/apply/page.tsx b/src/app/business/apply/page.tsx
index 50c1038..1e18c81 100644
--- a/src/app/business/apply/page.tsx
+++ b/src/app/business/apply/page.tsx
@@ -1,834 +1,185 @@
"use client";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Switch } from "@/components/ui/switch";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
-import { useEffect, useState } from "react";
-import { Textarea } from "@/components/ui/textarea";
-import { useForm } from "react-hook-form";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { useState, useEffect, useRef } from "react";
+import { SubmitHandler } from "react-hook-form";
import { z } from "zod";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { DualOptionSelector } from "@/components/dualSelector";
-import { MultipleOptionSelector } from "@/components/multipleSelector";
+import BusinessForm from "@/components/BusinessForm";
+import { businessFormSchema } from "@/types/schemas/application.schema";
+import Swal from "sweetalert2";
+import { getCurrentUserID } from "@/app/api/userApi";
+import { uploadFile } from "@/app/api/generalApi";
+import { Loader } from "@/components/loading/loader";
-export default function Apply() {
- const [industry, setIndustry] = useState
([]);
- const [isInUS, setIsInUS] = useState("");
- const [isForSale, setIsForSale] = useState("");
- const [isGenerating, setIsGenerating] = useState("");
- const [businessPitch, setBusinessPitch] = useState("text");
- const [projectType, setProjectType] = useState([]);
- const [projectPitch, setProjectPitch] = useState("text");
+type businessSchema = z.infer;
+const BUCKET_PITCH_NAME = "business-application";
+let supabase = createSupabaseClient();
+
+export default function ApplyBusiness() {
const [applyProject, setApplyProject] = useState(false);
- const [selectedImages, setSelectedImages] = useState([]);
- const [businessPitchFile, setBusinessPitchFile] = useState("");
- const [projectPitchFile, setProjectPitchFile] = useState("");
- const MAX_FILE_SIZE = 5000000;
- const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png"];
- const createPitchDeckSchema = (inputType: string) => {
- if (inputType === "text") {
- 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(
- (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 alertShownRef = useRef(false);
+ const [success, setSucess] = useState(false);
+
+ const onSubmit: SubmitHandler = async (data) => {
+ const transformedData = await transformChoice(data);
+ await sendApplication(transformedData);
};
- 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 sendApplication = async (recvData: any) => {
+ setSucess(false);
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+ const pitchType = typeof recvData["businessPitchDeck"];
+ if (pitchType === "object") {
+ if (user?.id) {
+ const uploadSuccess = await uploadFile(
+ recvData["businessPitchDeck"],
+ BUCKET_PITCH_NAME,
+ // file structure: userId/fileName
+ `${user?.id}/pitch-file/pitch.md`
+ );
- 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: createPitchDeckSchema(projectPitch),
- projectLogo: imageSchema,
-
- projectPhotos: z.custom(
- (value) => {
- console.log("Tozod", value);
- if (value instanceof FileList || Array.isArray(value)) {
- if (value.length === 1) {
- return false;
- }
- return Array.from(value).every((item) => item instanceof File);
+ if (!uploadSuccess) {
+ return;
}
- return false;
- },
- { message: "Must be a FileList or an array of File objects with at least one file." }
- ),
- minInvest: z
- .number({
- required_error: "Minimum invesment must be a number.",
- invalid_type_error: "Minimum invesment must be a valid number.",
- })
- .positive()
- .max(9999999999, "Minimum invesment must be a realistic amount."),
- targetInvest: z
- .number({
- required_error: "Target invesment must be a number.",
- invalid_type_error: "Target invesment must be a valid number.",
- })
- .positive()
- .max(9999999999, "Target invesment 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: createPitchDeckSchema(businessPitch),
- });
- let supabase = createSupabaseClient();
- const {
- register,
- handleSubmit,
- setValue: setValueBusiness,
- formState: { errors: errorsBusiness },
- } = useForm({
- resolver: zodResolver(businessFormSchema),
- });
- const {
- register: registerSecondForm,
- handleSubmit: handleSecondSubmit,
- formState: { errors: errorsProject },
- setValue: setValueProject,
- } = useForm({
- resolver: zodResolver(projectFormSchema),
- });
- const communitySize = ["N/A", "0-5K", "5-10K", "10-20K", "20-50K", "50-100K", "100K+"];
-
- useEffect(() => {
- register("industry");
- register("isInUS");
- register("isForSale");
- register("isGenerating");
- }, [register]);
-
- const handleFileChange = (event: React.ChangeEvent) => {
- if (event.target.files) {
- const filesArray = Array.from(event.target.files);
- console.log("first file", filesArray);
- setSelectedImages((prevImages) => {
- const updatedImages = [...prevImages, ...filesArray];
- console.log("Updated Images Array:", updatedImages);
- // ensure we're setting an array of File objects
- setValueProject("projectPhotos", updatedImages);
- return updatedImages;
- });
+ console.log("file upload successful");
+ } else {
+ console.error("user ID is undefined.");
+ return;
+ }
}
- };
- const handleRemoveImage = (index: number) => {
- setSelectedImages((prevImages) => {
- const updatedImages = prevImages.filter((_, i) => i !== index);
- console.log("After removal - Updated Images:", updatedImages);
- // ensure we're setting an array of File objects
- setValueProject("projectPhotos", updatedImages);
- return updatedImages;
+ const { data, error } = await supabase
+ .from("business_application")
+ .insert([
+ {
+ user_id: user?.id,
+ business_name: recvData["companyName"],
+ business_type_id: recvData["industry"],
+ location: recvData["country"],
+ is_for_sale: recvData["isForSale"],
+ is_generating_revenue: recvData["isGenerating"],
+ is_in_us: recvData["isInUS"],
+ pitch_deck_url:
+ pitchType === "string" ? recvData["businessPitchDeck"] : "",
+ money_raised_to_date: recvData["totalRaised"],
+ community_size: recvData["communitySize"],
+ },
+ ])
+ .select();
+ setSucess(true);
+ // console.table(data);
+ Swal.fire({
+ icon: error == null ? "success" : "error",
+ title: error == null ? "success" : "Error: " + error.code,
+ text:
+ error == null ? "Your application has been submitted" : error.message,
+ confirmButtonColor: error == null ? "green" : "red",
+ }).then((result) => {
+ if (result.isConfirmed && applyProject) {
+ window.location.href = "/project/apply";
+ } else {
+ window.location.href = "/";
+ }
});
};
- const ensureArrayValue = (value: any): File[] => {
- if (Array.isArray(value)) return value;
- if (value instanceof File) return [value];
- return [];
+ const hasUserApplied = async (userID: string) => {
+ let { data: business, error } = await supabase
+ .from("business")
+ .select("*")
+ .eq("user_id", userID);
+ console.table(business);
+ if (error) {
+ console.error(error);
+ }
+ if (business) {
+ return true;
+ }
+ return false;
};
-
const transformChoice = (data: any) => {
// convert any yes and no to true or false
- const transformedData = Object.entries(data).reduce((acc: Record, [key, value]) => {
- if (typeof value === "string") {
- const lowerValue = value.toLowerCase();
- if (lowerValue === "yes") {
- acc[key] = true;
- } else if (lowerValue === "no") {
- acc[key] = false;
+ const transformedData = Object.entries(data).reduce(
+ (acc: Record, [key, value]) => {
+ if (typeof value === "string") {
+ const lowerValue = value.toLowerCase();
+ if (lowerValue === "yes") {
+ acc[key] = true;
+ } else if (lowerValue === "no") {
+ acc[key] = false;
+ } else {
+ acc[key] = value; // keep other string values unchanged
+ }
} else {
- acc[key] = value; // keep other string values unchanged
+ acc[key] = value; // keep other types unchanged
}
- } else {
- acc[key] = value; // keep other types unchanged
- }
- return acc;
- }, {});
+ return acc;
+ },
+ {}
+ );
return transformedData;
};
- const handleBusinessPitchChange = (type: string) => {
- setBusinessPitch(type);
- // clear out old data
- setValueBusiness("pitchDeck", "");
- };
-
- 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 handleProjectFieldChange = (fieldName: string, value: any) => {
- switch (fieldName) {
- }
- setValueProject(fieldName, value);
- };
-
- const fetchIndustry = async () => {
- let { data: BusinessType, error } = await supabase.from("business_type").select("value");
-
- if (error) {
- console.error(error);
- } else {
- if (BusinessType) {
- // console.table();
- setIndustry(BusinessType.map((item) => item.value));
- }
- }
- };
-
- const onSubmitSingleForm = (data: any) => {
- const pitchDeckSchema = createPitchDeckSchema(businessPitch);
- pitchDeckSchema.parse(data.businessPitchDeck);
- console.log("Valid form input:", data);
- alert(JSON.stringify(data));
- };
-
- const onSubmitBothForms = (firstFormData: any, secondFormData: any) => {
- const formattedSecondFormData = {
- ...secondFormData,
- projectPhotos: ensureArrayValue(secondFormData.projectPhotos),
- };
- alert(JSON.stringify(firstFormData));
- alert(JSON.stringify(formattedSecondFormData));
- console.log("Both forms submitted:", {
- firstFormData,
- formattedSecondFormData,
- });
- };
-
- const handleSubmitForms = (firstFormData: any) => {
- const transformedData = transformChoice(firstFormData);
- if (applyProject) {
- handleSecondSubmit((secondFormData: any) => {
- onSubmitBothForms(transformedData, secondFormData);
- })();
- } else {
- onSubmitSingleForm(transformedData);
- }
- };
- const fetchProjectType = async () => {
- let { data: ProjectType, error } = await supabase.from("project_type").select("value");
-
- if (error) {
- console.error(error);
- } else {
- if (ProjectType) {
- console.table(ProjectType);
- setProjectType(ProjectType.map((item) => item.value));
- }
- }
- };
useEffect(() => {
- fetchIndustry();
- fetchProjectType();
+ const fetchUserData = async () => {
+ try {
+ setSucess(false);
+ const userID = await getCurrentUserID();
+ if (userID) {
+ const hasApplied = await hasUserApplied(userID);
+ setSucess(true);
+ if (hasApplied && !alertShownRef.current) {
+ alertShownRef.current = true;
+ Swal.fire({
+ icon: "info",
+ title: "You Already Have an Account",
+ text: "You have already submitted your business application.",
+ confirmButtonText: "OK",
+ allowOutsideClick: false,
+ allowEscapeKey: false,
+ }).then((result) => {
+ if (result.isConfirmed) {
+ window.location.href = "/";
+ }
+ });
+ }
+ setSucess(false);
+ } else {
+ console.error("User ID is undefined.");
+ }
+ } catch (error) {
+ console.error("Error fetching user ID:", error);
+ }
+ };
+ // setSucess(true);
+ fetchUserData();
}, []);
+
return (
+
Apply to raise on B2DVentures
- All information submitted in this application is for internal use only and is treated with the utmost{" "}
+ All information submitted in this application is for internal use
+ only and is treated with the utmost{" "}
- confidentiality. Companies may apply to raise with B2DVentures more than once.
+ confidentiality. Companies may apply to raise with B2DVentures more
+ than once.
{/* form */}
-
+ {/*
*/}
+
);
-}
\ No newline at end of file
+}
diff --git a/src/app/dashboard/hook.ts b/src/app/dashboard/hook.ts
new file mode 100644
index 0000000..a220dc0
--- /dev/null
+++ b/src/app/dashboard/hook.ts
@@ -0,0 +1,17 @@
+import { useEffect, useState } from "react";
+import { Deal, getDealList } from "../api/dealApi";
+
+// custom hook for deal list
+export function useDealList() {
+ const [dealList, setDealList] = useState();
+
+ const fetchDealList = async () => {
+ setDealList(await getDealList());
+ }
+
+ useEffect(() => {
+ fetchDealList();
+ }, []);
+
+ return dealList;
+}
\ No newline at end of file
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 8554238..3d2840f 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -12,10 +12,22 @@ import { Overview } from "@/components/ui/overview";
import { RecentFunds } from "@/components/recent-funds";
import { useState } from "react";
+import { useDealList } from "./hook";
+
export default function Dashboard() {
const [graphType, setGraphType] = useState("line");
+ const dealList = useDealList();
+ const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0;
+
return (
<>
+ {dealList?.map((deal, index) => (
+
+
Deal Amount: {deal.deal_amount}
+
Created Time: {new Date(deal.created_time).toUTCString()}
+
Investor ID: {deal.investor_id}
+
+ ))}
-
Dashboard
+ Business Dashboard
@@ -63,10 +75,10 @@ export default function Dashboard() {
- $45,231.89
-
+
${totalDealAmount}
+ {/*
+20.1% from last month
-
+ */}
@@ -90,9 +102,9 @@ export default function Dashboard() {
+2350
-
+ {/*
+180.1% from last month
-
+ */}
@@ -117,9 +129,9 @@ export default function Dashboard() {
+12,234
-
+ {/*
+19% from last month
-
+ */}
{/*
@@ -181,11 +193,12 @@ export default function Dashboard() {
Recent Funds
- You made 265 sales this month.
+ You made {dealList?.length || 0} sales this month.
-
+
+
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 4e03c19..78f00bf 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,39 +1,63 @@
import Image from "next/image";
import { Button } from "@/components/ui/button";
import Link from "next/link";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { ProjectCard } from "@/components/projectCard";
import { getTopProjects } from "@/lib/data/projectQuery";
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
import { Suspense } from "react";
+import { FC } from "react";
-const TopProjects = async () => {
- const supabase = createSupabaseClient();
- const { data: topProjectsData, error: topProjectsError } = await getTopProjects(supabase);
+interface Project {
+ id: number;
+ project_name: string;
+ project_short_description: string;
+ card_image_url: string;
+ published_time: string;
+ business: { location: string }[];
+ project_tag: { tag: { id: number; value: string }[] }[];
+ project_investment_detail: {
+ min_investment: number;
+ total_investment: number;
+ }[];
+}
- if (topProjectsError) {
- return Error loading top projects: {topProjectsError}
;
- }
+interface TopProjectsProps {
+ projects: Project[];
+}
- if (!topProjectsData || topProjectsData.length === 0) {
+const TopProjects: FC = ({ projects }) => {
+ if (!projects || projects.length === 0) {
return No top projects available.
;
}
-
return (
- {topProjectsData.map((project) => (
+ {projects.map((project) => (
item.tag.value)}
- minInvestment={project.project_investment_detail[0]?.min_investment || 0}
+ location={project.business[0]?.location || ""}
+ tags={project.project_tag.flatMap(
+ (item: { tag: { id: number; value: string }[] }) =>
+ Array.isArray(item.tag) ? item.tag.map((tag) => tag.value) : []
+ )}
+ minInvestment={
+ project.project_investment_detail[0]?.min_investment || 0
+ }
totalInvestor={0}
- totalRaised={project.project_investment_detail[0]?.total_investment || 0}
+ totalRaised={
+ project.project_investment_detail[0]?.total_investment || 0
+ }
/>
))}
@@ -44,12 +68,18 @@ const TopProjects = async () => {
const ProjectsLoader = () => (
{[...Array(4)].map((_, index) => (
-
+
))}
);
-
export default async function Home() {
+ const supabase = createSupabaseClient();
+ const { data: topProjectsData, error: topProjectsError } =
+ await getTopProjects(supabase);
+
return (
@@ -57,9 +87,14 @@ export default async function Home() {
- Explore the world of ventures
+
+ Explore the world of ventures
+
- Unlock opportunities and connect with a community of passionate
+
+ Unlock opportunities and connect with a community of
+ passionate
+
investors and innovators.
Together, we turn ideas into impact.
@@ -107,11 +142,23 @@ export default async function Home() {
-
+
Github
-
+
Github
@@ -123,10 +170,12 @@ export default async function Home() {
Hottest Deals
- The deals attracting the most interest right now
+
+ The deals attracting the most interest right now
+
}>
-
+
diff --git a/src/app/project/apply/page.tsx b/src/app/project/apply/page.tsx
new file mode 100644
index 0000000..204164b
--- /dev/null
+++ b/src/app/project/apply/page.tsx
@@ -0,0 +1,282 @@
+"use client";
+import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
+import ProjectForm from "@/components/ProjectForm";
+import { projectFormSchema } from "@/types/schemas/application.schema";
+import { z } from "zod";
+import { SubmitHandler } from "react-hook-form";
+import Swal from "sweetalert2";
+import { uploadFile } from "@/app/api/generalApi";
+import { Loader } from "@/components/loading/loader";
+import { useState } from "react";
+import { errors } from "@playwright/test";
+
+type projectSchema = z.infer;
+let supabase = createSupabaseClient();
+const BUCKET_PITCH_APPLICATION_NAME = "project-application";
+
+export default function ApplyProject() {
+ const [isSuccess, setIsSuccess] = useState(true);
+ const onSubmit: SubmitHandler = async (data) => {
+ alert("มาแน้ววว");
+ await sendApplication(data);
+ // console.table(data);
+ // console.log(typeof data["projectPhotos"], data["projectPhotos"]);
+ };
+ const saveApplicationData = async (recvData: any, userId: string) => {
+ const pitchType = typeof recvData["projectPitchDeck"];
+ const { data: projectData, error: projectError } = await supabase
+ .from("project_application")
+ .insert([
+ {
+ user_id: userId,
+ pitch_deck_url:
+ pitchType === "string" ? recvData["projectPitchDeck"] : "",
+ target_investment: recvData["targetInvest"],
+ deadline: recvData["deadline"],
+ project_name: recvData["projectName"],
+ project_type_id: recvData["projectType"],
+ short_description: recvData["shortDescription"],
+ min_investment: recvData["minInvest"],
+ },
+ ])
+ .select();
+
+ return { projectId: projectData?.[0]?.id, error: projectError };
+ };
+ const saveTags = async (tags: string[], projectId: string) => {
+ const tagPromises = tags.map(async (tag) => {
+ const response = await supabase
+ .from("project_application_tag")
+ .insert([{ tag_id: tag, item_id: projectId }])
+ .select();
+
+ // console.log("Insert response for tag:", tag, response);
+
+ return response;
+ });
+
+ const results = await Promise.all(tagPromises);
+
+ // Collect errors
+ const errors = results
+ .filter((result) => result.error)
+ .map((result) => result.error);
+
+ return { errors };
+ };
+
+ const uploadPitchFile = async (
+ file: File,
+ userId: string,
+ projectId: string
+ ) => {
+ if (!file || !userId) {
+ console.error("Pitch file or user ID is undefined.");
+ return false;
+ }
+
+ return await uploadFile(
+ file,
+ BUCKET_PITCH_APPLICATION_NAME,
+ `${userId}/${projectId}/pitches/${file.name}`
+ );
+ };
+
+ const uploadLogoAndPhotos = async (
+ logoFile: File,
+ photos: File[],
+ userId: string,
+ projectId: string
+ ) => {
+ const uploadResults: { logo?: any; photos: any[] } = { photos: [] };
+
+ // upload logo
+ if (logoFile) {
+ const logoResult = await uploadFile(
+ logoFile,
+ BUCKET_PITCH_APPLICATION_NAME,
+ `${userId}/${projectId}/logo/${logoFile.name}`
+ );
+
+ if (!logoResult.success) {
+ console.error("Error uploading logo:", logoResult.errors);
+ return { success: false, logo: logoResult, photos: [] };
+ }
+
+ uploadResults.logo = logoResult;
+ }
+
+ // upload each photo
+ const uploadPhotoPromises = photos.map((image) =>
+ uploadFile(
+ image,
+ BUCKET_PITCH_APPLICATION_NAME,
+ `${userId}/${projectId}/photos/${image.name}`
+ )
+ );
+
+ const photoResults = await Promise.all(uploadPhotoPromises);
+ uploadResults.photos = photoResults;
+
+ // check if all uploads were successful
+ const allUploadsSuccessful = photoResults.every((result) => result.success);
+
+ return {
+ success: allUploadsSuccessful,
+ logo: uploadResults.logo,
+ photos: uploadResults.photos,
+ };
+ };
+
+ const displayAlert = (error: any) => {
+ Swal.fire({
+ icon: error == null ? "success" : "error",
+ title: error == null ? "Success" : `Error: ${error.code}`,
+ text:
+ error == null ? "Your application has been submitted" : error.message,
+ confirmButtonColor: error == null ? "green" : "red",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ // window.location.href = "/";
+ }
+ });
+ };
+
+ const sendApplication = async (recvData: any) => {
+ setIsSuccess(false);
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user?.id) {
+ console.error("User ID is undefined.");
+ return;
+ }
+
+ // save application data
+ const { projectId, error } = await saveApplicationData(recvData, user.id);
+
+ if (error) {
+ displayAlert(error);
+ return;
+ }
+ const tagError = await saveTags(recvData["tag"], projectId);
+ // if (tagError) {
+ // displayAlert(tagError);
+ // return;
+ // }
+
+ // upload pitch file if it’s a file
+ if (typeof recvData["projectPitchDeck"] === "object") {
+ const uploadPitchSuccess = await uploadPitchFile(
+ recvData["projectPitchDeck"],
+ user.id,
+ projectId
+ );
+
+ if (!uploadPitchSuccess) {
+ console.error("Error uploading pitch file.");
+ } else {
+ console.log("Pitch file uploaded successfully.");
+ }
+ }
+
+ // upload logo and photos
+ const { success, logo, photos } = await uploadLogoAndPhotos(
+ recvData["projectLogo"],
+ recvData["projectPhotos"],
+ user.id,
+ projectId
+ );
+ if (!success) {
+ console.error("Error uploading media files.");
+ }
+
+ // console.log("Bucket Name:", BUCKET_PITCH_APPLICATION_NAME);
+ // console.log("Logo Path:", logo.data.path);
+ // console.table(photos);
+
+ const logoURL = await getPrivateURL(
+ logo.data.path,
+ BUCKET_PITCH_APPLICATION_NAME
+ );
+ let photoURLsArray: string[] = [];
+ const photoURLPromises = photos.map(
+ async (item: {
+ success: boolean;
+ errors: typeof errors;
+ data: { path: string };
+ }) => {
+ const photoURL = await getPrivateURL(
+ item.data.path,
+ BUCKET_PITCH_APPLICATION_NAME
+ );
+ if (photoURL?.signedUrl) {
+ photoURLsArray.push(photoURL.signedUrl);
+ } else {
+ console.error("Signed URL for photo is undefined.");
+ }
+ }
+ );
+
+ await Promise.all(photoURLPromises);
+ // console.log(logoURL.publicUrl, projectId, logo.data.path);
+ // console.log(logoURL?.signedUrl, projectId);
+ // console.log(photoURLsArray[0], photoURLsArray[1]);
+ if (logoURL?.signedUrl) {
+ await updateImageURL(logoURL.signedUrl, "project_logo", projectId);
+ } else {
+ console.error("Signed URL for logo is undefined.");
+ }
+ await updateImageURL(photoURLsArray, "project_photos", projectId);
+ // console.log(logoURL, photosUrl);
+ setIsSuccess(true);
+ displayAlert(error);
+ };
+ const updateImageURL = async (
+ url: string | string[],
+ columnName: string,
+ projectId: number
+ ) => {
+ const { error } = await supabase
+ .from("project_application")
+ .update({ [columnName]: url })
+ .eq("id", projectId);
+ // console.log(
+ // `Updating ${columnName} with URL: ${url} for project ID: ${projectId}`
+ // );
+ if (error) {
+ console.error(error);
+ }
+ };
+ const getPrivateURL = async (path: string, bucketName: string) => {
+ const { data } = await supabase.storage
+ .from(bucketName)
+ .createSignedUrl(path, 9999999999999999999999999999);
+ // console.table(data);
+ return data;
+ };
+ return (
+
+
+
+
+ Apply to raise on B2DVentures
+
+
+
+ Begin Your First Fundraising Project. Starting a fundraising project
+ is mandatory for all businesses.
+
+
+ This step is crucial to begin your journey and unlock the necessary
+ tools for raising funds.
+
+
+
+
+
+ );
+}
diff --git a/src/components/BusinessForm.tsx b/src/components/BusinessForm.tsx
new file mode 100644
index 0000000..ced01a8
--- /dev/null
+++ b/src/components/BusinessForm.tsx
@@ -0,0 +1,501 @@
+import { useEffect, useState } from "react";
+import { SubmitHandler, useForm } from "react-hook-form";
+import { Button } from "@/components/ui/button";
+import { DualOptionSelector } from "@/components/dualSelector";
+import { MultipleOptionSelector } from "@/components/multipleSelector";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { businessFormSchema } from "@/types/schemas/application.schema";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@radix-ui/react-tooltip";
+import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
+
+type businessSchema = z.infer;
+
+interface BusinessFormProps {
+ applyProject: boolean;
+ setApplyProject: Function;
+ onSubmit: SubmitHandler;
+}
+const BusinessForm = ({
+ applyProject,
+ setApplyProject,
+ onSubmit,
+}: BusinessFormProps & { onSubmit: SubmitHandler }) => {
+ const communitySize = [
+ { id: 1, name: "N/A" },
+ { id: 2, name: "0-5K" },
+ { id: 3, name: "5-10K" },
+ { id: 4, name: "10-20K" },
+ { id: 5, name: "20-50K" },
+ { id: 6, name: "50-100K" },
+ { id: 7, name: "100K+" },
+ ];
+ const form = useForm({
+ resolver: zodResolver(businessFormSchema),
+ defaultValues: {},
+ });
+ let supabase = createSupabaseClient();
+ const [businessPitch, setBusinessPitch] = useState("text");
+ const [businessPitchFile, setBusinessPitchFile] = useState("");
+ const [countries, setCountries] = useState<{ id: number; name: string }[]>(
+ []
+ );
+ const [industry, setIndustry] = useState<{ id: number; name: string }[]>([]);
+ const fetchIndustry = async () => {
+ let { data: BusinessType, error } = await supabase
+ .from("business_type")
+ .select("id, value");
+
+ if (error) {
+ console.error(error);
+ } else {
+ if (BusinessType) {
+ // console.table();
+ setIndustry(
+ BusinessType.map((item) => ({
+ id: item.id,
+ name: item.value,
+ }))
+ );
+ }
+ }
+ };
+ const fetchCountries = async () => {
+ try {
+ const response = await fetch("https://restcountries.com/v3.1/all");
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
+ }
+ const data = await response.json();
+ const countryList = data.map(
+ (country: { name: { common: string } }, index: number) => ({
+ id: index + 1,
+ name: country.name.common,
+ })
+ );
+
+ setCountries(
+ countryList.sort((a: { name: string }, b: { name: any }) =>
+ a.name.localeCompare(b.name)
+ )
+ );
+ } catch (error) {
+ console.error("Error fetching countries:", error);
+ }
+ };
+ useEffect(() => {
+ fetchCountries();
+ fetchIndustry();
+ }, []);
+ return (
+
+ )}
+ className="space-y-8"
+ >
+
+
About your company
+
+ ** All requested
+ information in this section is required.
+
+
+ {/* Company Name */}
+
(
+
+
+ Company name
+
+
+
+
+
+
+ This should be the name your company uses on your{" "}
+
+ website and in the market.
+
+
+
+
+
+
+ )}
+ />
+ {/* Country */}
+ (
+
+
+ Country>}
+ fieldName="country"
+ choices={countries}
+ handleFunction={(selectedValues: any) => {
+ // console.log("Country selected: " + selectedValues.name);
+ field.onChange(selectedValues.name);
+ }}
+ description={
+ <>Select the country where your business is based.>
+ }
+ placeholder="Select a country"
+ selectLabel="Country"
+ />
+
+
+
+ )}
+ />
+
+ {/* Industry */}
+ (
+
+
+ Industry>}
+ fieldName="industry"
+ choices={industry}
+ handleFunction={(selectedValues: any) => {
+ // console.log("Type of selected value:", selectedValues.id);
+ field.onChange(selectedValues.id);
+ }}
+ description={
+ <>
+ Choose the industry that best aligns with your
+ business.
+ >
+ }
+ placeholder="Select an industry"
+ selectLabel="Industry"
+ />
+
+
+
+ )}
+ />
+
+ {/* Raised Money */}
+ (
+
+
+
+ How much money has your company raised to date?
+
+
+
+ {
+ const value = e.target.value;
+ field.onChange(value ? parseFloat(value) : null);
+ }}
+ value={field.value}
+ />
+
+ The sum total of past financing, including angel or
+ venture
+ capital, loans, grants, or token sales.
+
+
+
+
+
+
+ )}
+ />
+
+ {/* Incorporated in US */}
+ (
+
+
+
+
+ Is your company incorporated in the United States?
+ >
+ }
+ choice1="Yes"
+ choice2="No"
+ handleFunction={(selectedValues: string) => {
+ // setIsInUS;
+ field.onChange(selectedValues);
+ }}
+ description={<>>}
+ value={field.value}
+ />
+
+ Only companies that are incorporated or formed in the US
+ are eligible to raise via Reg CF.
+
+
+
+
+
+ )}
+ />
+
+ {/* Product for Sale */}
+ (
+
+
+
+ Is your product available (for sale) in market?>
+ }
+ choice1="Yes"
+ choice2="No"
+ handleFunction={(selectedValues: string) => {
+ // setIsForSale;
+ field.onChange(selectedValues);
+ }}
+ description={
+ <>
+ Only check this box if customers can access, use, or
+ buy your product today.
+ >
+ }
+ />
+
+
+
+
+ )}
+ />
+
+ {/* Generating Revenue */}
+ (
+
+
+
+ Is your company generating revenue?>}
+ choice1="Yes"
+ choice2="No"
+ value={field.value}
+ handleFunction={(selectedValues: string) => {
+ field.onChange(selectedValues);
+ }}
+ description={
+ <>
+ Only check this box if your company is making money.
+ Please elaborate on revenue below.
+ >
+ }
+ />
+
+
+
+
+ )}
+ />
+
+ {/* Pitch Deck */}
+ (
+
+
+
+ Pitch deck
+
+
+
+
+ setBusinessPitch("text")}
+ className="w-32 h-12 text-base"
+ >
+ Paste URL
+
+ setBusinessPitch("file")}
+ className="w-32 h-12 text-base"
+ >
+ Upload a file
+
+
+
+
{
+ const value = e.target;
+ if (businessPitch === "file") {
+ const file = value.files?.[0];
+ field.onChange(file || "");
+ } else {
+ field.onChange(value.value);
+ }
+ }}
+ className="w-96 mt-5"
+ />
+
+
+ Your pitch deck and other application info will be
+ used for
+ internal purposes only.
+ Please make sure this document is publicly
+ accessible. This can
+ be a DocSend, Box, Dropbox, Google Drive or other
+ link.
+
+
+ ** support only markdown(.md) format
+
+
+
+ {businessPitchFile && (
+
+ 1. {businessPitchFile}
+ {
+ setBusinessPitchFile("");
+ }}
+ >
+ Remove
+
+
+ )}
+
+
+
+
+
+ )}
+ />
+
+ {/* Community Size */}
+ (
+
+
+ What's the rough size of your community?>}
+ fieldName="communitySize"
+ choices={communitySize}
+ handleFunction={(selectedValues: any) => {
+ field.onChange(selectedValues.name);
+ }}
+ description={
+ <>
+ Include your email list, social media following (e.g.,
+ Instagram, Discord, Twitter).
+ >
+ }
+ placeholder="Select"
+ selectLabel="Select"
+ />
+
+
+
+ )}
+ />
+
+
setApplyProject(!applyProject)}
+ >
+
+
+
+
+ Would you like to apply for your first fundraising project
+ as well?
+
+
+
+
+ Toggling this option allows you to begin your first
+ project, which is crucial for unlocking the tools
+ necessary to raise funds.
+
+
+
+
+
+
+
+ Submit application
+
+
+
+
+
+
+ );
+};
+
+export default BusinessForm;
diff --git a/src/components/ProjectForm.tsx b/src/components/ProjectForm.tsx
new file mode 100644
index 0000000..9a0a81b
--- /dev/null
+++ b/src/components/ProjectForm.tsx
@@ -0,0 +1,594 @@
+import { useEffect, useState } from "react";
+import { SubmitHandler, useForm, ControllerRenderProps } from "react-hook-form";
+import { Button } from "@/components/ui/button";
+import { MultipleOptionSelector } from "@/components/multipleSelector";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { projectFormSchema } from "@/types/schemas/application.schema";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Label } from "@/components/ui/label";
+import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
+import { Textarea } from "./ui/textarea";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { cn } from "@/lib/utils";
+import { ChevronsUpDown, Check, X } from "lucide-react";
+
+type projectSchema = z.infer;
+type FieldType = ControllerRenderProps;
+
+interface ProjectFormProps {
+ onSubmit: SubmitHandler;
+}
+const ProjectForm = ({
+ onSubmit,
+}: ProjectFormProps & { onSubmit: SubmitHandler }) => {
+ const form = useForm({
+ resolver: zodResolver(projectFormSchema),
+ defaultValues: {},
+ });
+ let supabase = createSupabaseClient();
+ const [projectType, setProjectType] = useState<
+ { id: number; name: string }[]
+ >([]);
+ const [projectPitch, setProjectPitch] = useState("text");
+ const [selectedImages, setSelectedImages] = useState([]);
+ const [projectPitchFile, setProjectPitchFile] = useState("");
+ const [tag, setTag] = useState<{ id: number; value: string }[]>([]);
+ const [open, setOpen] = useState(false);
+ const [selectedTag, setSelectedTag] = useState<
+ { id: number; value: string }[]
+ >([]);
+
+ const handleFileChange = (
+ event: React.ChangeEvent,
+ field: FieldType
+ ) => {
+ if (event.target.files) {
+ const filesArray = Array.from(event.target.files);
+ console.log("first file", filesArray);
+ setSelectedImages((prevImages) => {
+ const updatedImages = [...prevImages, ...filesArray];
+ console.log("Updated Images Array:", updatedImages);
+ field.onChange(updatedImages);
+ return updatedImages;
+ });
+ }
+ };
+
+ const handleRemoveImage = (index: number, field: FieldType) => {
+ setSelectedImages((prevImages) => {
+ const updatedImages = prevImages.filter((_, i) => i !== index);
+ console.log("After removal - Updated Images:", updatedImages);
+ field.onChange(updatedImages);
+
+ return updatedImages;
+ });
+ };
+
+ const fetchProjectType = async () => {
+ let { data: ProjectType, error } = await supabase
+ .from("project_type")
+ .select("id, value");
+
+ if (error) {
+ console.error(error);
+ } else {
+ if (ProjectType) {
+ setProjectType(
+ ProjectType.map((item) => ({
+ id: item.id,
+ name: item.value,
+ }))
+ );
+ }
+ }
+ };
+ const fetchTag = async () => {
+ let { data: tag, error } = await supabase.from("tag").select("id, value");
+
+ if (error) {
+ console.error(error);
+ } else {
+ if (tag) {
+ setTag(
+ tag.map((item) => ({
+ id: item.id,
+ value: item.value,
+ }))
+ );
+ }
+ }
+ };
+ useEffect(() => {
+ fetchProjectType();
+ fetchTag();
+ }, []);
+ return (
+
+ )}
+ className="space-y-8"
+ >
+
+ {/* project name */}
+
(
+
+
+
+ Project name
+
+
+
+
+
+
+
+
+
+ )}
+ />
+ {/* project type */}
+ (
+
+
+ Project type>}
+ fieldName="projectType"
+ choices={projectType}
+ handleFunction={(selectedValues: any) => {
+ field.onChange(selectedValues.id);
+ }}
+ description={
+ <>Please specify the primary purpose of the funds>
+ }
+ placeholder="Select a Project type"
+ selectLabel="Project type"
+ />
+
+
+
+ )}
+ />
+
+ {/* short description */}
+ (
+
+
+
+
+ Short description
+
+
+
+
+ Could you provide a brief description of your project{" "}
+ in one or two sentences?
+
+
+
+
+
+
+ )}
+ />
+
+ {/* Pitch Deck */}
+ (
+
+
+
+ Pitch deck
+
+
+
+
+ setProjectPitch("text")}
+ className="w-32 h-12 text-base"
+ >
+ Paste URL
+
+ setProjectPitch("file")}
+ className="w-32 h-12 text-base"
+ >
+ Upload a file
+
+
+
+ {
+ const value = e.target;
+ if (projectPitch === "file") {
+ const file = value.files?.[0];
+ field.onChange(file || "");
+ } else {
+ field.onChange(value.value);
+ }
+ }}
+ className="w-96 mt-5"
+ />
+
+
+ Please upload a file or paste a link to your pitch,
+ which should
+ cover key aspects of your project: what it will do,
+ what investors can expect to gain, and any
+ highlights that make it stand out.
+
+
+ {projectPitchFile && (
+
+ 1. {projectPitchFile}
+ {
+ setProjectPitchFile("");
+ }}
+ >
+ Remove
+
+
+ )}
+
+
+
+
+
+ )}
+ />
+
+ {/* project logo */}
+ (
+
+
+
+
+ Project logo
+
+ {
+ const file = e.target.files?.[0];
+ field.onChange(file || "");
+ }}
+ />
+
+ Please upload the logo picture that best represents your
+ project.
+
+
+
+
+
+ )}
+ />
+
+ {/* project photos */}
+ (
+
+
+
+
+ Project photos
+
+
+ {
+ handleFileChange(event, field);
+ }}
+ />
+
+ Please upload the logo picture that best represents your
+ project.
+
+
+
+ {selectedImages.map((image, index) => (
+
+ {image.name}
+ handleRemoveImage(index, field)}
+ className="ml-4"
+ type="reset"
+ >
+ Remove
+
+
+ ))}
+
+
+
+
+
+ )}
+ />
+
+ {/* Minimum investment */}
+ (
+
+
+
+ Minimum investment
+
+
+
+ {
+ const value = e.target.value;
+ field.onChange(value ? parseFloat(value) : null);
+ }}
+ value={field.value}
+ />
+
+ This helps set clear expectations for investors
+
+
+
+
+
+
+ )}
+ />
+ {/* Target investment */}
+ (
+
+
+
+ Target investment
+
+
+
+ {
+ const value = e.target.value;
+ field.onChange(value ? parseFloat(value) : null);
+ }}
+ value={field.value}
+ />
+
+ We encourage you to set a specific target investment{" "}
+ amount that reflects your funding goals.
+
+
+
+
+
+
+ )}
+ />
+ {/* Deadline */}
+ (
+
+
+
Deadline
+
+
+
+
+ What is the deadline for your fundraising project?
+ Setting a clear timeline can help motivate
+ potential investors.
+
+
+
+
+
+
+ )}
+ />
+ {/* Tags */}
+ (
+
+
+
Tags
+
+
+
+
+
+ {selectedTag.length > 0
+ ? selectedTag.map((t) => t.value).join(", ")
+ : "Select tags..."}
+
+
+
+
+
+
+
+ No tags found.
+
+ {tag.map((tag) => (
+ {
+ setSelectedTag((prev) => {
+ const exists = prev.find(
+ (t) => t.id === tag.id
+ );
+ const updatedTags = exists
+ ? prev.filter((t) => t.id !== tag.id)
+ : [...prev, tag];
+ field.onChange(
+ updatedTags.map((t) => t.id)
+ );
+ return updatedTags;
+ });
+ setOpen(false);
+ }}
+ >
+ t.id === tag.id)
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ {tag.value}
+
+ ))}
+
+
+
+
+
+
+ Add 1 to 5 tags that describe your project. Tags help{" "}
+
+ investors understand your focus.
+
+
+
+
+
+ {/* display selected tags */}
+
+ {selectedTag.map((tag) => (
+
+ {tag.value}
+ {
+ setSelectedTag((prev) => {
+ const updatedTags = prev.filter(
+ (t) => t.id !== tag.id
+ );
+ field.onChange(updatedTags.map((t) => t.id));
+ return updatedTags;
+ });
+ }}
+ >
+
+
+
+ ))}
+
+
+ )}
+ />
+
+
+
+ Submit application
+
+
+
+
+ );
+};
+
+export default ProjectForm;
diff --git a/src/components/dualSelector.tsx b/src/components/dualSelector.tsx
index e55b066..9943ab9 100644
--- a/src/components/dualSelector.tsx
+++ b/src/components/dualSelector.tsx
@@ -14,7 +14,7 @@ interface SelectorInterface {
export function DualOptionSelector(props: SelectorInterface) {
return (
-
+
{props.label}
@@ -23,7 +23,7 @@ export function DualOptionSelector(props: SelectorInterface) {
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) {
props.handleFunction(props.name, props.choice2)}
+ onClick={() => props.handleFunction(props.choice2)}
className="w-20 h-12 text-base"
>
{props.choice2}
diff --git a/src/components/loading/loader.tsx b/src/components/loading/loader.tsx
new file mode 100644
index 0000000..cedb548
--- /dev/null
+++ b/src/components/loading/loader.tsx
@@ -0,0 +1,27 @@
+import Lottie from "react-lottie";
+import * as loadingData from "./loading.json";
+
+const loadingOption = {
+ loop: true,
+ autoplay: true,
+ animationData: loadingData,
+ rendererSettings: {
+ preserveAspectRatio: "xMidYMid slice",
+ },
+};
+
+interface LoaderProps {
+ isSuccess: boolean;
+}
+
+export function Loader(props: LoaderProps) {
+ return (
+ <>
+ {!props.isSuccess && (
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/loading/loading.json b/src/components/loading/loading.json
new file mode 100644
index 0000000..c199f2a
--- /dev/null
+++ b/src/components/loading/loading.json
@@ -0,0 +1,2549 @@
+{
+ "v": "5.7.4",
+ "fr": 29.9700012207031,
+ "ip": 0,
+ "op": 30.0000012219251,
+ "w": 100,
+ "h": 100,
+ "nm": "loading",
+ "ddd": 0,
+ "assets": [],
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 4,
+ "nm": "Layer 1 Outlines",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [50, 50.029, 0],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [48.861, 46.038, 0],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100, 100],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "ef": [
+ {
+ "ty": 21,
+ "nm": "Fill",
+ "np": 9,
+ "mn": "ADBE Fill",
+ "ix": 1,
+ "en": 1,
+ "ef": [
+ {
+ "ty": 10,
+ "nm": "Fill Mask",
+ "mn": "ADBE Fill-0001",
+ "ix": 1,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 1
+ }
+ },
+ {
+ "ty": 7,
+ "nm": "All Masks",
+ "mn": "ADBE Fill-0007",
+ "ix": 2,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 2
+ }
+ },
+ {
+ "ty": 2,
+ "nm": "Color",
+ "mn": "ADBE Fill-0002",
+ "ix": 3,
+ "v": {
+ "a": 0,
+ "k": [0.623529434204, 0.415686279535, 1, 1],
+ "ix": 3
+ }
+ },
+ {
+ "ty": 7,
+ "nm": "Invert",
+ "mn": "ADBE Fill-0006",
+ "ix": 4,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Horizontal Feather",
+ "mn": "ADBE Fill-0003",
+ "ix": 5,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Vertical Feather",
+ "mn": "ADBE Fill-0004",
+ "ix": 6,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Opacity",
+ "mn": "ADBE Fill-0005",
+ "ix": 7,
+ "v": {
+ "a": 0,
+ "k": 1,
+ "ix": 7
+ }
+ }
+ ]
+ }
+ ],
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [3, 48.649],
+ [19.787, 48.649]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 2",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [0, 3.292],
+ [0, 0],
+ [-2.902, 0],
+ [0, -2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [3.292, 0],
+ [0, 0],
+ [0, -2.902],
+ [2.901, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [-8.659, 10.089],
+ [-7.81, 10.089],
+ [-1.85, 4.13],
+ [-1.85, -4.836],
+ [3.405, -10.09],
+ [8.659, -4.836],
+ [8.659, 10.089]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [28.446, 38.56],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 4",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 2,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [2.902, 0],
+ [0, 2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 2.902],
+ [-2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [5.254, -14.213],
+ [5.254, 8.959],
+ [0, 14.213],
+ [-5.254, 8.959],
+ [-5.254, -14.213]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [42.359, 62.862],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 5",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 3,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [-2.902, 0],
+ [0, -2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, -2.902],
+ [2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [-5.254, 16.824],
+ [-5.254, -11.57],
+ [0, -16.824],
+ [5.254, -11.57],
+ [5.254, 16.824]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [52.867, 31.825],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 6",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 4,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [0, -3.385],
+ [0, 0],
+ [2.902, 0],
+ [0, 2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [-3.384, 0],
+ [0, 0],
+ [0, 2.902],
+ [-2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [8.407, -9.019],
+ [8.407, -9.019],
+ [2.102, -2.891],
+ [2.102, 3.765],
+ [-3.153, 9.019],
+ [-8.406, 3.765],
+ [-8.406, -9.019]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [66.529, 57.668],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 3",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 5,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [94.722, 48.649],
+ [74.935, 48.649]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 1",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 6,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "tm",
+ "s": {
+ "a": 0,
+ "k": 26,
+ "ix": 1
+ },
+ "e": {
+ "a": 0,
+ "k": 60,
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [0.982],
+ "y": [1]
+ },
+ "o": {
+ "x": [0.024],
+ "y": [0]
+ },
+ "t": 0,
+ "s": [0]
+ },
+ {
+ "i": {
+ "x": [0.667],
+ "y": [1]
+ },
+ "o": {
+ "x": [0.333],
+ "y": [0]
+ },
+ "t": 30,
+ "s": [360]
+ },
+ {
+ "t": 60.0000024438501,
+ "s": [0]
+ }
+ ],
+ "ix": 3
+ },
+ "m": 1,
+ "ix": 7,
+ "nm": "Trim Paths 1",
+ "mn": "ADBE Vector Filter - Trim",
+ "hd": false
+ }
+ ],
+ "ip": 0,
+ "op": 30.0000012219251,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 2,
+ "ty": 4,
+ "nm": "Layer 1 Outlines 2",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [50, 50.029, 0],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [48.861, 46.038, 0],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100, 100],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "ef": [
+ {
+ "ty": 21,
+ "nm": "Fill",
+ "np": 9,
+ "mn": "ADBE Fill",
+ "ix": 1,
+ "en": 1,
+ "ef": [
+ {
+ "ty": 10,
+ "nm": "Fill Mask",
+ "mn": "ADBE Fill-0001",
+ "ix": 1,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 1
+ }
+ },
+ {
+ "ty": 7,
+ "nm": "All Masks",
+ "mn": "ADBE Fill-0007",
+ "ix": 2,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 2
+ }
+ },
+ {
+ "ty": 2,
+ "nm": "Color",
+ "mn": "ADBE Fill-0002",
+ "ix": 3,
+ "v": {
+ "a": 0,
+ "k": [1, 0.415686279535, 0.827450990677, 1],
+ "ix": 3
+ }
+ },
+ {
+ "ty": 7,
+ "nm": "Invert",
+ "mn": "ADBE Fill-0006",
+ "ix": 4,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Horizontal Feather",
+ "mn": "ADBE Fill-0003",
+ "ix": 5,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Vertical Feather",
+ "mn": "ADBE Fill-0004",
+ "ix": 6,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Opacity",
+ "mn": "ADBE Fill-0005",
+ "ix": 7,
+ "v": {
+ "a": 0,
+ "k": 1,
+ "ix": 7
+ }
+ }
+ ]
+ }
+ ],
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [3, 48.649],
+ [19.787, 48.649]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 2",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [0, 3.292],
+ [0, 0],
+ [-2.902, 0],
+ [0, -2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [3.292, 0],
+ [0, 0],
+ [0, -2.902],
+ [2.901, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [-8.659, 10.089],
+ [-7.81, 10.089],
+ [-1.85, 4.13],
+ [-1.85, -4.836],
+ [3.405, -10.09],
+ [8.659, -4.836],
+ [8.659, 10.089]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [28.446, 38.56],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 4",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 2,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [2.902, 0],
+ [0, 2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 2.902],
+ [-2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [5.254, -14.213],
+ [5.254, 8.959],
+ [0, 14.213],
+ [-5.254, 8.959],
+ [-5.254, -14.213]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [42.359, 62.862],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 5",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 3,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [-2.902, 0],
+ [0, -2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, -2.902],
+ [2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [-5.254, 16.824],
+ [-5.254, -11.57],
+ [0, -16.824],
+ [5.254, -11.57],
+ [5.254, 16.824]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [52.867, 31.825],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 6",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 4,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [0, -3.385],
+ [0, 0],
+ [2.902, 0],
+ [0, 2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [-3.384, 0],
+ [0, 0],
+ [0, 2.902],
+ [-2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [8.407, -9.019],
+ [8.407, -9.019],
+ [2.102, -2.891],
+ [2.102, 3.765],
+ [-3.153, 9.019],
+ [-8.406, 3.765],
+ [-8.406, -9.019]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [66.529, 57.668],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 3",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 5,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [94.722, 48.649],
+ [74.935, 48.649]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 1",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 6,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "tm",
+ "s": {
+ "a": 0,
+ "k": 50,
+ "ix": 1
+ },
+ "e": {
+ "a": 0,
+ "k": 65,
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [0.982],
+ "y": [1]
+ },
+ "o": {
+ "x": [0.024],
+ "y": [0]
+ },
+ "t": 0,
+ "s": [40]
+ },
+ {
+ "i": {
+ "x": [0.667],
+ "y": [1]
+ },
+ "o": {
+ "x": [0.333],
+ "y": [0]
+ },
+ "t": 30,
+ "s": [401]
+ },
+ {
+ "t": 60.0000024438501,
+ "s": [40]
+ }
+ ],
+ "ix": 3
+ },
+ "m": 1,
+ "ix": 7,
+ "nm": "Trim Paths 1",
+ "mn": "ADBE Vector Filter - Trim",
+ "hd": false
+ }
+ ],
+ "ip": 0,
+ "op": 30.0000012219251,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 3,
+ "ty": 4,
+ "nm": "Layer 1 Outlines 3",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [50, 50.029, 0],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [48.861, 46.038, 0],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100, 100],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "ef": [
+ {
+ "ty": 21,
+ "nm": "Fill",
+ "np": 9,
+ "mn": "ADBE Fill",
+ "ix": 1,
+ "en": 1,
+ "ef": [
+ {
+ "ty": 10,
+ "nm": "Fill Mask",
+ "mn": "ADBE Fill-0001",
+ "ix": 1,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 1
+ }
+ },
+ {
+ "ty": 7,
+ "nm": "All Masks",
+ "mn": "ADBE Fill-0007",
+ "ix": 2,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 2
+ }
+ },
+ {
+ "ty": 2,
+ "nm": "Color",
+ "mn": "ADBE Fill-0002",
+ "ix": 3,
+ "v": {
+ "a": 0,
+ "k": [1, 0.815686285496, 0.993494808674, 1],
+ "ix": 3
+ }
+ },
+ {
+ "ty": 7,
+ "nm": "Invert",
+ "mn": "ADBE Fill-0006",
+ "ix": 4,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Horizontal Feather",
+ "mn": "ADBE Fill-0003",
+ "ix": 5,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Vertical Feather",
+ "mn": "ADBE Fill-0004",
+ "ix": 6,
+ "v": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ }
+ },
+ {
+ "ty": 0,
+ "nm": "Opacity",
+ "mn": "ADBE Fill-0005",
+ "ix": 7,
+ "v": {
+ "a": 0,
+ "k": 1,
+ "ix": 7
+ }
+ }
+ ]
+ }
+ ],
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [3, 48.649],
+ [19.787, 48.649]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 2",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [0, 3.292],
+ [0, 0],
+ [-2.902, 0],
+ [0, -2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [3.292, 0],
+ [0, 0],
+ [0, -2.902],
+ [2.901, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [-8.659, 10.089],
+ [-7.81, 10.089],
+ [-1.85, 4.13],
+ [-1.85, -4.836],
+ [3.405, -10.09],
+ [8.659, -4.836],
+ [8.659, 10.089]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [28.446, 38.56],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 4",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 2,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [2.902, 0],
+ [0, 2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 2.902],
+ [-2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [5.254, -14.213],
+ [5.254, 8.959],
+ [0, 14.213],
+ [-5.254, 8.959],
+ [-5.254, -14.213]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [42.359, 62.862],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 5",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 3,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [-2.902, 0],
+ [0, -2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, -2.902],
+ [2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [-5.254, 16.824],
+ [-5.254, -11.57],
+ [0, -16.824],
+ [5.254, -11.57],
+ [5.254, 16.824]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [52.867, 31.825],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 6",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 4,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0],
+ [0, -3.385],
+ [0, 0],
+ [2.902, 0],
+ [0, 2.902],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [-3.384, 0],
+ [0, 0],
+ [0, 2.902],
+ [-2.902, 0],
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [8.407, -9.019],
+ [8.407, -9.019],
+ [2.102, -2.891],
+ [2.102, 3.765],
+ [-3.153, 9.019],
+ [-8.406, 3.765],
+ [-8.406, -9.019]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [66.529, 57.668],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 3",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 5,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [0, 0],
+ [0, 0]
+ ],
+ "o": [
+ [0, 0],
+ [0, 0]
+ ],
+ "v": [
+ [94.722, 48.649],
+ [74.935, 48.649]
+ ],
+ "c": false
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [0.172549019608, 0.192156877705, 0.286274509804, 1],
+ "ix": 3
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "w": {
+ "a": 0,
+ "k": 6,
+ "ix": 5
+ },
+ "lc": 2,
+ "lj": 2,
+ "bm": 0,
+ "nm": "Stroke 1",
+ "mn": "ADBE Vector Graphic - Stroke",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [0, 0],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [100, 100],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 1",
+ "np": 2,
+ "cix": 2,
+ "bm": 0,
+ "ix": 6,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ },
+ {
+ "ty": "tm",
+ "s": {
+ "a": 0,
+ "k": 50,
+ "ix": 1
+ },
+ "e": {
+ "a": 0,
+ "k": 66,
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [0.982],
+ "y": [1]
+ },
+ "o": {
+ "x": [0.024],
+ "y": [0]
+ },
+ "t": 0,
+ "s": [88]
+ },
+ {
+ "i": {
+ "x": [0.667],
+ "y": [1]
+ },
+ "o": {
+ "x": [0.333],
+ "y": [0]
+ },
+ "t": 30,
+ "s": [448]
+ },
+ {
+ "t": 60.0000024438501,
+ "s": [40]
+ }
+ ],
+ "ix": 3
+ },
+ "m": 1,
+ "ix": 7,
+ "nm": "Trim Paths 1",
+ "mn": "ADBE Vector Filter - Trim",
+ "hd": false
+ }
+ ],
+ "ip": 0,
+ "op": 30.0000012219251,
+ "st": 0,
+ "bm": 0
+ }
+ ],
+ "markers": []
+}
diff --git a/src/components/multipleSelector.tsx b/src/components/multipleSelector.tsx
index 74b4796..6ad439e 100644
--- a/src/components/multipleSelector.tsx
+++ b/src/components/multipleSelector.tsx
@@ -8,19 +8,20 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { ReactElement } from "react";
+import { ReactElement, useState } from "react";
-interface MultipleOptionSelector {
+interface MultipleOptionSelectorProps {
header: ReactElement;
fieldName: string;
- choices: string[];
- handleFunction: Function;
+ choices: { id: number; name: string }[];
+ handleFunction: Function | null;
description: ReactElement;
placeholder: string;
selectLabel: string;
}
-export function MultipleOptionSelector(props: MultipleOptionSelector) {
+export function MultipleOptionSelector(props: MultipleOptionSelectorProps) {
+ const [value, setValue] = useState("");
return (
@@ -28,9 +29,15 @@ export function MultipleOptionSelector(props: MultipleOptionSelector) {
{
- props.handleFunction(props.fieldName, value);
- // console.log(value, props.fieldName);
+ value={value}
+ onValueChange={(id) => {
+ setValue(id);
+ const selectedChoice = props.choices.find(
+ (choice) => choice.id.toString() === id
+ );
+ if (selectedChoice && props.handleFunction) {
+ props.handleFunction(selectedChoice);
+ }
}}
>
@@ -40,8 +47,8 @@ export function MultipleOptionSelector(props: MultipleOptionSelector) {
{props.selectLabel}
{props.choices.map((i) => (
-
- {i}
+
+ {i.name}
))}
diff --git a/src/components/navigationBar/nav.tsx b/src/components/navigationBar/nav.tsx
index f7679a4..8579b13 100644
--- a/src/components/navigationBar/nav.tsx
+++ b/src/components/navigationBar/nav.tsx
@@ -51,8 +51,8 @@ export function NavigationBar() {
const projectComponents = [
{
title: "Projects",
- href: "/landing",
- description: "Raise on B2DVentures",
+ href: "/project/apply",
+ description: "Start your new project on B2DVentures",
},
];
diff --git a/src/components/navigationBar/profileBar.tsx b/src/components/navigationBar/profileBar.tsx
index 41b31b9..e701ddb 100644
--- a/src/components/navigationBar/profileBar.tsx
+++ b/src/components/navigationBar/profileBar.tsx
@@ -32,7 +32,7 @@ const UnAuthenticatedComponents = () => {
);
};
-const AuthenticatedComponents = () => {
+const AuthenticatedComponents = ({ uid }: { uid: string }) => {
let notifications = 100;
const displayValue = notifications >= 100 ? "..." : notifications;
return (
@@ -58,7 +58,7 @@ const AuthenticatedComponents = () => {
- Profile
+ Profile
Settings
@@ -88,7 +88,7 @@ export function ProfileBar() {
<>
{sessionLoaded ? (
user ? (
-
+
) : (
)
diff --git a/src/components/projectCard.tsx b/src/components/projectCard.tsx
index 5734f04..b29d726 100644
--- a/src/components/projectCard.tsx
+++ b/src/components/projectCard.tsx
@@ -25,7 +25,7 @@ export function ProjectCard(props: ProjectCardProps) {
return (
@@ -58,7 +58,7 @@ export function ProjectCard(props: ProjectCardProps) {
{/* Info 1 */}
-
+
@@ -79,7 +79,7 @@ export function ProjectCard(props: ProjectCardProps) {
{/* Info 2 */}
-
+
{/* Info 2 (Visible on hover) */}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 0ba4277..36496a2 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
- "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
new file mode 100644
index 0000000..1a37e67
--- /dev/null
+++ b/src/components/ui/command.tsx
@@ -0,0 +1,155 @@
+"use client"
+
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => (
+
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
new file mode 100644
index 0000000..ce264ae
--- /dev/null
+++ b/src/components/ui/form.tsx
@@ -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 = FieldPath
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ )
+}
+
+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 ")
+ }
+
+ 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(
+ {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+
+
+
+ )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message) : children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+
+ {body}
+
+ )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 0000000..a0ec48b
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }
diff --git a/src/lib/data/projectQuery.ts b/src/lib/data/projectQuery.ts
index f1a5d3c..2f131a4 100644
--- a/src/lib/data/projectQuery.ts
+++ b/src/lib/data/projectQuery.ts
@@ -2,7 +2,7 @@ import { SupabaseClient } from "@supabase/supabase-js";
async function getTopProjects(
client: SupabaseClient,
- numberOfRecords: number = 4,
+ numberOfRecords: number = 4
) {
try {
const { data, error } = await client
@@ -21,7 +21,7 @@ async function getTopProjects(
target_investment,
investment_deadline
),
- item_tag (
+ project_tag (
tag (
id,
value
@@ -30,7 +30,7 @@ async function getTopProjects(
business (
location
)
- `,
+ `
)
.order("published_time", { ascending: false })
.limit(numberOfRecords);
@@ -48,8 +48,10 @@ async function getTopProjects(
}
function getProjectDataQuery(client: SupabaseClient, projectId: number) {
- return client.from("project").select(
- `
+ return client
+ .from("project")
+ .select(
+ `
project_name,
project_short_description,
project_description,
@@ -65,13 +67,17 @@ function getProjectDataQuery(client: SupabaseClient, projectId: number) {
tag_name:value
)
)
- `,
- ).eq("id", projectId).single();
+ `
+ )
+ .eq("id", projectId)
+ .single();
}
async function getProjectData(client: SupabaseClient, projectId: number) {
- const query = client.from("project").select(
- `
+ const query = client
+ .from("project")
+ .select(
+ `
project_name,
project_short_description,
project_description,
@@ -87,8 +93,10 @@ async function getProjectData(client: SupabaseClient, projectId: number) {
tag_name:value
)
)
- `,
- ).eq("id", projectId).single();
+ `
+ )
+ .eq("id", projectId)
+ .single();
const { data, error } = await query;
return { data, error };
@@ -118,19 +126,21 @@ function searchProjectsQuery(
sortByTimeFilter,
page = 1,
pageSize = 4,
- }: FilterProjectQueryParams,
+ }: FilterProjectQueryParams
) {
const start = (page - 1) * pageSize;
const end = start + pageSize - 1;
- let query = client.from("project").select(
- `
+ let query = client
+ .from("project")
+ .select(
+ `
project_id:id,
project_name,
published_time,
project_short_description,
card_image_url,
- ...project_status!project_project_status_id_fkey!inner (
+ ...project_status!inner (
project_status:value
),
...project_investment_detail!inner (
@@ -150,8 +160,10 @@ function searchProjectsQuery(
),
business_location:location
)
- `,
- ).order("published_time", { ascending: false }).range(start, end);
+ `
+ )
+ .order("published_time", { ascending: false })
+ .range(start, end);
if (sortByTimeFilter === "all") {
sortByTimeFilter = undefined;
diff --git a/src/types/schemas/application.schema.ts b/src/types/schemas/application.schema.ts
new file mode 100644
index 0000000..f4472e4
--- /dev/null
+++ b/src/types/schemas/application.schema.ts
@@ -0,0 +1,163 @@
+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.number({
+ 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)) {
+ return (
+ value.length > 0 &&
+ 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.",
+ }),
+ tag: z
+ .array(z.number())
+ .min(1, "Please provide at least one tag.")
+ .max(5, "You can provide up to 5 tags."),
+});
+
+const businessFormSchema = z.object({
+ companyName: z.string().min(5, {
+ message: "Company name must be at least 5 characters.",
+ }),
+ industry: z.number({
+ 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.toLowerCase().endsWith(".md"), {
+ message: "File must be a markdown file (.md).",
+ }),
+ ]),
+ country: z.string({
+ required_error: "Please select one of the option",
+ }),
+});
+
+export { businessFormSchema, projectFormSchema };