Skip to content

Commit

Permalink
Completed responsive Courses Page. Minor changes to MemberGroup/Membe…
Browse files Browse the repository at this point in the history
…rDisplay in order to make components more reusable.
  • Loading branch information
JasonMun7 committed Sep 27, 2024
1 parent 02d371f commit 9cb85bd
Show file tree
Hide file tree
Showing 13 changed files with 781 additions and 147 deletions.
64 changes: 64 additions & 0 deletions new-dti-website/components/courses/DDProjects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState } from 'react';

// *INTERFACE
interface DDProjectsProps {
title: string;
description: string;
imageSrc: string;
}

/**
* `DDProjects` Component - Displays information about past student Projects within Trends :)
*
* @remarks
* This component is used to present information about student projects, including the title,
* description, and an image representing the project that they did in Trends class. There is an interactive expand/collapse
* functionality, allowing the user to toggle additional details with a smooth transition effect.
* The card's background color changes based on its state (open/closed). The component is also responsive to screen size.
*
* The component is designed to receive data via props and a json object.
*
* @param props - Contains:
* - `title`: The title of the project, displayed prominently at the top of the card.
* - `description`: A brief description of the project, revealed when the card is expanded.
* - `imageSrc`: The URL for the image that represents the project, displayed in the expanded view.
*/
export default function DDProjects({ title, description, imageSrc }: DDProjectsProps) {
const [isOpen, setIsOpen] = useState(false);

const toggleCard = () => {
setIsOpen(!isOpen);
};

return (
<div
className={`transition-all duration-300 ease-in-out ${
isOpen ? 'bg-[#D63D3D]' : 'bg-white'
} w-full max-w-8xl rounded-xl drop-shadow-sm px-10 py-8 border-1 border-[#E4E4E4]`}
>
<div className="flex justify-between items-center">
<h3 className={`md:text-3xl text-xl font-bold ${isOpen ? 'text-white' : 'text-black'}`}>
{title}
</h3>
<button
className={`md:text-4xl text-2xl font-thin ${isOpen ? 'text-white' : 'text-gray-700'}`}
onClick={toggleCard}
>
{isOpen ? '−' : '+'}
</button>
</div>

{/* Smooth transition for the Additional Content onClick */}
<div
className={`overflow-hidden transition-all duration-700 ease-in-out ${
isOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="mt-4">
<p className="text-white">{description}</p>
<img src={imageSrc} alt={title} className="mt-4 w-full h-48 object-cover rounded-lg" />
</div>
</div>
</div>
);
}
21 changes: 18 additions & 3 deletions new-dti-website/components/courses/Experiences.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import React from 'react';
import Image from 'next/image';

// * INTERFACE
interface IconProps {
icon: string;
title: string;
description: string;
}

/**
* `Experiences` Component - Displays key experiences for students in Trends :)
*
* @remarks
* This component is used to highlight three core experiences students will get from Trends,
* including best practices, deploying web applications, and completing a final project. Each experience consists
* of an icon, a title, and a brief description. The component adapts its layout based on screen size, with responsive
* text sizes and image scaling.
*
* @param props - Contains:
* - `icon`: The URL path to the icon image associated with the experience.
* - `title`: The title of the key experience.
* - `description`: A short description about the past student's projects.
*/
export default function Experiences({ icon, title, description }: IconProps) {
return (
<>
Expand All @@ -18,11 +33,11 @@ export default function Experiences({ icon, title, description }: IconProps) {
height={150}
alt={icon}
unoptimized
className="w-24 md:w-[35%]"
className="w-24 md:w-[30%]"
/>
<div className="lg:text-text-6xl xs:text-4xl font-extrabold">{title}</div>
<div className="text-4xl font-extrabold">{title}</div>
</div>
<div className="md:text-2xl xs:text-2xl mt-8">{description}</div>
<div className="mt-8 text-lg md:text-md lg:text-2xl">{description}</div>
</div>
</>
);
Expand Down
Empty file.
35 changes: 35 additions & 0 deletions new-dti-website/components/courses/TestimonialCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

// * INTERFACE
interface TestimonialCardProps {
description: string;
name: string;
semesterTaken: string;
}

/**
* `TestimonialCard` Component - Displays a single testimonial from a student who has done Trends.
*
* @remarks
* This component is used to present testimonials from students about their experiences taking the course. It showcases
* a brief description of the testimonial, the name of the student (or anonymous), and the semester in which they took the class.
*
* @param props - Contains:
* - `description`: The student's written testimonial about the course.
* - `name`: The name of the student who gave the testimonial, or "Anonymous" if the student prefers not to disclose their name.
* - `semesterTaken`: The semester when the student took the course.
*/
export default function TestimonialCard({
description,
name,
semesterTaken
}: TestimonialCardProps) {
return (
<div className="bg-white max-w-md w-[800px] p-10 rounded-xl drop-shadow-sm flex-shrink-0">
<div className="text-3xl text-gray-800 mb-4 tracking-wider font-black">❛❛</div>
<p className="text-lg text-gray-700 mb-6">{description}</p>
<div className="text-gray-900 font-bold pt-8">{name}</div>
<div className="text-gray-500">{semesterTaken}</div>
</div>
);
}
Empty file.
219 changes: 219 additions & 0 deletions new-dti-website/components/courses/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import React, { useLayoutEffect, useRef, useState } from 'react';

// * TYPES
export type Event = {
title: string;
date: string;
time: string;
};

// * INTERFACE
type TimelineProps = {
events: Event[];
currentDate: Date;
};

/**
* `Timeline` Component - Displays a chronological timeline of Trends events.
*
* @remarks
* This component is used to render the timeline with all the events. The timeline automatically adjusts based on screen size
* (mobile vs. desktop) via a useEffect and displays a progress line showing how far along the current date is relative to the events.
* Each event includes a title, date, and time. The date string should be formatted with the month (abbreviated), day, and
* optionally, the time (formatted as `hh:mm AM/PM`). If no time is provided, the default will be 12:00 AM.
* If no Year is provided, the default will be the Current Year
*
* @param props - Contains:
* - `events`: An array of event objects. Each object should contain:
* - `title`: The name of the event.
* - `date`: The date of the event in the format `MMM DD, YYYY` (e.g., "Feb 19, 2024"). MONTH NOT REQUIRED TO BE ABBREVIATED both Feb and February are valid
* - `time`: (Optional) The time of the event in the format `hh:mm AM/PM` (e.g., "12:00 PM EST").
* - `currentDate`: A `Date` object representing the current date and time, used to calculate the progress through the timeline.
*/
export default function Timeline({ events, currentDate }: TimelineProps) {
const firstEventRef = useRef<HTMLDivElement | null>(null);
const lastEventRef = useRef<HTMLDivElement | null>(null);
const [lineLength, setLineLength] = useState(0);
const [isMobile, setIsMobile] = useState(false);

/**
* Calculates the length of the line connecting the first and last events.
*
* @remarks
* This function determines the position of the first and last event elements
* on the page and calculates the distance between them, either vertically
* (on mobile) or horizontally (on desktop). It updates the line length state accordingly.
*
* @returns A number representing the length of the line in pixels.
*
* @example
* ```ts
* calculateLineLength();
* ```
*/
const calculateLineLength = () => {
if (events.length === 1 || events.length === 0) {
setLineLength(0);
return;
}
if (firstEventRef.current && lastEventRef.current) {
const firstPos = firstEventRef.current.getBoundingClientRect();
const lastPos = lastEventRef.current.getBoundingClientRect();

const firstCenter = {
x: firstPos.left + firstPos.width / 2,
y: firstPos.top + firstPos.height / 2
};

const lastCenter = {
x: lastPos.left + lastPos.width / 2,
y: lastPos.top + lastPos.height / 2
};

if (isMobile) {
const verticalDistance = Math.abs(lastCenter.y - firstCenter.y);
setLineLength(verticalDistance);
} else {
const horizontalDistance = Math.abs(lastCenter.x - firstCenter.x);
setLineLength(horizontalDistance);
}
}
};

/**
* Parses an event's date and time to a `Date` object.
*
* @remarks
* This function converts a date string (formatted as `MMM DD`) and an optional time string (formatted as `hh:mm AM/PM`)
* into a JavaScript `Date` object. If no time is provided, the default time is 12:00 AM. The year is automatically
* set to the current year if no year is provided, so ensure that the date values are formatted properly.
*
* @param dateStr - The date string of the event in the format `MMM DD, YYYY` (e.g., "Feb 19, 2024"). MONTH NOT REQUIRED TO BE ABBREVIATED
* @param timeStr - (Optional) The time of the event in the format `hh:mm AM/PM` (e.g., "12:00 PM EST").
*
* @returns A `Date` object corresponding to the event's date and time.
*
* @example
* ```ts
* const eventDate = parseEventDate("Feb 19", "7:30 PM");
* ```
*/
const parseEventDate = (dateStr: string, timeStr: string) => {
const currentYear = new Date().getFullYear();
let date = new Date(`${dateStr} ${currentYear} ${timeStr || '12:00 AM'}`);

Check failure on line 103 in new-dti-website/components/courses/Timeline.tsx

View workflow job for this annotation

GitHub Actions / check

'date' is never reassigned. Use 'const' instead
return date;
};

/**
* Calculates the percentage of progress through the events based on the current date.
*
* @remarks
* This function sorts the events by date, calculates the total time span between the first
* and last events, and determines how far along the current date is within that time span.
* The result is a percentage that is used to fill the progress line on the timeline.
*
* @returns A number representing the percentage of progress (from 0 to 100).
*
* @example
* ```ts
* const progressPercentage = getProgressPercentage();
* ```
*/ const getProgressPercentage = () => {
const sortedEvents = events.sort((a, b) => {
const aDate = parseEventDate(a.date, a.time);
const bDate = parseEventDate(b.date, b.time);
return aDate.getTime() - bDate.getTime();
});

const firstEventDate = parseEventDate(sortedEvents[0].date, sortedEvents[0].time);
const lastEventDate = parseEventDate(
sortedEvents[sortedEvents.length - 1].date,
sortedEvents[sortedEvents.length - 1].time
);

const totalTimeSpan = lastEventDate.getTime() - firstEventDate.getTime();
const timeElapsed = Math.max(0, currentDate.getTime() - firstEventDate.getTime());
const progress = (timeElapsed / totalTimeSpan) * 100;

return Math.min(100, Math.max(0, progress));
};

const progressPercentage = getProgressPercentage();

// * Use Layout Effect to Handle resizing of the Timeline
useLayoutEffect(() => {
const handleResize = () => {
const mobile = window.innerWidth < 640;
setIsMobile(mobile);
console.log(isMobile);

Check warning on line 148 in new-dti-website/components/courses/Timeline.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
calculateLineLength();
console.log(lineLength);

Check warning on line 150 in new-dti-website/components/courses/Timeline.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
};

handleResize();
window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('resize', handleResize);
};
}, [isMobile]);

Check warning on line 159 in new-dti-website/components/courses/Timeline.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook useLayoutEffect has missing dependencies: 'calculateLineLength' and 'lineLength'. Either include them or remove the dependency array

return (
<>
<div className="flex flex-col min-h-[80vh] sm:min-h-0 items-start sm:flex-row sm:items-center justify-around relative">
<div
className="absolute flex flex-col items-center sm:flex-row "
style={{
height: isMobile ? `${lineLength}px` : '6px',
width: isMobile ? '2px' : `${lineLength}px`,
left: isMobile ? '65px' : '50%', // This Centers it horizontally on non-mobile
transform: isMobile ? 'translateY(-10px)' : 'translateX(-50%) translateY(67px)' // Use this to fine tune position
}}
>
<div className="absolute sm:h-2 h-full sm:w-full w-2 bg-gray-300 z-10" />
<div
className="absolute sm:h-1 h-full sm:w-full w-1 bg-[#D63D3D] z-20"
style={{
height: `calc(${progressPercentage}% + 1px)`,
width: isMobile ? '6px' : `${progressPercentage}%`
}}
/>
</div>

{events.map((event, idx) => {
const eventDate = parseEventDate(event.date, event.time);
const isPast = eventDate < currentDate;

return (
<div
key={idx}
className="flex flex-row pl-[53px] space-x-10 sm:pl-0 sm:flex-col sm:items-center sm:justify-end sm:h-40 z-30"
ref={idx === 0 ? firstEventRef : idx === events.length - 1 ? lastEventRef : null}

Check failure on line 191 in new-dti-website/components/courses/Timeline.tsx

View workflow job for this annotation

GitHub Actions / check

Do not nest ternary expressions
>
{/* Red Dot for Completed / Grey Dot for Mobile */}
<div
className={`w-[26px] h-[26px] rounded-full ${
isPast ? 'bg-[#D63D3D]' : 'bg-gray-300'
} sm:hidden block`}
/>
{/* Title and Date */}
<div className="sm:text-center sm:mb-4">
<p className="font-extrabold lg:text-3xl text-xl">{event.title}</p>
<div className="flex flex-row gap-x-2 sm:flex-col">
<p className="text-md lg:text-lg">{event.date}</p>
<p className="text-md lg:text-lg">{event.time}</p>
</div>
</div>
{/* Red Dot for Completed / Grey Dot for Tablet/Laptop */}
<div
className={`w-[26px] h-[26px] rounded-full ${
isPast ? 'bg-[#D63D3D]' : 'bg-gray-300'
} hidden sm:block`}
/>
</div>
);
})}
</div>
</>
);
}
24 changes: 24 additions & 0 deletions new-dti-website/components/courses/data/student_projects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"student_projects": [
{
"title": "Project Name1",
"description": "This project is about blah blah blah blah blah blah blah blah blah blah blah blah blah.",
"imageSrc": "https://via.placeholder.com/400"
},
{
"title": "Project Name2",
"description": "This project is about blah blah blah blah blah blah blah blah blah blah blah blah blah.",
"imageSrc": "https://via.placeholder.com/400"
},
{
"title": "Project Name3",
"description": "This project is about blah blah blah blah blah blah blah blah blah blah blah blah blah.",
"imageSrc": "https://via.placeholder.com/400"
},
{
"title": "Project Name4",
"description": "This project is about blah blah blah blah blah blah blah blah blah blah blah blah blah.",
"imageSrc": "https://via.placeholder.com/400"
}
]
}
Loading

0 comments on commit 9cb85bd

Please sign in to comment.