Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
PUBLIC_STELLAR_RPC_URL="https://soroban-testnet.stellar.org:443"
STELLAR_ACCOUNT="collins"
STELLAR_NETWORK="testnet"

# Cloudinary
CLOUDINARY_CLOUD_NAME= #neccesary
CLOUDINARY_API_KEY= #neccesary
CLOUDINARY_API_SECRET= #neccesary
CLOUDINARY_URL= #neccesary
240 changes: 168 additions & 72 deletions app/(dashboard)/projects/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,149 @@
"use client";

import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Users, Wallet, Plus } from "lucide-react";
import { Users, Wallet } from "lucide-react";
import Image from "next/image";
import { ProjectActions } from "./project-actions";
import { MilestoneTracker } from "./milestone-tracker";
// import { VotingSection } from "./voting-section"
import { FundingSection } from "./funding-section";
import { VotingSection } from "./voting-section";
import { useSession } from "next-auth/react";
import { TeamSection } from "./team-section";
import type { Vote } from "@prisma/client";

type ValidationStatus = "PENDING" | "REJECTED" | "VALIDATED";

// Note this should be uncommented when project id is provided. Blocked by DB creation and priject creation
// interface ProjectPageProps {
// params: {
// id: string
// }
// }
type Project = {
id: string;
userId: string;
title: string;
description: string;
fundingGoal: number;
category: string;
bannerUrl: string | null;
profileUrl: string | null;
blockchainTx: string | null;
ideaValidation: ValidationStatus;
createdAt: string;
user: {
id: string;
name: string | null;
image: string | null;
};
votes: Vote[];
teamMembers: {
id: string;
fullName: string;
role: string;
bio: string | null;
profileImage: string | null;
github: string | null;
twitter: string | null;
discord: string | null;
linkedin: string | null;
userId: string | null;
}[];
_count: {
votes: number;
teamMembers: number;
};
};

