Skip to content

Commit d71bd4a

Browse files
authored
feat: implement new veiw for project viewers, logic for user check and status (boundlessfi#69)
1 parent fb484cf commit d71bd4a

27 files changed

Lines changed: 1560 additions & 279 deletions
Lines changed: 4 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -1,276 +1,5 @@
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

Comments
 (0)