-
handleMemberClick()}
- >
+ {/* Project Info */}
+
- {true ? (
+ {project?.media?.logo ? (
) : (
@@ -121,23 +164,23 @@ const MilstoneOverview = () => {
width={48}
height={48}
src='/avatar.png'
- alt='Default avatar'
+ alt='Default project logo'
className='h-full w-full object-cover'
/>
)}
- {/* Member Info */}
Project
- BitMed
+
+ {project?.title || 'Unknown Project'}
+
- {/* Chevron */}
-
handleMemberClick()}
- >
-
-
-
- {true ? (
-
- ) : (
+
+ {/* Project Creator */}
+ {project?.creator && (
+
+
- {/* Member Info */}
-
-
- John Doe
-
-
OWNER
+
+
+ {project.creator.profile?.firstName}{' '}
+ {project.creator.profile?.lastName}
+
+ OWNER
+
-
- {/* Chevron */}
-
-
+
+
+ )}
diff --git a/components/project-details/project-sidebar.tsx b/components/project-details/project-sidebar.tsx
deleted file mode 100644
index 51cc8de7f..000000000
--- a/components/project-details/project-sidebar.tsx
+++ /dev/null
@@ -1,260 +0,0 @@
-'use client';
-
-import {
- Calendar,
- Github,
- Globe,
- Youtube,
- X,
- Share2,
- UserPlus,
- ArrowUp,
- DollarSign,
-} from 'lucide-react';
-import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
-import { Button } from '@/components/ui/button';
-import { Progress } from '@/components/ui/progress';
-import Image from 'next/image';
-import type { CrowdfundingProject } from '@/lib/api/types';
-import { voteProject } from '@/lib/api/project';
-
-interface ProjectSidebarProps {
- project: CrowdfundingProject & {
- // Additional fields that might be added during transformation
- daysToDeadline?: number;
- additionalCreator?: {
- name: string;
- role: string;
- avatar: string;
- };
- links?: Array<{
- type: string;
- url: string;
- icon: string;
- }>;
- // Legacy fields for backward compatibility
- name?: string;
- description?: string;
- logo?: string;
- validation?: string;
- date?: string;
- votes?: number;
- totalVotes?: number;
- };
- isMobile?: boolean;
-}
-
-export function ProjectSidebar({
- project,
- isMobile = false,
-}: ProjectSidebarProps) {
- const votePercentage = project.voting
- ? (project.votes / project.voting.totalVotes) * 100
- : 0;
-
- const getIcon = (iconType: string) => {
- switch (iconType) {
- case 'github':
- return
;
- case 'twitter':
- return
;
- case 'globe':
- return
;
- case 'youtube':
- return
;
- default:
- return
;
- }
- };
- const getStatusStyles = () => {
- switch (project.status) {
- case 'Funding':
- return 'bg-blue-ish border-blue-ish-darker text-blue-ish-darker';
- case 'Funded':
- return 'bg-transparent border-primary text-primary';
- case 'Completed':
- return 'bg-success-green border-success-green-darker text-success-green-darker';
- case 'Validation':
- case 'idea':
- return 'bg-warning-orange border-warning-orange-darker text-warning-orange-darker';
- default:
- return '';
- }
- };
-
- return (
-
- {/* Project Header Section */}
-
-
-
-
-
-
-
- {project.title}
-
-
- {/* Category and Validation badges - side by side */}
-
-
- {project.category}
-
-
- {project.status}
-
-
-
- {/* Date */}
-
-
-
- {new Date(project.createdAt).toLocaleDateString('en-US', {
- day: '2-digit',
- month: 'short',
- year: 'numeric',
- })}
-
-
-
-
-
- {/* Description */}
-
{project.vision}
-
- {/* Voting Progress */}
-
-
-
- {project.votes}/{project.voting?.totalVotes || 0}{' '}
- votes
-
-
- {project.daysToDeadline || 0} days to deadline
-
-
-
-
-
- {/* Action Buttons - Responsive widths */}
-
-
-
- {/* Funding Button */}
- {/*
-
- */}
- {project.status === 'Funding' && (
-
- )}
-
-
-
-
- {/* Creator Info and Links - Only show on desktop */}
- {!isMobile && (
- <>
- {/* Creator Info */}
-
-
-
-
-
- {(
- project.additionalCreator?.name ||
- project.creator.profile.firstName +
- ' ' +
- project.creator.profile.lastName
- )
- .split(' ')
- .map((n: string) => n[0])
- .join('')
- .toUpperCase()}
-
-
-
-
- {project.additionalCreator?.name ||
- project.creator.profile.firstName +
- ' ' +
- project.creator.profile.lastName}
-
-
- {project.additionalCreator?.role || 'CREATOR'}
-
-
-
-
-
- {/* Project Links */}
-
- >
- )}
-
- );
-}
diff --git a/components/project-details/project-sidebar/ProjectSidebarActions.tsx b/components/project-details/project-sidebar/ProjectSidebarActions.tsx
new file mode 100644
index 000000000..2bfcd28ce
--- /dev/null
+++ b/components/project-details/project-sidebar/ProjectSidebarActions.tsx
@@ -0,0 +1,146 @@
+'use client';
+
+import { useState } from 'react';
+import {
+ ArrowUp,
+ DollarSign,
+ CheckCircle,
+ UserPlus,
+ Share2,
+ ThumbsUp,
+ HandCoins,
+} from 'lucide-react';
+import { ProjectSidebarActionsProps } from './types';
+import { BoundlessButton } from '@/components/buttons';
+import { SharePopup } from './SharePopup';
+import FundProject from '@/components/modals/fund-project';
+
+export function ProjectSidebarActions({
+ project,
+ projectStatus,
+ isVoting,
+ userVote,
+ onVote,
+}: ProjectSidebarActionsProps) {
+ const [isSharePopupOpen, setIsSharePopupOpen] = useState(false);
+ const [isFundModalOpen, setIsFundModalOpen] = useState(false);
+
+ const handleShareClick = () => {
+ setIsSharePopupOpen(true);
+ };
+
+ const handleCloseSharePopup = () => {
+ setIsSharePopupOpen(false);
+ };
+
+ const handleFundClick = () => {
+ setIsFundModalOpen(true);
+ };
+
+ return (
+
+ {projectStatus === 'Validation' && (
+
onVote(1)}
+ disabled={isVoting || userVote === 1}
+ // variant={userVote === 1 ? 'default' : 'outline'}
+ loading={isVoting}
+ iconPosition={userVote === 1 ? 'right' : 'left'}
+ icon={
+ userVote === 1 ? (
+
+ ) : (
+
+ )
+ }
+ className={`flex h-12 flex-1 items-center justify-center gap-2 rounded-lg text-base font-semibold shadow-lg transition-all duration-200 hover:shadow-xl ${
+ userVote === 1
+ ? 'bg-primary/10 border-primary/24 text-primary border'
+ : 'bg-[#A7F950] text-black hover:bg-[#A7F950]'
+ } `}
+ >
+
+ {isVoting ? 'Voting...' : userVote === 1 ? 'Upvoted' : 'Upvote'}
+
+
+ )}
+
+ {projectStatus === 'Funding' ||
+ (projectStatus === 'live' && (
+
}
+ iconPosition='left'
+ >
+
Back Project
+
+ ))}
+
+ {projectStatus === 'Completed' && (
+
}
+ iconPosition='left'
+ >
+
Completed
+
+ )}
+
+ {projectStatus === 'Funded' && (
+
}
+ iconPosition='left'
+ >
+
Funded
+
+ )}
+
+
}
+ iconPosition='left'
+ >
+
Follow
+
+
+
+ }
+ iconPosition='left'
+ >
+ Share
+
+
+
+
+
+ {/* Fund Project Modal */}
+
+
+ );
+}
diff --git a/components/project-details/project-sidebar/ProjectSidebarCreator.tsx b/components/project-details/project-sidebar/ProjectSidebarCreator.tsx
new file mode 100644
index 000000000..48fe3c175
--- /dev/null
+++ b/components/project-details/project-sidebar/ProjectSidebarCreator.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
+import { ProjectSidebarCreatorProps } from './types';
+
+export function ProjectSidebarCreator({ project }: ProjectSidebarCreatorProps) {
+ const creatorName =
+ project.additionalCreator?.name ||
+ `${project.creator.profile.firstName} ${project.creator.profile.lastName}`;
+
+ const creatorInitials = creatorName
+ .split(' ')
+ .map((n: string) => n[0])
+ .join('')
+ .toUpperCase();
+
+ return (
+
+
+
+
+
+ {creatorInitials}
+
+
+
+
+ {creatorName}
+
+
+ {project.additionalCreator?.role || 'CREATOR'}
+
+
+
+
+ );
+}
diff --git a/components/project-details/project-sidebar/ProjectSidebarHeader.tsx b/components/project-details/project-sidebar/ProjectSidebarHeader.tsx
new file mode 100644
index 000000000..4ae58cebc
--- /dev/null
+++ b/components/project-details/project-sidebar/ProjectSidebarHeader.tsx
@@ -0,0 +1,69 @@
+'use client';
+
+import { Calendar } from 'lucide-react';
+import Image from 'next/image';
+import { ProjectSidebarHeaderProps } from './types';
+
+export function ProjectSidebarHeader({
+ project,
+ projectStatus,
+}: ProjectSidebarHeaderProps) {
+ const getStatusStyles = () => {
+ switch (projectStatus) {
+ case 'Funding':
+ return 'bg-secondary-75 border-secondary-600 text-secondary-600';
+ case 'Funded':
+ return 'bg-[rgba(167,249,80,0.08)] border-primary text-primary';
+ case 'Completed':
+ return 'bg-success-75 border-success-600 text-success-600';
+ case 'Validation':
+ return 'bg-warning-75 border-warning-600 text-warning-600';
+ case 'idea':
+ return 'bg-warning-75 border-warning-600 text-warning-600';
+ default:
+ return '';
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ {project.title}
+
+
+
+
+ {project.category}
+
+
+ {projectStatus}
+
+
+
+
+
+
+ {new Date(project.createdAt).toLocaleDateString('en-US', {
+ day: '2-digit',
+ month: 'short',
+ year: 'numeric',
+ })}
+
+
+
+
+ );
+}
diff --git a/components/project-details/project-sidebar/ProjectSidebarLinks.tsx b/components/project-details/project-sidebar/ProjectSidebarLinks.tsx
new file mode 100644
index 000000000..3feab998d
--- /dev/null
+++ b/components/project-details/project-sidebar/ProjectSidebarLinks.tsx
@@ -0,0 +1,91 @@
+'use client';
+
+import { Github, Globe, Youtube, X } from 'lucide-react';
+import { ProjectSidebarLinksProps } from './types';
+
+export function ProjectSidebarLinks({ project }: ProjectSidebarLinksProps) {
+ const getIcon = (iconType: string) => {
+ switch (iconType) {
+ case 'github':
+ return
;
+ case 'twitter':
+ return
;
+ case 'globe':
+ return
;
+ case 'youtube':
+ return
;
+ default:
+ return
;
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/components/project-details/project-sidebar/ProjectSidebarProgress.tsx b/components/project-details/project-sidebar/ProjectSidebarProgress.tsx
new file mode 100644
index 000000000..9e0e8248c
--- /dev/null
+++ b/components/project-details/project-sidebar/ProjectSidebarProgress.tsx
@@ -0,0 +1,113 @@
+'use client';
+
+import { Progress } from '@/components/ui/progress';
+import { ProjectSidebarProgressProps } from './types';
+
+export function ProjectSidebarProgress({
+ project,
+ crowdfund,
+ projectStatus,
+}: ProjectSidebarProgressProps) {
+ const votePercentage = crowdfund
+ ? (crowdfund.totalVotes / crowdfund.thresholdVotes) * 100
+ : project.voting
+ ? (project.votes || 0 / project.voting.totalVotes) * 100
+ : 0;
+
+ const fundingPercentage = project.funding
+ ? (project.funding.raised / project.funding.goal) * 100
+ : 0;
+
+ const milestonePercentage = project.milestones
+ ? (project.milestones.filter(m => m.status === 'completed').length /
+ project.milestones.length) *
+ 100
+ : 0;
+
+ const renderProgressSection = () => {
+ switch (projectStatus) {
+ case 'Funding':
+ return (
+
+
+
+ ${project.funding?.raised || 0}/${project.funding?.goal || 0}{' '}
+ USD Raised
+
+
+ {project.daysToDeadline || 0} days to deadline
+
+
+
+
+ );
+
+ case 'Validation':
+ return (
+
+
+
+ {crowdfund?.totalVotes || 0}/{crowdfund?.thresholdVotes || 0}{' '}
+ Votes
+
+
+ {project.daysToDeadline || 0} days to deadline
+
+
+
+
+ );
+
+ case 'Completed':
+ return (
+
+
+
+ {project.milestones?.filter(m => m.status === 'completed')
+ .length || 0}
+ /{project.milestones?.length || 0}{' '}
+
+ Milestones Submitted
+
+
+ {project.milestones?.filter(m => m.status === 'rejected').length >
+ 0 && (
+
+ {
+ project.milestones.filter(m => m.status === 'rejected')
+ .length
+ }{' '}
+ milestone rejected
+
+ )}
+
+
+
+ );
+
+ default:
+ return (
+
+
+
+ {crowdfund?.totalVotes || 0}/{crowdfund?.thresholdVotes || 0}{' '}
+ votes
+
+
+ {project.daysToDeadline || 0} days to deadline
+
+
+
+
+ );
+ }
+ };
+
+ return <>{renderProgressSection()}>;
+}
diff --git a/components/project-details/project-sidebar/SharePopup.tsx b/components/project-details/project-sidebar/SharePopup.tsx
new file mode 100644
index 000000000..f263d25e7
--- /dev/null
+++ b/components/project-details/project-sidebar/SharePopup.tsx
@@ -0,0 +1,151 @@
+'use client';
+
+import { useState, useRef, useEffect } from 'react';
+import { Send, Check, Link2 } from 'lucide-react';
+
+interface SharePopupProps {
+ isOpen: boolean;
+ onClose: () => void;
+ projectTitle: string;
+ projectUrl: string;
+}
+
+export function SharePopup({
+ isOpen,
+ onClose,
+ projectTitle,
+ projectUrl,
+}: SharePopupProps) {
+ const [copied, setCopied] = useState(false);
+ const popupRef = useRef
(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ popupRef.current &&
+ !popupRef.current.contains(event.target as Node)
+ ) {
+ onClose();
+ }
+ };
+
+ if (isOpen) {
+ document.addEventListener('mousedown', handleClickOutside);
+ }
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [isOpen, onClose]);
+
+ useEffect(() => {
+ const handleEscape = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ onClose();
+ }
+ };
+
+ if (isOpen) {
+ document.addEventListener('keydown', handleEscape);
+ }
+
+ return () => {
+ document.removeEventListener('keydown', handleEscape);
+ };
+ }, [isOpen, onClose]);
+
+ const shareOptions = [
+ {
+ name: 'X (Twitter)',
+ icon: (
+
+ ),
+ color: 'hover:bg-black/10 hover:text-black',
+ action: () => {
+ const text = `Check out this project: ${projectTitle}`;
+ const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(projectUrl)}`;
+ window.open(url, '_blank', 'width=600,height=400');
+ onClose();
+ },
+ },
+ {
+ name: 'Discord',
+ icon: (
+
+ ),
+ color: 'hover:bg-indigo-500/10 hover:text-indigo-500',
+ action: () => {
+ const text = `**${projectTitle}**\n${projectUrl}`;
+ navigator.clipboard.writeText(text).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ });
+ onClose();
+ },
+ },
+ {
+ name: 'Telegram',
+ icon: ,
+ color: 'hover:bg-blue-500/10 hover:text-blue-500',
+ action: () => {
+ const text = `Check out this project: ${projectTitle}`;
+ const url = `https://t.me/share/url?url=${encodeURIComponent(projectUrl)}&text=${encodeURIComponent(text)}`;
+ window.open(url, '_blank', 'width=600,height=400');
+ onClose();
+ },
+ },
+ {
+ name: 'Copy Link',
+ icon: copied ? (
+
+ ) : (
+
+ ),
+ color: copied
+ ? 'bg-green-500/10 text-green-500'
+ : 'hover:bg-gray-500/10 hover:text-gray-500',
+ action: () => {
+ navigator.clipboard.writeText(projectUrl).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ });
+ onClose();
+ },
+ },
+ ];
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {shareOptions.map((option, index) => (
+
+ ))}
+
+
+ );
+}
diff --git a/components/project-details/project-sidebar/index.tsx b/components/project-details/project-sidebar/index.tsx
new file mode 100644
index 000000000..d098cbf81
--- /dev/null
+++ b/components/project-details/project-sidebar/index.tsx
@@ -0,0 +1,67 @@
+'use client';
+
+import { useState } from 'react';
+import { ProjectSidebarHeader } from './ProjectSidebarHeader';
+import { ProjectSidebarProgress } from './ProjectSidebarProgress';
+import { ProjectSidebarActions } from './ProjectSidebarActions';
+import { ProjectSidebarCreator } from './ProjectSidebarCreator';
+import { ProjectSidebarLinks } from './ProjectSidebarLinks';
+import { voteProject } from '@/lib/api/project';
+import { getProjectStatus } from './utils';
+import { ProjectSidebarProps } from './types';
+
+export function ProjectSidebar({
+ project,
+ crowdfund,
+ isMobile = false,
+}: ProjectSidebarProps) {
+ const [isVoting, setIsVoting] = useState(false);
+ const [userVote, setUserVote] = useState<1 | -1 | null>(null);
+
+ const projectStatus = getProjectStatus(project, crowdfund);
+
+ const handleVote = async (value: 1 | -1) => {
+ if (isVoting) return;
+
+ setIsVoting(true);
+ try {
+ await voteProject(project._id, value);
+ setUserVote(value);
+ } catch {
+ // Handle error silently or show user-friendly message
+ // You can add toast notifications here if needed
+ } finally {
+ setIsVoting(false);
+ }
+ };
+
+ return (
+
+
+
+
{project.vision}
+
+
+
+
+
+ {!isMobile && (
+ <>
+
+
+ >
+ )}
+
+ );
+}
diff --git a/components/project-details/project-sidebar/types.ts b/components/project-details/project-sidebar/types.ts
new file mode 100644
index 000000000..ffc2c3ba8
--- /dev/null
+++ b/components/project-details/project-sidebar/types.ts
@@ -0,0 +1,76 @@
+import type { CrowdfundingProject } from '@/lib/api/types';
+
+export interface ExtendedProject extends CrowdfundingProject {
+ // Additional fields that might be added during transformation
+ daysToDeadline?: number;
+ additionalCreator?: {
+ name: string;
+ role: string;
+ avatar: string;
+ };
+ links?: Array<{
+ type: string;
+ url: string;
+ icon: string;
+ }>;
+ // Legacy fields for backward compatibility
+ name?: string;
+ logo?: string;
+ validation?: string;
+ date?: string;
+ totalVotes?: number;
+}
+
+export interface CrowdfundData {
+ _id: string;
+ projectId: string;
+ thresholdVotes: number;
+ voteDeadline: string;
+ totalVotes: number;
+ status: string;
+ isVotingActive: boolean;
+ voteProgress: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface ProjectSidebarProps {
+ project: ExtendedProject;
+ crowdfund?: CrowdfundData;
+ isMobile?: boolean;
+}
+
+export interface ProjectSidebarHeaderProps {
+ project: ExtendedProject;
+ projectStatus: ProjectStatus;
+}
+
+export interface ProjectSidebarProgressProps {
+ project: ExtendedProject;
+ crowdfund?: CrowdfundData;
+ projectStatus: string;
+}
+
+export interface ProjectSidebarActionsProps {
+ project: ExtendedProject;
+ crowdfund?: CrowdfundData;
+ projectStatus: string;
+ isVoting: boolean;
+ userVote: 1 | -1 | null;
+ onVote: (value: 1 | -1) => void;
+}
+
+export interface ProjectSidebarCreatorProps {
+ project: ExtendedProject;
+}
+
+export interface ProjectSidebarLinksProps {
+ project: ExtendedProject;
+}
+
+export type ProjectStatus =
+ | 'Funding'
+ | 'Funded'
+ | 'Completed'
+ | 'Validation'
+ | 'idea';
diff --git a/components/project-details/project-sidebar/utils.ts b/components/project-details/project-sidebar/utils.ts
new file mode 100644
index 000000000..6029222b7
--- /dev/null
+++ b/components/project-details/project-sidebar/utils.ts
@@ -0,0 +1,20 @@
+import { ExtendedProject, CrowdfundData, ProjectStatus } from './types';
+
+/**
+ * Determines the project status based on project data and crowdfund information
+ */
+export function getProjectStatus(
+ project: ExtendedProject,
+ crowdfund?: CrowdfundData
+): ProjectStatus {
+ if (project.status === 'idea' && crowdfund?.isVotingActive) {
+ return 'Validation';
+ }
+ if (project.funding?.raised >= project.funding?.goal) {
+ return 'Funded';
+ }
+ if (project.status === 'idea' && !crowdfund?.isVotingActive) {
+ return 'Funding';
+ }
+ return project.status as ProjectStatus;
+}
diff --git a/components/project-details/project-team.tsx b/components/project-details/project-team.tsx
index fbfb39f65..d4009c610 100644
--- a/components/project-details/project-team.tsx
+++ b/components/project-details/project-team.tsx
@@ -1,5 +1,6 @@
'use client';
+import React from 'react';
import { CrowdfundingProject } from '@/lib/api/types';
import Image from 'next/image';
@@ -12,55 +13,82 @@ interface TeamMember {
name: string;
role: 'OWNER' | 'MEMBER';
avatar?: string;
+ joinedAt?: string;
+ username?: string;
}
-// Mock data
-const mockTeamMembers: TeamMember[] = [
- {
- id: '1',
- name: 'Member Name',
- role: 'OWNER',
- avatar: '/avatar.png',
- },
- {
- id: '2',
- name: 'Member Name',
- role: 'MEMBER',
- avatar: '/avatar.png',
- },
- {
- id: '3',
- name: 'Member Name',
- role: 'MEMBER',
- avatar: '/avatar.png',
- },
- {
- id: '4',
- name: 'Member Name',
- role: 'MEMBER',
- avatar: '/avatar.png',
- },
-];
-
export function ProjectTeam({ project }: ProjectTeamProps) {
- const teamMembers =
- project.team && project.team.length > 0
- ? project.team.map(member => ({
- id: member?._id || '',
- name: member?.profile
- ? `${member.profile.firstName} ${member.profile.lastName}`
- : 'Unknown Member',
- role:
- member.role === 'OWNER' ? ('OWNER' as const) : ('MEMBER' as const),
- avatar: (member?.profile as { avatar?: string })?.avatar,
- }))
- : mockTeamMembers;
+ // Create team members from real project data
+ const teamMembers: TeamMember[] = React.useMemo(() => {
+ const members: TeamMember[] = [];
+
+ // Add project creator as owner
+ if (project.creator) {
+ members.push({
+ id: project.creator._id,
+ name: `${project.creator.profile?.firstName} ${project.creator.profile?.lastName}`,
+ role: 'OWNER',
+ avatar: (project.creator as { profile?: { avatar?: string } })?.profile
+ ?.avatar,
+ username: project.creator.profile?.username,
+ });
+ }
+
+ // Add team members
+ if (project.team && project.team.length > 0) {
+ project.team.forEach(member => {
+ // Skip if this member is already added as creator
+ if (member._id !== project.creator._id) {
+ members.push({
+ id: member._id,
+ name: `${member.profile?.firstName} ${member.profile?.lastName}`,
+ role: member.role === 'OWNER' ? 'OWNER' : 'MEMBER',
+ avatar: (member.profile as { avatar?: string })?.avatar,
+ joinedAt: member.joinedAt,
+ username: member.profile?.username,
+ });
+ }
+ });
+ }
+
+ return members;
+ }, [project.creator, project.team]);
const getRoleColor = (role: 'OWNER' | 'MEMBER') => {
return role === 'OWNER' ? 'text-[#DBF936]' : 'text-[#B5B5B5]';
};
- const handleMemberClick = () => {};
+ const handleMemberClick = () => {
+ // TODO: Navigate to member profile or show member details
+ // Handle member click action
+ };
+
+ // Show empty state if no team members
+ if (teamMembers.length === 0) {
+ return (
+
+
+
No Team Members
+
+ This project doesn't have any team members yet.
+
+
+ );
+ }
return (
@@ -69,7 +97,7 @@ export function ProjectTeam({ project }: ProjectTeamProps) {
handleMemberClick()}
+ onClick={handleMemberClick}
>
{/* Avatar */}
@@ -93,6 +121,10 @@ export function ProjectTeam({ project }: ProjectTeamProps) {
/>
)}
+ {/* Role indicator */}
+ {member.role === 'OWNER' && (
+
+ )}
{/* Member Info */}
@@ -100,9 +132,21 @@ export function ProjectTeam({ project }: ProjectTeamProps) {
{member.name}
-
- {member.role}
-
+
+
+ {member.role}
+
+ {member.username && (
+
+ @{member.username}
+
+ )}
+
+ {member.joinedAt && (
+
+ Joined {new Date(member.joinedAt).toLocaleDateString()}
+
+ )}
diff --git a/components/project-details/project-voters/Empty.tsx b/components/project-details/project-voters/Empty.tsx
index ae84e1f57..483297f3f 100644
--- a/components/project-details/project-voters/Empty.tsx
+++ b/components/project-details/project-voters/Empty.tsx
@@ -3,7 +3,11 @@ import { BoundlessButton } from '@/components/buttons/BoundlessButton';
import { ArrowUpIcon } from 'lucide-react';
import LottieAnimation from '@/components/LottieAnimation';
-const Empty = () => {
+interface EmptyProps {
+ onVote?: () => void;
+}
+
+const Empty = ({ onVote }: EmptyProps) => {
return (