diff --git a/app/(landing)/projects/[id]/page.tsx b/app/(landing)/projects/[id]/page.tsx index 1a9d5d734..03ec67037 100644 --- a/app/(landing)/projects/[id]/page.tsx +++ b/app/(landing)/projects/[id]/page.tsx @@ -67,7 +67,6 @@ async function ProjectContent({ id }: { id: string }) { try { const response = await getCrowdfundingProject(id); - console.log(response); if (response.success && response.data) { projectData = transformCrowdfundingProject( response.data.project, diff --git a/components/EmptyState.tsx b/components/EmptyState.tsx index 3a5350416..c222845b5 100644 --- a/components/EmptyState.tsx +++ b/components/EmptyState.tsx @@ -11,7 +11,7 @@ export interface EmptyStateProps { onAddClick?: () => void; className?: string; type?: 'default' | 'compact' | 'custom'; - action?: React.ReactNode; + action?: React.ReactNode | boolean; } const EmptyState: React.FC = ({ @@ -21,7 +21,7 @@ const EmptyState: React.FC = ({ onAddClick = () => {}, className = '', type = 'default', - action, + action = false, }) => { const containerRef = useRef(null); const titleRef = useRef(null); diff --git a/components/Project-Page-Hero.tsx b/components/Project-Page-Hero.tsx index 4db41aac6..320dca43c 100644 --- a/components/Project-Page-Hero.tsx +++ b/components/Project-Page-Hero.tsx @@ -3,9 +3,41 @@ import React from 'react'; import Image from 'next/image'; import { ArrowDown } from 'lucide-react'; import { BoundlessButton } from './buttons/BoundlessButton'; -import Link from 'next/link'; export default function ProjectPageHero() { + const scrollToProjects = () => { + const projectsSection = document.getElementById('explore-project'); + if (projectsSection) { + const targetPosition = projectsSection.offsetTop - 100; + const startPosition = window.pageYOffset; + const distance = targetPosition - startPosition; + const duration = 1000; + let start: number | null = null; + + const animation = (currentTime: number) => { + if (start === null) start = currentTime; + const timeElapsed = currentTime - start; + const run = easeInOutQuad( + timeElapsed, + startPosition, + distance, + duration + ); + window.scrollTo(0, run); + if (timeElapsed < duration) requestAnimationFrame(animation); + }; + + const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2; + if (t < 1) return (c / 2) * t * t + b; + t--; + return (-c / 2) * (t * (t - 2) - 1) + b; + }; + + requestAnimationFrame(animation); + } + }; + return (
@@ -43,17 +75,16 @@ export default function ProjectPageHero() { Validated by the community. Backed milestone by milestone.

- - - - Start Exploring Projects - - - - + + + Start Exploring Projects + + +
diff --git a/components/landing-page/BackedBy.tsx b/components/landing-page/BackedBy.tsx index 7511c8ba6..ed12578a9 100644 --- a/components/landing-page/BackedBy.tsx +++ b/components/landing-page/BackedBy.tsx @@ -229,12 +229,12 @@ const BackedBy = () => {

-
+
{backedBy.map(item => ( { - const [backers] = useState([]); - const handleBackerClick = () => {}; +const ProjectBackers = ({ project }: { project: CrowdfundingProject }) => { + const [backers] = useState( + project.funding.contributors as Backer[] + ); + + const handleBackerClick = (backer: Backer) => { + window.open(`/profile/${backer.user.profile.username}`, '_blank'); + }; + if (backers.length === 0) { return ; } @@ -27,17 +38,18 @@ const ProjectBackers = () => {
handleBackerClick()} + onClick={() => handleBackerClick(backer)} >
+ {/* Avatar */}
- {backer.avatar ? ( + {backer.user.profile.avatar ? ( {backer.profile.firstName} ) : ( @@ -52,16 +64,10 @@ const ProjectBackers = () => {
+ {/* Backer Info */}
- {backer.isAnonymous - ? 'Anonymous' - : `${backer.profile.firstName} ${backer.profile.lastName}`}{' '} - {!backer.isAnonymous && ( - - @{backer.profile.username} - - )} + {backer.user.profile.firstName} {backer.user.profile.lastName} ${backer.amount} diff --git a/components/project-details/project-layout.tsx b/components/project-details/project-layout.tsx index 4bca6ceb2..3d9ae1a98 100644 --- a/components/project-details/project-layout.tsx +++ b/components/project-details/project-layout.tsx @@ -340,10 +340,10 @@ export function ProjectLayout({ project, crowdfund }: ProjectLayoutProps) { - + - + diff --git a/components/project-details/project-sidebar/ProjectSidebarHeader.tsx b/components/project-details/project-sidebar/ProjectSidebarHeader.tsx index 4ae58cebc..cebdfe020 100644 --- a/components/project-details/project-sidebar/ProjectSidebarHeader.tsx +++ b/components/project-details/project-sidebar/ProjectSidebarHeader.tsx @@ -10,7 +10,7 @@ export function ProjectSidebarHeader({ }: ProjectSidebarHeaderProps) { const getStatusStyles = () => { switch (projectStatus) { - case 'Funding': + case 'campaigning': return 'bg-secondary-75 border-secondary-600 text-secondary-600'; case 'Funded': return 'bg-[rgba(167,249,80,0.08)] border-primary text-primary'; diff --git a/components/project-details/project-sidebar/types.ts b/components/project-details/project-sidebar/types.ts index ffc2c3ba8..a6117f33b 100644 --- a/components/project-details/project-sidebar/types.ts +++ b/components/project-details/project-sidebar/types.ts @@ -69,7 +69,7 @@ export interface ProjectSidebarLinksProps { } export type ProjectStatus = - | 'Funding' + | 'campaigning' | 'Funded' | 'Completed' | 'Validation' diff --git a/components/project-details/project-sidebar/utils.ts b/components/project-details/project-sidebar/utils.ts index 6029222b7..1d19ead7c 100644 --- a/components/project-details/project-sidebar/utils.ts +++ b/components/project-details/project-sidebar/utils.ts @@ -13,8 +13,8 @@ export function getProjectStatus( if (project.funding?.raised >= project.funding?.goal) { return 'Funded'; } - if (project.status === 'idea' && !crowdfund?.isVotingActive) { - return 'Funding'; + if (project.status === 'campaigning' && !crowdfund?.isVotingActive) { + return 'campaigning'; } return project.status as ProjectStatus; } diff --git a/components/project-details/project-voters/index.tsx b/components/project-details/project-voters/index.tsx index e945beba5..eb239db62 100644 --- a/components/project-details/project-voters/index.tsx +++ b/components/project-details/project-voters/index.tsx @@ -26,46 +26,65 @@ const ProjectVoters = ({ project }: ProjectVotersProps) => { return []; } + // Debug: Log the raw voter data to understand the structure + return project.voting.voters.map( (voter: { _id?: string; - userId?: string; - profile?: { - firstName?: string; - lastName?: string; - username?: string; - bio?: string; - avatar?: string; - }; - firstName?: string; - lastName?: string; - username?: string; - bio?: string; - avatar?: string; - voteType?: string; - vote?: number; + userId?: + | { + _id?: string; + profile?: { + firstName?: string; + lastName?: string; + username?: string; + bio?: string; + avatar?: string; + }; + firstName?: string; + lastName?: string; + username?: string; + bio?: string; + avatar?: string; + } + | string; + vote?: string | number; votedAt?: string; createdAt?: string; - }) => ({ - _id: voter._id || voter.userId || '', - profile: { - firstName: voter.profile?.firstName || voter.firstName || 'Unknown', - lastName: voter.profile?.lastName || voter.lastName || 'User', - username: voter.profile?.username || voter.username || 'unknown_user', - bio: voter.profile?.bio || voter.bio || 'Project supporter', - avatar: voter.profile?.avatar || voter.avatar, - }, - voteType: - (voter.voteType as 'positive' | 'negative') || - (voter.vote && voter.vote > 0 ? 'positive' : 'negative'), - votedAt: voter.votedAt || voter.createdAt, - }) + }) => { + // Handle both userId as object and string cases + const userData = typeof voter.userId === 'object' ? voter.userId : null; + const userIdString = + typeof voter.userId === 'string' ? voter.userId : userData?._id || ''; + + return { + _id: voter._id || userIdString, + profile: { + firstName: + userData?.profile?.firstName || userData?.firstName || 'Unknown', + lastName: + userData?.profile?.lastName || userData?.lastName || 'User', + username: + userData?.profile?.username || + userData?.username || + 'unknown_user', + bio: userData?.profile?.bio || userData?.bio || 'Project supporter', + avatar: userData?.profile?.avatar || userData?.avatar, + }, + voteType: + voter.vote === 'positive' || voter.vote === 1 + ? 'positive' + : 'negative', + votedAt: voter.votedAt || voter.createdAt, + }; + } ); }, [project?.voting?.voters]); - const handleVoterClick = () => { + const handleVoterClick = (voter: Voter) => { // TODO: Navigate to voter profile or show voter details - // Handle voter click action + // Could implement navigation to user profile page + window.open(`/profile/${voter.profile.username}`, '_blank'); }; if (voters.length === 0) { @@ -77,9 +96,10 @@ const ProjectVoters = ({ project }: ProjectVotersProps) => {
handleVoterClick(voter)} >
+ {/* Avatar */}
{voter.profile.avatar ? ( @@ -100,52 +120,20 @@ const ProjectVoters = ({ project }: ProjectVotersProps) => { /> )}
- {voter.voteType && ( -
- )}
+ {/* Member Info */}
-
- - {voter.profile.firstName} {voter.profile.lastName} - - {voter.voteType && ( - - {voter.voteType === 'positive' ? '↑' : '↓'} - - )} -
-
- - @{voter.profile.username} - - {voter.votedAt && ( - - • {new Date(voter.votedAt).toLocaleDateString()} - - )} -
- {voter.profile.bio && ( - - {voter.profile.bio} - - )} + + {voter.profile.firstName} {voter.profile.lastName} + + + {voter.profile.bio} +
+ {/* Chevron */} +
{/* Results Summary */} - {!loading && !error && ( + {!loading && !error && projects.length > 0 && (
- {projects.length > 0 ? ( - - Showing {projects.length} project - {projects.length !== 1 ? 's' : ''} - {filters.search && ` for "${filters.search}"`} - {filters.category && ` in ${filters.category}`} - {filters.status && ` with status ${filters.status}`} - - ) : ( - No projects found - )} + + Showing {projects.length} project + {projects.length !== 1 ? 's' : ''} + {filters.search && ` for "${filters.search}"`} + {filters.category && ` in ${filters.category}`} + {filters.status && ` with status ${filters.status}`} +
{filters.search && ( - + )}
)} {/* Loading State */} {loading && ( -
- -

Loading projects...

+
+
)} @@ -86,17 +90,22 @@ export default function ProjectsClient() { {error && (
-
⚠️
-

- Something went wrong -

-

{error}

- + } + iconPosition='right' + > + Try Again + + } + />
)} @@ -105,22 +114,26 @@ export default function ProjectsClient() { {!loading && !error && projects.length === 0 && (
-
🔍
-

- No projects found -

-

- {filters.search || filters.category || filters.status - ? 'Try adjusting your filters or search terms' - : 'No projects are available at the moment'} -

+ + {(filters.search || filters.category || filters.status) && ( - +
+ + Clear all filters + +
)}
@@ -129,28 +142,37 @@ export default function ProjectsClient() { {/* Projects Grid */} {!loading && !error && projects.length > 0 && ( <> -
+
{projectCards}
{/* Load More Button */} {hasMore && (
- + {loadingMore + ? 'Loading more projects...' + : 'Load More Projects'} +
)} )} - +
); } diff --git a/components/projects/ExploreHeader.tsx b/components/projects/ExploreHeader.tsx index 9b44c5c29..e0a6424bf 100644 --- a/components/projects/ExploreHeader.tsx +++ b/components/projects/ExploreHeader.tsx @@ -19,6 +19,7 @@ interface ExploreHeaderProps { onStatusChange?: (status: string) => void; onCategoryChange?: (category: string) => void; className?: string; + searchPlaceholder?: string; } const ExploreHeader = ({ @@ -27,60 +28,67 @@ const ExploreHeader = ({ onStatusChange, onCategoryChange, className, + searchPlaceholder = 'Search project or creator...', }: ExploreHeaderProps) => { const [searchTerm, setSearchTerm] = useState(''); - const [selectedSort, setSelectedSort] = useState('Sort'); - const [selectedStatus, setSelectedStatus] = useState('Status'); - const [selectedCategory, setSelectedCategory] = useState('Category'); + const [selectedSort, setSelectedSort] = useState('Newest First'); + const [selectedStatus, setSelectedStatus] = useState('All Status'); + const [selectedCategory, setSelectedCategory] = useState('All Categories'); const handleSearch = (value: string) => { setSearchTerm(value); if (onSearch) onSearch(value); }; - const handleSort = (sortType: string) => { - setSelectedSort(sortType); - if (onSortChange) onSortChange(sortType); + const handleSort = (sortValue: string) => { + const option = sortOptions.find(opt => opt.value === sortValue); + setSelectedSort(option?.label || 'Newest First'); + if (onSortChange) onSortChange(sortValue); }; - const handleStatus = (status: string) => { - setSelectedStatus(status); - if (onStatusChange) onStatusChange(status); + const handleStatus = (statusValue: string) => { + const option = statusOptions.find(opt => opt.value === statusValue); + setSelectedStatus(option?.label || 'All Status'); + if (onStatusChange) onStatusChange(statusValue); }; - const handleCategory = (category: string) => { - setSelectedCategory(category); - if (onCategoryChange) onCategoryChange(category); + const handleCategory = (categoryValue: string) => { + const option = categoryOptions.find(opt => opt.value === categoryValue); + setSelectedCategory(option?.label || 'All Categories'); + if (onCategoryChange) onCategoryChange(categoryValue); }; const sortOptions = [ - 'Newest First', - 'Oldest First', - 'Most Funded', - 'Least Funded', - 'Ending Soon', - 'Recently Started', + { label: 'Newest First', value: 'newest' }, + { label: 'Oldest First', value: 'oldest' }, + { label: 'Most Funded', value: 'funding_goal_high' }, + { label: 'Least Funded', value: 'funding_goal_low' }, + { label: 'Ending Soon', value: 'deadline_soon' }, + { label: 'Recently Started', value: 'deadline_far' }, ]; const statusOptions = [ - 'All Status', - 'Active', - 'Completed', - 'Draft', - 'Under Review', - 'Validated', - 'Approved', + { label: 'All Status', value: 'all' }, + { label: 'Active', value: 'active' }, + { label: 'Completed', value: 'completed' }, + { label: 'Draft', value: 'draft' }, + { label: 'Under Review', value: 'under_review' }, + { label: 'Validated', value: 'validated' }, + { label: 'Funding', value: 'campaigning' }, + { label: 'Funded', value: 'funded' }, + { label: 'Live', value: 'live' }, + { label: 'Idea', value: 'idea' }, ]; const categoryOptions = [ - 'All Categories', - 'Technology', - 'Art & Creative', - 'Environment', - 'Education', - 'Healthcare', - 'Community', - 'DeFi', - 'NFT', - 'Web3', + { label: 'All Categories', value: 'all' }, + { label: 'Technology', value: 'technology' }, + { label: 'Art & Creative', value: 'art_creative' }, + { label: 'Environment', value: 'environment' }, + { label: 'Education', value: 'education' }, + { label: 'Healthcare', value: 'healthcare' }, + { label: 'Community', value: 'community' }, + { label: 'DeFi', value: 'defi' }, + { label: 'NFT', value: 'nft' }, + { label: 'Web3', value: 'web3' }, ]; return ( @@ -122,11 +130,11 @@ const ExploreHeader = ({ > {sortOptions.map(option => ( handleSort(option)} + key={option.value} + onClick={() => handleSort(option.value)} className='cursor-pointer hover:bg-gray-800' > - {option} + {option.label} ))} @@ -148,11 +156,11 @@ const ExploreHeader = ({ > {statusOptions.map(option => ( handleStatus(option)} + key={option.value} + onClick={() => handleStatus(option.value)} className='cursor-pointer hover:bg-gray-800' > - {option} + {option.label} ))} @@ -174,11 +182,11 @@ const ExploreHeader = ({ > {categoryOptions.map(option => ( handleCategory(option)} + key={option.value} + onClick={() => handleCategory(option.value)} className='cursor-pointer hover:bg-gray-800' > - {option} + {option.label} ))} @@ -190,7 +198,7 @@ const ExploreHeader = ({ handleSearch(e.target.value)} className='bg-background w-full rounded-lg border-gray-900 py-3 pr-4 pl-10 text-base text-white placeholder-gray-400 focus:border-gray-400 focus:ring-1 focus:ring-gray-400' diff --git a/hooks/project/use-project.ts b/hooks/project/use-project.ts index c10918154..0e447a37c 100644 --- a/hooks/project/use-project.ts +++ b/hooks/project/use-project.ts @@ -51,6 +51,11 @@ export function useProjects( const [hasMore, setHasMore] = React.useState(true); const [filters, setFilters] = React.useState(initialFilters); + // Update filters when initialFilters change + React.useEffect(() => { + setFilters(initialFilters); + }, [initialFilters]); + // Get project deadline in milliseconds const getProjectDeadline = React.useCallback( (project: CrowdfundingProject): number => { diff --git a/lib/api/api.ts b/lib/api/api.ts index 1af831c4d..edeb5c309 100644 --- a/lib/api/api.ts +++ b/lib/api/api.ts @@ -2,8 +2,8 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import Cookies from 'js-cookie'; import { useAuthStore } from '@/lib/stores/auth-store'; -const API_BASE_URL = 'https://staging-api.boundlessfi.xyz/api'; -// const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL; +// const API_BASE_URL = 'https://staging-api.boundlessfi.xyz/api'; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL; if (!API_BASE_URL) { throw new Error('NEXT_PUBLIC_API_URL environment variable is not defined'); }