From 06904bc6dd3e24d0bbff3842aee505aba889bc21 Mon Sep 17 00:00:00 2001 From: sirin Date: Wed, 9 Oct 2024 05:40:21 +0700 Subject: [PATCH] feat: dynamically load some data for project deals page --- src/app/deals/[id]/followShareButton.tsx | 61 +++++ src/app/deals/[id]/page.tsx | 162 ++++++++++++ src/components/ui/carousel.tsx | 318 ++++++++++------------- src/lib/data/projectQuery.ts | 69 ++--- 4 files changed, 384 insertions(+), 226 deletions(-) create mode 100644 src/app/deals/[id]/followShareButton.tsx create mode 100644 src/app/deals/[id]/page.tsx diff --git a/src/app/deals/[id]/followShareButton.tsx b/src/app/deals/[id]/followShareButton.tsx new file mode 100644 index 0000000..d143860 --- /dev/null +++ b/src/app/deals/[id]/followShareButton.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { ShareIcon, StarIcon } from "lucide-react"; +import { redirect } from "next/navigation"; +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); + const [isFollow, setIsFollow] = useState(false); + + useEffect(() => { + if (!loading) { + setSessionLoaded(true); + } + }, [loading]); + + const handleShare = () => { + const currentUrl = window.location.href; + if (document.hasFocus()) { + navigator.clipboard.writeText(currentUrl).then(() => { + toast.success("URL copied to clipboard!"); + }); + } + }; + const handleFollow = () => { + if (user) { + setIsFollow((prevState) => !prevState); + } else { + redirect("/login"); + } + }; + + return ( +
+
+ + + + + + +

Follow NVIDIA

+
+
+
+
+
+ +
+
+ ); +}; + +export default FollowShareButtons; diff --git a/src/app/deals/[id]/page.tsx b/src/app/deals/[id]/page.tsx new file mode 100644 index 0000000..92b21aa --- /dev/null +++ b/src/app/deals/[id]/page.tsx @@ -0,0 +1,162 @@ +import Image from "next/image"; +import Link from "next/link"; + +import ReactMarkdown from "react-markdown"; + +import * as Tabs from "@radix-ui/react-tabs"; +import { Button } from "@/components/ui/button"; +import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { Progress } from "@/components/ui/progress"; +import { Separator } from "@/components/ui/separator"; +import { createSupabaseClient } from "@/lib/supabase/serverComponentClient"; +import FollowShareButtons from "./followShareButton"; + +import { getProjectData } from "@/lib/data/projectQuery"; + +export default async function ProjectDealPage({ params }: { params: { id: number } }) { + const supabase = createSupabaseClient(); + + const { data: projectData, error: projectDataError } = await getProjectData(supabase, params.id); + + const carouselData = [ + { src: "/boiler1.jpg", alt: "Boiler 1" }, + { src: "/boiler1.jpg", alt: "Boiler 1" }, + { src: "/boiler1.jpg", alt: "Boiler 1" }, + { src: "/boiler1.jpg", alt: "Boiler 1" }, + { src: "/boiler1.jpg", alt: "Boiler 1" }, + ]; + + if (projectDataError) { + return
Error
; + } + + return ( +
+
+
+ {/* Name, star and share button packed */} + +
+ {/* image carousel */} +
+ + + {carouselData.map((item, index) => ( + + {item.alt} + + ))} + + + + + + + + {carouselData.map((item, index) => ( + + {item.alt} + + ))} + + +
+
+
+ +

${projectData?.total_investment}

+

5% raised of \$5M max goal

+ +
+ +

+

{projectData?.total_investment}

+

+

Investors

+
+ + +

+

1 hours

+

Left to invest

+
+ +
+
+
+
+ {/* menu */} +
+
+ + + Pitch + General Data + Updates + + + + + + + + + +
+ {projectData?.project_description || "No pitch available."} +
+
+
+
+ + + + general + general Description + + +

general Content

+
+
+
+ + + + update + update Description + + +

update Content

+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx index ec505d0..59831f9 100644 --- a/src/components/ui/carousel.tsx +++ b/src/components/ui/carousel.tsx @@ -1,124 +1,108 @@ -"use client" +"use client"; -import * as React from "react" -import useEmblaCarousel, { - type UseEmblaCarouselType, -} from "embla-carousel-react" -import { ArrowLeft, ArrowRight } from "lucide-react" +import * as React from "react"; +import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; -type CarouselApi = UseEmblaCarouselType[1] -type UseCarouselParameters = Parameters -type CarouselOptions = UseCarouselParameters[0] -type CarouselPlugin = UseCarouselParameters[1] +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; type CarouselProps = { - opts?: CarouselOptions - plugins?: CarouselPlugin - orientation?: "horizontal" | "vertical" - setApi?: (api: CarouselApi) => void -} + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: "horizontal" | "vertical"; + setApi?: (api: CarouselApi) => void; +}; type CarouselContextProps = { - carouselRef: ReturnType[0] - api: ReturnType[1] - scrollPrev: () => void - scrollNext: () => void - canScrollPrev: boolean - canScrollNext: boolean -} & CarouselProps + carouselRef: ReturnType[0]; + api: ReturnType[1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; -const CarouselContext = React.createContext(null) +const CarouselContext = React.createContext(null); function useCarousel() { - const context = React.useContext(CarouselContext) + const context = React.useContext(CarouselContext); if (!context) { - throw new Error("useCarousel must be used within a ") + throw new Error("useCarousel must be used within a "); } - return context + return context; } -const Carousel = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & CarouselProps ->( - ( - { - orientation = "horizontal", - opts, - setApi, - plugins, - className, - children, - ...props - }, - ref - ) => { +const Carousel = React.forwardRef & CarouselProps>( + ({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => { const [carouselRef, api] = useEmblaCarousel( { ...opts, axis: orientation === "horizontal" ? "x" : "y", }, plugins - ) - const [canScrollPrev, setCanScrollPrev] = React.useState(false) - const [canScrollNext, setCanScrollNext] = React.useState(false) + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); const onSelect = React.useCallback((api: CarouselApi) => { if (!api) { - return + return; } - setCanScrollPrev(api.canScrollPrev()) - setCanScrollNext(api.canScrollNext()) - }, []) + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); const scrollPrev = React.useCallback(() => { - api?.scrollPrev() - }, [api]) + api?.scrollPrev(); + }, [api]); const scrollNext = React.useCallback(() => { - api?.scrollNext() - }, [api]) + api?.scrollNext(); + }, [api]); const handleKeyDown = React.useCallback( (event: React.KeyboardEvent) => { if (event.key === "ArrowLeft") { - event.preventDefault() - scrollPrev() + event.preventDefault(); + scrollPrev(); } else if (event.key === "ArrowRight") { - event.preventDefault() - scrollNext() + event.preventDefault(); + scrollNext(); } }, [scrollPrev, scrollNext] - ) + ); React.useEffect(() => { if (!api || !setApi) { - return + return; } - setApi(api) - }, [api, setApi]) + setApi(api); + }, [api, setApi]); React.useEffect(() => { if (!api) { - return + return; } - onSelect(api) - api.on("reInit", onSelect) - api.on("select", onSelect) + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); return () => { - api?.off("select", onSelect) - } - }, [api, onSelect]) + api?.off("select", onSelect); + }; + }, [api, onSelect]); return ( + }}>
+ {...props}> {children}
- ) + ); } -) -Carousel.displayName = "Carousel" +); +Carousel.displayName = "Carousel"; -const CarouselContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => { - const { carouselRef, orientation } = useCarousel() +const CarouselContent = React.forwardRef>( + ({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); - return ( -
+ return ( +
+
+
+ ); + } +); +CarouselContent.displayName = "CarouselContent"; + +const CarouselItem = React.forwardRef>( + ({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return (
-
- ) -}) -CarouselContent.displayName = "CarouselContent" + ); + } +); +CarouselItem.displayName = "CarouselItem"; -const CarouselItem = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => { - const { orientation } = useCarousel() +const CarouselPrevious = React.forwardRef>( + ({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); - return ( -
- ) -}) -CarouselItem.displayName = "CarouselItem" + return ( + + ); + } +); +CarouselPrevious.displayName = "CarouselPrevious"; -const CarouselPrevious = React.forwardRef< - HTMLButtonElement, - React.ComponentProps ->(({ className, variant = "outline", size = "icon", ...props }, ref) => { - const { orientation, scrollPrev, canScrollPrev } = useCarousel() +const CarouselNext = React.forwardRef>( + ({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel(); - return ( - - ) -}) -CarouselPrevious.displayName = "CarouselPrevious" + return ( + + ); + } +); +CarouselNext.displayName = "CarouselNext"; -const CarouselNext = React.forwardRef< - HTMLButtonElement, - React.ComponentProps ->(({ className, variant = "outline", size = "icon", ...props }, ref) => { - const { orientation, scrollNext, canScrollNext } = useCarousel() - - return ( - - ) -}) -CarouselNext.displayName = "CarouselNext" - -export { - type CarouselApi, - Carousel, - CarouselContent, - CarouselItem, - CarouselPrevious, - CarouselNext, -} +export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }; diff --git a/src/lib/data/projectQuery.ts b/src/lib/data/projectQuery.ts index ce652b9..75e3f01 100644 --- a/src/lib/data/projectQuery.ts +++ b/src/lib/data/projectQuery.ts @@ -44,54 +44,29 @@ async function getTopProjects(client: SupabaseClient, numberOfRecords: number = } } -async function searchProjects(client: SupabaseClient, searchTerm: string | null, page: number = 1, pageSize: number = 4) { - const start = (page - 1) * pageSize; - const end = start + pageSize - 1; - - try { - let query = client.from("Project").select( - ` - id, - projectName, - businessId, - publishedTime, - projectShortDescription, - cardImage, - ProjectInvestmentDetail ( - minInvestment, - totalInvestment, - targetInvestment, - investmentDeadline - ), - ItemTag ( - Tag ( - id, - value - ) - ), - Business ( - location +async function getProjectData(client: SupabaseClient, projectId: number) { + const query = client.from("Project").select( + ` + project_name:projectName, + project_short_description:projectShortDescription, + project_description:projectDescription, + published_time:publishedTime, + ...ProjectInvestmentDetail!inner ( + min_investment:minInvestment, + total_investment:totalInvestment, + target_investment:targetInvestment, + investment_deadline:investmentDeadline + ), + tags:ItemTag!inner ( + ...Tag!inner ( + tag_name:value ) - ` - ).order("publishedTime", { ascending: false }) - .range(start, end); + ) + ` + ).eq("id", projectId).single() - if (searchTerm) { - query = query.ilike('projectName', `%${searchTerm}%`); - } - - const { data, error } = await query; - - if (error) { - console.error("Error searching projects:", error.message); - return { data: null, error: error.message }; - } - - return { data, error: null }; - } catch (err) { - console.error("Unexpected error:", err); - return { data: null, error: "An unexpected error occurred." }; - } + const {data, error} = await query; + return { data, error } } export interface FilterParams { @@ -178,5 +153,5 @@ function searchProjectsQuery(client: SupabaseClient, {searchTerm, tagsFilter, pr } -export { getTopProjects, searchProjects, searchProjectsQuery }; +export { getTopProjects, getProjectData, searchProjectsQuery };