From 28c3fd5041478f72917d60894d7c358e0ec97b53 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Sat, 27 Sep 2025 21:26:14 +0100 Subject: [PATCH 1/5] fix: fix hero svg --- components/landing-page/BackedBy.tsx | 126 +++++++++--------- components/landing-page/Banner.tsx | 2 +- components/landing-page/Hero.tsx | 64 +++++++-- .../landing-page/about/AboutUsDifferent.tsx | 28 +++- public/elipse1.svg | 18 +-- public/elipse2.svg | 18 +-- public/elipse3.svg | 18 +-- 7 files changed, 163 insertions(+), 111 deletions(-) diff --git a/components/landing-page/BackedBy.tsx b/components/landing-page/BackedBy.tsx index daef4dfc3..c47fd9304 100644 --- a/components/landing-page/BackedBy.tsx +++ b/components/landing-page/BackedBy.tsx @@ -101,7 +101,7 @@ const BackedBy = () => { }; useGSAP( - (context, contextSafe) => { + () => { if ( !containerRef.current || !column1Ref.current || @@ -110,82 +110,82 @@ const BackedBy = () => { ) return; - const container = containerRef.current; + // const container = containerRef.current; - const createSeamlessScrollAnimation = ( - element: HTMLElement, - direction: 'up' | 'down' = 'up' - ) => { - const children = Array.from(element.children); - const totalHeight = children.reduce((acc, child) => { - const rect = child.getBoundingClientRect(); - return acc + rect.height + 24; - }, 0); + // const createSeamlessScrollAnimation = ( + // element: HTMLElement, + // direction: 'up' | 'down' = 'up' + // ) => { + // const children = Array.from(element.children); + // const totalHeight = children.reduce((acc, child) => { + // const rect = child.getBoundingClientRect(); + // return acc + rect.height + 24; + // }, 0); - const wrapper = document.createElement('div'); - wrapper.style.position = 'relative'; + // const wrapper = document.createElement('div'); + // wrapper.style.position = 'relative'; - children.forEach(child => { - wrapper.appendChild(child); - }); + // children.forEach(child => { + // wrapper.appendChild(child); + // }); - const clone = wrapper.cloneNode(true) as HTMLElement; - wrapper.appendChild(clone); + // const clone = wrapper.cloneNode(true) as HTMLElement; + // wrapper.appendChild(clone); - element.innerHTML = ''; - element.appendChild(wrapper); + // element.innerHTML = ''; + // element.appendChild(wrapper); - const scrollDistance = direction === 'up' ? -totalHeight : totalHeight; + // const scrollDistance = direction === 'up' ? -totalHeight : totalHeight; - gsap.set(wrapper, { - y: 0, - force3D: true, - willChange: 'transform', - }); + // gsap.set(wrapper, { + // y: 0, + // force3D: true, + // willChange: 'transform', + // }); - const animation = gsap.to(wrapper, { - y: scrollDistance, - duration: 30, - ease: 'none', - repeat: -1, - force3D: true, - transformOrigin: 'center center', - immediateRender: false, - lazy: false, - }); + // const animation = gsap.to(wrapper, { + // y: scrollDistance, + // duration: 30, + // ease: 'none', + // repeat: -1, + // force3D: true, + // transformOrigin: 'center center', + // immediateRender: false, + // lazy: false, + // }); - return animation; - }; + // return animation; + // }; - const animations = [ - createSeamlessScrollAnimation(column1Ref.current, 'up'), - createSeamlessScrollAnimation(column2Ref.current, 'down'), - createSeamlessScrollAnimation(column3Ref.current, 'up'), - ]; + // const animations = [ + // createSeamlessScrollAnimation(column1Ref.current, 'up'), + // createSeamlessScrollAnimation(column2Ref.current, 'down'), + // createSeamlessScrollAnimation(column3Ref.current, 'up'), + // ]; - const handleMouseEnter = - contextSafe?.(() => { - animations.forEach(anim => anim.pause()); - }) || - (() => { - animations.forEach(anim => anim.pause()); - }); + // const handleMouseEnter = + // contextSafe?.(() => { + // animations.forEach(anim => anim.pause()); + // }) || + // (() => { + // animations.forEach(anim => anim.pause()); + // }); - const handleMouseLeave = - contextSafe?.(() => { - animations.forEach(anim => anim.resume()); - }) || - (() => { - animations.forEach(anim => anim.resume()); - }); + // const handleMouseLeave = + // contextSafe?.(() => { + // animations.forEach(anim => anim.resume()); + // }) || + // (() => { + // animations.forEach(anim => anim.resume()); + // }); - container.addEventListener('mouseenter', handleMouseEnter); - container.addEventListener('mouseleave', handleMouseLeave); + // container.addEventListener('mouseenter', handleMouseEnter); + // container.addEventListener('mouseleave', handleMouseLeave); - return () => { - container.removeEventListener('mouseenter', handleMouseEnter); - container.removeEventListener('mouseleave', handleMouseLeave); - }; + // return () => { + // container.removeEventListener('mouseenter', handleMouseEnter); + // container.removeEventListener('mouseleave', handleMouseLeave); + // }; }, { scope: containerRef, diff --git a/components/landing-page/Banner.tsx b/components/landing-page/Banner.tsx index 2e757b517..61506a13a 100644 --- a/components/landing-page/Banner.tsx +++ b/components/landing-page/Banner.tsx @@ -4,7 +4,7 @@ import Link from 'next/link'; const CleanBanner = () => { return ( -
+
diff --git a/components/landing-page/Hero.tsx b/components/landing-page/Hero.tsx index ee58554ff..0d6aefcc4 100644 --- a/components/landing-page/Hero.tsx +++ b/components/landing-page/Hero.tsx @@ -41,16 +41,31 @@ export default function Hero({ className = '' }: HeroProps) { const setupAnimations = useCallback(() => { if (!heroRef.current) return; - const ellipseImg = heroRef.current.querySelector('.ellipse-image'); + const ellipseImg = heroRef.current.querySelectorAll('.ellipse-image'); const sphereImg = heroRef.current.querySelector('.sphere-image'); if (ellipseImg) { - gsap.to(ellipseImg, { - ...animationConfig.ellipse, - willChange: 'transform', - force3D: true, - immediateRender: false, - lazy: true, + ellipseImg.forEach((img, i) => { + // Common scale animation + gsap.to(img, { + ...animationConfig.ellipse, + willChange: 'transform', + force3D: true, + immediateRender: false, + lazy: true, + }); + + // Rotation animation per ellipse + const direction = i === 0 || i === 1 ? 2 : -1; + gsap.to(img, { + rotation: direction * 360, + ease: 'none', + duration: 60, + repeat: -1, + transformOrigin: '50% 50%', + force3D: true, + willChange: 'transform', + }); }); } @@ -75,7 +90,29 @@ export default function Hero({ className = '' }: HeroProps) { }, { scope: heroRef, dependencies: [setupAnimations] } ); - + const ellipses = [ + { + src: '/elipse1.svg', + width: 'w-[50%]', + z: 'z-10', + opacity: '', + sizes: '50vw', + }, + { + src: '/elipse2.svg', + width: 'w-[65%]', + z: 'z-10', + opacity: '', + sizes: '90vw', + }, + { + src: '/elipse3.svg', + width: 'w-[80%]', + z: 'z-10', + opacity: '', + sizes: '100vw', + }, + ]; return (
*/} - {[1, 2, 3].map(elipse => ( + {ellipses.map((ellipse, i) => ( ))} -
(
+ +
-
+ +

- + + - - - + + + - - - - + + + + diff --git a/public/elipse2.svg b/public/elipse2.svg index 7f0326214..f737d99af 100644 --- a/public/elipse2.svg +++ b/public/elipse2.svg @@ -1,14 +1,14 @@ - - + + - - - + + + - - - - + + + + diff --git a/public/elipse3.svg b/public/elipse3.svg index c7939e4a2..2ddc42f5c 100644 --- a/public/elipse3.svg +++ b/public/elipse3.svg @@ -1,14 +1,14 @@ - - + + - - - + + + - - - - + + + + From a1521a5136e3380d977bb1ebc9447cc5f3641966 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Tue, 30 Sep 2025 16:29:48 +0100 Subject: [PATCH 2/5] fix: revert backed by component --- components/landing-page/BackedBy.tsx | 126 +++++++++++++-------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/components/landing-page/BackedBy.tsx b/components/landing-page/BackedBy.tsx index c47fd9304..daef4dfc3 100644 --- a/components/landing-page/BackedBy.tsx +++ b/components/landing-page/BackedBy.tsx @@ -101,7 +101,7 @@ const BackedBy = () => { }; useGSAP( - () => { + (context, contextSafe) => { if ( !containerRef.current || !column1Ref.current || @@ -110,82 +110,82 @@ const BackedBy = () => { ) return; - // const container = containerRef.current; + const container = containerRef.current; - // const createSeamlessScrollAnimation = ( - // element: HTMLElement, - // direction: 'up' | 'down' = 'up' - // ) => { - // const children = Array.from(element.children); - // const totalHeight = children.reduce((acc, child) => { - // const rect = child.getBoundingClientRect(); - // return acc + rect.height + 24; - // }, 0); + const createSeamlessScrollAnimation = ( + element: HTMLElement, + direction: 'up' | 'down' = 'up' + ) => { + const children = Array.from(element.children); + const totalHeight = children.reduce((acc, child) => { + const rect = child.getBoundingClientRect(); + return acc + rect.height + 24; + }, 0); - // const wrapper = document.createElement('div'); - // wrapper.style.position = 'relative'; + const wrapper = document.createElement('div'); + wrapper.style.position = 'relative'; - // children.forEach(child => { - // wrapper.appendChild(child); - // }); + children.forEach(child => { + wrapper.appendChild(child); + }); - // const clone = wrapper.cloneNode(true) as HTMLElement; - // wrapper.appendChild(clone); + const clone = wrapper.cloneNode(true) as HTMLElement; + wrapper.appendChild(clone); - // element.innerHTML = ''; - // element.appendChild(wrapper); + element.innerHTML = ''; + element.appendChild(wrapper); - // const scrollDistance = direction === 'up' ? -totalHeight : totalHeight; + const scrollDistance = direction === 'up' ? -totalHeight : totalHeight; - // gsap.set(wrapper, { - // y: 0, - // force3D: true, - // willChange: 'transform', - // }); + gsap.set(wrapper, { + y: 0, + force3D: true, + willChange: 'transform', + }); - // const animation = gsap.to(wrapper, { - // y: scrollDistance, - // duration: 30, - // ease: 'none', - // repeat: -1, - // force3D: true, - // transformOrigin: 'center center', - // immediateRender: false, - // lazy: false, - // }); + const animation = gsap.to(wrapper, { + y: scrollDistance, + duration: 30, + ease: 'none', + repeat: -1, + force3D: true, + transformOrigin: 'center center', + immediateRender: false, + lazy: false, + }); - // return animation; - // }; + return animation; + }; - // const animations = [ - // createSeamlessScrollAnimation(column1Ref.current, 'up'), - // createSeamlessScrollAnimation(column2Ref.current, 'down'), - // createSeamlessScrollAnimation(column3Ref.current, 'up'), - // ]; + const animations = [ + createSeamlessScrollAnimation(column1Ref.current, 'up'), + createSeamlessScrollAnimation(column2Ref.current, 'down'), + createSeamlessScrollAnimation(column3Ref.current, 'up'), + ]; - // const handleMouseEnter = - // contextSafe?.(() => { - // animations.forEach(anim => anim.pause()); - // }) || - // (() => { - // animations.forEach(anim => anim.pause()); - // }); + const handleMouseEnter = + contextSafe?.(() => { + animations.forEach(anim => anim.pause()); + }) || + (() => { + animations.forEach(anim => anim.pause()); + }); - // const handleMouseLeave = - // contextSafe?.(() => { - // animations.forEach(anim => anim.resume()); - // }) || - // (() => { - // animations.forEach(anim => anim.resume()); - // }); + const handleMouseLeave = + contextSafe?.(() => { + animations.forEach(anim => anim.resume()); + }) || + (() => { + animations.forEach(anim => anim.resume()); + }); - // container.addEventListener('mouseenter', handleMouseEnter); - // container.addEventListener('mouseleave', handleMouseLeave); + container.addEventListener('mouseenter', handleMouseEnter); + container.addEventListener('mouseleave', handleMouseLeave); - // return () => { - // container.removeEventListener('mouseenter', handleMouseEnter); - // container.removeEventListener('mouseleave', handleMouseLeave); - // }; + return () => { + container.removeEventListener('mouseenter', handleMouseEnter); + container.removeEventListener('mouseleave', handleMouseLeave); + }; }, { scope: containerRef, From b862d17ad7d174b4e2b8288e6e7dda73ac85a1a7 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Tue, 7 Oct 2025 01:05:40 +0100 Subject: [PATCH 3/5] fix: modify md editor --- components/auth/SignupForm.tsx | 4 +- .../project/CreateProjectModal/Details.tsx | 2 +- .../CreateProjectModal/md-editor-custom.css | 99 ++++++++++++------- 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/components/auth/SignupForm.tsx b/components/auth/SignupForm.tsx index c6a845856..5bcc088b8 100644 --- a/components/auth/SignupForm.tsx +++ b/components/auth/SignupForm.tsx @@ -106,10 +106,10 @@ const SignupForm = ({ onLoadingChange }: SignupFormProps) => { // }); // } } - } catch { + } catch (error) { form.setError('root', { type: 'manual', - message: 'Failed to create account. Please try again.', + message: `Failed to create account. Please try again. ${error instanceof Error ? error.message : String(error)}`, }); } }; diff --git a/components/landing-page/project/CreateProjectModal/Details.tsx b/components/landing-page/project/CreateProjectModal/Details.tsx index 21792cedb..f09efa829 100644 --- a/components/landing-page/project/CreateProjectModal/Details.tsx +++ b/components/landing-page/project/CreateProjectModal/Details.tsx @@ -5,7 +5,7 @@ import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; import { z } from 'zod'; import MDEditor from '@uiw/react-md-editor'; -// import './md-editor-custom.css'; +import './md-editor-custom.css'; interface DetailsProps { onDataChange?: (data: DetailsFormData) => void; diff --git a/components/landing-page/project/CreateProjectModal/md-editor-custom.css b/components/landing-page/project/CreateProjectModal/md-editor-custom.css index 54acbd0eb..cb0aef649 100644 --- a/components/landing-page/project/CreateProjectModal/md-editor-custom.css +++ b/components/landing-page/project/CreateProjectModal/md-editor-custom.css @@ -1,4 +1,3 @@ -/* Custom styles for MDEditor to match the current UI theme */ .w-md-editor { background-color: #1a1a1a !important; border: none !important; @@ -7,17 +6,25 @@ .w-md-editor-toolbar { background-color: #1a1a1a !important; border-bottom: 1px solid #484848 !important; - padding: 8px 12px !important; + padding: 15px 12px !important; } .w-md-editor-toolbar button { color: #b5b5b5 !important; background-color: transparent !important; border: none !important; - padding: 8px !important; + padding: 10px !important; margin: 2px !important; border-radius: 4px !important; transition: all 0.2s ease !important; + font-size: 15px !important; + min-width: 26px !important; + min-height: 26px !important; +} + +.w-md-editor-toolbar button svg { + width: 10px !important; + height: 10px !important; } .w-md-editor-toolbar button:hover { @@ -30,21 +37,22 @@ color: #000000 !important; } +/* Removed vertical demarcators/dividers */ .w-md-editor-toolbar .w-md-editor-divider { - background-color: #484848 !important; - margin: 4px 8px !important; + display: none !important; } .w-md-editor-text-pre, .w-md-editor-text-input { - background-color: #101010 !important; color: #ffffff !important; + background: #101010 !important; font-size: 14px !important; line-height: 1.5 !important; } -.w-md-editor-text-pre { - color: #b5b5b5 !important; +/* Removed conflicting color override for text-pre */ +.w-md-editor-text-pre > * { + color: #ffffff !important; } .w-md-editor-text-input::placeholder { @@ -123,32 +131,6 @@ gap: 4px; } -.w-md-editor-toolbar .w-md-editor-toolbar-group:not(:last-child)::after { - content: ''; - width: 1px; - height: 20px; - background-color: #484848; - margin: 0 8px; -} - -/* Hide specific toolbar items if needed */ -/* Uncomment the lines below to hide specific toolbar buttons */ - -/* Hide fullscreen button */ -/* .w-md-editor-toolbar button[data-name="fullscreen"] { - display: none !important; -} */ - -/* Hide help button */ -/* .w-md-editor-toolbar button[data-name="help"] { - display: none !important; -} */ - -/* Hide preview toggle */ -/* .w-md-editor-toolbar button[data-name="preview"] { - display: none !important; -} */ - /* Custom button styling for specific commands */ .w-md-editor-toolbar button[data-name='bold'] { font-weight: bold !important; @@ -161,3 +143,52 @@ .w-md-editor-toolbar button[data-name='strikethrough'] { text-decoration: line-through !important; } + +.w-md-editor-text { + height: 100vh !important; + background-color: #101010 !important; +} + +.wmde-markdown-color { + color: #ffffff !important; +} + +.w-md-editor-text-input, +.w-md-editor-text-input textarea { + color: #ffffff !important; + caret-color: #ffffff !important; + background-color: transparent !important; +} + +.w-md-editor-area textarea, +.w-md-editor-input textarea, +textarea.w-md-editor-text-input { + color: #ffffff !important; + background-color: transparent !important; +} + +/* Override any inherited black text color */ +.w-md-editor-text * { + color: inherit !important; +} + +.w-md-editor-text, +.w-md-editor-area, +.w-md-editor-input { + color: #ffffff !important; +} + +.w-md-editor-content { + border: 1px solid transparent !important; + transition: border-color 0.2s ease !important; +} + +.w-md-editor-content:has(.w-md-editor-text:focus-within) { + border-color: #a7f950 !important; +} + +.w-md-editor-toolbar button[data-name='help'], +.w-md-editor-toolbar button[data-name='quote'], +.w-md-editor-toolbar button[data-name='hr'] { + display: none !important; +} From f594e5d32d101784d1bd9b9bab0c8feaa55b6df4 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Fri, 10 Oct 2025 01:05:07 +0100 Subject: [PATCH 4/5] fix: make projects page server components --- app/(landing)/projects/page.tsx | 412 +----------------- app/globals.css | 4 + components/Project-Page-Hero.tsx | 22 +- .../landing-page/project/ProjectCard.tsx | 1 + components/project/ProjectsPage.tsx | 156 +++++++ components/projects/ExploreHeader.tsx | 5 +- hooks/project/use-project-filters.ts | 87 ++++ hooks/project/use-project-transform.ts | 98 +++++ hooks/project/use-project.ts | 196 +++++++++ lib/mock.ts | 2 +- 10 files changed, 563 insertions(+), 420 deletions(-) create mode 100644 components/project/ProjectsPage.tsx create mode 100644 hooks/project/use-project-filters.ts create mode 100644 hooks/project/use-project-transform.ts create mode 100644 hooks/project/use-project.ts diff --git a/app/(landing)/projects/page.tsx b/app/(landing)/projects/page.tsx index ad7ab3568..e83a75679 100644 --- a/app/(landing)/projects/page.tsx +++ b/app/(landing)/projects/page.tsx @@ -1,417 +1,11 @@ -'use client'; - -import React, { useState, useEffect, useCallback, useMemo } from 'react'; import ProjectPageHero from '@/components/Project-Page-Hero'; -import ExploreHeader from '@/components/projects/ExploreHeader'; -import ProjectCard from '@/components/landing-page/project/ProjectCard'; -import { getCrowdfundingProjects } from '@/lib/api/project'; -import { CrowdfundingProject } from '@/lib/api/types'; -import LoadingSpinner from '@/components/LoadingSpinner'; -// import { useDebounce } from '@/hooks/use-debounce'; - -// Types for sorting and filtering -type SortOption = - | 'newest' - | 'oldest' - | 'funding_goal_high' - | 'funding_goal_low' - | 'deadline_soon' - | 'deadline_far'; - -interface ProjectFilters { - category?: string; - status?: string; - search?: string; - sort?: SortOption; -} - -function ProjectsPage() { - const [projects, setProjects] = useState([]); - const [loading, setLoading] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [error, setError] = useState(null); - const [currentPage, setCurrentPage] = useState(1); - const [hasMore, setHasMore] = useState(true); - const [filters, setFilters] = useState({}); - const [searchTerm, setSearchTerm] = useState(''); - - // Debounce search term to avoid excessive API calls - const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedSearchTerm(searchTerm); - }, 500); - - return () => { - clearTimeout(handler); - }; - }, [searchTerm]); - - // Get project deadline in days - const getProjectDeadline = useCallback( - (project: CrowdfundingProject): number => { - if (project.status === 'idea') { - return new Date(project.voting.endDate).getTime(); - } else if ( - project.status === 'active' || - project.status === 'completed' - ) { - return new Date(project.funding.endDate).getTime(); - } - return 0; - }, - [] - ); - - // Sort projects based on sort option - const sortProjects = useCallback( - ( - projects: CrowdfundingProject[], - sortOption: SortOption - ): CrowdfundingProject[] => { - const sorted = [...projects]; - - switch (sortOption) { - case 'newest': - return sorted.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - case 'oldest': - return sorted.sort( - (a, b) => - new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() - ); - case 'funding_goal_high': - return sorted.sort((a, b) => b.funding.goal - a.funding.goal); - case 'funding_goal_low': - return sorted.sort((a, b) => a.funding.goal - b.funding.goal); - case 'deadline_soon': - return sorted.sort((a, b) => { - const aDeadline = getProjectDeadline(a); - const bDeadline = getProjectDeadline(b); - return aDeadline - bDeadline; - }); - case 'deadline_far': - return sorted.sort((a, b) => { - const aDeadline = getProjectDeadline(a); - const bDeadline = getProjectDeadline(b); - return bDeadline - aDeadline; - }); - default: - return sorted; - } - }, - [getProjectDeadline] - ); - - const fetchProjects = useCallback( - async (page = 1, newFilters: ProjectFilters = {}, append = false) => { - try { - if (append) { - setLoadingMore(true); - } else { - setLoading(true); - } - setError(null); - - const response = await getCrowdfundingProjects(page, 9, { - category: newFilters.category, - status: newFilters.status, - }); - - let fetchedProjects = response.data.projects; - - // Apply client-side search filtering - if (newFilters.search) { - const searchLower = newFilters.search.toLowerCase(); - fetchedProjects = fetchedProjects.filter( - project => - project.title.toLowerCase().includes(searchLower) || - project.vision.toLowerCase().includes(searchLower) || - project.description.toLowerCase().includes(searchLower) || - project.category.toLowerCase().includes(searchLower) || - `${project.creator.profile.firstName} ${project.creator.profile.lastName}` - .toLowerCase() - .includes(searchLower) - ); - } - - // Apply client-side sorting - if (newFilters.sort) { - fetchedProjects = sortProjects(fetchedProjects, newFilters.sort); - } - - if (append) { - setProjects(prev => [...prev, ...fetchedProjects]); - } else { - setProjects(fetchedProjects); - } - - setHasMore(response.data.pagination.pages > page); - } catch (err) { - // eslint-disable-next-line no-console - console.error('Error fetching projects:', err); - setError('Failed to fetch projects. Please try again.'); - } finally { - setLoading(false); - setLoadingMore(false); - } - }, - [sortProjects] - ); - - // Update filters when debounced search term changes - useEffect(() => { - setFilters(prev => ({ ...prev, search: debouncedSearchTerm })); - }, [debouncedSearchTerm]); - - // Fetch projects when filters change - useEffect(() => { - setCurrentPage(1); - setHasMore(true); - fetchProjects(1, filters); - }, [filters, fetchProjects]); - - const handleSearch = useCallback((searchTerm: string) => { - setSearchTerm(searchTerm); - }, []); - - const handleSort = useCallback((sortType: string) => { - setFilters(prev => ({ ...prev, sort: sortType as SortOption })); - }, []); - - const handleStatus = useCallback((status: string) => { - setFilters(prev => ({ - ...prev, - status: status === 'all' ? undefined : status, - })); - }, []); - - const handleCategory = useCallback((category: string) => { - setFilters(prev => ({ - ...prev, - category: category === 'all' ? undefined : category, - })); - }, []); - - const handleLoadMore = useCallback(() => { - const nextPage = currentPage + 1; - setCurrentPage(nextPage); - fetchProjects(nextPage, filters, true); - }, [currentPage, filters, fetchProjects]); - - // Transform project data for ProjectCard component - const transformProjectForCard = useCallback( - (project: CrowdfundingProject) => { - let deadlineInDays: number | null = null; - - try { - if (project.status === 'idea') { - const now = new Date(); - const end = new Date(project.voting.endDate); - deadlineInDays = Math.ceil( - (end.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) - ); - } else if ( - project.status === 'active' || - project.status === 'completed' - ) { - const now = new Date(); - const end = new Date(project.funding.endDate); - deadlineInDays = Math.ceil( - (end.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) - ); - } - } catch (error) { - // eslint-disable-next-line no-console - console.warn( - 'Error calculating deadline for project:', - project._id, - error - ); - deadlineInDays = null; - } - - // Map project status to card status - let cardStatus: 'Validation' | 'Funding' | 'Funded' | 'Completed' = - 'Funding'; - if (project.status === 'draft' || project.status === 'idea') { - cardStatus = 'Validation'; - } else if (project.status === 'active') { - cardStatus = 'Funding'; - } else if (project.status === 'completed') { - cardStatus = 'Completed'; - } - - return { - projectId: project._id, - creatorName: `${project.creator.profile.firstName} ${project.creator.profile.lastName}`, - creatorLogo: '/avatar.png', // Default avatar, should be from project.creator.avatar - projectImage: - project.media?.logo || - project.media?.logo || - '/landing/explore/project-placeholder-1.png', - projectTitle: project.title, - projectDescription: project.vision || project.description, - status: cardStatus, - deadlineInDays: deadlineInDays || 0, - funding: { - current: project.funding?.raised || 0, - goal: project.funding?.goal || 0, - currency: project.funding?.currency || 'USDC', - }, - votes: - project.status === 'idea' - ? { - current: project.votes || 0, - goal: project.voting?.totalVotes || 100, - } - : undefined, - }; - }, - [] - ); - - // Memoized project cards to prevent unnecessary re-renders - const projectCards = useMemo(() => { - return projects.map(project => { - const cardData = transformProjectForCard(project); - return ; - }); - }, [projects, transformProjectForCard]); - - // Load More Button component - const LoadMoreButton = useMemo(() => { - if (!hasMore) return null; - - return ( -
- -
- ); - }, [hasMore, loadingMore, handleLoadMore]); +import ProjectsClient from '@/components/project/ProjectsPage'; +export default function ProjectsPage() { return (
- - -
- {/* Results Summary */} - {!loading && !error && ( -
-
- {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 - )} -
- {filters.search && ( - - )} -
- )} - - {/* Loading State */} - {loading && ( -
- -

Loading projects...

-
- )} - - {/* Error State */} - {error && ( -
-
-
⚠️
-

- Something went wrong -

-

{error}

- -
-
- )} - - {/* Empty State */} - {!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) && ( - - )} -
-
- )} - - {/* Projects Grid */} - {!loading && !error && projects.length > 0 && ( - <> -
- {projectCards} -
- {LoadMoreButton} - - )} -
+
); } - -export default ProjectsPage; diff --git a/app/globals.css b/app/globals.css index 2899e4edd..ce48e5a7b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -80,6 +80,10 @@ z-index: 2; } +body { + scroll-behavior: smooth; +} + @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); diff --git a/components/Project-Page-Hero.tsx b/components/Project-Page-Hero.tsx index f144e5134..80e9bdb18 100644 --- a/components/Project-Page-Hero.tsx +++ b/components/Project-Page-Hero.tsx @@ -1,7 +1,9 @@ +'use client'; 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() { return ( @@ -41,15 +43,17 @@ export default function ProjectPageHero() { Validated by the community. Backed milestone by milestone.

- - - Start Exploring Projects - - - + + + + Start Exploring Projects + + + +

diff --git a/components/landing-page/project/ProjectCard.tsx b/components/landing-page/project/ProjectCard.tsx index caa81b54d..fd64e1ed9 100644 --- a/components/landing-page/project/ProjectCard.tsx +++ b/components/landing-page/project/ProjectCard.tsx @@ -1,3 +1,4 @@ +'use client'; import { Progress } from '@/components/ui/progress'; import { formatNumber } from '@/lib/utils'; import { useRouter } from 'nextjs-toploader/app'; diff --git a/components/project/ProjectsPage.tsx b/components/project/ProjectsPage.tsx new file mode 100644 index 000000000..10fda18fb --- /dev/null +++ b/components/project/ProjectsPage.tsx @@ -0,0 +1,156 @@ +'use client'; + +import React from 'react'; +import ProjectCard from '@/components/landing-page/project/ProjectCard'; +import ExploreHeader from '@/components/projects/ExploreHeader'; +import LoadingSpinner from '@/components/LoadingSpinner'; +import { useProjects } from '@/hooks/project/use-project'; +import { useProjectFilters } from '@/hooks/project/use-project-filters'; +import { useProjectTransform } from '@/hooks/project/use-project-transform'; + +export default function ProjectsClient() { + const { + filters, + handleSearch, + handleSort, + handleStatus, + handleCategory, + clearSearch, + clearAllFilters, + } = useProjectFilters(); + + const { projects, loading, loadingMore, error, hasMore, loadMore, refetch } = + useProjects({ initialFilters: filters }); + + const { transformProjectForCard } = useProjectTransform(); + + // Memoized project cards to prevent unnecessary re-renders + const projectCards = React.useMemo(() => { + return projects.map(project => { + const cardData = transformProjectForCard(project); + return ; + }); + }, [projects, transformProjectForCard]); + + return ( + <> + + +
+ {/* Results Summary */} + {!loading && !error && ( +
+
+ {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 + )} +
+ {filters.search && ( + + )} +
+ )} + + {/* Loading State */} + {loading && ( +
+ +

Loading projects...

+
+ )} + + {/* Error State */} + {error && ( +
+
+
⚠️
+

+ Something went wrong +

+

{error}

+ +
+
+ )} + + {/* Empty State */} + {!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) && ( + + )} +
+
+ )} + + {/* Projects Grid */} + {!loading && !error && projects.length > 0 && ( + <> +
+ {projectCards} +
+ + {/* Load More Button */} + {hasMore && ( +
+ +
+ )} + + )} +
+ + ); +} diff --git a/components/projects/ExploreHeader.tsx b/components/projects/ExploreHeader.tsx index 091472943..94fe2e510 100644 --- a/components/projects/ExploreHeader.tsx +++ b/components/projects/ExploreHeader.tsx @@ -87,7 +87,10 @@ const ExploreHeader = ({
-

+

Explore Boundless Projects

diff --git a/hooks/project/use-project-filters.ts b/hooks/project/use-project-filters.ts new file mode 100644 index 000000000..69349d6fe --- /dev/null +++ b/hooks/project/use-project-filters.ts @@ -0,0 +1,87 @@ +'use client'; + +import * as React from 'react'; +import { useDebounce } from '../use-debounce'; + +type SortOption = + | 'newest' + | 'oldest' + | 'funding_goal_high' + | 'funding_goal_low' + | 'deadline_soon' + | 'deadline_far'; + +interface ProjectFilters { + category?: string; + status?: string; + search?: string; + sort?: SortOption; +} + +interface UseProjectFiltersReturn { + filters: ProjectFilters; + searchTerm: string; + debouncedSearchTerm: string; + handleSearch: (searchTerm: string) => void; + handleSort: (sortType: string) => void; + handleStatus: (status: string) => void; + handleCategory: (category: string) => void; + clearSearch: () => void; + clearAllFilters: () => void; +} + +export function useProjectFilters( + initialFilters: ProjectFilters = {} +): UseProjectFiltersReturn { + const [filters, setFilters] = React.useState(initialFilters); + const [searchTerm, setSearchTerm] = React.useState(''); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + + // Update filters when debounced search term changes + React.useEffect(() => { + setFilters(prev => ({ ...prev, search: debouncedSearchTerm })); + }, [debouncedSearchTerm]); + + const handleSearch = React.useCallback((searchTerm: string) => { + setSearchTerm(searchTerm); + }, []); + + const handleSort = React.useCallback((sortType: string) => { + setFilters(prev => ({ ...prev, sort: sortType as SortOption })); + }, []); + + const handleStatus = React.useCallback((status: string) => { + setFilters(prev => ({ + ...prev, + status: status === 'all' ? undefined : status, + })); + }, []); + + const handleCategory = React.useCallback((category: string) => { + setFilters(prev => ({ + ...prev, + category: category === 'all' ? undefined : category, + })); + }, []); + + const clearSearch = React.useCallback(() => { + setSearchTerm(''); + }, []); + + const clearAllFilters = React.useCallback(() => { + setSearchTerm(''); + setFilters({}); + }, []); + + return { + filters, + searchTerm, + debouncedSearchTerm, + handleSearch, + handleSort, + handleStatus, + handleCategory, + clearSearch, + clearAllFilters, + }; +} diff --git a/hooks/project/use-project-transform.ts b/hooks/project/use-project-transform.ts new file mode 100644 index 000000000..4b5d18902 --- /dev/null +++ b/hooks/project/use-project-transform.ts @@ -0,0 +1,98 @@ +'use client'; + +import * as React from 'react'; +import type { CrowdfundingProject } from '@/lib/api/types'; + +interface TransformedProject { + projectId: string; + creatorName: string; + creatorLogo: string; + projectImage: string; + projectTitle: string; + projectDescription: string; + status: 'Validation' | 'Funding' | 'Funded' | 'Completed'; + deadlineInDays: number; + funding: { + current: number; + goal: number; + currency: string; + }; + votes?: { + current: number; + goal: number; + }; +} + +export function useProjectTransform() { + const transformProjectForCard = React.useCallback( + (project: CrowdfundingProject): TransformedProject => { + let deadlineInDays: number | null = null; + + try { + if (project.status === 'idea') { + const now = new Date(); + const end = new Date(project.voting.endDate); + deadlineInDays = Math.ceil( + (end.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) + ); + } else if ( + project.status === 'active' || + project.status === 'completed' + ) { + const now = new Date(); + const end = new Date(project.funding.endDate); + deadlineInDays = Math.ceil( + (end.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) + ); + } + } catch (error) { + console.warn( + 'Error calculating deadline for project:', + project._id, + error + ); + deadlineInDays = null; + } + + // Map project status to card status + let cardStatus: 'Validation' | 'Funding' | 'Funded' | 'Completed' = + 'Funding'; + if (project.status === 'draft' || project.status === 'idea') { + cardStatus = 'Validation'; + } else if (project.status === 'active') { + cardStatus = 'Funding'; + } else if (project.status === 'completed') { + cardStatus = 'Completed'; + } + + return { + projectId: project._id, + creatorName: `${project.creator.profile.firstName} ${project.creator.profile.lastName}`, + creatorLogo: '/avatar.png', + projectImage: + project.media?.logo || + project.media?.logo || + '/landing/explore/project-placeholder-1.png', + projectTitle: project.title, + projectDescription: project.vision || project.description, + status: cardStatus, + deadlineInDays: deadlineInDays || 0, + funding: { + current: project.funding?.raised || 0, + goal: project.funding?.goal || 0, + currency: project.funding?.currency || 'USDC', + }, + votes: + project.status === 'idea' + ? { + current: project.votes || 0, + goal: project.voting?.totalVotes || 100, + } + : undefined, + }; + }, + [] + ); + + return { transformProjectForCard }; +} diff --git a/hooks/project/use-project.ts b/hooks/project/use-project.ts new file mode 100644 index 000000000..53544f63c --- /dev/null +++ b/hooks/project/use-project.ts @@ -0,0 +1,196 @@ +'use client'; + +import * as React from 'react'; +import { getCrowdfundingProjects } from '@/lib/api/project'; +import type { CrowdfundingProject } from '@/lib/api/types'; + +type SortOption = + | 'newest' + | 'oldest' + | 'funding_goal_high' + | 'funding_goal_low' + | 'deadline_soon' + | 'deadline_far'; + +interface ProjectFilters { + category?: string; + status?: string; + search?: string; + sort?: SortOption; +} + +interface UseProjectsOptions { + initialPage?: number; + pageSize?: number; + initialFilters?: ProjectFilters; +} + +interface UseProjectsReturn { + projects: CrowdfundingProject[]; + loading: boolean; + loadingMore: boolean; + error: string | null; + hasMore: boolean; + currentPage: number; + filters: ProjectFilters; + setFilters: React.Dispatch>; + loadMore: () => void; + refetch: () => void; +} + +export function useProjects( + options: UseProjectsOptions = {} +): UseProjectsReturn { + const { initialPage = 1, pageSize = 9, initialFilters = {} } = options; + + const [projects, setProjects] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const [loadingMore, setLoadingMore] = React.useState(false); + const [error, setError] = React.useState(null); + const [currentPage, setCurrentPage] = React.useState(initialPage); + const [hasMore, setHasMore] = React.useState(true); + const [filters, setFilters] = React.useState(initialFilters); + + // Get project deadline in milliseconds + const getProjectDeadline = React.useCallback( + (project: CrowdfundingProject): number => { + if (project.status === 'idea') { + return new Date(project.voting.endDate).getTime(); + } else if ( + project.status === 'active' || + project.status === 'completed' + ) { + return new Date(project.funding.endDate).getTime(); + } + return 0; + }, + [] + ); + + // Sort projects based on sort option + const sortProjects = React.useCallback( + ( + projects: CrowdfundingProject[], + sortOption: SortOption + ): CrowdfundingProject[] => { + const sorted = [...projects]; + + switch (sortOption) { + case 'newest': + return sorted.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); + case 'oldest': + return sorted.sort( + (a, b) => + new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + ); + case 'funding_goal_high': + return sorted.sort((a, b) => b.funding.goal - a.funding.goal); + case 'funding_goal_low': + return sorted.sort((a, b) => a.funding.goal - b.funding.goal); + case 'deadline_soon': + return sorted.sort((a, b) => { + const aDeadline = getProjectDeadline(a); + const bDeadline = getProjectDeadline(b); + return aDeadline - bDeadline; + }); + case 'deadline_far': + return sorted.sort((a, b) => { + const aDeadline = getProjectDeadline(a); + const bDeadline = getProjectDeadline(b); + return bDeadline - aDeadline; + }); + default: + return sorted; + } + }, + [getProjectDeadline] + ); + + const fetchProjects = React.useCallback( + async (page: number, currentFilters: ProjectFilters, append = false) => { + try { + if (append) { + setLoadingMore(true); + } else { + setLoading(true); + } + setError(null); + + const response = await getCrowdfundingProjects(page, pageSize, { + category: currentFilters.category, + status: currentFilters.status, + }); + + let fetchedProjects = response.data.projects; + + // Apply client-side search filtering + if (currentFilters.search) { + const searchLower = currentFilters.search.toLowerCase(); + fetchedProjects = fetchedProjects.filter( + project => + project.title.toLowerCase().includes(searchLower) || + project.vision.toLowerCase().includes(searchLower) || + project.description.toLowerCase().includes(searchLower) || + project.category.toLowerCase().includes(searchLower) || + `${project.creator.profile.firstName} ${project.creator.profile.lastName}` + .toLowerCase() + .includes(searchLower) + ); + } + + // Apply client-side sorting + if (currentFilters.sort) { + fetchedProjects = sortProjects(fetchedProjects, currentFilters.sort); + } + + if (append) { + setProjects(prev => [...prev, ...fetchedProjects]); + } else { + setProjects(fetchedProjects); + } + + setHasMore(response.data.pagination.pages > page); + } catch (err) { + console.error('Error fetching projects:', err); + setError('Failed to fetch projects. Please try again.'); + } finally { + setLoading(false); + setLoadingMore(false); + } + }, + [pageSize, sortProjects] + ); + + // Fetch projects when filters change + React.useEffect(() => { + setCurrentPage(1); + setHasMore(true); + fetchProjects(1, filters); + }, [filters, fetchProjects]); + + const loadMore = React.useCallback(() => { + const nextPage = currentPage + 1; + setCurrentPage(nextPage); + fetchProjects(nextPage, filters, true); + }, [currentPage, filters, fetchProjects]); + + const refetch = React.useCallback(() => { + fetchProjects(currentPage, filters); + }, [currentPage, filters, fetchProjects]); + + return { + projects, + loading, + loadingMore, + error, + hasMore, + currentPage, + filters, + setFilters, + loadMore, + refetch, + }; +} diff --git a/lib/mock.ts b/lib/mock.ts index 227822c39..d546a209d 100644 --- a/lib/mock.ts +++ b/lib/mock.ts @@ -28,7 +28,7 @@ export const mockProjects: Project[] = [ category: 'defi', type: 'crowdfunding', amount: 85000, - status: 'approved', + status: 'funding', createdAt: '2024-01-10T09:15:00Z', updatedAt: '2024-01-18T16:45:00Z', }, From f12775b68d45ed62ca6686cfd9f2058072833999 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Fri, 10 Oct 2025 11:54:11 +0100 Subject: [PATCH 5/5] fix: refactor project details page --- app/(landing)/projects/[id]/page.tsx | 189 ++---------------- .../comment-section/comments-empty-state.tsx | 4 +- components/project-details/project-layout.tsx | 4 +- .../project-details/project-sidebar.tsx | 18 +- lib/api/project.ts | 7 + 5 files changed, 45 insertions(+), 177 deletions(-) diff --git a/app/(landing)/projects/[id]/page.tsx b/app/(landing)/projects/[id]/page.tsx index 10e29c235..e1aee744d 100644 --- a/app/(landing)/projects/[id]/page.tsx +++ b/app/(landing)/projects/[id]/page.tsx @@ -1,16 +1,16 @@ import { ProjectLayout } from '@/components/project-details/project-layout'; import { ProjectLoading } from '@/components/project-details/project-loading'; import { notFound } from 'next/navigation'; -import { getCrowdfundingProject, getProjectDetails } from '@/lib/api/project'; -import { CrowdfundingProject, CrowdfundData } from '@/lib/api/types'; +import { getCrowdfundingProject } from '@/lib/api/project'; +import type { CrowdfundingProject, CrowdfundData } from '@/lib/api/types'; import { Suspense } from 'react'; + interface ProjectPageProps { params: Promise<{ id: string; }>; } -// Transform crowdfunding project data to match ProjectLayout interface function transformCrowdfundingProject( project: CrowdfundingProject, crowdfund: CrowdfundData @@ -19,11 +19,12 @@ function transformCrowdfundingProject( ? `${project.creator.profile.firstName} ${project.creator.profile.lastName}` : 'Unknown Creator'; - const daysToDeadline = project.funding?.endDate + const deadlineDate = crowdfund.voteDeadline || project.funding?.endDate; + const daysToDeadline = deadlineDate ? Math.max( 0, Math.ceil( - (new Date(project.funding.endDate).getTime() - new Date().getTime()) / + (new Date(deadlineDate).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24) ) ) @@ -32,12 +33,11 @@ function transformCrowdfundingProject( return { project: { ...project, - daysToDeadline: Math.max(0, daysToDeadline), - // Add additional fields without overriding the original creator + daysToDeadline, additionalCreator: { name: creatorName, role: 'OWNER', - avatar: '/user.png', // Default avatar, could be enhanced with actual user avatar + avatar: '/user.png', }, links: [ ...(project.githubUrl @@ -55,6 +55,7 @@ function transformCrowdfundingProject( icon: link.platform.toLowerCase(), })) || []), ], + votes: crowdfund.totalVotes || 0, }, crowdfund, }; @@ -65,144 +66,18 @@ async function ProjectContent({ id }: { id: string }) { let error: string | null = null; try { - // Try to fetch as crowdfunding project first - try { - const response = await getCrowdfundingProject(id); - - if (response.success && response.data) { - projectData = transformCrowdfundingProject( - response.data.project, - response.data.crowdfund - ); - } else { - throw new Error('Failed to fetch crowdfunding project'); - } - } catch { - // If crowdfunding project fails, try regular project - try { - const regularProject = await getProjectDetails(id); - - // Transform regular project data if needed - // For regular projects, we need to create a mock CrowdfundingProject structure - const mockProject: CrowdfundingProject = { - _id: regularProject.id || regularProject._id, - title: regularProject.title, - description: regularProject.description, - category: regularProject.category, - owner: { - type: 'individual', - }, - vision: regularProject.description, - githubUrl: regularProject.githubUrl, - projectWebsite: regularProject.projectWebsite, - demoVideo: regularProject.demoVideo, - socialLinks: regularProject.socialLinks, - status: regularProject.status, - creator: { - _id: 'unknown', - profile: { - firstName: regularProject.ownerName?.split(' ')[0] || 'Unknown', - lastName: regularProject.ownerName?.split(' ')[1] || 'Unknown', - username: regularProject.ownerUsername || 'unknown', - }, - }, - contact: { - primary: '', - backup: '', - }, - createdAt: regularProject.createdAt, - updatedAt: regularProject.updatedAt || regularProject.createdAt, - funding: { - goal: 0, - raised: 0, - currency: 'USD', - endDate: new Date( - Date.now() + 30 * 24 * 60 * 60 * 1000 - ).toISOString(), - contributors: [], - }, - voting: { - startDate: new Date().toISOString(), - endDate: new Date( - Date.now() + 30 * 24 * 60 * 60 * 1000 - ).toISOString(), - totalVotes: 0, - positiveVotes: 0, - negativeVotes: 0, - voters: [], - }, - milestones: [], - team: [], - media: { - banner: regularProject.image, - logo: regularProject.image, - thumbnail: regularProject.image, - }, - documents: { - whitepaper: '', - pitchDeck: '', - }, - tags: [], - grant: { - isGrant: false, - totalBudget: 0, - totalDisbursed: 0, - proposalsReceived: 0, - proposalsApproved: 0, - status: 'pending', - applications: [], - }, - summary: regularProject.description, - type: 'crowdfund', - votes: 0, - stakeholders: { - serviceProvider: '', - approver: '', - releaseSigner: '', - disputeResolver: '', - receiver: '', - platformAddress: '', - }, - trustlessWorkStatus: 'pending', - escrowType: 'pending', - __v: 0, - }; + const response = await getCrowdfundingProject(id); - const mockCrowdfund: CrowdfundData = { - _id: 'mock-crowdfund', - projectId: regularProject.id || regularProject._id, - thresholdVotes: 0, - voteDeadline: new Date( - Date.now() + 30 * 24 * 60 * 60 * 1000 - ).toISOString(), - totalVotes: 0, - voteProgress: 0, - status: regularProject.status, - createdAt: regularProject.createdAt, - updatedAt: regularProject.updatedAt || regularProject.createdAt, - __v: 0, - isVotingActive: false, - id: 'mock-crowdfund', - }; - - projectData = { - project: { - ...mockProject, - daysToDeadline: 30, - additionalCreator: { - name: regularProject.ownerName || 'Unknown Creator', - role: 'OWNER', - avatar: regularProject.ownerAvatar || '/user.png', - }, - links: [], - }, - crowdfund: mockCrowdfund, - }; - } catch { - error = 'Project not found'; - } + if (response.success && response.data) { + projectData = transformCrowdfundingProject( + response.data.project, + response.data.crowdfund + ); + } else { + throw new Error(response.message || 'Failed to fetch project'); } - } catch { + } catch (err) { + console.error('Error fetching project:', err); error = 'Failed to fetch project data'; } @@ -212,32 +87,6 @@ async function ProjectContent({ id }: { id: string }) { return (
- {/*
-
-
- - - - - - - -
-
-
*/} -
-
- +
+

Be the first to Leave a Comment diff --git a/components/project-details/project-layout.tsx b/components/project-details/project-layout.tsx index f4ecf507e..c24747968 100644 --- a/components/project-details/project-layout.tsx +++ b/components/project-details/project-layout.tsx @@ -13,7 +13,7 @@ import { ProjectComments } from './comment-section/project-comments'; import ProjectMilestone from './project-milestone'; import ProjectVoters from './project-voters'; import ProjectBackers from './project-backers'; -import FundProject from '@/components/modals/fund-project'; +// import FundProject from '@/components/modals/fund-project'; interface ProjectLayoutProps { project: CrowdfundingProject & { @@ -344,7 +344,7 @@ export function ProjectLayout({ project }: ProjectLayoutProps) {

- {}} /> + {/* {}} /> */}
); } diff --git a/components/project-details/project-sidebar.tsx b/components/project-details/project-sidebar.tsx index d4d72bf51..51cc8de7f 100644 --- a/components/project-details/project-sidebar.tsx +++ b/components/project-details/project-sidebar.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Calendar, Github, @@ -7,12 +9,14 @@ import { 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 { CrowdfundingProject } from '@/lib/api/types'; +import type { CrowdfundingProject } from '@/lib/api/types'; +import { voteProject } from '@/lib/api/project'; interface ProjectSidebarProps { project: CrowdfundingProject & { @@ -142,7 +146,10 @@ export function ProjectSidebar({ {/* Action Buttons - Responsive widths */}
- @@ -159,7 +166,12 @@ export function ProjectSidebar({ Fund */} - + {project.status === 'Funding' && ( + + )}