diff --git a/new-dti-website/components/courses/DDProjects.tsx b/new-dti-website/components/courses/DDProjects.tsx
new file mode 100644
index 00000000..2b9b00d4
--- /dev/null
+++ b/new-dti-website/components/courses/DDProjects.tsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react';
+
+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 (
+
+
+
+ {title}
+
+
+
+
+ {/* Smooth transition for the Additional Content onClick */}
+
+
+
{description}
+
+
+
+
+ );
+}
diff --git a/new-dti-website/components/courses/Experiences.tsx b/new-dti-website/components/courses/Experiences.tsx
new file mode 100644
index 00000000..ab13b924
--- /dev/null
+++ b/new-dti-website/components/courses/Experiences.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import Image from 'next/image';
+
+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 (
+ <>
+
+
+
+
{title}
+
+
{description}
+
+ >
+ );
+}
diff --git a/new-dti-website/components/courses/TestimonialCard.tsx b/new-dti-website/components/courses/TestimonialCard.tsx
new file mode 100644
index 00000000..37204b29
--- /dev/null
+++ b/new-dti-website/components/courses/TestimonialCard.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+export 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 (
+
+
❛❛
+
{description}
+
{name}
+
{semesterTaken}
+
+ );
+}
diff --git a/new-dti-website/components/courses/TestimonialSlider.tsx b/new-dti-website/components/courses/TestimonialSlider.tsx
new file mode 100644
index 00000000..dcb120d0
--- /dev/null
+++ b/new-dti-website/components/courses/TestimonialSlider.tsx
@@ -0,0 +1,65 @@
+import React, { useRef, useState, useEffect } from 'react';
+import TestimonialCard, { TestimonialCardProps } from './TestimonialCard';
+
+interface TestimonialSliderProps {
+ testimonials: TestimonialCardProps[];
+}
+
+export default function TestimonialSlider({ testimonials }: TestimonialSliderProps) {
+ const sliderRef = useRef(null);
+ const [isScrolling, setIsScrolling] = useState(false);
+ const [scrollAtEnd, setScrollAtEnd] = useState(false);
+
+ useEffect(() => {
+ const slider = sliderRef.current;
+
+ // Return early if there are no testimonials or if we've reached the end
+ if (!slider || testimonials.length === 0 || scrollAtEnd || isScrolling) {
+ return undefined; // Return `undefined` explicitly to satisfy consistent-return rule
+ }
+
+ const scrollInterval = setInterval(() => {
+ const maxScrollLeft = slider.scrollWidth - slider.clientWidth;
+
+ if (slider.scrollLeft < maxScrollLeft) {
+ slider.scrollLeft += 2; // Adjust the scroll speed as needed
+ } else {
+ setScrollAtEnd(true); // Stop scrolling when the end is reached
+ // slider.scrollLeft = 0 if we want to start from the begnning :)
+ }
+ }, 25); // Adjust the interval as needed for smoother/faster scrolling
+
+ // Return the cleanup function
+ return () => {
+ clearInterval(scrollInterval);
+ };
+ }, [testimonials, isScrolling, scrollAtEnd]);
+
+ const handleMouseEnter = () => {
+ setIsScrolling(true);
+ };
+
+ const handleMouseLeave = () => {
+ setIsScrolling(false);
+ };
+
+ return (
+
+ );
+}
diff --git a/new-dti-website/components/courses/Timeline.tsx b/new-dti-website/components/courses/Timeline.tsx
new file mode 100644
index 00000000..d224f631
--- /dev/null
+++ b/new-dti-website/components/courses/Timeline.tsx
@@ -0,0 +1,217 @@
+import React, { useLayoutEffect, useRef, useState } from 'react';
+import useScreenSize from '../../src/hooks/useScreenSize';
+import { MOBILE_BREAKPOINT } from '../../src/consts';
+
+export type Event = {
+ title: string;
+ date: string;
+ time: string;
+};
+
+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(null);
+ const lastEventRef = useRef(null);
+ const [lineLength, setLineLength] = useState(0);
+ const [isMobile, setIsMobile] = useState(false);
+
+ /**
+ * 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();
+ const date = new Date(`${dateStr} ${currentYear} ${timeStr || '12:00 AM'}`);
+ 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();
+ const { width } = useScreenSize();
+
+ useLayoutEffect(() => {
+ /**
+ * 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);
+ }
+ }
+ };
+
+ if (width) {
+ const mobile = width < MOBILE_BREAKPOINT;
+ setIsMobile(mobile);
+ calculateLineLength();
+ }
+ }, [isMobile, lineLength, events.length, width]);
+
+ return (
+ <>
+
+ {/* Red Dot for Completed / Grey Dot for Mobile */}
+
+ {/* Title and Date */}
+
+
{event.title}
+
+
{event.date}
+
{event.time}
+
+
+ {/* Red Dot for Completed / Grey Dot for Tablet/Laptop */}
+
+
+ );
+ })}
+
+ >
+ );
+}
diff --git a/new-dti-website/components/courses/data/key_experiences.json b/new-dti-website/components/courses/data/key_experiences.json
new file mode 100644
index 00000000..ae4bec83
--- /dev/null
+++ b/new-dti-website/components/courses/data/key_experiences.json
@@ -0,0 +1,19 @@
+{
+ "key_experiences": [
+ {
+ "icon": "/icons/courses/pencil.png",
+ "title": "Best Practices",
+ "description": "We emphasize best engineering practices for every element of the course taught, from API design to frontend modularization"
+ },
+ {
+ "icon": "/icons/courses/rocket.png",
+ "title": "Deploy",
+ "description": "Learn how to deploy your web applications ot the cloud using service provider such as Heroku or the Google Cloud Platform"
+ },
+ {
+ "icon": "/icons/courses/presentation.png",
+ "title": "Final Project",
+ "description": "The class ends with a final project capstone project consolidating all class topics, which can be used on your resume or portfolio"
+ }
+ ]
+}
diff --git a/new-dti-website/components/courses/data/student_projects.json b/new-dti-website/components/courses/data/student_projects.json
new file mode 100644
index 00000000..4d964c05
--- /dev/null
+++ b/new-dti-website/components/courses/data/student_projects.json
@@ -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"
+ }
+ ]
+}
diff --git a/new-dti-website/components/courses/data/testimonials.json b/new-dti-website/components/courses/data/testimonials.json
new file mode 100644
index 00000000..671fcdf7
--- /dev/null
+++ b/new-dti-website/components/courses/data/testimonials.json
@@ -0,0 +1,39 @@
+{
+ "testimonials": [
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ },
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ },
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ },
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ },
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ },
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ },
+ {
+ "description": "It was so great yeah you should take it like it was crazy like all the best things in the world would want to go take the class like I feel enlightened",
+ "name": "NAME/ANONYMOUS 1",
+ "semesterTaken": "Fall 2023"
+ }
+ ]
+}
diff --git a/new-dti-website/components/courses/data/timeline_events.json b/new-dti-website/components/courses/data/timeline_events.json
new file mode 100644
index 00000000..ad6da5cb
--- /dev/null
+++ b/new-dti-website/components/courses/data/timeline_events.json
@@ -0,0 +1,24 @@
+{
+ "timeline_events": [
+ {
+ "title": "Sign-up Deadline",
+ "date": "Feb 16",
+ "time": "12:00 PM EST"
+ },
+ {
+ "title": "First Day",
+ "date": "Feb 19",
+ "time": "7:30 PM EST"
+ },
+ {
+ "title": "Final Project",
+ "date": "April 12",
+ "time": ""
+ },
+ {
+ "title": "Project Presentation",
+ "date": "November 26, 2024",
+ "time": ""
+ }
+ ]
+}
diff --git a/new-dti-website/components/courses/data/trend_instructors.json b/new-dti-website/components/courses/data/trend_instructors.json
new file mode 100644
index 00000000..83339178
--- /dev/null
+++ b/new-dti-website/components/courses/data/trend_instructors.json
@@ -0,0 +1,64 @@
+{
+ "trend_instructors": [
+ {
+ "lastName": "Wang",
+ "hometown": "",
+ "github": "https://github.com/oscarwang20",
+ "website": "",
+ "formerSubteams": [],
+ "role": "developer",
+ "minor": "",
+ "doubleMajor": "",
+ "netid": "ow39",
+ "about": "",
+ "linkedin": "",
+ "firstName": "Oscar",
+ "major": "",
+ "graduation": "",
+ "pronouns": "",
+ "subteams": ["idol"],
+ "roleDescription": "Developer",
+ "email": "ow39@cornell.edu"
+ },
+ {
+ "lastName": "Wang",
+ "hometown": "",
+ "github": "https://github.com/oscarwang20",
+ "website": "",
+ "formerSubteams": [],
+ "role": "developer",
+ "minor": "",
+ "doubleMajor": "",
+ "netid": "ow39",
+ "about": "",
+ "linkedin": "",
+ "firstName": "Oscar",
+ "major": "",
+ "graduation": "",
+ "pronouns": "",
+ "subteams": ["idol"],
+ "roleDescription": "Developer",
+ "email": "ow39@cornell.edu"
+ },
+ {
+ "lastName": "Wang",
+ "hometown": "",
+ "github": "https://github.com/oscarwang20",
+ "website": "",
+ "formerSubteams": [],
+ "role": "developer",
+ "minor": "",
+ "doubleMajor": "",
+ "netid": "ow39",
+ "about": "",
+ "linkedin": "",
+ "firstName": "Oscar",
+ "major": "",
+ "graduation": "",
+ "pronouns": "",
+ "subteams": ["idol"],
+ "roleDescription": "Developer",
+ "email": "ow39@cornell.edu"
+ }
+ ]
+}
diff --git a/new-dti-website/components/team/MemberDisplay.tsx b/new-dti-website/components/team/MemberDisplay.tsx
index e55b8337..39d9f5be 100644
--- a/new-dti-website/components/team/MemberDisplay.tsx
+++ b/new-dti-website/components/team/MemberDisplay.tsx
@@ -89,6 +89,7 @@ const MemberDisplay: React.FC = () => {
selectedMember={selectedMember}
selectedRole={selectedRole}
memberDetailsRef={memberDetailsRef}
+ isCard={false}
/>
);
})}
diff --git a/new-dti-website/components/team/MemberGroup.tsx b/new-dti-website/components/team/MemberGroup.tsx
index 2eeb0e2c..a377ff5a 100644
--- a/new-dti-website/components/team/MemberGroup.tsx
+++ b/new-dti-website/components/team/MemberGroup.tsx
@@ -242,6 +242,7 @@ type MemberGroupProps = {
selectedMember: IdolMember | undefined;
selectedRole: string;
memberDetailsRef: RefObject;
+ isCard: boolean;
};
const MemberGroup: React.FC = ({
@@ -251,7 +252,8 @@ const MemberGroup: React.FC = ({
setSelectedMember,
selectedMember,
selectedRole,
- memberDetailsRef
+ memberDetailsRef,
+ isCard
}) => {
const selectedMemberIndex: number = useMemo(
() => (selectedMember ? members.indexOf(selectedMember) : -1),
@@ -295,16 +297,9 @@ const MemberGroup: React.FC = ({
const onCloseMemberDetails = () => setSelectedMember(undefined);
return (
- (selectedRole === roleName || selectedRole === 'Full Team') && (
-
+ A project team is meant, above all, to be a learning experience. Given our mission
+ of community impact, we want to help everyone
+ learn and grow
+ through our training course in{' '}
+ product development.
+
+
+
+
+
+
+
+
+
+ {/* WRAPPER */}
+
+ {/* LOGO SECTION */}
+
+
+
+
+
+
+
+
+ MODERN INDUSTRY-LEADING TECHNOLOGY
+
+
+
+ Trends in Web Development
+
+
+
+ Trends in Web Development in a 1-credit S/U course that showcase modern full-stack
+ development and best practices used within industry. We cover technologies like
+ TypeScript, React, Node.js, Firebase, Express and more, all of which are deployed
+ at scale by leading tech companies
+