diff --git a/components/BulletPoint.tsx b/components/BulletPoint.tsx new file mode 100644 index 0000000..76c9d8d --- /dev/null +++ b/components/BulletPoint.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import styles from '../styles/components/BulletPoint.module.scss'; +import { type SectionStyle, getSectionStyling, Fonts } from '../utils'; +import { type BulletPoint } from '../config'; +import Icon from './Icon'; + +interface Props { + bullet: BulletPoint; + style: SectionStyle; +} + +const Button = ({ bullet, style }: Props): JSX.Element => { + const sectionStyle = getSectionStyling(style); + return ( +
+
+ +
+

{bullet.title}

+ {bullet.text} +
+ ); +}; + +export default Button; diff --git a/components/Icon.tsx b/components/Icon.tsx new file mode 100644 index 0000000..1ba6464 --- /dev/null +++ b/components/Icon.tsx @@ -0,0 +1,62 @@ +/** + * @module Icon: A dynamic icon component. + */ +import React from 'react'; +// Icon Imports - Icons at https://react-icons.github.io/react-icons/search +import { MdOutlinePeopleOutline } from 'react-icons/md'; +import { + FaAward, + FaRegLightbulb, + FaHourglassStart, + FaFacebook, + FaInstagram, + FaTwitter, + FaLinkedin, +} from 'react-icons/fa'; +import { LuChevronsLeftRight } from 'react-icons/lu'; +import { FiTarget } from 'react-icons/fi'; + +// Icon Types +export enum IconType { + // General + People = 'People', + Award = 'Award', + LightBulb = 'LightBulb', + Development = 'Development', + Target = 'Target', + HourGlass = 'HourGlass', + // Social + Facebook = 'Facebook', + Instagram = 'Instagram', + Twitter = 'Twitter', + Linkedin = 'linkedin', +} + +export const Icon = ({ iconType }: { iconType: IconType }): JSX.Element => { + switch (iconType) { + // General + case IconType.People: + return ; + case IconType.Award: + return ; + case IconType.LightBulb: + return ; + case IconType.Development: + return ; + case IconType.Target: + return ; + case IconType.HourGlass: + return ; + // Social + case IconType.Facebook: + return ; + case IconType.Instagram: + return ; + case IconType.Twitter: + return ; + case IconType.Linkedin: + return ; + } +}; + +export default Icon; diff --git a/config.ts b/config.ts index cf9bd13..02ee0f6 100644 --- a/config.ts +++ b/config.ts @@ -13,6 +13,7 @@ import z, { type ZodError } from 'zod'; import { fromZodError } from 'zod-validation-error'; +import { IconType } from './components/Icon'; // Import Config import rawConfig from './config.yaml'; // General Types @@ -29,14 +30,22 @@ export enum SectionType { TextSection = 'TextSection', NewsSection = 'NewsSection', EventSection = 'EventSection', + AboutSection = 'AboutSection', } +export enum HeaderStyle { + Default = 'Default', + Inline = 'Inline', +} +const headerStyle = z.nativeEnum(HeaderStyle); interface SectionBase { // section_type: SectionType; -- We cannot have this here because of the whole filtering by sections stuff, but it is necessary on each type section_header: string; + header_style: HeaderStyle; } const sectionBase = z.strictObject({ section_type: z.nativeEnum(SectionType), section_header: z.string(), + header_style: headerStyle.optional().default(HeaderStyle.Default), }); // Config Types interface SocialIcon { @@ -210,31 +219,56 @@ const eventSection = sectionBase.extend({ } ), }); -export type Section = TextSection | NewsSection | EventSection; -const section = z.union([textSection, newsSection, eventSection]); -// Home page -interface HomePage { + +const iconType = z.nativeEnum(IconType); +export interface BulletPoint { + icon: IconType; + title: string; + text: string; +} +const bulletPoint = z.strictObject({ + icon: iconType, + title: z.string(), + text: z.string(), +}); +export interface AboutSection extends SectionBase { + section_type: SectionType.AboutSection; + text: string; + image: ImageDescription; + bullet_points: BulletPoint[]; +} +const aboutSection = sectionBase.extend({ + section_type: z.literal(SectionType.AboutSection), + text: z.string(), + image: imageDescription, + bullet_points: z.array(bulletPoint).max(3).optional().default([]), +}); +export type Section = TextSection | NewsSection | EventSection | AboutSection; +const section = z.union([textSection, newsSection, eventSection, aboutSection]); +// Pages +interface ComposablePage { sections: Section[]; } -const homePage = z.strictObject({ +const composablePage = z.strictObject({ sections: z.array(section), }); - // config interface ValidConfig { website_config: WebsiteConfig; page_list: PageItem[]; footer_config: FooterConfig; // Page Configs - home_page: HomePage; + home_page: ComposablePage; events: EventItem[]; + about_page: ComposablePage; } const configValidator = z.strictObject({ website_config: websiteConfig, page_list: z.array(pageItem), footer_config: footerConfig, - home_page: homePage, + home_page: composablePage, events: z.array(eventItem), + about_page: composablePage, }); // Config Section Spaced out for an easier error // ========================================================================= @@ -274,3 +308,4 @@ export const footer_config = config.footer_config; export const page_list = config.page_list; export const home_page = config.home_page; export const events = config.events; +export const about_page = config.about_page; diff --git a/config.yaml b/config.yaml index 9afd74e..580688d 100644 --- a/config.yaml +++ b/config.yaml @@ -30,7 +30,7 @@ page_list: page_link: / display_in_navbar: true - page_name: About Us - page_link: /About + page_link: /about display_in_navbar: true - page_name: Events page_link: /Events @@ -90,4 +90,55 @@ home_page: - text: NEWS ABOUT EXCITING EVENT date: 2024-07-25 # href: / - href: / +# Configuration for About page +about_page: + sections: + - section_type: AboutSection + section_header: Who Are We? + header_style: Inline + text: The Trent Computer Science Club Association is a student-run organization dedicated to enriching the computer science experience at Trent University. From coding workshops to hackathons, the TCSCA offers a variety of opportunities for students to expand their knowledge and network effectively to break into the tech industry. + image: + src: /about1.png + alt: Currently a placeholder + bullet_points: + - icon: People + title: Our Dedicated Members + text: Meet the individuals who lead our club and drive our initiatives. + - icon: HourGlass + title: More Text + text: Example text to say stuff about something about us + - icon: Award + title: Even More text + text: Example text to say stuff about something about us + - section_type: AboutSection + section_header: Our Mission + text: Our mission is to create a vibrant community of computer science enthusiasts, where students can explore their interests, develop their skills, and collaborate on innovative projects. We strive to inspire the next generation of technology leaders and empower them to make a positive impact on the world. + image: + src: /about2.png + alt: Currently a placeholder + bullet_points: + - icon: LightBulb + title: Inspiring Innovation + text: We aim to inspire our members to explore the boundless possibilities of computer science and technology. + - icon: Development + title: Empowering Developers + text: We empower our members to develop their skills, collaborate on projects, and make a meaningful impact. + - icon: Target + title: Fostering Innovation + text: We encourage our members to think outside the box and develop innovative solutions to real-world problems. + - section_type: AboutSection + section_header: Why Join Us? + text: Our mission is to create a vibrant community of computer science enthusiasts, where students can explore their interests, develop their skills, and collaborate on innovative projects. We strive to inspire the next generation of technology leaders and empower them to make a positive impact on the world. + image: + src: /about3.png + alt: Currently a placeholder + bullet_points: + - icon: People + title: Networking Opportunities + text: Connect with a diverse community of computer science enthusiasts and build lasting professional relationships. + - icon: HourGlass + title: Skill Development + text: Participate in workshops, hackathons, and project-based learning to enhance your technical and soft skills. + - icon: Award + title: Community Involvement + text: Contribute to the local community through volunteering, outreach programs, and collaborative projects. diff --git a/layouts/AboutSection.tsx b/layouts/AboutSection.tsx new file mode 100644 index 0000000..32ab739 --- /dev/null +++ b/layouts/AboutSection.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Image from '../components/Image'; +import styles from '../styles/layouts/AboutSection.module.scss'; +import BulletPoint from '../components/BulletPoint'; +import { type SectionStyle, getSectionStyling, Fonts } from '../utils'; +import { type AboutSection } from '../config'; + +interface Props { + section: AboutSection; + style: SectionStyle; + className?: string; +} + +const AboutSection = ({ + section: { text, image, bullet_points }, + style, + className, +}: Props) => { + const sectionStyle = getSectionStyling(style); + return ( +
+
+
+

{text}

+
+ {bullet_points.length != 0 && ( +
+ {bullet_points.map((bullet, i) => ( + + ))} +
+ )} +
+ {image.alt} +
+
+
+ ); +}; + +export default AboutSection; diff --git a/layouts/EventSection.tsx b/layouts/EventSection.tsx index fed78c6..35e0d57 100644 --- a/layouts/EventSection.tsx +++ b/layouts/EventSection.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import styles from '../styles/layouts/EventSection.module.scss'; +import { type SectionStyle, getSectionStyling } from '../utils'; import { EventGridStyle, type EventSection, type EventItem } from '../config'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import Event from '../components/Event'; @@ -38,10 +39,12 @@ const getSectionLayout = ( interface Props { className?: string; + style: SectionStyle; section: EventSection; } -export default function EventSection({ className, section }: Props) { +export default function EventSection({ section, className, style }: Props) { + const sectionStyle = getSectionStyling(style); const [currentView, setCurrentView] = useState(0); // Map the events const events = section.events.map((event, i): [EventItem, JSX.Element] => [ @@ -59,7 +62,13 @@ export default function EventSection({ className, section }: Props) { ); // Build ui return ( -
+
{/* Event Container */}
{event_view}
{/* Possible Buttons */} diff --git a/layouts/NewsSection.tsx b/layouts/NewsSection.tsx index b29de88..ca86bb2 100644 --- a/layouts/NewsSection.tsx +++ b/layouts/NewsSection.tsx @@ -3,7 +3,13 @@ import React from 'react'; import Link from 'next/link'; import Button from '../components/Button'; import { SlCalender, SlLocationPin } from 'react-icons/sl'; -import { Fonts, DateFormat, formatDate } from '../utils'; +import { + type SectionStyle, + getSectionStyling, + Fonts, + DateFormat, + formatDate, +} from '../utils'; import { type NewsSection, type NewsItem } from '../config'; const NewsItemComponent = ({ newsItem }: { newsItem: NewsItem }) => { @@ -43,12 +49,16 @@ const NewsItemComponent = ({ newsItem }: { newsItem: NewsItem }) => { interface NewsSectionProps { section: NewsSection; + style: SectionStyle; className?: string; } -const NewsSection = ({ section, className }: NewsSectionProps) => { +const NewsSection = ({ section, style, className }: NewsSectionProps) => { const { news_feed } = section; + const sectionStyle = getSectionStyling(style); return ( -
+
{news_feed.map((item, i) => ( ))} diff --git a/layouts/Section.tsx b/layouts/Section.tsx index 810de7d..3c019d2 100644 --- a/layouts/Section.tsx +++ b/layouts/Section.tsx @@ -1,9 +1,10 @@ -import styles from '../styles/layouts/Section.module.scss'; import React from 'react'; import SectionHeader, { Alignment } from './SectionHeader'; import NewsSection from './NewsSection'; import TextSection from './TextSection'; import EventSection from './EventSection'; +import AboutSection from './AboutSection'; +import { SectionStyle } from '../utils'; import { SectionType, type Section } from '../config'; interface SectionProps { @@ -14,14 +15,12 @@ interface SectionProps { const getStyle = (index: number) => { if (index % 2 === 0) { return { - headerStyle: styles.primaryHeader, - sectionStyle: styles.primaryStyle, + style: SectionStyle.Primary, alignment: Alignment.Left, }; } else { return { - headerStyle: styles.secondaryHeader, - sectionStyle: styles.secondaryStyle, + style: SectionStyle.Secondary, alignment: Alignment.Right, }; } @@ -29,30 +28,32 @@ const getStyle = (index: number) => { const getContent = ( sectionConfig: Section, - index: number, - sectionStyle: string -) => { + sectionStyle: SectionStyle +): JSX.Element => { switch (sectionConfig.section_type) { case SectionType.TextSection: - return ; + return ; case SectionType.NewsSection: - return ; + return ; case SectionType.EventSection: - return ; + return ; + case SectionType.AboutSection: + return ; } }; -const Section: React.FC = ({ sectionConfig, index }) => { - const { headerStyle, sectionStyle, alignment } = getStyle(index); +const Section = ({ sectionConfig, index }: SectionProps) => { + const { style, alignment } = getStyle(index); - const content = getContent(sectionConfig, index, sectionStyle); + const content = getContent(sectionConfig, style); return (
{content}
diff --git a/layouts/SectionHeader.tsx b/layouts/SectionHeader.tsx index 47c9f0b..2c731eb 100644 --- a/layouts/SectionHeader.tsx +++ b/layouts/SectionHeader.tsx @@ -1,5 +1,7 @@ import React from 'react'; import styles from '../styles/layouts/SectionHeader.module.scss'; +import { HeaderStyle } from '../config'; +import { type SectionStyle, getSectionStyling } from '../utils'; export enum Alignment { Left, @@ -19,19 +21,39 @@ const getAlignment = (alignment: Alignment) => { interface Props { title: string; alignment: Alignment; + headerType: HeaderStyle; + style: SectionStyle; className?: string; } -const SectionHeader = ({ title, alignment, className }: Props) => { - return ( -
- -

{title}

- -
- ); +const SectionHeader = ({ + title, + alignment, + headerType, + className, + style, +}: Props): JSX.Element => { + const sectionStyle = getSectionStyling(style); + switch (headerType) { + case HeaderStyle.Default: + return ( +
+ +

{title}

+ +
+ ); + case HeaderStyle.Inline: + return ( +
+

{title}

+
+ ); + } }; export default SectionHeader; diff --git a/layouts/TextSection.tsx b/layouts/TextSection.tsx index e0dedb4..71665da 100644 --- a/layouts/TextSection.tsx +++ b/layouts/TextSection.tsx @@ -2,19 +2,25 @@ import React from 'react'; import Image from '../components/Image'; import Button from '../components/Button'; import styles from '../styles/layouts/TextSection.module.scss'; +import { type SectionStyle, getSectionStyling } from '../utils'; import { type TextSection } from '../config'; interface Props { section: TextSection; + style: SectionStyle; className?: string; } const TextSection = ({ section: { text, image, button }, + style, className, }: Props) => { + const sectionStyle = getSectionStyling(style); return ( -
+
{image.alt}
diff --git a/pages/about.tsx b/pages/about.tsx new file mode 100644 index 0000000..ffe3fa6 --- /dev/null +++ b/pages/about.tsx @@ -0,0 +1,27 @@ +import styles from '../styles/About.module.scss'; +import { about_page } from '../config'; +// Internal Components +import NavBar from '../components/NavBar'; +import EventBanner from '../components/EventBanner'; +import Footer from '../components/Footer'; +import Section from '../layouts/Section'; + +export default function About() { + const { sections } = about_page; + + return ( + <> + + +
+
+ {/* Sections found in the config */} + {sections.map((section, i) => ( +
+ ))} +
+
+ + ); +} diff --git a/public/about1.png b/public/about1.png new file mode 100644 index 0000000..30b2e75 Binary files /dev/null and b/public/about1.png differ diff --git a/public/about2.png b/public/about2.png new file mode 100644 index 0000000..ef96b34 Binary files /dev/null and b/public/about2.png differ diff --git a/public/about3.png b/public/about3.png new file mode 100644 index 0000000..84fc28c Binary files /dev/null and b/public/about3.png differ diff --git a/styles/About.module.scss b/styles/About.module.scss new file mode 100644 index 0000000..7aefa4c --- /dev/null +++ b/styles/About.module.scss @@ -0,0 +1,7 @@ +.container { + @aply flex flex-col; +} + +.MainArea { + @apply bg-color-1 flex-grow; +} diff --git a/styles/components/BulletPoint.module.scss b/styles/components/BulletPoint.module.scss new file mode 100644 index 0000000..91fe1bd --- /dev/null +++ b/styles/components/BulletPoint.module.scss @@ -0,0 +1,16 @@ +.container { + @apply text-color-2 text-center; + .iconContainer { + @apply flex items-center justify-center; + @apply w-16 aspect-square mx-auto mb-4 rounded-full p-5; + svg { + @apply w-full h-full; + } + } + h4 { + @apply text-lg font-bold; + } + span { + @apply text-sm; + } +} diff --git a/styles/globals.scss b/styles/globals.scss index b573d65..a2140e5 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -6,3 +6,17 @@ hr { @apply bg-color-2 h-0.5; @apply w-4/5; } + +// Default Styles +.primaryBackground { + @apply bg-color-3; +} +.primaryForeground { + @apply bg-color-1; +} +.secondaryBackground { + @apply bg-color-1; +} +.secondaryForeground { + @apply bg-color-3; +} diff --git a/styles/layouts/AboutSection.module.scss b/styles/layouts/AboutSection.module.scss new file mode 100644 index 0000000..dafe299 --- /dev/null +++ b/styles/layouts/AboutSection.module.scss @@ -0,0 +1,28 @@ +.container { + @apply py-16 px-10; + & > div { + @apply max-w-5xl mx-auto; + } + @apply items-center; + p { + @apply text-color-2 leading-relaxed text-xl text-center; + @apply mb-4; + // Desktop + @apply md:text-2xl md:indent-12 md:text-start; + } + + .bulletList { + @apply grid max-[678px]:grid-rows-3 md:grid-cols-3; + @apply gap-4 mb-8; + } + + .imageContainer { + @apply relative aspect-[16/9]; + @apply w-full; + @apply overflow-hidden; + @apply rounded-xl; + & > image { + @apply object-cover object-center; + } + } +} diff --git a/styles/layouts/Section.module.scss b/styles/layouts/Section.module.scss deleted file mode 100644 index fa20e13..0000000 --- a/styles/layouts/Section.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -// Header Styles -.primaryHeader { - @apply bg-color-3; - & > span { - @apply bg-color-1; - } -} -.secondaryHeader { - @apply bg-color-1; - & > span { - @apply bg-color-3; - } -} -// Default Styles -.primaryStyle { - @apply bg-color-3; - & > span { - @apply bg-color-1; - } -} -.secondaryStyle { - @apply bg-color-1; - & > span { - @apply bg-color-3; - } -} diff --git a/styles/layouts/SectionHeader.module.scss b/styles/layouts/SectionHeader.module.scss index 0652256..a4aac15 100644 --- a/styles/layouts/SectionHeader.module.scss +++ b/styles/layouts/SectionHeader.module.scss @@ -1,12 +1,5 @@ .container { @apply grid grid-cols-3 w-full overflow-hidden; - h2 { - @apply bg-inherit; - @apply rounded-t-lg; - @apply px-4 py-2; - @apply text-2xl font-bold text-center text-color-2; - @apply z-10; - } // Border Radius span { @apply relative flex-grow; @@ -38,3 +31,16 @@ grid-template-columns: 1fr max-content 3rem; } } +.container, +.inline { + h2 { + @apply rounded-t-lg; + @apply px-4 py-2; + @apply text-2xl font-bold text-center text-color-2; + @apply z-10; + } +} + +.inline > h2 { + @apply text-start text-4xl pl-32 pt-24; +} diff --git a/utils.ts b/utils.ts index df635f1..f21e9fb 100644 --- a/utils.ts +++ b/utils.ts @@ -68,3 +68,39 @@ export const formatDate = ( return moment(date).format('YYYY-MM-DD'); } }; + +// SectionStyling +/** + * The type of style to apply. + */ +export enum SectionStyle { + Primary, + Secondary, +} +/** + * The styling information for a section. + */ +interface SectionStyling { + foregroundColor: string; + backgroundColor: string; +} +/** + * A global helper for getting the style information within sections. + * @param style: The style of the section + * @returns {Object} The style information for the section + */ +export const getSectionStyling = (style: SectionStyle): SectionStyling => { + // Note: These classes are in ./styles/globals.scss + switch (style) { + case SectionStyle.Primary: + return { + foregroundColor: 'primaryForeground', + backgroundColor: 'primaryBackground', + }; + case SectionStyle.Secondary: + return { + foregroundColor: 'secondaryForeground', + backgroundColor: 'secondaryBackground', + }; + } +};