From ef428003fb62392014c770c7e64be1994c3a19a7 Mon Sep 17 00:00:00 2001 From: Jennifer Date: Sun, 22 Mar 2026 22:01:11 -0400 Subject: [PATCH 1/3] feat: added about us api integration --- frontend/components/ExecutiveSection.jsx | 346 ++++++++--------------- frontend/components/FlipCard.jsx | 37 ++- frontend/next.config.ts | 15 +- 3 files changed, 157 insertions(+), 241 deletions(-) diff --git a/frontend/components/ExecutiveSection.jsx b/frontend/components/ExecutiveSection.jsx index 76e1724..65ed3bc 100644 --- a/frontend/components/ExecutiveSection.jsx +++ b/frontend/components/ExecutiveSection.jsx @@ -1,245 +1,135 @@ +"use client"; + +import { useEffect, useState } from "react"; import FlipCard from "./FlipCard"; import SectionHeader from "./SectionHeader"; -const execTeamData = [ - { - title: "Presidents", - members: [ - { - name: "Danielle Santos", - position: "Co-President", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Josephine Tjhia", - position: "Co-President", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - }, - { - title: "Marketing", - members: [ - { - name: "Vicky Zhong", - position: "VP Marketing", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Krisha Patel", - position: "Director of Graphics", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Maissara Said Bakar", - position: "Director of Campus", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Conrad Mo", - position: "Director of Socials", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Fareeha Noor", - position: "Graphic Designer", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Kaitlyn Qian", - position: "Graphic Designer", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - }, - { - title: "Finance", - members: [ - { - name: "Rayyan Anam", - position: "VP Finance", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - }, - { - title: "Events", - members: [ - { - name: "Jennifer Huang", - position: "VP Events", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Shangavi Muhunthan", - position: "Event Coordinator", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Shubham Garg", - position: "Event Coordinator", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Sylvia Xu", - position: "Event Coordinator", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - }, - { - title: "Corporations", - members: [ - { - name: "Maanya Gupta", - position: "Associate", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Kehu Li", - position: "Associate", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Shubhra Rashmi", - position: "Associate", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - }, - { - title: "Mentorship", - members: [ - { - name: "Arushi Bhatt", - position: "VP Mentorship", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Kayleigh Doopan", - position: "Associate", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - }, - { - title: "First Years", - members: [ - { - name: "Rose Gu", - position: "First Year Rep", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Ire Oluyemi", - position: "First Year Rep", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - }, - { - name: "Thanusa Paskaran", - position: "First Year Rep", - description: "This person is super important for WiCSM. This person is super important for WiCSM.", - imageUrl: "/IMG_1710.jpg", - } - ] - } +async function getExecs() { + const res = await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/exec`, { + headers: { + Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`, + }, + }); + if (!res.ok) throw new Error("Failed to fetch exec data"); + const data = await res.json(); + return data.execs; +} + + +const SECTION_CONFIG = [ + { title: "Presidents", indexes: [[0, 1]] }, + { title: "Marketing", indexes: [[2, 5]] }, + { title: "Finance", indexes: [[6, 7]] }, + { title: "Events", indexes: [[8, 11]] }, + { title: "Corporations", indexes: [[12, 14]] }, + { title: "Mentorship", indexes: [[15, 17]] }, + { title: "First Years", indexes: [[18, 20]] }, ]; +function indexInRanges(index, ranges) { + return ranges.some(([start, end]) => index >= start && index <= end); +} + +function groupExecs(execs) { + const assignedIndexes = new Set(); + + const sections = SECTION_CONFIG + .map(({ title, indexes }) => { + const members = execs.filter((_, i) => { + if (indexInRanges(i, indexes)) { + assignedIndexes.add(i); + return true; + } + return false; + }); + return { title, members }; + }) + .filter((s) => s.members.length > 0); + + // Catch-all: everyone not assigned to a named section + const remaining = execs.filter((_, i) => !assignedIndexes.has(i)); + if (remaining.length > 0) { + sections.push({ title: "Executive Team", members: remaining }); + } + + return sections; +} + +function splitByRole(members) { + const vp = members.filter( + (m) => + m.role.includes("Vice President") || + m.role.includes("Co-President") || + m.role.includes("VP") + ); + const director = members.filter((m) => m.role.includes("Director")); + const other = members.filter( + (m) => + !m.role.includes("Vice President") && + !m.role.includes("Co-President") && + !m.role.includes("VP") && + !m.role.includes("Director") + ); + return { vp, director, other }; +} + +function MemberRow({ members }) { + if (!members.length) return null; + return ( +
+ {members.map((member) => ( + + ))} +
+ ); +} + export default function ExecutiveSection() { + const [execTeamData, setExecTeamData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + getExecs() + .then((execs) => setExecTeamData(groupExecs(execs))) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)); + }, []); + return (
- - Executive Team - + Executive Team

Meet the amazing people at WiCSM!

- {execTeamData.map((team) => { - const vpMembers = team.members.filter(m => m.position.includes("Vice President") || m.position.includes("Co-President") || m.position.includes("VP")); - const directorMembers = team.members.filter(m => m.position.includes("Director")); - const otherMembers = team.members.filter(m => - !m.position.includes("Vice President") && - !m.position.includes("Co-President") && - !m.position.includes("VP") && - !m.position.includes("Director") - ); - - return ( -
-

- {team.title} -

- - {/* VPs/Co-Prez row */} - {vpMembers.length > 0 && ( -
- {vpMembers.map((member) => ( - - ))} -
- )} - - {/* Directors row */} - {directorMembers.length > 0 && ( -
- {directorMembers.map((member) => ( - - ))} -
- )} - - {/* Other Members row */} - {otherMembers.length > 0 && ( -
- {otherMembers.map((member) => ( - - ))} -
- )} -
- ); - })} + {loading &&

Loading team...

} + {error &&

{error}

} + + {!loading && + !error && + execTeamData.map((team) => { + const { vp, director, other } = splitByRole(team.members); + return ( +
+

+ {team.title} +

+ + + + +
+ ); + })}
); diff --git a/frontend/components/FlipCard.jsx b/frontend/components/FlipCard.jsx index aa61cb2..7053543 100644 --- a/frontend/components/FlipCard.jsx +++ b/frontend/components/FlipCard.jsx @@ -3,6 +3,26 @@ import { useState } from "react"; import Image from "next/image"; +function PersonPlaceholder() { + return ( +
+
+ + {/* Head */} + + {/* Body */} + + +
+
+ ); +} + export default function FlipCard({ name, position, description, imageUrl }) { const [isFlipped, setIsFlipped] = useState(false); const [isHovered, setIsHovered] = useState(false); @@ -23,9 +43,7 @@ export default function FlipCard({ name, position, description, imageUrl }) { >
- {imageUrl && ( + {imageUrl ? (
+ ) : ( + )} -
-

+ +

+

{name}

-

+

{position}

@@ -83,4 +104,4 @@ export default function FlipCard({ name, position, description, imageUrl }) {
); -} +} \ No newline at end of file diff --git a/frontend/next.config.ts b/frontend/next.config.ts index e9ffa30..07d7c3b 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,7 +1,12 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - /* config options here */ +const nextConfig = { + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "uhugvwdetrwvhhrsyvoz.supabase.co", + }, + ], + }, }; -export default nextConfig; +export default nextConfig; \ No newline at end of file From dd1ca7e052b75a2a1724c7fa3fc8078018bb1b0c Mon Sep 17 00:00:00 2001 From: Jennifer Date: Sat, 11 Apr 2026 23:29:55 -0400 Subject: [PATCH 2/3] fix: fix branch based on PR comments --- frontend/app/api/execs/route.js | 32 ++++++++++ frontend/components/ExecutiveSection.jsx | 77 ++++++++++++----------- frontend/components/FlipCard.jsx | 22 +------ frontend/components/Navbar.tsx | 2 +- frontend/components/PersonPlaceholder.jsx | 21 +++++++ 5 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 frontend/app/api/execs/route.js create mode 100644 frontend/components/PersonPlaceholder.jsx diff --git a/frontend/app/api/execs/route.js b/frontend/app/api/execs/route.js new file mode 100644 index 0000000..ed74ecf --- /dev/null +++ b/frontend/app/api/execs/route.js @@ -0,0 +1,32 @@ + +export async function GET() { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; + const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + + if (!supabaseUrl || !supabaseAnonKey) { + return Response.json( + { + error: + "Server misconfiguration: NEXT_PUBLIC_SUPABASE_URL and/or " + + "NEXT_PUBLIC_SUPABASE_ANON_KEY are not set.", + }, + { status: 500 } + ); + } + + const res = await fetch(`${supabaseUrl}/functions/v1/exec`, { + headers: { + Authorization: `Bearer ${supabaseAnonKey}`, + }, + }); + + if (!res.ok) { + return Response.json( + { error: `Upstream error from Supabase: ${res.status} ${res.statusText}` }, + { status: res.status } + ); + } + + const data = await res.json(); + return Response.json(data); +} \ No newline at end of file diff --git a/frontend/components/ExecutiveSection.jsx b/frontend/components/ExecutiveSection.jsx index 65ed3bc..4e773f7 100644 --- a/frontend/components/ExecutiveSection.jsx +++ b/frontend/components/ExecutiveSection.jsx @@ -3,41 +3,36 @@ import { useEffect, useState } from "react"; import FlipCard from "./FlipCard"; import SectionHeader from "./SectionHeader"; +import ErrorState from "./ErrorState"; async function getExecs() { - const res = await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/exec`, { - headers: { - Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`, - }, - }); + const res = await fetch("/api/execs"); if (!res.ok) throw new Error("Failed to fetch exec data"); const data = await res.json(); return data.execs; } - const SECTION_CONFIG = [ - { title: "Presidents", indexes: [[0, 1]] }, - { title: "Marketing", indexes: [[2, 5]] }, - { title: "Finance", indexes: [[6, 7]] }, - { title: "Events", indexes: [[8, 11]] }, - { title: "Corporations", indexes: [[12, 14]] }, - { title: "Mentorship", indexes: [[15, 17]] }, - { title: "First Years", indexes: [[18, 20]] }, + { title: "Presidents", orderIndexes: [[1, 2]] }, + { title: "Marketing", orderIndexes: [[3, 11]] }, + { title: "Events", orderIndexes: [[12, 15]] }, + { title: "Corporations", orderIndexes: [[16, 18]] }, + { title: "Mentorship", orderIndexes: [[19, 20]] }, + { title: "Finance", orderIndexes: [[21, 21]] }, ]; -function indexInRanges(index, ranges) { - return ranges.some(([start, end]) => index >= start && index <= end); +function orderIndexInRanges(orderIndex, ranges) { + return ranges.some(([start, end]) => orderIndex >= start && orderIndex <= end); } function groupExecs(execs) { - const assignedIndexes = new Set(); + const assignedIds = new Set(); const sections = SECTION_CONFIG - .map(({ title, indexes }) => { - const members = execs.filter((_, i) => { - if (indexInRanges(i, indexes)) { - assignedIndexes.add(i); + .map(({ title, orderIndexes }) => { + const members = execs.filter((m) => { + if (orderIndexInRanges(m.order_index, orderIndexes)) { + assignedIds.add(m.id); return true; } return false; @@ -46,8 +41,7 @@ function groupExecs(execs) { }) .filter((s) => s.members.length > 0); - // Catch-all: everyone not assigned to a named section - const remaining = execs.filter((_, i) => !assignedIndexes.has(i)); + const remaining = execs.filter((m) => !assignedIds.has(m.id)); if (remaining.length > 0) { sections.push({ title: "Executive Team", members: remaining }); } @@ -56,20 +50,27 @@ function groupExecs(execs) { } function splitByRole(members) { - const vp = members.filter( - (m) => - m.role.includes("Vice President") || - m.role.includes("Co-President") || - m.role.includes("VP") - ); - const director = members.filter((m) => m.role.includes("Director")); - const other = members.filter( - (m) => - !m.role.includes("Vice President") && - !m.role.includes("Co-President") && - !m.role.includes("VP") && - !m.role.includes("Director") - ); + const vp = members.filter((m) => { + const role = m.role ?? ""; + return ( + role.includes("Vice President") || + role.includes("Co-President") || + role.includes("VP") + ); + }); + const director = members.filter((m) => { + const role = m.role ?? ""; + return role.includes("Director"); + }); + const other = members.filter((m) => { + const role = m.role ?? ""; + return ( + !role.includes("Vice President") && + !role.includes("Co-President") && + !role.includes("VP") && + !role.includes("Director") + ); + }); return { vp, director, other }; } @@ -112,7 +113,9 @@ export default function ExecutiveSection() {

{loading &&

Loading team...

} - {error &&

{error}

} + {error && ( + + )} {!loading && !error && diff --git a/frontend/components/FlipCard.jsx b/frontend/components/FlipCard.jsx index 7053543..91f19e6 100644 --- a/frontend/components/FlipCard.jsx +++ b/frontend/components/FlipCard.jsx @@ -2,26 +2,7 @@ import { useState } from "react"; import Image from "next/image"; - -function PersonPlaceholder() { - return ( -
-
- - {/* Head */} - - {/* Body */} - - -
-
- ); -} +import PersonPlaceholder from "./PersonPlaceholder"; export default function FlipCard({ name, position, description, imageUrl }) { const [isFlipped, setIsFlipped] = useState(false); @@ -67,6 +48,7 @@ export default function FlipCard({ name, position, description, imageUrl }) { src={imageUrl} alt={name} fill + sizes="320px" className="object-cover rounded-1xl [filter:drop-shadow(0_1px_3px_rgba(0,0,0,0.3))]" />
diff --git a/frontend/components/Navbar.tsx b/frontend/components/Navbar.tsx index aeda09b..089363b 100644 --- a/frontend/components/Navbar.tsx +++ b/frontend/components/Navbar.tsx @@ -6,7 +6,7 @@ import { useState } from "react"; const navItems = [ { href: "/", label: "HOME" }, - { href: "/about", label: "ABOUT" }, + { href: "/aboutus", label: "ABOUT" }, { href: "/events", label: "EVENTS" }, { href: "/contact", label: "CONTACT" }, ]; diff --git a/frontend/components/PersonPlaceholder.jsx b/frontend/components/PersonPlaceholder.jsx new file mode 100644 index 0000000..7e8cec1 --- /dev/null +++ b/frontend/components/PersonPlaceholder.jsx @@ -0,0 +1,21 @@ +export default function PersonPlaceholder() { + return ( +
+
+ +
+
+ ); +} \ No newline at end of file From 13cf730a7c56bb39ff4ad97edea8cf29e7aad100 Mon Sep 17 00:00:00 2001 From: Jennifer Date: Wed, 15 Jul 2026 20:59:01 -0400 Subject: [PATCH 3/3] fix: made changes based on pr feedback --- frontend/app/api/execs/route.js | 64 ++++-- frontend/components/ExecutiveSection.jsx | 275 ++++++++++++++--------- frontend/components/FlipCard.jsx | 17 +- frontend/components/Footer.tsx | 2 +- frontend/next.config.ts | 17 +- 5 files changed, 224 insertions(+), 151 deletions(-) diff --git a/frontend/app/api/execs/route.js b/frontend/app/api/execs/route.js index ed74ecf..2e6184c 100644 --- a/frontend/app/api/execs/route.js +++ b/frontend/app/api/execs/route.js @@ -1,32 +1,48 @@ +import { + getSupabaseCredentials, + hasMissingAnonCredentials, + requestSupabaseEdgeFunction, +} from "../utils/supabase"; export async function GET() { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; - const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + const credentials = getSupabaseCredentials(); - if (!supabaseUrl || !supabaseAnonKey) { - return Response.json( - { - error: - "Server misconfiguration: NEXT_PUBLIC_SUPABASE_URL and/or " + - "NEXT_PUBLIC_SUPABASE_ANON_KEY are not set.", - }, - { status: 500 } - ); - } + if (hasMissingAnonCredentials(credentials)) { + return Response.json( + { + error: + "Server misconfiguration: SUPABASE_URL and/or SUPABASE_ANON_KEY are not set.", + }, + { status: 500 }, + ); + } - const res = await fetch(`${supabaseUrl}/functions/v1/exec`, { - headers: { - Authorization: `Bearer ${supabaseAnonKey}`, - }, - }); + try { + const { response, body } = await requestSupabaseEdgeFunction( + credentials.url, + "exec", + credentials.anonKey, + ); - if (!res.ok) { - return Response.json( - { error: `Upstream error from Supabase: ${res.status} ${res.statusText}` }, - { status: res.status } - ); + if (!response.ok) { + return Response.json( + { + error: + body?.error || + body?.message || + `Upstream error from Supabase: ${response.status} ${response.statusText}`, + }, + { status: response.status }, + ); } - const data = await res.json(); - return Response.json(data); + return Response.json(body); + } catch (error) { + console.error("Failed to fetch executive data:", error); + + return Response.json( + { error: "Unable to retrieve executive data." }, + { status: 500 }, + ); + } } \ No newline at end of file diff --git a/frontend/components/ExecutiveSection.jsx b/frontend/components/ExecutiveSection.jsx index 4e773f7..49e704d 100644 --- a/frontend/components/ExecutiveSection.jsx +++ b/frontend/components/ExecutiveSection.jsx @@ -6,134 +6,193 @@ import SectionHeader from "./SectionHeader"; import ErrorState from "./ErrorState"; async function getExecs() { - const res = await fetch("/api/execs"); - if (!res.ok) throw new Error("Failed to fetch exec data"); - const data = await res.json(); - return data.execs; + const response = await fetch("/api/execs"); + const data = await response.json().catch(() => ({})); + + if (!response.ok) { + throw new Error(data.error || "Failed to fetch executive data"); + } + + if (!Array.isArray(data.execs)) { + throw new Error("Executive data was returned in an invalid format"); + } + + return data.execs; } const SECTION_CONFIG = [ - { title: "Presidents", orderIndexes: [[1, 2]] }, - { title: "Marketing", orderIndexes: [[3, 11]] }, - { title: "Events", orderIndexes: [[12, 15]] }, - { title: "Corporations", orderIndexes: [[16, 18]] }, - { title: "Mentorship", orderIndexes: [[19, 20]] }, - { title: "Finance", orderIndexes: [[21, 21]] }, + { title: "Presidents", orderIndexes: [[1, 2]] }, + { title: "Marketing", orderIndexes: [[3, 11]] }, + { title: "Events", orderIndexes: [[12, 15]] }, + { title: "Corporations", orderIndexes: [[16, 18]] }, + { title: "Mentorship", orderIndexes: [[19, 20]] }, + { title: "Finance", orderIndexes: [[21, 21]] }, ]; function orderIndexInRanges(orderIndex, ranges) { - return ranges.some(([start, end]) => orderIndex >= start && orderIndex <= end); + return ranges.some( + ([start, end]) => orderIndex >= start && orderIndex <= end, + ); } function groupExecs(execs) { - const assignedIds = new Set(); - - const sections = SECTION_CONFIG - .map(({ title, orderIndexes }) => { - const members = execs.filter((m) => { - if (orderIndexInRanges(m.order_index, orderIndexes)) { - assignedIds.add(m.id); - return true; - } - return false; - }); - return { title, members }; - }) - .filter((s) => s.members.length > 0); - - const remaining = execs.filter((m) => !assignedIds.has(m.id)); - if (remaining.length > 0) { - sections.push({ title: "Executive Team", members: remaining }); - } + const assignedIds = new Set(); - return sections; -} + const sections = SECTION_CONFIG.map(({ title, orderIndexes }) => { + const members = execs.filter((member) => { + if (orderIndexInRanges(member.order_index, orderIndexes)) { + assignedIds.add(member.id); + return true; + } -function splitByRole(members) { - const vp = members.filter((m) => { - const role = m.role ?? ""; - return ( - role.includes("Vice President") || - role.includes("Co-President") || - role.includes("VP") - ); + return false; }); - const director = members.filter((m) => { - const role = m.role ?? ""; - return role.includes("Director"); - }); - const other = members.filter((m) => { - const role = m.role ?? ""; - return ( - !role.includes("Vice President") && - !role.includes("Co-President") && - !role.includes("VP") && - !role.includes("Director") - ); + + return { + title, + members, + }; + }).filter((section) => section.members.length > 0); + + const remainingMembers = execs.filter( + (member) => !assignedIds.has(member.id), + ); + + if (remainingMembers.length > 0) { + sections.push({ + title: "Executive Team", + members: remainingMembers, }); - return { vp, director, other }; + } + + return sections; } -function MemberRow({ members }) { - if (!members.length) return null; +function splitByRole(members) { + const vicePresidents = members.filter((member) => { + const role = member.role ?? ""; + return ( -
- {members.map((member) => ( - - ))} -
+ role.includes("Vice President") || + role.includes("Co-President") || + role.includes("VP") ); -} + }); -export default function ExecutiveSection() { - const [execTeamData, setExecTeamData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const directors = members.filter((member) => { + const role = member.role ?? ""; + return role.includes("Director"); + }); - useEffect(() => { - getExecs() - .then((execs) => setExecTeamData(groupExecs(execs))) - .catch((err) => setError(err.message)) - .finally(() => setLoading(false)); - }, []); + const otherMembers = members.filter((member) => { + const role = member.role ?? ""; return ( -
-
- Executive Team - -

- Meet the amazing people at WiCSM! -

- - {loading &&

Loading team...

} - {error && ( - - )} - - {!loading && - !error && - execTeamData.map((team) => { - const { vp, director, other } = splitByRole(team.members); - return ( -
-

- {team.title} -

- - - - -
- ); - })} -
-
+ !role.includes("Vice President") && + !role.includes("Co-President") && + !role.includes("VP") && + !role.includes("Director") ); + }); + + return { + vicePresidents, + directors, + otherMembers, + }; +} + +function MemberRow({ members }) { + if (members.length === 0) { + return null; + } + + return ( +
+ {members.map((member) => ( + + ))} +
+ ); +} + +export default function ExecutiveSection() { + const [execTeamData, setExecTeamData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + + useEffect(() => { + async function loadExecs() { + try { + setLoading(true); + setError(""); + + const execs = await getExecs(); + setExecTeamData(groupExecs(execs)); + } catch (err) { + setError( + err instanceof Error + ? err.message + : "Unable to load executive data", + ); + } finally { + setLoading(false); + } + } + + loadExecs(); + }, []); + + return ( +
+
+ Executive Team + +

+ Meet the amazing people at WiCSM! +

+ + {loading && ( +

+ Loading team... +

+ )} + + {!loading && error && ( + + )} + + {!loading && + !error && + execTeamData.map((team) => { + const { + vicePresidents, + directors, + otherMembers, + } = splitByRole(team.members); + + return ( +
+

+ {team.title} +

+ + + + +
+ ); + })} +
+
+ ); } \ No newline at end of file diff --git a/frontend/components/FlipCard.jsx b/frontend/components/FlipCard.jsx index 91f19e6..2523c58 100644 --- a/frontend/components/FlipCard.jsx +++ b/frontend/components/FlipCard.jsx @@ -2,7 +2,6 @@ import { useState } from "react"; import Image from "next/image"; -import PersonPlaceholder from "./PersonPlaceholder"; export default function FlipCard({ name, position, description, imageUrl }) { const [isFlipped, setIsFlipped] = useState(false); @@ -24,7 +23,9 @@ export default function FlipCard({ name, position, description, imageUrl }) { >
- {imageUrl ? ( + {imageUrl && (
{name}
- ) : ( - )} - -
-

+

+

{name}

-

+

{position}

diff --git a/frontend/components/Footer.tsx b/frontend/components/Footer.tsx index 41a2f85..a0b0bf3 100644 --- a/frontend/components/Footer.tsx +++ b/frontend/components/Footer.tsx @@ -29,7 +29,7 @@ export default function Footer() { {/* Quick Links */}
Home - About + About Events Contact
diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 07d7c3b..2d8c49b 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,12 +1,13 @@ const nextConfig = { - images: { - remotePatterns: [ - { - protocol: "https", - hostname: "uhugvwdetrwvhhrsyvoz.supabase.co", - }, - ], - }, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "uhugvwdetrwvhhrsyvoz.supabase.co", + pathname: "/storage/v1/object/public/exec-images/**", + }, + ], + }, }; export default nextConfig; \ No newline at end of file