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
12 changes: 12 additions & 0 deletions app/(landing)/projects/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Metadata } from 'next';
import { generatePageMetadata } from '@/lib/metadata';

export const metadata: Metadata = generatePageMetadata('projects');

export default function ProjectsLayout({
children,
}: {
children: React.ReactNode;
}) {
return <>{children}</>;
}
36 changes: 28 additions & 8 deletions app/(landing)/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
'use client';

import React from 'react';
import { Metadata } from 'next';
import { generatePageMetadata } from '@/lib/metadata';
import ProjectPageHero from '@/components/Project-Page-Hero';
import ExploreHeader from '@/components/projects/ExploreHeader';

function ProjectsPage() {
const handleSearch = (searchTerm: string) => {
console.log('Search:', searchTerm);

Check warning on line 9 in app/(landing)/projects/page.tsx

View workflow job for this annotation

GitHub Actions / Code Quality & Linting

Unexpected console statement
};

const handleSort = (sortType: string) => {
console.log('Sort:', sortType);

Check warning on line 13 in app/(landing)/projects/page.tsx

View workflow job for this annotation

GitHub Actions / Code Quality & Linting

Unexpected console statement
};

const handleStatus = (status: string) => {
console.log('Status:', status);

Check warning on line 17 in app/(landing)/projects/page.tsx

View workflow job for this annotation

GitHub Actions / Code Quality & Linting

Unexpected console statement
};

export const metadata: Metadata = generatePageMetadata('projects');
const handleCategory = (category: string) => {
console.log('Category:', category);

Check warning on line 21 in app/(landing)/projects/page.tsx

View workflow job for this annotation

GitHub Actions / Code Quality & Linting

Unexpected console statement
};

const ProjectsPage = () => {
return (
<div className=''>
<div>
<ProjectPageHero />
<h1 className='mb-10'>Projects</h1>
<div className='flex flex-wrap justify-center gap-8'></div>
<ExploreHeader
onSearch={handleSearch}
onSortChange={handleSort}
onStatusChange={handleStatus}
onCategoryChange={handleCategory}
/>
<div className='flex flex-wrap justify-center gap-8 p-8'></div>
</div>
);
};
}

export default ProjectsPage;
194 changes: 194 additions & 0 deletions components/projects/ExploreHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
'use client';

import React, { useState } from 'react';
import { Search, ChevronDown } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils';
import Image from 'next/image';

interface ExploreHeaderProps {
onSearch?: (searchTerm: string) => void;
onSortChange?: (sortType: string) => void;
onStatusChange?: (status: string) => void;
onCategoryChange?: (category: string) => void;
className?: string;
}

const ExploreHeader = ({
onSearch,
onSortChange,
onStatusChange,
onCategoryChange,
className,
}: ExploreHeaderProps) => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedSort, setSelectedSort] = useState('Sort');
const [selectedStatus, setSelectedStatus] = useState('Status');
const [selectedCategory, setSelectedCategory] = useState('Category');

const handleSearch = (value: string) => {
setSearchTerm(value);
if (onSearch) onSearch(value);
};

const handleSort = (sortType: string) => {
setSelectedSort(sortType);
if (onSortChange) onSortChange(sortType);
};

const handleStatus = (status: string) => {
setSelectedStatus(status);
if (onStatusChange) onStatusChange(status);
};

const handleCategory = (category: string) => {
setSelectedCategory(category);
if (onCategoryChange) onCategoryChange(category);
};

const sortOptions = [
'Newest First',
'Oldest First',
'Most Funded',
'Least Funded',
'Ending Soon',
'Recently Started',
];
const statusOptions = [
'All Status',
'Active',
'Completed',
'Draft',
'Under Review',
'Validated',
'Approved',
];
const categoryOptions = [
'All Categories',
'Technology',
'Art & Creative',
'Environment',
'Education',
'Healthcare',
'Community',
'DeFi',
'NFT',
'Web3',
];

return (
<div className={cn('w-full bg-black px-4 py-12 text-white', className)}>
<div className='mx-auto max-w-7xl'>
<div className='mb-12'>
<h1 className='font-inter text-center text-4xl font-bold text-white md:text-left md:text-5xl lg:text-6xl'>
Explore Boundless Projects
</h1>
</div>

<div className='flex flex-col items-start gap-6 lg:flex-row lg:items-center lg:gap-8'>
<div className='flex flex-wrap items-center gap-3 lg:gap-4'>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant='outline'
className='min-w-[100px] justify-between rounded-lg border-gray-600 bg-transparent px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 hover:text-white'
>
<div className='flex items-center gap-2'>
<Image
src='/sort.svg'
alt='Sort'
width={16}
height={16}
className='h-4 w-4'
/>
{selectedSort}
</div>
<ChevronDown className='ml-2 h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='border-gray-700 bg-gray-900 text-white'>
{sortOptions.map(option => (
<DropdownMenuItem
key={option}
onClick={() => handleSort(option)}
className='cursor-pointer hover:bg-gray-800'
>
{option}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant='outline'
className='min-w-[100px] justify-between rounded-lg border-gray-600 bg-transparent px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 hover:text-white'
>
{selectedStatus}
<ChevronDown className='ml-2 h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='border-gray-700 bg-gray-900 text-white'>
{statusOptions.map(option => (
<DropdownMenuItem
key={option}
onClick={() => handleStatus(option)}
className='cursor-pointer hover:bg-gray-800'
>
{option}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant='outline'
className='min-w-[100px] justify-between rounded-lg border-gray-600 bg-transparent px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 hover:text-white'
>
{selectedCategory}
<ChevronDown className='ml-2 h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='border-gray-700 bg-gray-900 text-white'>
{categoryOptions.map(option => (
<DropdownMenuItem
key={option}
onClick={() => handleCategory(option)}
className='cursor-pointer hover:bg-gray-800'
>
{option}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>

<div className='relative ml-auto w-full lg:max-w-md'>
<div className='relative'>
<Search className='absolute top-1/2 left-3 h-5 w-5 -translate-y-1/2 transform text-gray-400' />
<Input
type='text'
placeholder='Search project or creator...'
value={searchTerm}
onChange={e => handleSearch(e.target.value)}
className='w-full rounded-lg border-gray-600 bg-transparent py-3 pr-4 pl-10 text-base text-white placeholder-gray-400 focus:border-gray-400 focus:ring-1 focus:ring-gray-400'
/>
</div>
</div>
</div>
</div>
</div>
);
};

export default ExploreHeader;
3 changes: 3 additions & 0 deletions public/sort.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading