Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gabe - "Who We Are" and "Our Mission" Sections #16

Merged
merged 12 commits into from
Jan 10, 2025
15 changes: 15 additions & 0 deletions app/dev/who-we-are/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import OurMission from "@/components/OurMission";
import WhoWeAre from "@/components/WhoWeAre";


export default function WhoWeArePage() {

return (
<>
<div className="bg-background font-inter">
<WhoWeAre />
<OurMission />
</div>
</>
)
}
11 changes: 11 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");

@tailwind base;
@tailwind components;
@tailwind utilities;

.hide-scrollbar::-webkit-scrollbar {
display: none;
}

.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
65 changes: 65 additions & 0 deletions components/FeaturedStatistics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @fileoverview A section that displays a set of large statistics
* and statistic descriptions.
*
* @file FeaturedStatistics.tsx
* @date January 5th, 2023
* @author Gabriel Sessions
*
*/

import clsx from "clsx"

interface FeaturedStatisticsProps {
statistics: Array<Statistic>,
className?: string
}

/**
* Displays a set of statistics in a row (or column depending on screen size)
*
* @param props - An array of statistics to be fed into DisplayStatistic
*/
export default function FeaturedStatistics(props: FeaturedStatisticsProps) {
return (
<div className={
clsx(
"block md:flex justify-center md:space-x-6 lg:space-x-16 flex-wrap space-y-8 md:space-y-0 text-center",
props.className
)
}>
{
props.statistics.map((stat, idx) =>
<div key={idx}>
<DisplayStatistic
key={idx}
{...stat}
/>
</div>
)
}
</div>
)
}

interface Statistic {
statistic: string,
label: string
}

/**
* Displays a bold statistic (e.g. number)
* and a short description of the statistic
*
* @param props - A statistic and a descriptor (label)
*/
function DisplayStatistic(props: Statistic) {
return (
<>
<div className="text-white">
<h2 className="font-semibold text-2xl md:text-3xl lg:text-5xl mb-2">{props.statistic}</h2>
<h3 className="font-normal text-md md:text-lg lg:text-xl">{props.label}</h3>
</div>
</>
)
}
31 changes: 31 additions & 0 deletions components/FeaturedTextBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @fileoverview A simple section with a small title and a block of bolded text
*
* @file FeaturedTextBlock.tsx
* @date January 5th, 2025
* @author Gabriel Sessions
*
*/

import { ReactNode } from "react"

interface FeaturedTextBlockProps {
title: ReactNode,
featuredText: ReactNode
}

/**
* Displays a header and descriptor text below the header.
*
* @param Takes in a title (header) and featured text (descriptor)
*/
export default function FeaturedTextBlock(props: FeaturedTextBlockProps) {
return (
<>
<div className="flex py-10 px-6 md:py-10 md:px-12 lg:py-24 lg:px-28 flex-col justify-center items-start bg-gray gap-2">
<h2 className="text-md md:text-lg lg:text-xl text-white opacity-60 font-bold">{props.title}</h2>
<h3 className="text-xl md:text-3xl lg:text-5xl text-white font-semibold lg:leading-[56px]">{props.featuredText}</h3>
</div>
</>
)
}
119 changes: 119 additions & 0 deletions components/ImageCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @fileoverview A scrollable image carousel which shows a preview of the
* next/previous images.
*
* @file ImageCarousel.tsx
* @date January 5th, 2023
* @author Gabriel Sessions
*
* @todo Remove hardcoded width/height values if this component is reused
* with different image dimensions.
*/

"use client"

import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { clsx } from 'clsx';

const IMG_HEIGHT = 389;
const IMG_WIDTH = 665;

/**
* @param path - Local or absolute path to an image asset
* @param altText - A text descriptor of the image
* @param className - Optional field to add CSS styles
*/
interface ImageProps {
path: string,
altText: string,
className?: string
}

/**
* @param images - An array of objects to be fed into the Image component
* @param className - Optional field to add CSS styles
*/
interface ImageCarouselProps {
images: Array<ImageProps>,
className?: string
}

/**
* An Image Carousel with basic scrolling functionality
*
* @param props - An array of images
* @todo Adjust the useEffect scrolling logic to display an even number
* of images properly.
*/
export default function ImageCarousel(props: ImageCarouselProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);

