1- "use client" ;
1+ import { ProjectViewerPage } from "./viewer/project-viewer-page"
22
3- import { Avatar , AvatarFallback , AvatarImage } from "@/components/ui/avatar" ;
4- import { Badge } from "@/components/ui/badge" ;
5- import { Button } from "@/components/ui/button" ;
6- import { Card , CardContent } from "@/components/ui/card" ;
7- import { Progress } from "@/components/ui/progress" ;
8- import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
9- import type { Vote } from "@prisma/client" ;
10- import { Users , Wallet } from "lucide-react" ;
11- import { useSession } from "next-auth/react" ;
12- import Image from "next/image" ;
13- import { useParams , useRouter } from "next/navigation" ;
14- import { useEffect , useState } from "react" ;
15- import { CommentsSection } from "./comments-section" ;
16- import { FundingSection } from "./funding-section" ;
17- import { MilestoneTracker } from "./milestone-tracker" ;
18- import { ProjectActions } from "./project-actions" ;
19- import { TeamSection } from "./team-section" ;
20- import { VotingSection } from "./voting-section" ;
21-
22- type ValidationStatus = "PENDING" | "REJECTED" | "VALIDATED" ;
23-
24- type Project = {
25- id : string ;
26- userId : string ;
27- title : string ;
28- description : string ;
29- fundingGoal : number ;
30- category : string ;
31- bannerUrl : string | null ;
32- profileUrl : string | null ;
33- blockchainTx : string | null ;
34- ideaValidation : ValidationStatus ;
35- createdAt : string ;
36- user : {
37- id : string ;
38- name : string | null ;
39- image : string | null ;
40- } ;
41- votes : Vote [ ] ;
42- teamMembers : {
43- id : string ;
44- fullName : string ;
45- role : string ;
46- bio : string | null ;
47- profileImage : string | null ;
48- github : string | null ;
49- twitter : string | null ;
50- discord : string | null ;
51- linkedin : string | null ;
52- userId : string | null ;
53- } [ ] ;
54- _count : {
55- votes : number ;
56- teamMembers : number ;
57- } ;
58- } ;
59-
60- export default function ProjectPage ( ) {
61- const params = useParams ( ) ;
62- const router = useRouter ( ) ;
63- const { data : session } = useSession ( ) ;
64- const [ project , setProject ] = useState < Project | null > ( null ) ;
65- const [ loading , setLoading ] = useState ( true ) ;
66- const [ error , setError ] = useState < string | null > ( null ) ;
67-
68- // Check if user is a team member
69- const isTeamMember =
70- project ?. userId === session ?. user ?. id ||
71- project ?. teamMembers . some ( ( member ) => member . userId === session ?. user ?. id ) ;
72-
73- useEffect ( ( ) => {
74- async function fetchProject ( ) {
75- try {
76- const id = params ?. id as string ;
77- if ( ! id ) return ;
78-
79- const response = await fetch ( `/api/projects/${ id } ` ) ;
80-
81- if ( response . status === 404 ) {
82- router . push ( "/projects" ) ;
83- return ;
84- }
85-
86- if ( ! response . ok ) {
87- throw new Error ( "Failed to fetch project" ) ;
88- }
89-
90- const data = await response . json ( ) ;
91- setProject ( data ) ;
92- } catch ( err ) {
93- setError ( err instanceof Error ? err . message : "An error occurred" ) ;
94- console . error ( err ) ;
95- } finally {
96- setLoading ( false ) ;
97- }
98- }
99-
100- fetchProject ( ) ;
101- } , [ params , router ] ) ;
102-
103- if ( loading ) {
104- return < div className = "container py-8 text-center" > Loading project...</ div > ;
105- }
106-
107- if ( error ) {
108- return (
109- < div className = "container py-8 text-center text-destructive" >
110- Error: { error }
111- </ div >
112- ) ;
113- }
114-
115- if ( ! project ) {
116- return < div className = "container py-8 text-center" > Project not found</ div > ;
117- }
118-
119- // Calculate validation progress - this is just an example
120- const validationProgress =
121- project . ideaValidation === "VALIDATED"
122- ? 100
123- : project . ideaValidation === "REJECTED"
124- ? 0
125- : Math . min ( project . _count . votes , 100 ) ;
126-
127- // Determine validation phase
128- const getValidationPhase = ( ) => {
129- switch ( project . ideaValidation ) {
130- case "VALIDATED" :
131- return "Phase 4 of 4" ;
132- case "REJECTED" :
133- return "Rejected" ;
134- case "PENDING" :
135- if ( project . _count . votes >= 75 ) return "Phase 3 of 4" ;
136- if ( project . _count . votes >= 50 ) return "Phase 2 of 4" ;
137- if ( project . _count . votes >= 25 ) return "Phase 1 of 4" ;
138- return "Initial Phase" ;
139- }
140- } ;
141-
142- return (
143- < div className = "flex min-h-screen flex-col" >
144- < div className = "relative h-[200px] md:h-[300px] lg:h-[400px] w-full overflow-hidden" >
145- < Image
146- src = { project . bannerUrl || "/banner.png" }
147- alt = { `${ project . title } Banner` }
148- fill
149- className = "object-cover"
150- priority
151- />
152- < div className = "absolute inset-0 bg-gradient-to-t from-background/80 to-transparent" />
153- </ div >
154-
155- < div className = "container relative z-10 -mt-32 px-4 sm:px-6 lg:px-8" >
156- < div className = "rounded-xl bg-card p-6 shadow-lg" >
157- < div className = "flex flex-col gap-6 md:flex-row md:items-start" >
158- < Avatar className = "h-24 w-24 shrink-0 border-4 border-background md:h-32 md:w-32" >
159- < AvatarImage
160- src = { project . profileUrl || "/project.svg" }
161- alt = { project . title }
162- />
163- < AvatarFallback >
164- { project . title . substring ( 0 , 2 ) . toUpperCase ( ) }
165- </ AvatarFallback >
166- </ Avatar >
167-
168- < div className = "flex flex-1 flex-col gap-4" >
169- < div className = "flex flex-col gap-2 md:flex-row md:items-center md:justify-between" >
170- < div >
171- < h1 className = "text-2xl font-bold md:text-3xl" >
172- { project . title }
173- </ h1 >
174- < div className = "mt-1 flex flex-wrap gap-2 text-sm text-muted-foreground" >
175- < Badge
176- variant = "secondary"
177- className = "flex items-center gap-1"
178- >
179- < Users className = "h-3 w-3" /> { project . _count . votes } { " " }
180- Supporters
181- </ Badge >
182- < Badge
183- variant = "secondary"
184- className = "flex items-center gap-1"
185- >
186- < Wallet className = "h-3 w-3" /> $
187- { project . fundingGoal . toLocaleString ( ) } Goal
188- </ Badge >
189- </ div >
190- </ div >
191- < ProjectActions isTeamMember = { ! ! isTeamMember } />
192- </ div >
193-
194- { /* Progress Section */ }
195- < div className = "rounded-lg bg-muted p-4" >
196- < div className = "mb-2 flex items-center justify-between" >
197- < h3 className = "font-semibold" > Validation Progress</ h3 >
198- < Badge > { getValidationPhase ( ) } </ Badge >
199- </ div >
200- < Progress value = { validationProgress } className = "h-2" />
201- < p className = "mt-2 text-sm text-muted-foreground" >
202- { project . ideaValidation === "VALIDATED"
203- ? "Project has been validated and is now in funding stage"
204- : project . ideaValidation === "REJECTED"
205- ? "Project did not receive enough community support"
206- : "Currently in community validation phase" }
207- </ p >
208- </ div >
209- </ div >
210- </ div >
211- </ div >
212-
213- < Tabs defaultValue = "description" className = "mt-6" >
214- < TabsList className = "w-full justify-start overflow-x-auto" >
215- < TabsTrigger value = "description" > Description</ TabsTrigger >
216- < TabsTrigger value = "milestones" > Milestones</ TabsTrigger >
217- < TabsTrigger value = "voting" > Voting</ TabsTrigger >
218- < TabsTrigger value = "funding" > Funding</ TabsTrigger >
219- < TabsTrigger value = "team" > Team</ TabsTrigger >
220- < TabsTrigger value = "comments" > Comments</ TabsTrigger >
221- </ TabsList >
222-
223- < TabsContent value = "description" className = "mt-6 space-y-6" >
224- < Card >
225- < CardContent className = "pt-6" >
226- < div className = "prose max-w-none dark:prose-invert" >
227- < h3 className = "font-semibold" > About the Project</ h3 >
228- < p > { project . description } </ p >
229- < div className = "not-prose grid gap-4 md:grid-cols-2 mt-4" >
230- < Button variant = "outline" className = "w-full" >
231- View Pitch Deck
232- </ Button >
233- < Button variant = "outline" className = "w-full" >
234- View Whitepaper
235- </ Button >
236- </ div >
237- </ div >
238- </ CardContent >
239- </ Card >
240- </ TabsContent >
241-
242- < TabsContent value = "milestones" className = "mt-6" >
243- < MilestoneTracker isTeamMember = { ! ! isTeamMember } />
244- </ TabsContent >
245-
246- < TabsContent value = "voting" className = "mt-6" >
247- < VotingSection
248- projectId = { project . id }
249- initialVoteCount = { project . _count . votes }
250- initialUserVoted = { project . votes . some (
251- ( vote ) => vote . userId === session ?. user ?. id ,
252- ) }
253- ideaValidation = { project . ideaValidation }
254- />
255- </ TabsContent >
256-
257- < TabsContent value = "funding" className = "mt-6" >
258- < FundingSection projectId = { project . id } />
259- </ TabsContent >
260-
261- < TabsContent value = "team" className = "mt-6" >
262- < TeamSection
263- projectId = { project . id }
264- teamMembers = { project . teamMembers }
265- isTeamMember = { ! ! isTeamMember }
266- />
267- </ TabsContent >
268-
269- < TabsContent value = "comments" className = "mt-6" >
270- < CommentsSection projectId = { project . id } />
271- </ TabsContent >
272- </ Tabs >
273- </ div >
274- </ div >
275- ) ;
276- }
3+ export default function ProjectPage ( { params } : { params : { id : string } } ) {
4+ return < ProjectViewerPage />
5+ }
0 commit comments