Merge branch 'front-end' into back-end

This commit is contained in:
Pattadon 2024-11-20 09:26:20 +07:00
commit 38e09f80b6
13 changed files with 384 additions and 174 deletions

View File

@ -26,8 +26,12 @@ const nextConfig = {
hostname: "avatars.githubusercontent.com",
pathname: "/**",
},
{
protocol: "https",
hostname: "assets.republic.com",
pathname: "/**",
},
],
},
};
export default nextConfig;

View File

@ -98,7 +98,7 @@ export default async function ProjectDealPage({ params }: { params: { id: number
))}
</div>
</div>
<div id="sub-content" className="flex flex-row mt-5">
<div id="sub-content" className="grid grid-cols-1 md:grid-cols-2 mt-5 space-y-5 md:space-y-0">
{/* image carousel */}
<div id="image-carousel" className="w-full">
<Gallery images={carouselData} />
@ -152,62 +152,28 @@ export default async function ProjectDealPage({ params }: { params: { id: number
</Button>
)}
</div>
<div className="flex justify-between w-full">
<Button className="w-[48%] h-12 dark:text-white" variant={"outline"}>
<Link href={`/dataroom/${params.id}/files`}>Access Dataroom</Link>
<div className="flex justify-between gap-4 w-full">
<Button
className="flex justify-center items-center w-[48%] h-12 text-xs md:text-sm text-center dark:text-white"
variant="outline"
>
<Link href={`/dataroom/${params.id}/files`} className="block text-center break-words">
<span className="block lg:inline">Access</span>
<span className="block lg:inline"> Dataroom</span>
</Link>
</Button>
<Button className="w-[48%] h-12 dark:text-white" variant={"outline"}>
<Link href={`/dataroom/overview`}>Request Dataroom Access</Link>
<Button
className="flex justify-center items-center w-[48%] h-12 text-xs md:text-sm text-center dark:text-white"
variant="outline"
>
<Link href={`/dataroom/overview`} className="block text-center break-words">
<span className="block lg:inline">Request</span>
<span className="block lg:inline"> Dataroom Access</span>
</Link>
</Button>
</div>
</CardFooter>
</Card>
{/* <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 dark:text-white">
<Link href={`/invest/${params.id}`}>Invest in {projectData?.project_name}</Link>
</Button>
<div className="flex flex-col space-y-2 py-4 mt-5 w-3/4 h-12 border-2 border-border rounded-md">
<p className="text-md font-bold">Dataroom</p>
<Button className=" dark:text-white">
<Link href={`/invest/${params.id}`}>Access Dataroom</Link>
</Button>
<Button className=" dark:text-white">
<Link href={`/invest/${params.id}`}>Manage Dataroom</Link>
</Button>
</div>
</div>
</div> */}
</div>
{/* menu */}
<div id="deck">
@ -215,7 +181,6 @@ export default async function ProjectDealPage({ params }: { params: { id: number
<Tabs.Root defaultValue="pitch" className="w-full">
<Tabs.List className="list-none flex gap-10 text-lg md:text-xl">
<Tabs.Trigger value="pitch">Pitch</Tabs.Trigger>
{/* <Tabs.Trigger value="general">General Data</Tabs.Trigger> */}
<Tabs.Trigger value="update">Updates</Tabs.Trigger>
</Tabs.List>
<Separator className="mb-4 mt-2 w-full border-1" />
@ -235,17 +200,6 @@ export default async function ProjectDealPage({ params }: { params: { id: number
</CardContent>
</Card>
</Tabs.Content>
{/* <Tabs.Content value="general">
<Card>
<CardHeader>
<CardTitle>general</CardTitle>
<CardDescription>general Description</CardDescription>
</CardHeader>
<CardContent>
<p>general Content</p>
</CardContent>
</Card>
</Tabs.Content> */}
<Tabs.Content value="update">
<Card>
<CardHeader>

View File

@ -0,0 +1,31 @@
import { Button } from "@/components/ui/button";
import Image from "next/image";
import Link from "next/link";
type CardProps = {
imageSrc: string;
imageAlt: string;
heading: string;
content: string[];
link: string;
buttonText: string;
};
const InfoCard = ({ imageSrc, imageAlt, heading, content, link, buttonText }: CardProps) => {
return (
<div className="flex flex-col items-center justify-center">
<Image alt={imageAlt} width={460} height={460} className="w-36" src={imageSrc} />
<h1 className="text-2xl font-bold mt-3">{heading}</h1>
{content.map((text, index) => (
<p key={index} className={index === 0 ? "mt-3" : ""}>
{text}
</p>
))}
<Link href={link}>
<Button className="p-6 font-semibold text-base mt-5">{buttonText}</Button>
</Link>
</div>
);
};
export default InfoCard;

View File

@ -1,5 +1,6 @@
import Image from "next/image";
"use client";
import { Separator } from "@/components/ui/separator";
import InfoCard from "./infoCard";
export default function About() {
// Static data for the cards
@ -38,11 +39,69 @@ export default function About() {
},
];
const imageData = {
img1: "https://assets.republic.com/assets/static_pages/about/growth_opportunities/individual_investors-0e85dfd02359a24ac4b232be008c7168fc57d3437a2f526f5d5889b874b20221.png",
img2: "https://assets.republic.com/assets/static_pages/about/growth_opportunities/accredited_investors-42d6aa046861adb7f0648f26ca3f798b07f3b13bf7024f7dc17c17acb78fdf2c.png",
img3: "https://assets.republic.com/assets/static_pages/about/growth_opportunities/entrepreneurs-a0ff450c2f3ba0cea82e2c55cd9265ad5612455c79ec831adaa2c94d09a0e617.png",
};
return (
<div className="container max-w-screen-xl p-10">
<h1 className="mt-3 font-bold text-lg md:text-3xl">About us</h1>
<Separator className="my-3" />
<div className="border border-border rounded-md">
<div className="flex flex-col items-center justify-center">
<h1 className="mt-3 font-bold text-lg md:text-5xl font-black">Growth opportunities for all sides </h1>
<h1 className="mt-3 font-bold text-lg md:text-5xl font-black">of the investment market</h1>
<h1 className="text-gray-500 text-2xl mt-1">
B2DVentures is where both accredited and non-accredited investors meet
</h1>
<h1 className="text-gray-500 text-2xl">entrepreneurs and access high-growth potential deals across a range</h1>
<h1 className="text-gray-500 text-2xl">of private markets.</h1>
</div>
<div className="mt-10">
<div className="grid grid-cols-1 md:grid-cols-[1fr,auto,1fr] gap-3">
<InfoCard
imageSrc={imageData.img1}
imageAlt="Image1"
heading="Individual investors"
content={[
"B2DVentures's success has been built off our hundreds",
"of sourced private deals, all available for",
"investment from you with as little as $10 or as ",
"much as $124,000.",
]}
link="/deals"
buttonText="Explore opportunities"
/>
<Separator orientation="vertical" />
<InfoCard
imageSrc={imageData.img2}
imageAlt="Image2"
heading="Accredited investors"
content={[
"The benefits of the Republic platform, optimized for",
"accredited investors. Access a curated investor",
"portal for unique private investment opportunities. ",
]}
link="/dataroom/overview"
buttonText="Learn more"
/>
</div>
<Separator className="mt-5 mb-5" />
<InfoCard
imageSrc={imageData.img3}
imageAlt="Image3"
heading="Entrepreneurs"
content={[
"Seek funding from a wider base of diverse",
"investors while simultaneously growing a loyal",
"base and leveraging Republics private investment",
"network.",
]}
link="/project/apply"
buttonText="Raise money"
/>
</div>
{/* <div className="border border-border rounded-md">
<p className="p-5">
Welcome to B2D Ventures! We&apos;re a dynamic platform committed to bridging the gap between visionary
entrepreneurs and passionate investors. Our mission is to empower innovation by connecting groundbreaking
@ -56,15 +115,15 @@ export default function About() {
lasting impact.
</p>
<p className="p-5">Let&apos;s build the future, together.</p>
</div>
</div> */}
<div className="mt-10 text-center">
{/* <div className="mt-10 text-center">
<h2 className="font-bold text-lg md:text-3xl">Our Team</h2>
<Separator className="my-3" />
</div>
</div> */}
{/* Card Section */}
<div className="mt-10 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{/* <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" />
@ -74,7 +133,7 @@ export default function About() {
</div>
</div>
))}
</div>
</div> */}
</div>
);
}

View File

@ -27,7 +27,7 @@ export default function ProjectCardCalendarManageSection({ projectData }: Projec
const [currentProjectId, setCurrentProjectId] = useState<number | undefined>(undefined);
return (
<div id="content" className="grid grid-cols-2 space-x-2">
<div id="content" className="grid grid-cols-1 md:grid-cols-2 space-x-2">
{projectData != null ? (
projectData.map((project) => (
<Card key={project.id} className="mb-3">

View File

@ -208,7 +208,7 @@ export function DataTable({ data }: { data: ModalProps[] }) {
placeholder="Filter names..."
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
onChange={(event) => table.getColumn("name")?.setFilterValue(event.target.value)}
className="max-w-sm"
className="md:max-w-sm max-w-xs"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>

View File

@ -0,0 +1,28 @@
import { NavigationMenuLink } from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils";
import React from "react";
const ListItem = React.forwardRef<React.ElementRef<"a">, React.ComponentPropsWithoutRef<"a">>(
({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<hr />
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">{children}</p>
</a>
</NavigationMenuLink>
</li>
);
}
);
ListItem.displayName = "ListItem";
export default ListItem;

View File

@ -1,27 +0,0 @@
"use client";
import { useState } from "react";
import { Menu, X } from "lucide-react";
import { Button } from "./ui/button";
import { motion } from "framer-motion";
export function MobileMenu() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<Button onClick={() => setIsVisible((prev) => !prev)}>
<Menu />
</Button>
{isVisible && (
<motion.div
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ duration: 0.3 }}
className="fixed top-0 left-0 w-full bg-gray-800 text-white"
>
<X className="cursor-pointer" onClick={() => setIsVisible(false)} />
</motion.div>
)}
</div>
);
}

View File

@ -0,0 +1,23 @@
export const businessComponents = [
{
title: "Business",
href: "/business/apply",
description: "Apply to raise on on B2DVentures",
},
];
export const projectComponents = [
{
title: "Projects",
href: "/project/apply",
description: "Start your new project on B2DVentures",
},
];
export const dataroomComponents = [
{
title: "Overview",
href: "/dataroom/overview",
description: "View all dataroom available to you",
},
];

View File

@ -0,0 +1,103 @@
"use client";
import { useState } from "react";
import { Menu, X } from "lucide-react";
import { Button } from "../ui/button";
import { AnimatePresence, motion } from "framer-motion";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
import React from "react";
import { SearchBar } from "./serchBar";
import ListItem from "../listItem";
import { businessComponents, projectComponents, dataroomComponents } from "./menu";
import { ThemeToggle } from "../theme-toggle";
export function MobileMenu() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<Button onClick={() => setIsVisible((prev) => !prev)}>
<Menu />
</Button>
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ y: -100 }}
animate={{ y: 0 }}
exit={{ y: -100 }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
className="fixed top-0 left-0 w-full bg-white dark:bg-slate-900 border-b dark:border-slate-800 shadow-sm z-50"
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center">
<button
onClick={() => setIsVisible(false)}
className="p-2 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800"
>
<X className="w-5 h-5" />
</button>
<NavigationMenu>
<NavigationMenuList className="flex space-x-2">
<NavigationMenuItem>
<NavigationMenuTrigger className="text-sm font-medium">Businesses</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="w-[240px] p-4 gap-3">
{businessComponents.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-sm font-medium">Projects</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="w-[240px] p-4 gap-3">
{projectComponents.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-sm font-medium">Dataroom</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="w-[240px] p-4 gap-3">
{dataroomComponents.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
<div className="flex items-center ml-2">
<SearchBar />
<div className="-ml-2">
<ThemeToggle />
</div>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

View File

@ -2,7 +2,6 @@ import * as React from "react";
import Link from "next/link";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator";
import { ThemeToggle } from "@/components/theme-toggle";
import {
@ -21,31 +20,9 @@ import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
import { getUserId } from "@/lib/supabase/actions/getUserId";
import { getUnreadNotificationCountByUserId } from "@/lib/data/notificationQuery";
import { MobileMenu } from "../mobileMenu";
const ListItem = React.forwardRef<React.ElementRef<"a">, React.ComponentPropsWithoutRef<"a">>(
({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<hr />
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">{children}</p>
</a>
</NavigationMenuLink>
</li>
);
}
);
ListItem.displayName = "ListItem";
import { MobileMenu } from "./mobileMenu";
import ListItem from "../listItem";
import { businessComponents, projectComponents, dataroomComponents } from "./menu";
export async function NavigationBar() {
const client = createSupabaseClient();
@ -59,30 +36,6 @@ export async function NavigationBar() {
notification_count = 0;
}
const businessComponents = [
{
title: "Business",
href: "/business/apply",
description: "Apply to raise on on B2DVentures",
},
];
const projectComponents = [
{
title: "Projects",
href: "/project/apply",
description: "Start your new project on B2DVentures",
},
];
const dataroomComponents = [
{
title: "Overview",
href: "/dataroom/overview",
description: "View all dataroom available to you",
},
];
return (
<header className="sticky top-0 flex flex-wrap w-full bg-card text-sm py-3 border-b-2 border-border z-50">
<nav className="max-w-screen-xl w-full mx-auto px-4">
@ -93,15 +46,31 @@ export async function NavigationBar() {
href="/"
aria-label="Brand"
>
<span className="inline-flex items-center gap-x-2 text-xl font-semibold dark:text-white">
<Image src="/logo.svg" alt="logo" width={50} height={50} />
B2DVentures
<span className="lg:inline-flex flex items-center gap-x-2 text-xl font-semibold dark:text-white">
<Image src="/logo.svg" alt="logo" width={50} height={50} className="w-10 h-10 sm:w-16 sm:h-16" />
<span className="block lg:inline">
B2D<span className=" lg:inline">Ventures</span>
</span>
</span>
</Link>
</div>
<div className="md:hidden">
<MobileMenu />
<div className="md:hidden grid grid-cols-2 justify-items-center items-center">
<div className="flex justify-end w-10">
{userId ? (
<AuthenticatedComponents
uid={userId}
avatarUrl={avatarUrl?.avatar_url}
notificationCount={notification_count}
/>
) : (
<UnAuthenticatedComponents />
)}
</div>
<div className="justify-end flex">
<MobileMenu />
</div>
</div>
<div className="hidden md:flex items-center ">
<NavigationMenu>
<NavigationMenuList>

View File

@ -2,34 +2,100 @@
import * as React from "react";
import { useRouter } from "next/navigation";
import { Search } from "lucide-react";
import { Search, X } from "lucide-react";
import { cn } from "@/lib/utils";
import { useEffect, useRef } from "react";
export function SearchBar() {
const [searchActive, setSearchActive] = React.useState(false);
const [query, setQuery] = React.useState("");
const router = useRouter();
const inputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const handleKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
const query = (e.target as HTMLInputElement).value.trim();
if (query) {
router.push(`/find?query=${encodeURIComponent(query)}`);
const trimmedQuery = query.trim();
if (trimmedQuery) {
router.push(`/find?query=${encodeURIComponent(trimmedQuery)}`);
setSearchActive(false);
setQuery("");
}
} else if (e.key === "Escape") {
setSearchActive(false);
setQuery("");
}
};
// Handle clicks outside of search bar
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setSearchActive(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
// Focus input when search becomes active
useEffect(() => {
if (searchActive && inputRef.current) {
inputRef.current.focus();
}
}, [searchActive]);
return (
<div className="flex items-center">
<Search onClick={() => setSearchActive(!searchActive)} className="cursor-pointer" />
<input
type="text"
placeholder="Enter business name..."
className={cn(
"ml-2 border rounded-md px-2 py-1 transition-all duration-300 ease-in-out",
searchActive ? "w-48 opacity-100" : "w-0 opacity-0"
)}
onKeyDown={handleKeyDown}
/>
<div
ref={containerRef}
className={cn(
"relative flex items-center transition-all duration-300",
searchActive &&
"fixed inset-0 bg-white/95 dark:bg-slate-900/95 z-50 px-4 md:relative md:bg-transparent md:dark:bg-transparent md:px-0"
)}
>
{/* Mobile overlay header when search is active */}
{searchActive && (
<div className="absolute top-0 left-0 right-0 flex items-center p-4 md:hidden">
<X
className="w-6 h-6 cursor-pointer text-slate-600 dark:text-slate-400"
onClick={() => {
setSearchActive(false);
setQuery("");
}}
/>
</div>
)}
<div className={cn("flex items-center w-full md:w-auto", searchActive ? "mt-16 md:mt-0" : "")}>
<Search
onClick={() => setSearchActive(!searchActive)}
className={cn(
"w-5 h-5 cursor-pointer transition-colors",
searchActive ? "text-blue-500" : "text-slate-600 dark:text-slate-400",
"md:hover:text-blue-500"
)}
/>
<input
ref={inputRef}
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Enter business name..."
className={cn(
"ml-2 rounded-md transition-all duration-300 ease-in-out outline-none",
"placeholder:text-slate-400 dark:placeholder:text-slate-500",
"bg-transparent md:bg-slate-100 md:dark:bg-slate-800",
"md:px-3 md:py-1.5",
searchActive
? "w-full opacity-100 border-b md:border md:w-48 lg:w-64 md:border-slate-200 md:dark:border-slate-700"
: "w-0 opacity-0 border-transparent"
)}
onKeyDown={handleKeyDown}
/>
</div>
</div>
);
}

View File

@ -16,7 +16,7 @@ export function SiteFooter() {
Home
</Link>
<Link href="/about" className="hover:underline">
About Us
About
</Link>
<Link href="/privacy" className="hover:underline">
Privacy