Skip to content

Commit

Permalink
feat(projects): Implement Projects page and separate data
Browse files Browse the repository at this point in the history
- Implemented Projects page with dynamic project listing.
- Separated project data into a dedicated file.
- Added automatic and manual scrolling for highlighted projects.
  • Loading branch information
TKanX committed Nov 29, 2024
1 parent e3f8575 commit 787e91c
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 2 deletions.
4 changes: 2 additions & 2 deletions data/events.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
* @property {string} link The link to the event page. Must be in the /events directory.
* @property {boolean} archived Whether the event is archived or not. Archived events are not displayed on the events page.
*/
const eventsData = [];
const events = [];

export default eventsData;
export default events;
17 changes: 17 additions & 0 deletions data/projects.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @fileoverview Projects Data
* @description Data for the projects page.
*/

/**
* @type {Project[]}
* @typedef {Object} Project
* @property {string} name The name of the project.
* @property {string} description The description of the project.
* @property {string} backgroundImage The path to the background image of the project. Must be in the public/images/projects directory.
* @property {string} link The link to the project page. Must be a valid URL. (a demo, a GitHub repo, YouTube video, etc.)
* @property {boolean} isHighlighted Whether the project is highlighted or not. Highlighted projects are displayed in the top scrolling section.
*/
const projects = [];

export default projects;
173 changes: 173 additions & 0 deletions pages/projects.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* @fileoverview Projects Page
* @description The projects page of the website. (NOTE: Data is imported from data/projects.mjs)
*/

import { useEffect, useState, useRef } from 'react';
import projectsData from '../data/projects.mjs';

export default function ProjectsPage() {
const [highlightedProjects, setHighlightedProjects] = useState([]);
const [otherProjects, setOtherProjects] = useState([]);
const sliderRef = useRef(null);
const slideIntervalRef = useRef(null);
const currentIndex = useRef(0);

// Separate projects into highlighted and others
useEffect(() => {
const highlighted = projectsData.filter((project) => project.isHighlighted);
const others = projectsData.filter((project) => !project.isHighlighted);
setHighlightedProjects(highlighted);
setOtherProjects(others);
}, []);

// Auto-scroll highlighted projects every 3 seconds
useEffect(() => {
if (highlightedProjects.length > 0) {
slideIntervalRef.current = setInterval(() => {
const nextIndex =
(currentIndex.current + 1) % highlightedProjects.length;
currentIndex.current = nextIndex;
sliderRef.current?.scrollTo({
left: nextIndex * sliderRef.current.clientWidth,
behavior: 'smooth',
});
}, 3000);
}

return () => clearInterval(slideIntervalRef.current);
}, [highlightedProjects]);

// Scroll to the previous highlighted project
const handlePrevClick = () => {
const prevIndex =
(currentIndex.current - 1 + highlightedProjects.length) %
highlightedProjects.length;
currentIndex.current = prevIndex;
sliderRef.current?.scrollTo({
left: prevIndex * sliderRef.current.clientWidth,
behavior: 'smooth',
});
};

// Scroll to the next highlighted project
const handleNextClick = () => {
const nextIndex = (currentIndex.current + 1) % highlightedProjects.length;
currentIndex.current = nextIndex;
sliderRef.current?.scrollTo({
left: nextIndex * sliderRef.current.clientWidth,
behavior: 'smooth',
});
};

return (
<div className='p-8'>
{/* Page Header */}
<h1 className='text-3xl font-bold mb-6 text-primary'>Projects</h1>

{/* Highlighted Projects Section */}
<div className='relative mb-12'>
{highlightedProjects.length > 0 ? (
<div>
<div
ref={sliderRef}
className='flex overflow-x-hidden snap-x snap-mandatory transition-all duration-500'
>
{highlightedProjects.map((project) => (
<div
key={project.name}
className='min-w-full h-96 bg-cover bg-center relative flex-shrink-0 rounded-lg'
style={{
backgroundImage: project.backgroundImage
? `url(${project.backgroundImage})`
: 'none',
}}
>
<div className='absolute inset-0 bg-dark opacity-50'></div>
<div className='absolute inset-0 flex flex-col justify-center items-center text-center text-white px-4 py-6'>
<h3 className='text-2xl font-semibold'>{project.name}</h3>
<p className='text-md mt-2'>{project.description}</p>
{project.link && (
<a
href={project.link}
className='inline-block mt-4 text-accent hover:text-accent-600'
aria-label={`Learn more about ${project.name}`}
>
Learn More
</a>
)}
</div>
</div>
))}
</div>
{/* Scroll Controls */}
<button
className='absolute left-4 top-1/2 transform -translate-y-1/2 bg-black text-white p-2 rounded-full opacity-50 hover:opacity-100'
onClick={handlePrevClick}
aria-label='Previous'
>
&#10094;
</button>
<button
className='absolute right-4 top-1/2 transform -translate-y-1/2 bg-black text-white p-2 rounded-full opacity-50 hover:opacity-100'
onClick={handleNextClick}
aria-label='Next'
>
&#10095;
</button>
</div>
) : (
<div className='text-center text-light-800 p-8'>
<h3 className='text-2xl font-semibold'>No Highlighted Projects</h3>
<p className='text-md mt-2'>
Explore all projects below or check back later!
</p>
</div>
)}
</div>

{/* All Projects Section */}
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8'>
{[...highlightedProjects, ...otherProjects].length > 0 ? (
[...highlightedProjects, ...otherProjects].map((project) => (
<div
key={project.name}
className='relative group overflow-hidden rounded-2xl border border-gray-200 shadow-lg transition-transform transform hover:scale-105 hover:shadow-xl'
>
<div
className='w-full h-64 bg-cover bg-center'
style={{
backgroundImage: project.backgroundImage
? `url(${project.backgroundImage})`
: 'none',
}}
>
{!project.backgroundImage && (
<div className='absolute inset-0 bg-gray-200 opacity-50'></div>
)}
</div>
<div className='p-4'>
<h3 className='text-xl font-semibold'>{project.name}</h3>
<p className='text-md mt-2'>{project.description}</p>
{project.link && (
<a
href={project.link}
className='inline-block mt-2 text-accent hover:text-accent-600'
aria-label={`Learn more about ${project.name}`}
>
Learn More
</a>
)}
</div>
</div>
))
) : (
<div className='text-center text-light-800 p-8 col-span-full'>
<h3 className='text-2xl font-semibold'>No Projects Available</h3>
<p className='text-md mt-2'>Check back soon for updates!</p>
</div>
)}
</div>
</div>
);
}

0 comments on commit 787e91c

Please sign in to comment.