// export default function ProjectPage({ params }: ProjectPageProps) {
export default function ProjectPage() {
const isTeamMember = true; // This would come from your auth logic to be handled by Benjamin
const params = useParams();
const router = useRouter();
const { data: session } = useSession();
const [project, setProject] = useState<Project | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

// Check if user is a team member
const isTeamMember =
project?.userId === session?.user?.id ||
project?.teamMembers.some((member) => member.userId === session?.user?.id);

useEffect(() => {
async function fetchProject() {
try {
const id = params?.id as string;
if (!id) return;

const response = await fetch(`/api/projects/${id}`);

if (response.status === 404) {
router.push("/projects");
return;
}

if (!response.ok) {
throw new Error("Failed to fetch project");
}

const data = await response.json();
setProject(data);
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred");
console.error(err);
} finally {
setLoading(false);
}
}

fetchProject();
}, [params, router]);

if (loading) {
return <div className="container py-8 text-center">Loading project...</div>;
}

if (error) {
return (
<div className="container py-8 text-center text-destructive">
Error: {error}
</div>
);
}

if (!project) {
return <div className="container py-8 text-center">Project not found</div>;
}

// Calculate validation progress - this is just an example
const validationProgress =
project.ideaValidation === "VALIDATED"
? 100
: project.ideaValidation === "REJECTED"
? 0
: Math.min(project._count.votes, 100);

// Determine validation phase
const getValidationPhase = () => {
switch (project.ideaValidation) {
case "VALIDATED":
return "Phase 4 of 4";
case "REJECTED":
return "Rejected";
case "PENDING":
if (project._count.votes >= 75) return "Phase 3 of 4";
if (project._count.votes >= 50) return "Phase 2 of 4";
if (project._count.votes >= 25) return "Phase 1 of 4";
return "Initial Phase";
}
};

return (
<div className="flex min-h-screen flex-col">
<div className="relative h-[200px] md:h-[300px] lg:h-[400px] w-full overflow-hidden">
<Image
src="/banner.png"
alt="Project Banner"
src={project.bannerUrl || "/banner.png"}
alt={`${project.title} Banner`}
fill
className="object-cover"
priority
Expand All @@ -39,44 +155,54 @@ export default function ProjectPage() {
<div className="rounded-xl bg-card p-6 shadow-lg">
<div className="flex flex-col gap-6 md:flex-row md:items-start">
<Avatar className="h-24 w-24 shrink-0 border-4 border-background md:h-32 md:w-32">
<AvatarImage src="/project.svg" alt="Project" />
<AvatarFallback>PJ</AvatarFallback>
<AvatarImage
src={project.profileUrl || "/project.svg"}
alt={project.title}
/>
<AvatarFallback>
{project.title.substring(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>

<div className="flex flex-1 flex-col gap-4">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-2xl font-bold md:text-3xl"> Boundless</h1>
<h1 className="text-2xl font-bold md:text-3xl">
{project.title}
</h1>
<div className="mt-1 flex flex-wrap gap-2 text-sm text-muted-foreground">
<Badge
variant="secondary"
className="flex items-center gap-1"
>
<Users className="h-3 w-3" /> 50 Supporters
<Users className="h-3 w-3" /> {project._count.votes}{" "}
Supporters
</Badge>
{/* <Badge variant="secondary" className="flex items-center gap-1">
<MessageSquare className="h-3 w-3" /> 1.5k Points
</Badge> */}
<Badge
variant="secondary"
className="flex items-center gap-1"
>
<Wallet className="h-3 w-3" /> 50 Funded
<Wallet className="h-3 w-3" /> $
{project.fundingGoal.toLocaleString()} Goal
</Badge>
</div>
</div>
<ProjectActions isTeamMember={isTeamMember} />
<ProjectActions isTeamMember={!!isTeamMember} />
</div>

{/* Progress Section */}
<div className="rounded-lg bg-muted p-4">
<div className="mb-2 flex items-center justify-between">
<h3 className="font-semibold">Validation Progress</h3>
<Badge>Phase 4 of 4</Badge>
<Badge>{getValidationPhase()}</Badge>
</div>
<Progress value={100} className="h-2" />
<Progress value={validationProgress} className="h-2" />
<p className="mt-2 text-sm text-muted-foreground">
Currently in Technical Review Phase
{project.ideaValidation === "VALIDATED"
? "Project has been validated and is now in funding stage"
: project.ideaValidation === "REJECTED"
? "Project did not receive enough community support"
: "Currently in community validation phase"}
</p>
</div>
</div>
Expand All @@ -96,18 +222,9 @@ export default function ProjectPage() {
<Card>
<CardContent className="pt-6">
<div className="prose max-w-none dark:prose-invert">
<h3>About the Project</h3>
<p>Detailed project description...</p>

<h4>Project Goals</h4>
<ul>
<li>Goal 1</li>
<li>Goal 2</li>
<li>Goal 3</li>
</ul>

<h4>Resources</h4>
<div className="not-prose grid gap-4 md:grid-cols-2">
<h3 className="font-semibold">About the Project</h3>
<p>{project.description}</p>
<div className="not-prose grid gap-4 md:grid-cols-2 mt-4">
<Button variant="outline" className="w-full">
View Pitch Deck
</Button>
Expand All @@ -121,51 +238,30 @@ export default function ProjectPage() {
</TabsContent>

<TabsContent value="milestones" className="mt-6">
<MilestoneTracker isTeamMember={isTeamMember} />
<MilestoneTracker isTeamMember={!!isTeamMember} />
</TabsContent>

<TabsContent value="voting" className="mt-6">
{/* <VotingSection /> */}
<VotingSection
projectId={project.id}
initialVoteCount={project._count.votes}
initialUserVoted={project.votes.some(
(vote) => vote.userId === session?.user?.id,
)}
ideaValidation={project.ideaValidation}
/>
</TabsContent>

<TabsContent value="funding" className="mt-6">
<FundingSection />
</TabsContent>

<TabsContent value="team" className="mt-6">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Team Members</CardTitle>
{isTeamMember && (
<Button size="sm">
<Plus className="mr-2 h-4 w-4" /> Add Member
</Button>
)}
</div>
</CardHeader>
<CardContent>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 3 }).map((_, i) => (
<div
key={`item-${i}-${Date.now()}`}
className="flex items-center gap-4 rounded-lg border p-4"
>
<Avatar>
<AvatarImage src="/placeholder.svg" />
<AvatarFallback>TM</AvatarFallback>
</Avatar>
<div>
<div className="font-medium">Team Member Name</div>
<div className="text-sm text-muted-foreground">
Role
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
<TeamSection
teamMembers={project.teamMembers}
isTeamMember={!!isTeamMember}
projectId={project.id}
/>
</TabsContent>
</Tabs>
</div>
Expand Down
11 changes: 7 additions & 4 deletions app/(dashboard)/projects/[id]/project-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Edit, MoreVertical, Plus, Share2, Flag } from "lucide-react";
import { Edit, MoreVertical, Share2, Flag, Plus } from "lucide-react";
import Link from "next/link";

interface ProjectActionsProps {
isTeamMember: boolean;
Expand All @@ -22,9 +23,11 @@ export function ProjectActions({ isTeamMember }: ProjectActionsProps) {
<Button size="sm" variant="outline">
<Edit className="mr-2 h-4 w-4" /> Edit Project
</Button>
<Button size="sm">
<Plus className="mr-2 h-4 w-4" /> New Update
</Button>
<Link href={"/projects/new"}>
<Button size="sm">
<Plus className="mr-2 h-4 w-4" /> New Project
</Button>
</Link>
</>
) : (
<Button size="sm">Support Project</Button>
Expand Down
Loading
Loading