mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-18 21:44:06 +01:00
Merge branch 'front-end' into back-end
This commit is contained in:
commit
9db518cb6b
@ -15,6 +15,11 @@ const nextConfig = {
|
||||
hostname: "upload.wikimedia.org",
|
||||
pathname: "/wikipedia/**",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "avatars.githubusercontent.com",
|
||||
pathname: "/**",
|
||||
}
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
32
package-lock.json
generated
32
package-lock.json
generated
@ -35,6 +35,7 @@
|
||||
"@tanstack/react-query": "^5.59.0",
|
||||
"@tanstack/react-query-devtools": "^5.59.0",
|
||||
"b2d-ventures": "file:",
|
||||
"chart.js": "^4.4.6",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
@ -44,7 +45,8 @@
|
||||
"lucide-react": "^0.428.0",
|
||||
"next": "^14.2.15",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-day-picker": "^9",
|
||||
"react-dom": "^18",
|
||||
@ -68,7 +70,7 @@
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/next": "^8.0.7",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-fade-in": "^2.0.2",
|
||||
"@types/react-file-icon": "^1.0.4",
|
||||
@ -1132,6 +1134,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
|
||||
},
|
||||
"node_modules/@lexical/clipboard": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.17.1.tgz",
|
||||
@ -4724,6 +4731,17 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
|
||||
"integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@ -10143,6 +10161,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-chartjs-2": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz",
|
||||
"integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==",
|
||||
"peerDependencies": {
|
||||
"chart.js": "^4.1.1",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-countup": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz",
|
||||
@ -10260,6 +10287,7 @@
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.4.tgz",
|
||||
"integrity": "sha512-kBGxI+MIZGBf4wZhNCWwHkMcVP+kbpmrLWH/SkO0qCKc7D7eSPcxQbfpsmsCo8v2KCBYjuGSou+xTqK44D/jMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"lottie-web": "^5.1.3"
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"@tanstack/react-query": "^5.59.0",
|
||||
"@tanstack/react-query-devtools": "^5.59.0",
|
||||
"b2d-ventures": "file:",
|
||||
"chart.js": "^4.4.6",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
@ -46,7 +47,8 @@
|
||||
"lucide-react": "^0.428.0",
|
||||
"next": "^14.2.15",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-day-picker": "^9",
|
||||
"react-dom": "^18",
|
||||
@ -70,7 +72,7 @@
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/next": "^8.0.7",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-fade-in": "^2.0.2",
|
||||
"@types/react-file-icon": "^1.0.4",
|
||||
|
||||
42
src/app/(investment)/deals/[id]/displayImage.tsx
Normal file
42
src/app/(investment)/deals/[id]/displayImage.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import Image from "next/image";
|
||||
import { StaticImport } from "next/dist/shared/lib/get-img-props";
|
||||
|
||||
interface ItemProps {
|
||||
src: string | StaticImport;
|
||||
alt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ImageModal = ({ src, alt, width, height, className }: ItemProps) => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Image src={src} alt={alt} width={width} height={height} className={className} />
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Image Preview</DialogTitle>
|
||||
<DialogDescription>Click outside to close the image preview.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Image src={src} alt={alt} width={700} height={400} />
|
||||
<DialogFooter />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export function DisplayFullImage({ src, alt, width, height, className }: ItemProps) {
|
||||
return <ImageModal src={src} alt={alt} width={width} height={height} className={className} />;
|
||||
}
|
||||
@ -10,8 +10,6 @@ import useSession from "@/lib/supabase/useSession";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const FollowShareButtons = () => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [tab, setTab] = useState("Pitch");
|
||||
const { session, loading } = useSession();
|
||||
const user = session?.user;
|
||||
const [sessionLoaded, setSessionLoaded] = useState(false);
|
||||
|
||||
@ -11,17 +11,26 @@ import { Progress } from "@/components/ui/progress";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
||||
import FollowShareButtons from "./followShareButton";
|
||||
|
||||
import { DisplayFullImage } from "./displayImage";
|
||||
import { getProjectData } from "@/lib/data/projectQuery";
|
||||
import { getDealList } from "@/app/api/dealApi";
|
||||
import { sumByKey, toPercentage } from "@/lib/utils";
|
||||
import { redirect } from "next/navigation";
|
||||
const PHOTO_MATERIAL_ID = 2;
|
||||
|
||||
export default async function ProjectDealPage({ params }: { params: { id: number } }) {
|
||||
const supabase = createSupabaseClient();
|
||||
|
||||
const { data: projectData, error: projectDataError } = await getProjectData(supabase, params.id);
|
||||
|
||||
const { data: projectMaterial, error: projectMaterialError } = await supabase
|
||||
.from("project_material")
|
||||
.select("material_url")
|
||||
.eq("project_id", params.id)
|
||||
.eq("material_type_id", PHOTO_MATERIAL_ID);
|
||||
// console.log(projectMaterial);
|
||||
if (projectMaterialError) {
|
||||
console.error("Error while fetching project material" + projectMaterialError);
|
||||
}
|
||||
if (!projectData) {
|
||||
redirect("/deals");
|
||||
}
|
||||
@ -44,94 +53,107 @@ export default async function ProjectDealPage({ params }: { params: { id: number
|
||||
const timeDiff = Math.max(new Date(projectData.investment_deadline).getTime() - new Date().getTime(), 0);
|
||||
const hourLeft = Math.floor(timeDiff / (1000 * 60 * 60));
|
||||
|
||||
const carouselData = Array(5).fill({
|
||||
src: projectData.card_image_url || "/boiler1.jpg",
|
||||
alt: `${projectData.project_name} Image`,
|
||||
});
|
||||
const carouselData =
|
||||
projectMaterial && projectMaterial.length > 0
|
||||
? projectMaterial.flatMap((item) =>
|
||||
(item.material_url || ["/boiler1.jpg"]).map((url: string) => ({
|
||||
src: url,
|
||||
alt: "Image",
|
||||
}))
|
||||
)
|
||||
: [{ src: "/boiler1.jpg", alt: "Default Boiler Image" }];
|
||||
|
||||
// console.log(carouselData);
|
||||
|
||||
return (
|
||||
<div className="container max-w-screen-xl my-5">
|
||||
<div className="flex flex-col gap-y-10">
|
||||
<div id="content">
|
||||
{/* Name, star and share button packed */}
|
||||
<div id="header" className="flex flex-col">
|
||||
<div className="flex justify-between">
|
||||
<span className="flex">
|
||||
<Image src="/logo.svg" alt="logo" width={50} height={50} className="sm:scale-75" />
|
||||
<h1 className="mt-3 font-bold text-lg md:text-3xl">{projectData?.project_name}</h1>
|
||||
</span>
|
||||
<FollowShareButtons />
|
||||
</div>
|
||||
{/* end of pack */}
|
||||
<p className="mt-2 sm:text-sm">{projectData?.project_short_description}</p>
|
||||
<div className="flex flex-wrap mt-3">
|
||||
{projectData?.tags.map((tag, index) => (
|
||||
<span key={index} className="text-xs rounded-md bg-slate-200 dark:bg-slate-700 p-1 mx-1 mb-1">
|
||||
{tag.tag_name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{/* Name, star and share button packed */}
|
||||
<div id="header" className="flex flex-col">
|
||||
<div className="flex justify-between">
|
||||
<span className="flex">
|
||||
<Image src="/logo.svg" alt="logo" width={50} height={50} className="sm:scale-75" />
|
||||
<h1 className="mt-3 font-bold text-lg md:text-3xl">{projectData?.project_name}</h1>
|
||||
</span>
|
||||
<FollowShareButtons />
|
||||
</div>
|
||||
{/* end of pack */}
|
||||
<p className="mt-2 sm:text-sm">{projectData?.project_short_description}</p>
|
||||
<div className="flex flex-wrap mt-3">
|
||||
{projectData?.tags.map((tag, index) => (
|
||||
<span key={index} className="text-xs rounded-md bg-slate-200 dark:bg-slate-700 p-1 mx-1 mb-1">
|
||||
{tag.tag_name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div id="sub-content" className="flex flex-row mt-5">
|
||||
{/* image carousel */}
|
||||
<div id="image-carousel" className="shrink-0 w-[700px] flex flex-col">
|
||||
{/* first carousel */}
|
||||
<Carousel className="w-full h-[400px] ml-1 overflow-hidden">
|
||||
<CarouselContent className="flex h-full">
|
||||
{carouselData.map((item, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<Image src={item.src} alt={item.alt} width={700} height={400} className="rounded-lg object-cover" />
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious className="absolute left-2 top-1/2 transform -translate-y-1/2 z-10 text-white bg-black opacity-50 hover:opacity-100" />
|
||||
<CarouselNext className="absolute right-2 top-1/2 transform -translate-y-1/2 z-10 text-white bg-black opacity-50 hover:opacity-100" />
|
||||
</Carousel>
|
||||
{/* second carousel */}
|
||||
<Carousel className="w-full ml-1 h-[100px] mt-5 overflow-hidden">
|
||||
<CarouselContent className="flex space-x-1 h-[100px]">
|
||||
{carouselData.map((item, index) => (
|
||||
<CarouselItem key={index} className="flex">
|
||||
<DisplayFullImage
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
width={200}
|
||||
height={100}
|
||||
className="rounded-lg object-cover h-[100px] w-[200px]"
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
<div id="sub-content" className="flex flex-row mt-5">
|
||||
{/* image carousel */}
|
||||
<div id="image-corousel" className="shrink-0 w-[700px] flex flex-col">
|
||||
<Carousel className="w-full h-full ml-1">
|
||||
<CarouselContent className="flex h-full">
|
||||
{carouselData.map((item, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<Image src={item.src} alt={item.alt} width={700} height={400} className="rounded-lg" />
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
|
||||
<Carousel className="w-full ml-1 h-[100px]">
|
||||
<CarouselContent className="flex space-x-1">
|
||||
{carouselData.map((item, index) => (
|
||||
<CarouselItem key={index} className="flex">
|
||||
<Image src={item.src} alt={item.alt} width={200} height={100} className="rounded-lg basis-0" />
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
<div id="stats" className="flex flex-col w-full mt-4 pl-12">
|
||||
<div className="pl-5">
|
||||
<span>
|
||||
<h1 className="font-semibold text-xl md:text-4xl mt-8">${totalDealAmount}</h1>
|
||||
<p className="text-sm md:text-lg">
|
||||
{toPercentage(totalDealAmount, projectData?.target_investment)}% raised of $
|
||||
{projectData?.target_investment} max goal
|
||||
</p>
|
||||
<Progress
|
||||
value={toPercentage(totalDealAmount, projectData?.target_investment)}
|
||||
className="w-[60%] h-3 mt-3"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<h1 className="font-semibold text-4xl md:mt-8">
|
||||
<p className="text-xl md:text-4xl">{dealList.length}</p>
|
||||
</h1>
|
||||
<p className="text-sm md:text-lg">Investors</p>
|
||||
</span>
|
||||
<Separator decorative className="mt-3 w-3/4 ml-5" />
|
||||
<span>
|
||||
<h1 className="font-semibold text-xl md:text-4xl mt-8 ml-5"></h1>
|
||||
{projectData?.investment_deadline ? (
|
||||
<>
|
||||
<p className="text-xl md:text-4xl">{Math.floor(hourLeft)} hours</p>
|
||||
<p>Left to invest</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xl md:text-4xl">No deadline</p>
|
||||
)}
|
||||
</span>
|
||||
<Button className="mt-5 w-3/4 h-12">
|
||||
<Link href={`/invest/${params.id}`}>Invest in {projectData?.project_name}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div id="stats" className="flex flex-col w-full mt-4 pl-12">
|
||||
<div className="pl-5">
|
||||
<span>
|
||||
<h1 className="font-semibold text-xl md:text-4xl mt-8">${totalDealAmount}</h1>
|
||||
<p className="text-sm md:text-lg">
|
||||
{toPercentage(totalDealAmount, projectData?.target_investment)}% raised of $
|
||||
{projectData?.target_investment} max goal
|
||||
</p>
|
||||
<Progress
|
||||
value={toPercentage(totalDealAmount, projectData?.target_investment)}
|
||||
className="w-[60%] h-3 mt-3"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<h1 className="font-semibold text-4xl md:mt-8">
|
||||
<p className="text-xl md:text-4xl">{dealList.length}</p>
|
||||
</h1>
|
||||
<p className="text-sm md:text-lg">Investors</p>
|
||||
</span>
|
||||
<Separator decorative className="mt-3 w-3/4 ml-5" />
|
||||
<span>
|
||||
<h1 className="font-semibold text-xl md:text-4xl mt-8 ml-5"></h1>
|
||||
{projectData?.investment_deadline ? (
|
||||
<>
|
||||
<p className="text-xl md:text-4xl">{Math.floor(hourLeft)} hours</p>
|
||||
<p>Left to invest</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xl md:text-4xl">No deadline</p>
|
||||
)}
|
||||
</span>
|
||||
<Button className="mt-5 w-3/4 h-12">
|
||||
<Link href={`/invest/${params.id}`}>Invest in {projectData?.project_name}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
79
src/app/about/page.tsx
Normal file
79
src/app/about/page.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function About() {
|
||||
// Static data for the cards
|
||||
const cardData = [
|
||||
{
|
||||
imageSrc: 'https://avatars.githubusercontent.com/u/86756025',
|
||||
name: 'Pattadon Loyprasert',
|
||||
description:
|
||||
'Driven by a passion for innovation, Pattadon leads B2D Ventures with the belief that' +
|
||||
'every great idea deserves the resources and support to thrive. He’s dedicated to' +
|
||||
'helping entrepreneurs turn visions into reality.',
|
||||
},
|
||||
{
|
||||
imageSrc: 'https://avatars.githubusercontent.com/u/22256420',
|
||||
name: 'Sirin Puenggun',
|
||||
description:
|
||||
'Sirin brings a wealth of experience in empowering entrepreneurs, aiming to' +
|
||||
'create an ecosystem where bold ideas meet the right partners. He’s committed to' +
|
||||
'making a lasting impact on the entrepreneurial world.',
|
||||
},
|
||||
{
|
||||
imageSrc: 'https://avatars.githubusercontent.com/u/108450436',
|
||||
name: 'Naytitorn Chaovirachot',
|
||||
description:
|
||||
'With a strong foundation in collaboration and trust, Naytitorn is focused' +
|
||||
'on building lasting partnerships that help drive the success of both investors and founders.' +
|
||||
'He thrives on turning challenges into growth opportunities.',
|
||||
},
|
||||
{
|
||||
imageSrc: 'https://avatars.githubusercontent.com/u/114897362',
|
||||
name: 'Nantawat Sukrisunt',
|
||||
description:
|
||||
'Nantawat is a passionate advocate for innovation and teamwork.' +
|
||||
'He strives to foster a community where both investors and startups can achieve' +
|
||||
'their full potential, creating a future where collaboration leads to success.',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-10">
|
||||
<h1 className="mt-3 font-bold text-lg md:text-3xl">About us</h1>
|
||||
<p className="p-5">
|
||||
Welcome to B2D Ventures! We're a dynamic platform committed to bridging the gap
|
||||
between visionary entrepreneurs and passionate investors. Our mission is to empower
|
||||
innovation by connecting groundbreaking ideas with the resources they need to thrive.
|
||||
Through B2D Ventures, we foster a community where investors and innovators come together
|
||||
to transform concepts into impactful, real-world solutions.
|
||||
</p>
|
||||
<p className="p-5">
|
||||
At B2D Ventures, we believe in the power of collaboration. Whether you're an investor
|
||||
looking to support the next big idea or a founder aiming to bring your vision to life,
|
||||
our platform offers the tools and connections to make it happen.
|
||||
|
||||
Join us on a journey to reshape industries, drive positive change, and make a lasting impact.
|
||||
</p>
|
||||
<p className="p-5">
|
||||
Let's build the future, together.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 text-center">
|
||||
<h2 className="font-bold text-lg md:text-3xl">Our Team</h2>
|
||||
</div>
|
||||
|
||||
{/* Card Section */}
|
||||
<div className="mt-10 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{cardData.map((card, index) => (
|
||||
<div key={index} className="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<Image src={card.imageSrc} width={460} height={460} alt={card.name} className="w-full h-48 object-cover" />
|
||||
<div className="p-4">
|
||||
<h3 className="pt-5 text-xl font-semibold text-gray-800 text-center">{card.name}</h3>
|
||||
<p className="pt-5 text-gray-600 mt-2">{card.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -35,7 +35,7 @@ export async function getDealList(userId: string | undefined) {
|
||||
}
|
||||
|
||||
if (!dealData || !dealData.project.length) {
|
||||
alert("No project available");
|
||||
// alert("No project available");
|
||||
return []; // Exit if there's no data
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ export async function getRecentDealData(userId: string | undefined) {
|
||||
console.error("User not found");
|
||||
return; // Exit on error
|
||||
}
|
||||
|
||||
|
||||
const supabase = createSupabaseClient();
|
||||
const dealList = await getDealList(userId);
|
||||
|
||||
@ -111,7 +111,6 @@ export async function getRecentDealData(userId: string | undefined) {
|
||||
return { ...item, ...recentUserData[index] };
|
||||
});
|
||||
|
||||
|
||||
return recentDealData;
|
||||
}
|
||||
|
||||
@ -133,6 +132,8 @@ export function convertToGraphData(deals: Deal[]): Record<string, number> {
|
||||
|
||||
// Create a sorted graph data object
|
||||
const sortedGraphData: Record<string, number> = {};
|
||||
sortedKeys.forEach((key) => {sortedGraphData[key] = graphData[key]});
|
||||
sortedKeys.forEach((key) => {
|
||||
sortedGraphData[key] = graphData[key];
|
||||
});
|
||||
return sortedGraphData;
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ const BUCKET_PITCH_NAME = "business-application";
|
||||
let supabase = createSupabaseClient();
|
||||
|
||||
export default function ApplyBusiness() {
|
||||
const [applyProject, setApplyProject] = useState(false);
|
||||
const alertShownRef = useRef(false);
|
||||
const [success, setSucess] = useState(false);
|
||||
|
||||
@ -49,7 +48,7 @@ export default function ApplyBusiness() {
|
||||
}
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
const { data, error } = await supabase
|
||||
.from("business_application")
|
||||
.insert([
|
||||
{
|
||||
@ -67,28 +66,29 @@ export default function ApplyBusiness() {
|
||||
])
|
||||
.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 = "/";
|
||||
}
|
||||
}).then(() => {
|
||||
window.location.href = "/";
|
||||
});
|
||||
};
|
||||
|
||||
const hasUserApplied = async (userID: string) => {
|
||||
let { data: business, error } = await supabase.from("business").select("*").eq("user_id", userID);
|
||||
console.table(business);
|
||||
if (error) {
|
||||
let { data: businessApplication, error: applicationError } = await supabase
|
||||
.from("business_application")
|
||||
.select("*")
|
||||
.eq("user_id", userID);
|
||||
// console.table(business);
|
||||
if (error || applicationError) {
|
||||
console.error(error);
|
||||
console.error(applicationError);
|
||||
}
|
||||
if (business) {
|
||||
if ((business && business.length != 0) || (businessApplication && businessApplication.length != 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -165,7 +165,7 @@ export default function ApplyBusiness() {
|
||||
</div>
|
||||
{/* form */}
|
||||
{/* <form action="" onSubmit={handleSubmit(handleSubmitForms)}> */}
|
||||
<BusinessForm onSubmit={onSubmit} applyProject={applyProject} setApplyProject={setApplyProject} />
|
||||
<BusinessForm onSubmit={onSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export function useDealList() {
|
||||
const fetchDealList = async () => {
|
||||
// set the state to the deal list of current business user
|
||||
setDealList(await getDealList(await getCurrentUserID()));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchDealList();
|
||||
@ -28,7 +28,7 @@ export function useGraphData() {
|
||||
if (dealList) {
|
||||
setGraphData(convertToGraphData(dealList));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGraphData();
|
||||
@ -43,11 +43,11 @@ export function useRecentDealData() {
|
||||
const fetchRecentDealData = async () => {
|
||||
// set the state to the deal list of current business user
|
||||
setRecentDealData(await getRecentDealData(await getCurrentUserID()));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecentDealData();
|
||||
}, []);
|
||||
|
||||
return recentDealData;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +1,101 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Overview } from "@/components/ui/overview";
|
||||
import { RecentFunds } from "@/components/recent-funds";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDealList } from "./hook";
|
||||
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
||||
import useSession from "@/lib/supabase/useSession";
|
||||
import { getProjectByUserId } from "@/lib/data/projectQuery";
|
||||
import { Loader } from "@/components/loading/loader";
|
||||
|
||||
import { useDealList, useGraphData, useRecentDealData } from "./hook";
|
||||
import { sumByKey } from "@/lib/utils";
|
||||
const data = [
|
||||
{
|
||||
name: "Jan",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Feb",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Mar",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Apr",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "May",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Jun",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Jul",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Aug",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Sep",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Oct",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Nov",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Dec",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
];
|
||||
|
||||
export default function Dashboard() {
|
||||
let supabase = createSupabaseClient();
|
||||
const userId = useSession().session?.user.id;
|
||||
const [projects, setProjects] = useState<
|
||||
{ id: number; project_name: string; business_id: { user_id: number }[]; dataroom_id: number }[]
|
||||
>([]);
|
||||
const [isSuccess, setIsSuccess] = useState(false);
|
||||
const [graphType, setGraphType] = useState("line");
|
||||
const graphData = useGraphData();
|
||||
const [currentTab, setCurrentTab] = useState();
|
||||
const dealList = useDealList();
|
||||
// #TODO dependency injection refactor + define default value inside function (and not here)
|
||||
const recentDealData = useRecentDealData() || [];
|
||||
const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0;
|
||||
useEffect(() => {
|
||||
const fetchProjects = async () => {
|
||||
if (userId) {
|
||||
const { data, error } = await getProjectByUserId(supabase, userId);
|
||||
// alert(JSON.stringify(data));
|
||||
if (error) {
|
||||
console.error("Error while fetching projects");
|
||||
}
|
||||
if (data) {
|
||||
setProjects(data);
|
||||
// console.table(data);
|
||||
}
|
||||
} else {
|
||||
console.error("Error with UserId while fetching projects");
|
||||
}
|
||||
setIsSuccess(true);
|
||||
};
|
||||
fetchProjects();
|
||||
}, [supabase, userId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Loader isSuccess={isSuccess} />
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/dashboard-light.png"
|
||||
@ -45,18 +117,17 @@ export default function Dashboard() {
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Business Dashboard</h2>
|
||||
</div>
|
||||
<Tabs defaultValue="overview" className="space-y-4">
|
||||
<Tabs defaultValue={projects[0].project_name} className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||
{projects.map((project) => (
|
||||
<TabsTrigger value={project.project_name}>{project.project_name}</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<TabsContent value={projects[0].project_name} className="space-y-4">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Total Funds Raised
|
||||
</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Total Funds Raised</CardTitle>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
@ -71,7 +142,7 @@ export default function Dashboard() {
|
||||
</svg>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">${sumByKey(dealList, "deal_amount")}</div>
|
||||
<div className="text-2xl font-bold">${totalDealAmount}</div>
|
||||
{/* <p className="text-xs text-muted-foreground">
|
||||
+20.1% from last month
|
||||
</p> */}
|
||||
@ -79,9 +150,7 @@ export default function Dashboard() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Profile Views
|
||||
</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Profile Views</CardTitle>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
@ -105,9 +174,7 @@ export default function Dashboard() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Total Followers
|
||||
</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Total Followers</CardTitle>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
@ -162,23 +229,14 @@ export default function Dashboard() {
|
||||
<CardTitle>Overview</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pl-2">
|
||||
<Overview graphType={graphType} graphData={graphData} />
|
||||
<Overview graphType={graphType} data={data} />
|
||||
{/* tab to switch between line and bar graph */}
|
||||
<Tabs
|
||||
defaultValue="line"
|
||||
className="space-y-4 ml-[50%] mt-2"
|
||||
>
|
||||
<Tabs defaultValue="line" className="space-y-4 ml-[50%] mt-2">
|
||||
<TabsList>
|
||||
<TabsTrigger
|
||||
value="line"
|
||||
onClick={() => setGraphType("line")}
|
||||
>
|
||||
<TabsTrigger value="line" onClick={() => setGraphType("line")}>
|
||||
Line
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="bar"
|
||||
onClick={() => setGraphType("bar")}
|
||||
>
|
||||
<TabsTrigger value="bar" onClick={() => setGraphType("bar")}>
|
||||
Bar
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
@ -188,13 +246,10 @@ export default function Dashboard() {
|
||||
<Card className="col-span-3">
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Funds</CardTitle>
|
||||
<CardDescription>
|
||||
You made {dealList?.length || 0} sales this month.
|
||||
</CardDescription>
|
||||
<CardDescription>You made {dealList?.length || 0} sales this month.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<RecentFunds recentDealData={recentDealData}>
|
||||
</RecentFunds>
|
||||
<RecentFunds></RecentFunds>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -1,22 +1,4 @@
|
||||
import { Loader } from "@/components/loading/loader";
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="container flex items-center justify-center h-screen">
|
||||
<div className="text-center">
|
||||
<svg
|
||||
className="animate-spin h-12 w-12 text-gray-600 mx-auto mb-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291l-2.832 2.832A10.003 10.003 0 0112 22v-4a8.001 8.001 0 01-6-5.709z"
|
||||
></path>
|
||||
</svg>
|
||||
<p className="text-lg font-semibold text-gray-600">Loading data...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
@ -81,14 +81,14 @@ export default async function Home() {
|
||||
<CardTitle className="text-lg md:text-2xl">Follow Us</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex gap-2">
|
||||
<Button className="flex gap-1 border-2 border-border rounded-md p-1 bg-background text-foreground scale-75 md:scale-100">
|
||||
<Image src={"/github.svg"} width={20} height={20} alt="github" className="scale-75 md:scale-100" />
|
||||
Github
|
||||
</Button>
|
||||
<Button className="flex gap-1 border-2 border-border rounded-md p-1 bg-background text-foreground scale-75 md:scale-100">
|
||||
<Image src={"/github.svg"} width={20} height={20} alt="github" className="scale-75 md:scale-100" />
|
||||
Github
|
||||
</Button>
|
||||
<Link href="https://github.com/Sosokker/B2D-Ventures" passHref>
|
||||
<Button className="flex gap-1 border-2 border-border rounded-md p-1 bg-background text-foreground scale-75 md:scale-100">
|
||||
<div className="dark:bg-white rounded-full">
|
||||
<Image src={"/github.svg"} width={20} height={20} alt="github" className="scale-75 md:scale-100" />
|
||||
</div>
|
||||
Github
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
245
src/app/portfolio/[uid]/page.tsx
Normal file
245
src/app/portfolio/[uid]/page.tsx
Normal file
@ -0,0 +1,245 @@
|
||||
import { Overview } from "@/components/ui/overview";
|
||||
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
||||
import { getInvestorDeal } from "@/lib/data/investmentQuery";
|
||||
import PieChart from "@/components/pieChart";
|
||||
import {
|
||||
overAllGraphData,
|
||||
fourYearGraphData,
|
||||
dayOftheWeekData,
|
||||
getInvestorProjectTag,
|
||||
countTags,
|
||||
getBusinessTypeName,
|
||||
countValues,
|
||||
checkForInvest,
|
||||
getLatestInvestment,
|
||||
getTotalInvestment,
|
||||
} from "./query";
|
||||
import CountUpComponent from "@/components/countUp";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { RecentFunds } from "@/components/recent-funds";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import QuestionMarkIcon from "@/components/icon/questionMark";
|
||||
import { NoDataAlert } from "@/components/alert/noData/alert";
|
||||
import { error } from "console";
|
||||
import { UnAuthorizedAlert } from "@/components/alert/unauthorized/alert";
|
||||
|
||||
export default async function Portfolio({ params }: { params: { uid: string } }) {
|
||||
const supabase = createSupabaseClient();
|
||||
// if user hasn't invest in anything
|
||||
const hasInvestments = await checkForInvest(supabase, params.uid);
|
||||
if (!hasInvestments) {
|
||||
return (
|
||||
<div>
|
||||
<NoDataAlert />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { data: deals, error: investorDealError } = await getInvestorDeal(supabase, params.uid);
|
||||
if (investorDealError) {
|
||||
console.error(investorDealError);
|
||||
}
|
||||
const { data: localUser, error: localUserError } = await supabase.auth.getUser();
|
||||
if (localUserError) {
|
||||
console.error("Error while fetching user" + error);
|
||||
}
|
||||
// block user from try to see other user portfolio
|
||||
if (params.uid != localUser.user?.id) {
|
||||
return (
|
||||
<>
|
||||
<UnAuthorizedAlert />
|
||||
</>
|
||||
);
|
||||
}
|
||||
const username = localUser ? localUser.user.user_metadata.name : "Anonymous";
|
||||
// console.log(username)
|
||||
const overAllData = deals ? overAllGraphData(deals) : [];
|
||||
const fourYearData = deals ? fourYearGraphData(deals) : [];
|
||||
const dayOfWeekData = deals ? dayOftheWeekData(deals) : [];
|
||||
const tags = deals ? await getInvestorProjectTag(supabase, deals) : [];
|
||||
const latestDeals = deals
|
||||
? await Promise.all(
|
||||
(await getLatestInvestment(supabase, deals)).map(async (deal) => ({
|
||||
...deal,
|
||||
logo_url: await deal.logo_url,
|
||||
}))
|
||||
)
|
||||
: [];
|
||||
const totalInvestment = deals ? getTotalInvestment(deals) : 0;
|
||||
// console.log(latestDeals);
|
||||
|
||||
const tagCount = countTags(tags);
|
||||
// console.log(investedBusinessIds);
|
||||
const businessType = deals
|
||||
? await Promise.all(deals.map(async (item) => await getBusinessTypeName(supabase, item.project_id)))
|
||||
: [];
|
||||
const countedBusinessType = countValues(businessType.filter((item) => item !== null));
|
||||
// console.log(countedBusinessType);
|
||||
|
||||
// console.log(tagCount);
|
||||
return (
|
||||
<div className="p-5">
|
||||
{/* {JSON.stringify(params.uid)} */}
|
||||
{/* {JSON.stringify(tagCount)} */}
|
||||
{/* {JSON.stringify(deals)} */}
|
||||
{/* {JSON.stringify(dayOfWeekData)} */}
|
||||
{/* {JSON.stringify(overAllGraphData)} */}
|
||||
{/* {JSON.stringify(threeYearGraphData)} */}
|
||||
{/* {JSON.stringify(uniqueProjectIds)} */}
|
||||
{/* <div className="flex flex-row">
|
||||
<h1>Total Invest : </h1>
|
||||
<div>{totalInvest}</div>
|
||||
</div> */}
|
||||
{/* <CountUpComponent end={100} duration={3} /> */}
|
||||
<div className="text-center py-4">
|
||||
<h1 className="text-2xl font-semibold">Welcome to your Portfolio, {username}!</h1>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
Here‘s an overview of your investment journey and progress.
|
||||
</p>
|
||||
<p className="text-xl font-medium text-green-400">
|
||||
Total Investment: $
|
||||
<CountUpComponent end={totalInvestment} duration={1} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flew-rows-3 gap-10 mt-5 w-full">
|
||||
<Tabs defaultValue="daily" className="space-y-4 w-full">
|
||||
<TabsList className="grid w-96 grid-cols-3">
|
||||
<TabsTrigger value="daily">Daily</TabsTrigger>
|
||||
<TabsTrigger value="monthly">Monthly</TabsTrigger>
|
||||
<TabsTrigger value="yearly">Yearly</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="monthly">
|
||||
<Card className="w-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-md font-bold">Monthly Investment Trend</CardTitle>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<QuestionMarkIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Displays total investments each month over the past 12 <br />
|
||||
months, up to today.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-5">
|
||||
<Overview graphType="line" data={overAllData} graphHeight={500}></Overview>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="yearly">
|
||||
<Card className="w-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-md font-bold">Yearly Investment Summary</CardTitle>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<QuestionMarkIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Shows total investments for each of the last four years, <br />
|
||||
including the current year to date.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-5">
|
||||
<Overview graphType="bar" data={fourYearData} graphHeight={500}></Overview>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="daily">
|
||||
<Card className="w-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-md font-bold">Daily Investment Breakdown</CardTitle>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<QuestionMarkIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Illustrates total investments for each day over the past <br />
|
||||
year, up to today.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-5">
|
||||
<Overview graphType="bar" data={dayOfWeekData} graphHeight={500}></Overview>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div className="flex flex-cols-3 w-full gap-5 mt-5">
|
||||
<Card className="w-1/3">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-md font-bold">Categories of Invested Projects</CardTitle>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<QuestionMarkIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Displays the distribution of project tags in your <br />
|
||||
investments, highlighting areas of interest.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-5">
|
||||
<PieChart
|
||||
data={tagCount.map((item: { name: string; count: number }) => item.count)}
|
||||
labels={tagCount.map((item: { name: string; count: number }) => item.name)}
|
||||
header="Total"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="w-1/3">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-md font-bold">Types of Businesses Invested In</CardTitle>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<QuestionMarkIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Shows the breakdown of business types in your portfolio, <br />
|
||||
illustrating sector diversity.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-5">
|
||||
<PieChart
|
||||
data={Object.values(countedBusinessType)}
|
||||
labels={Object.keys(countedBusinessType)}
|
||||
header="Total"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="w-1/3">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-md font-bold">Recent investment</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-5">
|
||||
<RecentFunds data={latestDeals} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
256
src/app/portfolio/[uid]/query.ts
Normal file
256
src/app/portfolio/[uid]/query.ts
Normal file
@ -0,0 +1,256 @@
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { getProjectTag, getTagName } from "@/lib/data/tagQuery";
|
||||
|
||||
async function fetchLogoURL(supabase: SupabaseClient, projectId: number) {
|
||||
const logoIndex = 1;
|
||||
let { data: project_material, error } = await supabase
|
||||
.from("project_material")
|
||||
.select("material_url")
|
||||
.eq("project_id", projectId)
|
||||
.eq("material_type_id", logoIndex);
|
||||
if (error) {
|
||||
console.error("Error while fetching golo url" + error);
|
||||
}
|
||||
if (project_material && project_material.length > 0) {
|
||||
return project_material[0].material_url;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getTotalInvestment(deals: { deal_amount: number }[]) {
|
||||
let total = 0;
|
||||
for (let index = 0; index < deals.length; index++) {
|
||||
total += deals[index].deal_amount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
async function getLatestInvestment(
|
||||
supabase: SupabaseClient,
|
||||
deals: { project_id: number; deal_amount: number; created_time: Date }[]
|
||||
) {
|
||||
const llist = [];
|
||||
const count = 8;
|
||||
|
||||
for (let i = deals.length - 1; i >= 0 && llist.length < count; --i) {
|
||||
let { data: project, error } = await supabase.from("project").select("project_name").eq("id", deals[i].project_id);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
let url = fetchLogoURL(supabase, deals[i].project_id);
|
||||
llist.push({
|
||||
name: project?.[0]?.project_name,
|
||||
amount: deals[i].deal_amount,
|
||||
date: new Date(deals[i].created_time),
|
||||
logo_url: url,
|
||||
});
|
||||
}
|
||||
|
||||
return llist;
|
||||
}
|
||||
|
||||
async function checkForInvest(supabase: SupabaseClient, userId: string) {
|
||||
let { count, error } = await supabase
|
||||
.from("investment_deal")
|
||||
.select("*", { count: "exact" })
|
||||
.eq("investor_id", userId);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
// if user already invest in something
|
||||
if (count !== null && count > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function countValues(arr: { value: string }[][]): Record<string, number> {
|
||||
const counts: Record<string, number> = {};
|
||||
|
||||
arr.forEach((subArray) => {
|
||||
subArray.forEach((item) => {
|
||||
const value = item.value;
|
||||
counts[value] = (counts[value] || 0) + 1;
|
||||
});
|
||||
});
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
||||
async function getBusinessTypeName(supabase: SupabaseClient, projectId: number) {
|
||||
// step 1: get business id from project id
|
||||
let { data: project, error: projectError } = await supabase.from("project").select("business_id").eq("id", projectId);
|
||||
if (projectError) {
|
||||
console.error(projectError);
|
||||
}
|
||||
|
||||
// step 2: get business type's id from business id
|
||||
let { data: business, error: businessError } = await supabase
|
||||
.from("business")
|
||||
.select("business_type")
|
||||
.eq("id", project?.[0]?.business_id);
|
||||
if (businessError) {
|
||||
console.error(businessError);
|
||||
}
|
||||
// step 3: get business type from its id
|
||||
let { data: business_type, error: businessTypeError } = await supabase
|
||||
.from("business_type")
|
||||
.select("value")
|
||||
.eq("id", business?.[0]?.business_type);
|
||||
if (businessTypeError) {
|
||||
console.error(businessError);
|
||||
}
|
||||
return business_type;
|
||||
}
|
||||
|
||||
// only use deal that were made at most year ago
|
||||
interface Deal {
|
||||
created_time: string | number | Date;
|
||||
deal_amount: any;
|
||||
}
|
||||
|
||||
interface GraphData {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
function overAllGraphData(deals: Deal[]): GraphData[] {
|
||||
// Initialize all months with value 0
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
const acc: GraphData[] = months.map((month) => ({ name: month, value: 0 }));
|
||||
|
||||
deals
|
||||
.filter((item: Deal) => new Date(item.created_time) >= yearAgo(1))
|
||||
.forEach((item: Deal) => {
|
||||
const monthName = getMonthName(item.created_time.toString()).slice(0, 3);
|
||||
const monthEntry = acc.find((entry) => entry.name === monthName);
|
||||
|
||||
if (monthEntry) {
|
||||
monthEntry.value += item.deal_amount;
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
function fourYearGraphData(deals: Deal[]): GraphData[] {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const acc: GraphData[] = Array.from({ length: 4 }, (_, i) => ({
|
||||
name: (currentYear - i).toString(),
|
||||
value: 0,
|
||||
})).reverse();
|
||||
deals
|
||||
.filter((item: Deal) => new Date(item.created_time) >= yearAgo(3))
|
||||
.forEach((item: Deal) => {
|
||||
const year = new Date(item.created_time).getFullYear().toString();
|
||||
const yearEntry = acc.find((entry) => entry.name === year);
|
||||
|
||||
if (yearEntry) {
|
||||
yearEntry.value += item.deal_amount;
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
interface DayOfWeekData {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
function dayOftheWeekData(deals: Deal[]): DayOfWeekData[] {
|
||||
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const dayOfWeekData: DayOfWeekData[] = daysOfWeek.map((day) => ({
|
||||
name: day,
|
||||
value: 0,
|
||||
}));
|
||||
deals
|
||||
.filter((item: Deal) => new Date(item.created_time) >= yearAgo(1))
|
||||
.forEach((item: Deal) => {
|
||||
const day = getDayAbbreviation(item.created_time);
|
||||
const dayEntry = dayOfWeekData.find((entry) => entry.name === day);
|
||||
if (dayEntry) {
|
||||
dayEntry.value += item.deal_amount;
|
||||
}
|
||||
});
|
||||
return dayOfWeekData;
|
||||
}
|
||||
async function getInvestorProjectTag(supabase: SupabaseClient, deals: number | { project_id: number }[]) {
|
||||
// get unique project id from deals
|
||||
const uniqueProjectIds: number[] = Array.isArray(deals)
|
||||
? Array.from(new Set(deals.map((deal: { project_id: number }) => deal.project_id)))
|
||||
: [];
|
||||
|
||||
const tagIds = (
|
||||
await Promise.all(
|
||||
uniqueProjectIds.map(async (projectId: number) => {
|
||||
const { data: tagIdsArray, error: tagError } = await getProjectTag(supabase, projectId);
|
||||
if (tagError) {
|
||||
console.error(tagError);
|
||||
return [];
|
||||
}
|
||||
return tagIdsArray?.map((tag: { tag_id: any }) => tag.tag_id) || [];
|
||||
})
|
||||
)
|
||||
).flat();
|
||||
|
||||
// console.log(tagIds, uniqueProjectIds);
|
||||
const tagNames = await Promise.all(
|
||||
tagIds
|
||||
.filter((tagId) => tagId !== null)
|
||||
.map(async (id: number) => {
|
||||
const { data: tagName, error: nameError } = await getTagName(supabase, id);
|
||||
if (nameError) {
|
||||
console.error(nameError);
|
||||
return null;
|
||||
}
|
||||
return tagName;
|
||||
})
|
||||
);
|
||||
// console.log(tagNames);
|
||||
return tagNames.filter((tagName) => tagName !== null);
|
||||
}
|
||||
const countTags = (tags: any[]) => {
|
||||
const tagCounts = tags.flat().reduce(
|
||||
(acc, tag) => {
|
||||
const tagName = tag.value;
|
||||
acc[tagName] = (acc[tagName] || 0) + 1;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
);
|
||||
|
||||
return Object.entries(tagCounts).map(([name, count]) => ({
|
||||
name,
|
||||
count: count as number,
|
||||
}));
|
||||
};
|
||||
const getDayAbbreviation = (dateString: string | number | Date) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString("default", { weekday: "short" });
|
||||
};
|
||||
|
||||
const yearAgo = (num: number) => {
|
||||
const newDate = new Date();
|
||||
newDate.setFullYear(newDate.getFullYear() - num);
|
||||
return newDate;
|
||||
};
|
||||
|
||||
const getMonthName = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString("default", { month: "long", year: "numeric" });
|
||||
};
|
||||
|
||||
export {
|
||||
overAllGraphData,
|
||||
fourYearGraphData,
|
||||
dayOftheWeekData,
|
||||
getInvestorProjectTag,
|
||||
countTags,
|
||||
getBusinessTypeName,
|
||||
countValues,
|
||||
checkForInvest,
|
||||
getLatestInvestment,
|
||||
getTotalInvestment,
|
||||
fetchLogoURL,
|
||||
};
|
||||
@ -9,22 +9,14 @@ 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<typeof businessFormSchema>;
|
||||
|
||||
interface BusinessFormProps {
|
||||
applyProject: boolean;
|
||||
setApplyProject: Function;
|
||||
onSubmit: SubmitHandler<businessSchema>;
|
||||
}
|
||||
const BusinessForm = ({
|
||||
applyProject,
|
||||
setApplyProject,
|
||||
onSubmit,
|
||||
}: BusinessFormProps & { onSubmit: SubmitHandler<businessSchema> }) => {
|
||||
const BusinessForm = ({ onSubmit }: BusinessFormProps & { onSubmit: SubmitHandler<businessSchema> }) => {
|
||||
const communitySize = [
|
||||
{ id: 1, name: "N/A" },
|
||||
{ id: 2, name: "0-5K" },
|
||||
@ -385,24 +377,6 @@ const BusinessForm = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex space-x-5">
|
||||
<Switch onCheckedChange={() => setApplyProject(!applyProject)}></Switch>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-[12px] text-neutral-500 self-center cursor-pointer">
|
||||
Would you like to apply for your first fundraising project as well?
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-[11px]">
|
||||
Toggling this option allows you to begin your first project, <br /> which is crucial for unlocking
|
||||
the tools necessary to raise funds.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<center>
|
||||
<Button className="mt-12 mb-20 h-10 text-base font-bold py-6 px-5" type="submit">
|
||||
Submit application
|
||||
|
||||
@ -2,14 +2,7 @@ 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 { 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";
|
||||
@ -17,19 +10,8 @@ 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 { 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";
|
||||
|
||||
@ -39,30 +21,21 @@ type FieldType = ControllerRenderProps<any, "projectPhotos">;
|
||||
interface ProjectFormProps {
|
||||
onSubmit: SubmitHandler<projectSchema>;
|
||||
}
|
||||
const ProjectForm = ({
|
||||
onSubmit,
|
||||
}: ProjectFormProps & { onSubmit: SubmitHandler<projectSchema> }) => {
|
||||
const ProjectForm = ({ onSubmit }: ProjectFormProps & { onSubmit: SubmitHandler<projectSchema> }) => {
|
||||
const form = useForm<projectSchema>({
|
||||
resolver: zodResolver(projectFormSchema),
|
||||
defaultValues: {},
|
||||
});
|
||||
let supabase = createSupabaseClient();
|
||||
const [projectType, setProjectType] = useState<
|
||||
{ id: number; name: string }[]
|
||||
>([]);
|
||||
const [projectType, setProjectType] = useState<{ id: number; name: string }[]>([]);
|
||||
const [projectPitch, setProjectPitch] = useState("text");
|
||||
const [selectedImages, setSelectedImages] = useState<File[]>([]);
|
||||
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 [selectedTag, setSelectedTag] = useState<{ id: number; value: string }[]>([]);
|
||||
|
||||
const handleFileChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
field: FieldType
|
||||
) => {
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>, field: FieldType) => {
|
||||
if (event.target.files) {
|
||||
const filesArray = Array.from(event.target.files);
|
||||
console.log("first file", filesArray);
|
||||
@ -86,9 +59,7 @@ const ProjectForm = ({
|
||||
};
|
||||
|
||||
const fetchProjectType = async () => {
|
||||
let { data: ProjectType, error } = await supabase
|
||||
.from("project_type")
|
||||
.select("id, value");
|
||||
let { data: ProjectType, error } = await supabase.from("project_type").select("id, value");
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
@ -125,10 +96,7 @@ const ProjectForm = ({
|
||||
}, []);
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit as SubmitHandler<projectSchema>)}
|
||||
className="space-y-8"
|
||||
>
|
||||
<form onSubmit={form.handleSubmit(onSubmit as SubmitHandler<projectSchema>)} className="space-y-8">
|
||||
<div className="ml-96 space-y-10">
|
||||
{/* project name */}
|
||||
<FormField
|
||||
@ -137,17 +105,10 @@ const ProjectForm = ({
|
||||
render={({ field }: { field: any }) => (
|
||||
<FormItem>
|
||||
<div className="space-y-5">
|
||||
<FormLabel className="font-bold text-lg">
|
||||
Project name
|
||||
</FormLabel>
|
||||
<FormLabel className="font-bold text-lg">Project name</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
type="text"
|
||||
id="projectName"
|
||||
className="w-96"
|
||||
{...field}
|
||||
/>
|
||||
<Input type="text" id="projectName" className="w-96" {...field} />
|
||||
</div>
|
||||
</FormControl>
|
||||
</div>
|
||||
@ -169,9 +130,7 @@ const ProjectForm = ({
|
||||
handleFunction={(selectedValues: any) => {
|
||||
field.onChange(selectedValues.id);
|
||||
}}
|
||||
description={
|
||||
<>Please specify the primary purpose of the funds</>
|
||||
}
|
||||
description={<>Please specify the primary purpose of the funds</>}
|
||||
placeholder="Select a Project type"
|
||||
selectLabel="Project type"
|
||||
/>
|
||||
@ -189,18 +148,11 @@ const ProjectForm = ({
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="mt-10 space-y-5">
|
||||
<FormLabel className="font-bold text-lg">
|
||||
Short description
|
||||
</FormLabel>
|
||||
<FormLabel className="font-bold text-lg">Short description</FormLabel>
|
||||
<div className="flex space-x-5">
|
||||
<Textarea
|
||||
id="shortDescription"
|
||||
className="w-96"
|
||||
{...field}
|
||||
/>
|
||||
<Textarea id="shortDescription" className="w-96" {...field} />
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
Could you provide a brief description of your project{" "}
|
||||
<br /> in one or two sentences?
|
||||
Could you provide a brief description of your project <br /> in one or two sentences?
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -225,9 +177,7 @@ const ProjectForm = ({
|
||||
<div className="flex space-x-2 w-96">
|
||||
<Button
|
||||
type="button"
|
||||
variant={
|
||||
projectPitch === "text" ? "default" : "outline"
|
||||
}
|
||||
variant={projectPitch === "text" ? "default" : "outline"}
|
||||
onClick={() => setProjectPitch("text")}
|
||||
className="w-32 h-12 text-base"
|
||||
>
|
||||
@ -235,9 +185,7 @@ const ProjectForm = ({
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={
|
||||
projectPitch === "file" ? "default" : "outline"
|
||||
}
|
||||
variant={projectPitch === "file" ? "default" : "outline"}
|
||||
onClick={() => setProjectPitch("file")}
|
||||
className="w-32 h-12 text-base"
|
||||
>
|
||||
@ -247,11 +195,7 @@ const ProjectForm = ({
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
type={projectPitch === "file" ? "file" : "text"}
|
||||
placeholder={
|
||||
projectPitch === "file"
|
||||
? "Upload your Markdown file"
|
||||
: "https:// "
|
||||
}
|
||||
placeholder={projectPitch === "file" ? "Upload your Markdown file" : "https:// "}
|
||||
accept={projectPitch === "file" ? ".md" : undefined}
|
||||
onChange={(e) => {
|
||||
const value = e.target;
|
||||
@ -266,11 +210,9 @@ const ProjectForm = ({
|
||||
/>
|
||||
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
Please upload a file or paste a link to your pitch,
|
||||
which should <br />
|
||||
cover key aspects of your project: what it will do,
|
||||
what investors <br /> can expect to gain, and any
|
||||
highlights that make it stand out.
|
||||
Please upload a file or paste a link to your pitch, which should <br />
|
||||
cover key aspects of your project: what it will do, what investors <br /> can expect to gain,
|
||||
and any highlights that make it stand out.
|
||||
</span>
|
||||
</div>
|
||||
{projectPitchFile && (
|
||||
@ -302,23 +244,22 @@ const ProjectForm = ({
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="mt-10 space-y-5">
|
||||
<FormLabel className="font-bold text-lg mt-10">
|
||||
Project logo
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="file"
|
||||
id="projectLogo"
|
||||
className="w-96"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
field.onChange(file || "");
|
||||
}}
|
||||
/>
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
Please upload the logo picture that best represents your
|
||||
project.
|
||||
</span>
|
||||
<FormLabel className="font-bold text-lg mt-10">Project logo</FormLabel>
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
type="file"
|
||||
id="projectLogo"
|
||||
className="w-96"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
field.onChange(file || "");
|
||||
}}
|
||||
/>
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
Please upload the logo picture that best represents your project.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@ -334,9 +275,7 @@ const ProjectForm = ({
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="mt-10 space-y-5">
|
||||
<FormLabel className="font-bold text-lg mt-10">
|
||||
Project photos
|
||||
</FormLabel>
|
||||
<FormLabel className="font-bold text-lg mt-10">Project photos</FormLabel>
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
type="file"
|
||||
@ -349,16 +288,15 @@ const ProjectForm = ({
|
||||
}}
|
||||
/>
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
Please upload the logo picture that best represents your
|
||||
project.
|
||||
Please upload the photo that best represents your project.
|
||||
<p className="text-red-500">
|
||||
*** It is recommended that the photo be horizontal for better presentation.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-5 space-y-2 w-96">
|
||||
{selectedImages.map((image, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex justify-between items-center border p-2 rounded"
|
||||
>
|
||||
<div key={index} className="flex justify-between items-center border p-2 rounded">
|
||||
<span>{image.name}</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -385,9 +323,7 @@ const ProjectForm = ({
|
||||
render={({ field }: { field: any }) => (
|
||||
<FormItem>
|
||||
<div className="mt-10 space-y-5">
|
||||
<FormLabel className="font-bold text-lg">
|
||||
Minimum investment
|
||||
</FormLabel>
|
||||
<FormLabel className="font-bold text-lg">Minimum investment</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
@ -419,9 +355,7 @@ const ProjectForm = ({
|
||||
render={({ field }: { field: any }) => (
|
||||
<FormItem>
|
||||
<div className="mt-10 space-y-5">
|
||||
<FormLabel className="font-bold text-lg">
|
||||
Target investment
|
||||
</FormLabel>
|
||||
<FormLabel className="font-bold text-lg">Target investment</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
@ -437,8 +371,8 @@ const ProjectForm = ({
|
||||
value={field.value}
|
||||
/>
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
We encourage you to set a specific target investment{" "}
|
||||
<br /> amount that reflects your funding goals.
|
||||
We encourage you to set a specific target investment <br /> amount that reflects your funding
|
||||
goals.
|
||||
</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
@ -457,16 +391,10 @@ const ProjectForm = ({
|
||||
<FormLabel className="font-bold text-lg">Deadline</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex space-x-5">
|
||||
<Input
|
||||
type="datetime-local"
|
||||
id="deadline"
|
||||
className="w-96"
|
||||
{...field}
|
||||
/>
|
||||
<Input type="datetime-local" id="deadline" className="w-96" {...field} />
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
What is the deadline for your fundraising project?
|
||||
Setting <br /> a clear timeline can help motivate
|
||||
potential investors.
|
||||
What is the deadline for your fundraising project? Setting <br /> a clear timeline can help
|
||||
motivate potential investors.
|
||||
</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
@ -493,9 +421,7 @@ const ProjectForm = ({
|
||||
aria-expanded={open}
|
||||
className="w-96 justify-between overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{selectedTag.length > 0
|
||||
? selectedTag.map((t) => t.value).join(", ")
|
||||
: "Select tags..."}
|
||||
{selectedTag.length > 0 ? selectedTag.map((t) => t.value).join(", ") : "Select tags..."}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@ -511,15 +437,11 @@ const ProjectForm = ({
|
||||
value={tag.value}
|
||||
onSelect={() => {
|
||||
setSelectedTag((prev) => {
|
||||
const exists = prev.find(
|
||||
(t) => t.id === tag.id
|
||||
);
|
||||
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)
|
||||
);
|
||||
field.onChange(updatedTags.map((t) => t.id));
|
||||
return updatedTags;
|
||||
});
|
||||
setOpen(false);
|
||||
@ -528,9 +450,7 @@ const ProjectForm = ({
|
||||
<Check
|
||||
className={cn(
|
||||
"h-4",
|
||||
selectedTag.some((t) => t.id === tag.id)
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
selectedTag.some((t) => t.id === tag.id) ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{tag.value}
|
||||
@ -542,8 +462,7 @@ const ProjectForm = ({
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<span className="text-[12px] text-neutral-500 self-center">
|
||||
Add 1 to 5 tags that describe your project. Tags help{" "}
|
||||
<br />
|
||||
Add 1 to 5 tags that describe your project. Tags help <br />
|
||||
investors understand your focus.
|
||||
</span>
|
||||
</div>
|
||||
@ -561,9 +480,7 @@ const ProjectForm = ({
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedTag((prev) => {
|
||||
const updatedTags = prev.filter(
|
||||
(t) => t.id !== tag.id
|
||||
);
|
||||
const updatedTags = prev.filter((t) => t.id !== tag.id);
|
||||
field.onChange(updatedTags.map((t) => t.id));
|
||||
return updatedTags;
|
||||
});
|
||||
@ -579,10 +496,7 @@ const ProjectForm = ({
|
||||
/>
|
||||
</div>
|
||||
<center>
|
||||
<Button
|
||||
className="mt-12 mb-20 h-10 text-base font-bold py-6 px-5 "
|
||||
type="submit"
|
||||
>
|
||||
<Button className="mt-12 mb-20 h-10 text-base font-bold py-6 px-5 " type="submit">
|
||||
Submit application
|
||||
</Button>
|
||||
</center>
|
||||
|
||||
17614
src/components/alert/noData/alert.json
Normal file
17614
src/components/alert/noData/alert.json
Normal file
File diff suppressed because it is too large
Load Diff
20
src/components/alert/noData/alert.tsx
Normal file
20
src/components/alert/noData/alert.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
import Lottie from "react-lottie";
|
||||
import * as alertData from "./alert.json";
|
||||
|
||||
const alertOption = {
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
animationData: alertData,
|
||||
rendererSettings: {
|
||||
preserveAspectRatio: "xMidYMid slice",
|
||||
},
|
||||
};
|
||||
|
||||
export function NoDataAlert() {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-centerbg-black mt-10">
|
||||
<Lottie options={alertOption} height={"80%"} width={"50%"} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
2066
src/components/alert/unauthorized/alert.json
Normal file
2066
src/components/alert/unauthorized/alert.json
Normal file
File diff suppressed because it is too large
Load Diff
20
src/components/alert/unauthorized/alert.tsx
Normal file
20
src/components/alert/unauthorized/alert.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
import Lottie from "react-lottie";
|
||||
import * as alertData from "./alert.json";
|
||||
|
||||
const alertOption = {
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
animationData: alertData,
|
||||
rendererSettings: {
|
||||
preserveAspectRatio: "xMidYMid slice",
|
||||
},
|
||||
};
|
||||
|
||||
export function UnAuthorizedAlert() {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black mt-24 z-50">
|
||||
<Lottie options={alertOption} style={{ width: "50%", height: "auto" }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
19
src/components/countUp.tsx
Normal file
19
src/components/countUp.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
import CountUp from "react-countup";
|
||||
|
||||
interface CountUpComponentProps {
|
||||
end: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
export default function CountUpComponent(props: CountUpComponentProps) {
|
||||
return (
|
||||
<>
|
||||
<CountUp
|
||||
end={props.end}
|
||||
duration={props.duration}
|
||||
start={props.end / 2}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
17
src/components/icon/questionMark.tsx
Normal file
17
src/components/icon/questionMark.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
export default function QuestionMarkIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="18px"
|
||||
width="18px"
|
||||
version="1.1"
|
||||
id="_x32_"
|
||||
viewBox="0 0 512 512"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g>
|
||||
<path d="M256,0C114.616,0,0,114.612,0,256s114.616,256,256,256s256-114.612,256-256S397.385,0,256,0z M207.678,378.794 c0-17.612,14.281-31.893,31.893-31.893c17.599,0,31.88,14.281,31.88,31.893c0,17.595-14.281,31.884-31.88,31.884 C221.959,410.678,207.678,396.389,207.678,378.794z M343.625,218.852c-3.596,9.793-8.802,18.289-14.695,25.356 c-11.847,14.148-25.888,22.718-37.442,29.041c-7.719,4.174-14.533,7.389-18.769,9.769c-2.905,1.604-4.479,2.95-5.256,3.826 c-0.768,0.926-1.029,1.306-1.496,2.826c-0.273,1.009-0.558,2.612-0.558,5.091c0,6.868,0,12.512,0,12.512 c0,6.472-5.248,11.728-11.723,11.728h-28.252c-6.475,0-11.732-5.256-11.732-11.728c0,0,0-5.645,0-12.512 c0-6.438,0.752-12.744,2.405-18.777c1.636-6.008,4.215-11.718,7.508-16.694c6.599-10.083,15.542-16.802,23.984-21.48 c7.401-4.074,14.723-7.455,21.516-11.281c6.789-3.793,12.843-7.91,17.302-12.372c2.988-2.975,5.31-6.05,7.087-9.52 c2.335-4.628,3.955-10.067,3.992-18.389c0.012-2.463-0.698-5.702-2.632-9.405c-1.926-3.686-5.066-7.694-9.264-11.29 c-8.45-7.248-20.843-12.545-35.054-12.521c-16.285,0.058-27.186,3.876-35.587,8.62c-8.36,4.776-11.029,9.595-11.029,9.595 c-4.268,3.718-10.603,3.85-15.025,0.314l-21.71-17.397c-2.719-2.173-4.322-5.438-4.396-8.926c-0.063-3.479,1.425-6.81,4.061-9.099 c0,0,6.765-10.43,22.451-19.38c15.62-8.992,36.322-15.488,61.236-15.429c20.215,0,38.839,5.562,54.268,14.661 c15.434,9.148,27.897,21.744,35.851,36.876c5.281,10.074,8.525,21.43,8.533,33.38C349.211,198.042,347.248,209.058,343.625,218.852 z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import Lottie from "react-lottie";
|
||||
import * as loadingData from "./loading.json";
|
||||
|
||||
@ -11,17 +13,17 @@ const loadingOption = {
|
||||
};
|
||||
|
||||
interface LoaderProps {
|
||||
isSuccess: boolean;
|
||||
isSuccess?: boolean;
|
||||
}
|
||||
|
||||
export function Loader(props: LoaderProps) {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{!props.isSuccess && (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-white bg-opacity-10 backdrop-blur-sm z-50">
|
||||
<Lottie options={loadingOption} height={200} width={200} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -30,14 +30,16 @@ export const AuthenticatedComponents = ({ uid }: AuthenticatedComponentsProps) =
|
||||
<div className={`flex gap-3 pl-2 items-center ${businessClass}`}>
|
||||
<Link href={"/notification"}>
|
||||
<div className="relative inline-block">
|
||||
<Bell className="h-6 w-6" />
|
||||
<span className="absolute -top-1 -right-1 inline-flex items-center justify-center w-4 h-4 text-xs font-bold text-white bg-red-600 rounded-full">
|
||||
<Bell className="h-6 w-6 " />
|
||||
<span className="absolute -top-1 -right-1 inline-flex items-center justify-center w-4 h-4 text-xs font-bold text-white bg-red-600 rounded-full animate-ping">
|
||||
{displayValue}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Heart />
|
||||
<Wallet />
|
||||
<Link href={"/portfolio/" + uid}>
|
||||
<Wallet className="cursor-pointer" />
|
||||
</Link>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="overflow-hidden rounded-full">
|
||||
@ -59,6 +61,11 @@ export const AuthenticatedComponents = ({ uid }: AuthenticatedComponentsProps) =
|
||||
<Link href="/admin">Admin</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{data != null && data != undefined && data.role === "business" && (
|
||||
<DropdownMenuItem>
|
||||
<Link href="/dataroom/manage">Dataroom</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogoutButton />
|
||||
|
||||
@ -115,7 +115,9 @@ export async function NavigationBar() {
|
||||
</NavigationMenu>
|
||||
|
||||
<div className="flex gap-2 pl-2">
|
||||
<ThemeToggle />
|
||||
<div className="mt-1">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<Separator orientation="vertical" className="mx-3" />
|
||||
{userId ? <AuthenticatedComponents uid={userId} /> : <UnAuthenticatedComponents />}
|
||||
</div>
|
||||
|
||||
42
src/components/pieChart.tsx
Normal file
42
src/components/pieChart.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { Pie } from "react-chartjs-2";
|
||||
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
interface PieChartProps {
|
||||
labels: string[];
|
||||
data: number[];
|
||||
header: string;
|
||||
}
|
||||
|
||||
const PieChart = (props: PieChartProps) => {
|
||||
const chartData = {
|
||||
labels: props.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: props.header,
|
||||
data: props.data,
|
||||
backgroundColor: ["#FF6384", "#36A2EB", "#FFCE56"],
|
||||
hoverBackgroundColor: ["#FF6384", "#36A2EB", "#FFCE56"],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const options = {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "bottom" as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pie data={chartData} options={options} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PieChart;
|
||||
@ -5,35 +5,30 @@ export type RecentDealData = {
|
||||
deal_amount: number;
|
||||
investor_id: string;
|
||||
username: string;
|
||||
avatar_url?: string;
|
||||
logo_url?: string;
|
||||
// email: string;
|
||||
};
|
||||
|
||||
interface RecentFundsProps {
|
||||
recentDealData: RecentDealData[];
|
||||
data?: { name?: string; amount?: number; avatar?: string; date?: Date; logo_url?: string }[];
|
||||
}
|
||||
|
||||
export function RecentFunds({ recentDealData }: RecentFundsProps) {
|
||||
export function RecentFunds(props: RecentFundsProps) {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{recentDealData?.length > 0 ? (
|
||||
recentDealData.map((data) => (
|
||||
<div className="flex items-center" key={data.investor_id}>
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src={data.avatar_url} alt={data.username} />
|
||||
{/* #TODO make this not quick fix */}
|
||||
<AvatarFallback>{data.username ? data.username[0]: ""}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">{data.username}</p>
|
||||
{/* <p className="text-sm text-muted-foreground">{data.email}</p> */}
|
||||
</div>
|
||||
<div className="ml-auto font-medium">+${data.deal_amount}</div>
|
||||
{(props?.data || []).map((deal, index) => (
|
||||
<div className="flex items-center" key={index}>
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src={deal.logo_url} alt={deal.name} />
|
||||
<AvatarFallback>{(deal.name ?? "").slice(0, 2)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">{deal.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{deal?.date?.toLocaleDateString()}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p>No recent deals available.</p>
|
||||
)}
|
||||
<div className="ml-auto font-medium">+${deal.amount}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,7 +21,10 @@ export function SiteFooter() {
|
||||
<Link href="/services" className="hover:underline">
|
||||
Services
|
||||
</Link>
|
||||
<Link href="/contact" className="hover:underline">
|
||||
<Link
|
||||
href="mailto:b2d.ventures.contact@gmail.com"
|
||||
className="hover:underline"
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@ -1,66 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis, LineChart, Line } from "recharts";
|
||||
import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis, LineChart, Line, Tooltip } from "recharts";
|
||||
|
||||
// const data = [
|
||||
// {
|
||||
// name: "Jan",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Feb",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Mar",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Apr",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "May",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Jun",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Jul",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Aug",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Sep",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Oct",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Nov",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// {
|
||||
// name: "Dec",
|
||||
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||
// },
|
||||
// ];
|
||||
|
||||
interface OverViewProps {
|
||||
graphType: string;
|
||||
graphData: Record<string, number>; // Object with month-year as keys and sum as value
|
||||
data: { name: string; value: number }[];
|
||||
graphHeight?: number | string;
|
||||
}
|
||||
|
||||
export function Overview(props: OverViewProps) {
|
||||
// Transform the grouped data into the format for the chart
|
||||
const chartData = Object.entries(props.graphData).map(([monthYear, totalArray]) => ({
|
||||
name: monthYear,
|
||||
total: totalArray, // Get the total amount for the month
|
||||
}));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={350}>
|
||||
{props.graphType === 'line' ? (
|
||||
<LineChart data={chartData}>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Line
|
||||
dataKey="total"
|
||||
fill="currentColor"
|
||||
className="fill-primary"
|
||||
/>
|
||||
</LineChart>
|
||||
) : (
|
||||
<BarChart data={chartData}>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="total"
|
||||
fill="currentColor"
|
||||
className="fill-primary"
|
||||
/>
|
||||
</BarChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={props.graphHeight || 350}>
|
||||
{props.graphType === "line" ? (
|
||||
<LineChart data={props.data}>
|
||||
<XAxis dataKey="name" stroke="#888888" fontSize={12} tickLine={false} axisLine={false} />
|
||||
<YAxis
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value) => `$${value}`}
|
||||
contentStyle={{
|
||||
backgroundColor: "#f5f5f5",
|
||||
borderRadius: "5px",
|
||||
color: "#000",
|
||||
}}
|
||||
/>
|
||||
<Line dataKey="value" fill="currentColor" className="fill-primary" />
|
||||
</LineChart>
|
||||
) : (
|
||||
<BarChart data={props.data}>
|
||||
<XAxis dataKey="name" stroke="#888888" fontSize={12} tickLine={false} axisLine={false} />
|
||||
<YAxis
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value) => `$${value}`}
|
||||
contentStyle={{
|
||||
backgroundColor: "#f5f5f5",
|
||||
borderRadius: "5px",
|
||||
color: "#000",
|
||||
}}
|
||||
/>
|
||||
<Bar dataKey="value" fill="currentColor" className="fill-primary" radius={[15, 15, 0, 0]} />
|
||||
</BarChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,6 +18,17 @@ export const getAllBusinesses = (client: SupabaseClient) => {
|
||||
`);
|
||||
};
|
||||
|
||||
export async function getBusinessByUserId(client: SupabaseClient, userId: string) {
|
||||
const { data, error } = await client.from("business").select("*").eq("user_id", userId);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching business ID:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export const getBusinessByName = (
|
||||
client: SupabaseClient,
|
||||
params: { businessName?: string | null; single?: boolean } = { single: false }
|
||||
|
||||
@ -29,3 +29,11 @@ export const getInvestmentByUserId = (client: SupabaseClient, userId: string) =>
|
||||
)
|
||||
.eq("investor_id", userId);
|
||||
};
|
||||
|
||||
export function getInvestorDeal(client: SupabaseClient, userId: string) {
|
||||
return client
|
||||
.from("investment_deal")
|
||||
.select("*")
|
||||
.in("investor_id", [userId])
|
||||
.order("created_time", { ascending: true });
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ async function getTopProjects(client: SupabaseClient, numberOfRecords: number =
|
||||
if (error) {
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
|
||||
// console.log(data);
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
return { data: null, error: "An unexpected error occurred." };
|
||||
|
||||
0
src/lib/data/query.ts
Normal file
0
src/lib/data/query.ts
Normal file
@ -3,3 +3,11 @@ import { SupabaseClient } from "@supabase/supabase-js";
|
||||
export const getTagsByProjectIds = (client: SupabaseClient, projectIds: string[]) => {
|
||||
return client.from("project_tag").select(`item_id, ...tag (tag_value:value)`).in("item_id", projectIds);
|
||||
};
|
||||
|
||||
export function getProjectTag(client: SupabaseClient, projectId: number) {
|
||||
return client.from("project_tag").select("tag_id").in("item_id", [projectId]);
|
||||
}
|
||||
|
||||
export function getTagName(client: SupabaseClient, tagId: number) {
|
||||
return client.from("tag").select("value").in("id", [tagId]);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user