// The carousel is hidden from the user while it loads and waits for the
// useEffect to scroll the carousel
const [isVisible, setIsVisible] = useState(false);

// Scrolls the carousel to the center when the page loads
useEffect(() => {
if (scrollContainerRef.current) {
const scrollContainer = scrollContainerRef.current;
const centerPosition = (scrollContainer.scrollWidth - scrollContainer.clientWidth) / 2;
scrollContainer.scrollLeft = centerPosition;
setIsVisible(true);
}
}, []);

return (
<div className={
clsx(
"flex justify-center transition-opacity duration-300",
isVisible ? 'opacity-100' : 'opacity-0',
props.className
)
}
>
<div
className="relative flex overflow-x-auto hide-scrollbar scroll-snap-x space-x-6 px-2 max-w-full"
ref={scrollContainerRef}
>
{props.images.map((image, idx) => (
<ImageContainer
key={idx}
path={image.path}
altText={image.altText}
className="scroll-snap-center"
/>
))}
</div>
</div>
)
}


/**
* An image container generator. Creates a responsive, rounded box
* for images with a max width of 600px.
*
* @param props - Image path, alt text. Width and height are defined globally.
* @returns A React component
*
* @todo Change width/height to props values if the component needs to be reused
* with new image dimensions.
*/
function ImageContainer(props: ImageProps) {
return (
<Image
src={props.path}
alt={props.altText}
width={IMG_WIDTH}
height={IMG_HEIGHT}
className={
`border-background rounded-xl scroll-snap-center
w-[300px] h-auto
md:w-[450px]
xl:w-[600px] ${props.className}`
}
/>
)
}

26 changes: 26 additions & 0 deletions components/OurMission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @fileoverview The "Our Mission" section on the JumboCode homepage
*
* @file OurMission.tsx
* @date January 5th, 2025
* @author Gabriel Sessions
*/

import FeaturedTextBlock from "./FeaturedTextBlock"

const ourMissionText = {
title: "Our mission",
featuredText:
<>
To <span className="text-brand">strengthen communities</span> by developing custom software solutions that promote change and <span className="text-brand">foster student growth</span>.
</>
}

export default function OurMission() {

return (
<>
<FeaturedTextBlock {...ourMissionText} />
</>
)
}
66 changes: 66 additions & 0 deletions components/WhoWeAre.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @fileoverview The "Who We Are" section of the JumboCode homepage.
*
* @file WhoWeAre.tsx
* @date January 5th, 2025
* @author Gabriel Sessions
*/

import FeaturedStatistics from "./FeaturedStatistics";
import FeaturedTextBlock from "./FeaturedTextBlock";
import ImageCarousel from "./ImageCarousel";

const whoWeAreText = {
title: "Who we are",
featuredText:
<>
JumboCode is a <span className="text-brand">student-run digital agency</span> at Tufts University that provides custom and high-quality <span className="text-brand">software to nonprofits</span> through year-long pro bono projects.
</>
}

const whoWeAreImages = [
{
path: "/homepage/who-we-are-1.png",
altText: "A live demo at JumboCode final presentations"
},
{
path: "/homepage/who-we-are-2.png",
altText: "JumboCode Team Photo"
},
{
path: "/homepage/who-we-are-3.png",
altText: "A team having fun at a JumboCode hack night"
}
];

const featuredStatistics = [
{
statistic: "170+",
label: "Annual Members"
},
{
statistic: "80K+",
label: "Hours Volunteered"
},
{
statistic: "75+",
label: "Shipped Apps"
}
]

export default function WhoWeAre() {
return (
<>
<FeaturedTextBlock {...whoWeAreText} />
<ImageCarousel
images={whoWeAreImages}
className="md:py-6 md:pb-8 lg:py-20"
/>
<FeaturedStatistics
statistics={featuredStatistics}
className={"py-8 md:mt-8 lg:py-12 lg:pb-20"}
/>
</>
)
}

Binary file added public/homepage/who-we-are-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/homepage/who-we-are-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/homepage/who-we-are-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ const config: Config = {
],
theme: {
extend: {
fontFamily: {
inter: ['Inter', 'sans-serif']
},
colors: {
gray: colors.slate,
brand: "#32C89E",
background: "#171719",
subtext: "#A1A1A1"
},
},
},
Expand Down
Loading