diff --git a/.github/workflows/development.yaml b/.github/workflows/development.yaml index f3131be..0d7c055 100644 --- a/.github/workflows/development.yaml +++ b/.github/workflows/development.yaml @@ -28,6 +28,8 @@ jobs: - name: Build Next.js app run: npm run build + env: + NEXT_PUBLIC_CMS_BASE_URL: ${{ vars.CMS_BASE_URL }} - name: Success run: echo "!! Build ran successfully !!" diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml index beacd77..eaf578b 100644 --- a/.github/workflows/staging.yaml +++ b/.github/workflows/staging.yaml @@ -32,6 +32,8 @@ jobs: -n ${{ vars.VERCEL_PROJECT }} --yes --prod + --env CMS_BASE_URL=${{ vars.CMS_BASE_URL }} + --build-env CMS_BASE_URL=${{ vars.CMS_BASE_URL }} - name: Success run: echo "!! Deployment successful !!" diff --git a/app/(pages)/_components/AlumniPageCard/AlumniPageCard.jsx b/app/(pages)/_components/AlumniPageCard/AlumniPageCard.jsx index 2273339..371246b 100644 --- a/app/(pages)/_components/AlumniPageCard/AlumniPageCard.jsx +++ b/app/(pages)/_components/AlumniPageCard/AlumniPageCard.jsx @@ -91,8 +91,11 @@ export default function AlumniPageCard(info) {

{role2}

{role3}

- -

{description}

+ {/* Below is LONG_TEXT */} +
diff --git a/app/(pages)/_components/AlumniPageContent/AlumniPageContent.jsx b/app/(pages)/_components/AlumniPageContent/AlumniPageContent.jsx new file mode 100644 index 0000000..400a370 --- /dev/null +++ b/app/(pages)/_components/AlumniPageContent/AlumniPageContent.jsx @@ -0,0 +1,71 @@ +/* eslint-disable react/prop-types */ +"use client" +import styles from "./AlumniPageContent.module.scss"; +import AlumniPageCard from "../AlumniPageCard/AlumniPageCard.jsx"; +import Image from "next/image"; +import React, {useState} from "react"; + +export default function AlumniPageContent({cards}) { + const [searchTerm, setSearchTerm] = useState(""); + + return ( +
+ + +
+ +
+
+

Meet our Alumni

+
+ Magnifying glass icon + + setSearchTerm(e.target.value)} + /> + +
+
+
+ Jackie sticker +
+
+ +
+ {cards.map((alum, index) => ( + + ))} +
+
+
+ ); + +} \ No newline at end of file diff --git a/app/(pages)/alumni/page.module.scss b/app/(pages)/_components/AlumniPageContent/AlumniPageContent.module.scss similarity index 98% rename from app/(pages)/alumni/page.module.scss rename to app/(pages)/_components/AlumniPageContent/AlumniPageContent.module.scss index 29eab69..48e5dc4 100644 --- a/app/(pages)/alumni/page.module.scss +++ b/app/(pages)/_components/AlumniPageContent/AlumniPageContent.module.scss @@ -1,5 +1,5 @@ -@use "../_globals/mixins.scss"; +@use "../../_globals/mixins.scss"; .pagewrapper{ diff --git a/app/(pages)/_components/BoardMembers/BoardMembers.jsx b/app/(pages)/_components/BoardMembers/BoardMembers.jsx new file mode 100644 index 0000000..1c398b2 --- /dev/null +++ b/app/(pages)/_components/BoardMembers/BoardMembers.jsx @@ -0,0 +1,35 @@ +/* eslint-disable react/prop-types */ +import styles from "./BoardMembers.module.scss" +import Image from "next/image"; +import React from "react"; + +export default function BoardMembers({ board }) { + console.log(board); + return ( +
+

Our board

+
+
+ {board.map((member, i) => ( +
+
+ {member.imageAlt} +
+
+

{member.name}

+

{member.role}

+

{member.majors.length > 1 ? "Majors: " + member.majors.join(" & ") : "Major: " + member.majors[0]}

+ {member.minors.length > 0 &&

{member.minors.length > 1 ? "Minors: " + member.minors.join(" & ") : "Minor: " + member.minors[0]}

} +

{member.year}

+
+
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/app/(pages)/_components/BoardMembers/BoardMembers.module.scss b/app/(pages)/_components/BoardMembers/BoardMembers.module.scss new file mode 100644 index 0000000..ecb8682 --- /dev/null +++ b/app/(pages)/_components/BoardMembers/BoardMembers.module.scss @@ -0,0 +1,67 @@ +@use "app/(pages)/_globals/mixins.scss"; + +.board { + display: flex; + flex-direction: column; + align-items: center; + gap: 82px; + + h1 { + color: var(--gold); + } + + @include mixins.phone { + gap: 33px; + } +} + +.boardGrid { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 129px; + + @include mixins.phone { + gap: 33px; + } +} + +.member { + width: 370px; + gap: 15px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + + p { + font-size: 1.25rem; + text-align: center; + } + + @include mixins.phone { + width: 100%; + max-width: 300px; + + p { + font-size: 0.75rem; + } + } +} + +.memberImageContainer { + width: 225px; + height: 225px; + position: relative; + border-radius: 50%; + overflow: hidden; + + @include mixins.phone { + width: 100px; + height: 100px; + } +} + +.position { + font-weight: 500; +} \ No newline at end of file diff --git a/app/(pages)/_components/ExpandedAlumniCard/ExpandedAlumniCard.jsx b/app/(pages)/_components/ExpandedAlumniCard/ExpandedAlumniCard.jsx index 9da7f25..f609bdc 100644 --- a/app/(pages)/_components/ExpandedAlumniCard/ExpandedAlumniCard.jsx +++ b/app/(pages)/_components/ExpandedAlumniCard/ExpandedAlumniCard.jsx @@ -76,7 +76,11 @@ export default function ExpandedAlumniCard({ -

{description}

+ {/* Below is LONG_TEXT */} +

diff --git a/app/(pages)/_components/ProductDetailContent/ProductDetailContent.jsx b/app/(pages)/_components/ProductDetailContent/ProductDetailContent.jsx new file mode 100644 index 0000000..4fc150d --- /dev/null +++ b/app/(pages)/_components/ProductDetailContent/ProductDetailContent.jsx @@ -0,0 +1,72 @@ +/* eslint-disable react/prop-types */ +'use client'; +import styles from "./ProductDetailContent.module.scss"; +import React from 'react'; +import Image from 'next/image'; +import Link from 'next/link' + +export default function ProductDetailContent({ products, id }) { + const product = products.find(p => p.id == id); + const similarItems = product?.similar_item_ids?.map(id => products.find(p => p.id == id)).filter(Boolean); + const description = product?.description || "No description available."; + + if (!product) return

Product not found.

; + return ( +
+
+ < +

Merch Store

+
+ +
+ {product.imageAlt} + +
+

{product.name}

+
+

{product.price}

+

+

+
+
+
+ +
+ +

Similar Items

+ +
+ {similarItems?.map((item) => ( + +
+ {item.imageAlt} +
+ +
+

{item.name}

+

{item.price}

+
+ + ))} +
+
+ +
+ ); + } + + \ No newline at end of file diff --git a/app/(pages)/products/[id]/page.module.scss b/app/(pages)/_components/ProductDetailContent/ProductDetailContent.module.scss similarity index 100% rename from app/(pages)/products/[id]/page.module.scss rename to app/(pages)/_components/ProductDetailContent/ProductDetailContent.module.scss diff --git a/app/(pages)/_components/StoreListingContent/StoreListingContent.jsx b/app/(pages)/_components/StoreListingContent/StoreListingContent.jsx new file mode 100644 index 0000000..b9e375e --- /dev/null +++ b/app/(pages)/_components/StoreListingContent/StoreListingContent.jsx @@ -0,0 +1,117 @@ +/* eslint-disable react/prop-types */ +"use client"; +import styles from "./StoreListingContent.module.scss" +import React, { useState, useEffect } from 'react'; +import Image from 'next/image'; +import Link from 'next/link' +import Button from "@/app/(pages)/_components/Button/Button"; +import { useRouter } from 'next/navigation'; + +export function StoreListingContent({ products }) { + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 6; + + const initialCategory = 'All'; + const [selectedCategory, setSelectedCategory] = useState("All"); + + const filteredProducts = products.filter( + (product) => selectedCategory === "All" || product.categories.includes(selectedCategory) + ); + + const totalPages = Math.ceil(filteredProducts.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const currentProducts = filteredProducts.slice(startIndex, startIndex + itemsPerPage); + + const categories = [ + "All", + ...Array.from( + new Set( + products + .flatMap(product => product.categories?.split(',')) + .map(category => category?.trim()) + .filter(Boolean) + ) + ) + ]; + + const handlePageChange = (page) => { + if (page >= 1 && page <= totalPages) { + setCurrentPage(page); + } + }; + + const handleCategoryChange = (category) => { + setSelectedCategory(category); + setCurrentPage(1); + router.push(`/store?category=${category}`); + }; + + const router = useRouter(); + + useEffect(() => { + setSelectedCategory(initialCategory); + }, [initialCategory]); + + return ( +
+
+
+

Merch Store

+ +
+ {categories.map((category) => ( + + ))} + + +
+
+ +
+ +
+ {currentProducts.map((product) => ( + + {product.imageAlt} + +
+

{product.name}

+

{product.price}

+
+ + ))} +
+ + {totalPages > 1 && ( +
+
+ + + + {Array.from({ length: totalPages }, (_, i) => ( + + ))} + + + +
+
+ )} +
+ ); +} + diff --git a/app/(pages)/store/page.module.scss b/app/(pages)/_components/StoreListingContent/StoreListingContent.module.scss similarity index 98% rename from app/(pages)/store/page.module.scss rename to app/(pages)/_components/StoreListingContent/StoreListingContent.module.scss index 8441cec..6f7987b 100644 --- a/app/(pages)/store/page.module.scss +++ b/app/(pages)/_components/StoreListingContent/StoreListingContent.module.scss @@ -13,6 +13,7 @@ .main { width: 100%; + padding: 72px 0px 70px 0px; } .top { @@ -21,7 +22,6 @@ justify-content: space-between; width: 100%; padding-bottom: 84px; - padding-top: 72px; @include phone{ padding-bottom: 32px; diff --git a/app/(pages)/about-us/page.jsx b/app/(pages)/about-us/page.jsx index a2cb023..9e35b35 100644 --- a/app/(pages)/about-us/page.jsx +++ b/app/(pages)/about-us/page.jsx @@ -1,55 +1,40 @@ import styles from "./page.module.scss"; import Image from "next/image"; import React from "react"; +import BoardMembers from "../_components/BoardMembers/BoardMembers"; +import boardMembersFallbackData from "@/app/_data/board-members.json" -export default function Home() { - const board = [ - { - name: "Claudia Colorado", - role: "President", - img: "/aboutUsImages/president.png", - major: "Managerial Economics & English", - year: "Junior", - }, - { - name: "Yuri Nishii", - role: "Vice-President", - img: "/aboutUsImages/vp.png", - major: "Communications & English", - minor: "Technology Management", - year: "Senior", - }, - { - name: "Chris Calub", - role: "Treasurer", - img: "/aboutUsImages/treasurer.png", - major: "English & Communications", - year: "Junior", - }, - { - name: "Audrey Zhang", - role: "Outreach Chair", - img: "/aboutUsImages/outreach.png", - major: "Economics & English", - year: "Junior", - }, - { - name: "Nea Le", - role: "Publicity Chair", - img: "/aboutUsImages/publicity.png", - major: "Comparative Literature", - minor: "Japanese", - year: "Sophomore", - }, - { - name: "Ri Herrera", - role: "Design Lead", - img: "/aboutUsImages/design.png", - major: "Sociology & English", - minor: "History", - year: "Sophomore", +async function getBoardMembers() { + try { + const res = await fetch( + // eslint-disable-next-line no-undef + `${process.env.NEXT_PUBLIC_CMS_BASE_URL}/api/content/board-members?_published=true`, + { next: { tag: "cms" } } + ); + const data = await res.json(); + if (!data.ok || !data.body || data.body.length === 0) { + throw new Error(data.error); } - ]; + const parsedData = data.body.map((card) => { + return { + imageUrl: card.image[0].src, + imageAlt: card.image_alt_text, + name: card.name, + role: card.position, + majors: card.majors.split(',').map(s => s.trim()).filter(Boolean), + minors: card.minors ? card.minors.split(',').map(s => s.trim()).filter(Boolean) : [], + grade: card.grade, + }; + }); + return parsedData; + } catch (e) { + console.error(`Failed to fetch board-members: ${e.message}`); + return boardMembersFallbackData; + } +} + +export default async function Home() { + const boardMemberData = await getBoardMembers(); return (
@@ -141,32 +126,7 @@ export default function Home() { - -
-

Our board

-
-
- {board.map((member, i) => ( -
-
- {member.role} -
-
-

{member.name}

-

{member.role}

-

{member.major.includes("&")? "Majors: " + member.major : "Major: " + member.major}

- {member.minor &&

{member.minor.includes("&")? "Minors: " : "Minor: " + member.minor}

} -

{member.year}

-
-
- ))} -
-
-
+
); diff --git a/app/(pages)/about-us/page.module.scss b/app/(pages)/about-us/page.module.scss index cb73825..dafa9e9 100644 --- a/app/(pages)/about-us/page.module.scss +++ b/app/(pages)/about-us/page.module.scss @@ -161,69 +161,4 @@ width: 100%; height: 130px; } -} - -.board { - display: flex; - flex-direction: column; - align-items: center; - gap: 82px; - - h1 { - color: var(--gold); - } - - @include mixins.phone { - gap: 33px; - } -} - -.boardGrid { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 129px; - - @include mixins.phone { - gap: 33px; - } -} - -.member { - width: 370px; - gap: 15px; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - - p { - font-size: 1.25rem; - text-align: center; - } - - @include mixins.phone { - width: 100%; - max-width: 300px; - - p { - font-size: 0.75rem; - } - } -} - -.memberImageContainer { - width: 225px; - height: 225px; - position: relative; - border-radius: 50%; - - @include mixins.phone { - width: 100px; - height: 100px; - } -} - -.position { - font-weight: 500; } \ No newline at end of file diff --git a/app/(pages)/alumni/page.jsx b/app/(pages)/alumni/page.jsx index 4169d55..84c8f43 100644 --- a/app/(pages)/alumni/page.jsx +++ b/app/(pages)/alumni/page.jsx @@ -1,177 +1,47 @@ -"use client" -import styles from "./page.module.scss"; -import AlumniPageCard from "../_components/AlumniPageCard/AlumniPageCard.jsx"; -import Image from "next/image"; -import React, {useState} from "react"; - -const alumniList = [ - { - first_name:"Jack", - last_name:"Hyslop", - role1:"Founder, 2020–2022", - role2:"", - role3:"", - description:( - <> - Jack started the club with his friends Maeve and Isabella in Winter Quarter 2020. - He remained as primary leader until his graduation in Spring Quarter 2022. -
-
- Since graduating, he has stayed creatively engaged. - Recently, he has become a drummer in a band with his friends. - He has taken sewing classes at a local city college and joined Radical Sewing Club (@radicalsewingclub) as a volunteer assisting folks - with using a sewing machine to mend and alter their clothes. - He rescued a kitten named Boots about two years ago. - - ), - imageUrl:"/AlumniPage/jack.png", - instaHandle:"@radicalsewingclub", - instaLink:"https://www.instagram.com/", /*General link for now*/ - linkedinLink:"https://www.linkedin.com/", /*General link for now*/ - linkedinHandle:"linkedin link", - }, - { - first_name:"Leila", - last_name:"Sanchez", - role1:"President, 2020–2023", - description:( - <> - After graduation in 2023, Leila Sanchez now works at the Social Security Administration - while working on her writing career in Urban Fantasy and Horror. - She self published the short story, The Challenge at the Carmichael Mansion in late 2023, - and is currently on other writing projects, while breaking down tropes and providing her own - analysis on shows and movies on her TikTok and Instagram, @Leilasstorycorner. - - ), - imageUrl:"/AlumniPage/Leila.jpg", - instaHandle:"@Leilasstorycorner", - instaLink:"https://www.instagram.com/", /*General link for now*/ - linkedinLink:"https://www.linkedin.com/", /*General link for now*/ - linkedinHandle:"linkedin link", - }, - { - first_name:"Annie", - last_name:"Tran", - role1:"Vice President, 2022–2023", - role2:"President, 2023-2024", - role3:"", - description:( - <> - Annie Tran joined the club in fall of 2020—during lockdown—serving as president in her final year at davis. - Now having returned home, she is working in quality assurance and, in her leisure time, writing a fantasy novel. - - ), - imageUrl:"/AlumniPage/Annie.jpg", - instaHandle:"@instagram", - instaLink:"https://www.instagram.com/", /*General link for now*/ - linkedinLink:"https://www.linkedin.com/", /*General link for now*/ - linkedinHandle:"linkedin link", - }, - { - first_name:"Jakob", - last_name:"Stanton", - role1:"Vice President, 2023–2024", - description:( - <> - Jakob Stanton was the secondary leader of Aggie Fiction from 2023-2024. - He graduated 2024 with a bachelor's degree in History and a minor in sociology. - He researched and wrote an honors thesis about aeronautical culture in Germany from 1872-1929 titled We Dead Fliers. - He is currently pursuing a masters in Screenwriting at Chapman University in Orange CA where he is trying to solve the - puzzle that is screenwriting with two sweet black cats named Bucket and Boba. - - ), - imageUrl:"/AlumniPage/Jakob.png", - instaHandle:"@instagram", - instaLink:"https://www.instagram.com/", /*General link for now*/ - linkedinLink:"https://www.linkedin.com/", /*General link for now*/ - linkedinHandle:"linkedin link", - }, - { - first_name:"Erika", - last_name:"DiMaano", - role1:"Publicity Chair, 2022–2024", - role2:"", - role3:"", - description:( - <> - Erika DiMaano is an interaction designer who's been working on various interactive digital projects and design work. - She's recently been branching out to web development and learning new skills to bring her work further. - - ), - imageUrl:"/AlumniPage/Erika.png", - instaHandle:"@instagram", - instaLink:"https://www.instagram.com/", /*General link for now*/ - linkedinLink:"https://www.linkedin.com/", /*General link for now*/ - linkedinHandle:"linkedin link", - }, - -] - -export default function almuni() { - const [searchTerm, setSearchTerm] = useState(""); - - const filteredAlumni = alumniList.filter((alumniMember) => - `${alumniMember.first_name} ${alumniMember.last_name}`.toLowerCase().includes(searchTerm.toLowerCase()) +import React from "react"; +import AlumniPageContent from "../_components/AlumniPageContent/AlumniPageContent.jsx" +import AlumniFallbackData from "../../_data/alumni-cards.json" + +async function getCards() { + try { + const res = await fetch( + // eslint-disable-next-line no-undef + `${process.env.NEXT_PUBLIC_CMS_BASE_URL}/api/content/alumni-cards?_published=true`, + { next: { tag: "cms" } } ); - - return ( -
- - -
- -
-
-

Meet Our Alumni

-
- Magnifying glass icon - - setSearchTerm(e.target.value)} - /> - -
-
-
- Jackie sticker -
-
- -
- {filteredAlumni.map((alum, index) => ( - - ))} -
-
-
- ); - + const data = await res.json(); + if (!data.ok || !data.body) { + throw new Error(data.error); + } + if (data.body.length === 0) { + return []; + } + const parsedData = data.body.map((card) => { + const splitName = card.name.split(" "); + return { + imageUrl: card.image[0].src, + imageAlt: card.image_alt_text, + first_name: splitName[0], + last_name: splitName[1], + role1: card.position_1, + role2: card.position_2 ? card.position_2 : null, + role3: card.position_3 ? card.position_3 : null, + description: card.description, + instaHandle: card.instagram_username, + instaLink: `https://www.instagram.com/${card.instagram_username}`, + linkedinHandle: card.linkedin_username, + linkedinLink: card.linkedin_url, + }; + }); + return parsedData; + } catch (e) { + console.error(`Failed to fetch alumni-cards: ${e.message}`); + return AlumniFallbackData; + } +} + +export default async function Alumni() { + const cards = await getCards(); + + return } \ No newline at end of file diff --git a/app/(pages)/products/[id]/ProductDetailClient.jsx b/app/(pages)/products/[id]/ProductDetailClient.jsx new file mode 100644 index 0000000..954b0bf --- /dev/null +++ b/app/(pages)/products/[id]/ProductDetailClient.jsx @@ -0,0 +1,11 @@ +/* eslint-disable react/prop-types */ +"use client"; +import React from 'react'; +import { useParams } from 'next/navigation'; +import ProductDetailContent from '@/app/(pages)/_components/ProductDetailContent/ProductDetailContent'; + +export default function ProductDetailClient({ products }) { + const params = useParams(); + const id = params.id; + return ; +} \ No newline at end of file diff --git a/app/(pages)/products/[id]/page.jsx b/app/(pages)/products/[id]/page.jsx index 5a0d3d9..75ddb74 100644 --- a/app/(pages)/products/[id]/page.jsx +++ b/app/(pages)/products/[id]/page.jsx @@ -1,152 +1,43 @@ -'use client'; -import styles from "./page.module.scss"; import React from 'react'; -import Image from 'next/image'; -import Link from 'next/link' -import { useParams } from 'next/navigation'; - -const products = [ - { - id: 1, - name: "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", - cost: "$16.00", - image: "/TestImages/AgFiTote.png", - description: "[Input Description]", - similar: [1, 2, 5] - - }, - { - id: 2, - name: "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", - cost: "$16.00", - image: "/TestImages/JackieOnMoonTote.jpg", - description: "[Input Description]", - similar: [1, 2, 3] - }, - { - id: 3, - name: "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", - cost: "$16.00", - image: "/TestImages/JackieTote.png", - description: "[Input Description]", - similar: [4, 5, 6] - }, - { - id: 4, - name: "Colonizer Jackie Sticker", - cost: "$16.00", - image: "/TestImages/Jackie1.jpg", - description: "[Input Description]", - similar: [7, 8, 9] - }, - - { - id: 5, - name: "Coquette Jackie Sticker", - cost: "$16.00", - image: "/TestImages/Jackie2.jpg", - description: "[Input Description]", - similar: [1, 2, 6] - }, - - { - id: 6, - name: "Boba Jackie Sticker", - cost: "$16.00", - image: "/TestImages/JackieBoba.png", - description: "[Input Description]", - similar: [1, 2, 6] - }, - - { - id: 7, - name: "Eepy Jackie Sticker", - cost: "$16.00", - image: "/TestImages/EepyJackie.jpg", - description: "[Input Description]", - similar: [1, 2, 6] - }, - - { - id: 8, - name: "Santa Jackie Sticker", - cost: "$16.00", - image: "/TestImages/SantaJackie.jpg", - description: "[Input Description]", - similar: [1, 2, 6] - }, - - { - id: 9, - name: "Jackie Maud'dib Sticker", - cost: "$16.00", - image: "/TestImages/JackieMaud'dib.jpg", - description: "[Input Description]", - similar: [1, 2, 6] - }, - ]; - - -export default function ProductDetail() { - const params = useParams(); - const id = params.id; - - const product = products.find(p => p.id === Number(id)); - const similarItems = product?.similar?.map(id => products.find(p => p.id === id)).filter(Boolean); - - if (!product) return

Product not found.

; - return ( -
-
- < -

Merch Store

-
- -
- {product.name} - -
-

{product.name}

-
-

{product.cost}

-

{product.description || "No description available."}

-
-
-
- -
- -

Similar Items

- -
- {similarItems?.map((item) => ( - -
- {item.name} -
- -
-

{item.name}

-

{item.cost}

-
- - ))} -
-
- -
- ); - } +import storeProductsFallbackData from '@/app/_data/store-products.json'; +import ProductDetailClient from './ProductDetailClient'; + +async function getProducts() { + try { + const res = await fetch( + // eslint-disable-next-line no-undef + `${process.env.NEXT_PUBLIC_CMS_BASE_URL}/api/content/store-products?_published=true`, + { next: { tag: "cms" } } + ); + const data = await res.json(); + if (!data.ok || !data.body) { + throw new Error(data.error); + } + if (data.body.length === 0) { + return []; + } + const parsedData = data.body.map((card) => { + const similar_item_ids = card.similar_item_ids.split(',').map(s => s.trim()).filter(Boolean); + return { + id: card._id, + imageUrl: card.image[0].src, + imageAlt: card.image_alt_text, + name: card.name, + price: card.price, + description: card.description, + similar_item_ids, + categories: card.categories, + }; + }); + return parsedData; + } catch (e) { + console.error(`Failed to fetch store-products: ${e.message}`); + return storeProductsFallbackData; + } +} + +export default async function ProductDetail() { + const products = await getProducts(); + return ; +} diff --git a/app/(pages)/store/page.jsx b/app/(pages)/store/page.jsx index f6b03c8..e5913d5 100644 --- a/app/(pages)/store/page.jsx +++ b/app/(pages)/store/page.jsx @@ -1,175 +1,46 @@ -'use client'; -import styles from "./page.module.scss"; -import React, { useState, useEffect } from 'react'; -import Image from 'next/image'; -import Link from 'next/link' -import Button from "../_components/Button/Button"; -import { useRouter } from 'next/navigation'; - - -const products = [ - { - id: 1, - name: "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", - cost: "$16.00", - image: "/TestImages/AgFiTote.png", - category: "tote" - }, - { - id: 2, - name: "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", - cost: "$16.00", - image: "/TestImages/JackieOnMoonTote.jpg", - category: "tote" - }, - { - id: 3, - name: "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", - cost: "$16.00", - image: "/TestImages/JackieTote.png", - category: "tote" - }, - { - id: 4, - name: "Colonizer Jackie Sticker", - cost: "$16.00", - image: "/TestImages/Jackie1.jpg", - category: "sticker" - }, - { - id: 5, - name: "Coquette Jackie Sticker", - cost: "$16.00", - image: "/TestImages/Jackie2.jpg", - category: "sticker" - }, - { - id: 6, - name: "Boba Jackie Sticker", - cost: "$16.00", - image: "/TestImages/JackieBoba.png", - category: "sticker" - }, - { - id: 7, - name: "Eepy Jackie Sticker", - cost: "$16.00", - image: "/TestImages/EepyJackie.jpg", - category: "sticker" - }, - { - id: 8, - name: "Santa Jackie Sticker", - cost: "$16.00", - image: "/TestImages/SantaJackie.jpg", - category: "sticker" - }, - { - id: 9, - name: "Jackie Maud'dib Sticker", - cost: "$16.00", - image: "/TestImages/JackieMaud'dib.jpg", - category: "sticker" - }, - ]; - -export default function Storelisting() { - - const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 6; - - const initialCategory = 'all'; - const [selectedCategory, setSelectedCategory] = useState("all"); - - const filteredProducts = products.filter( - (product) => selectedCategory === "all" || product.category === selectedCategory - ); - - const totalPages = Math.ceil(filteredProducts.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const currentProducts = filteredProducts.slice(startIndex, startIndex + itemsPerPage); - - const handlePageChange = (page) => { - if (page >= 1 && page <= totalPages) { - setCurrentPage(page); +/* eslint-disable no-undef */ +import React from 'react'; +import { StoreListingContent } from '../_components/StoreListingContent/StoreListingContent' +import storeProductsFallbackData from '@/app/_data/store-products.json'; + +async function getProducts() { + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_CMS_BASE_URL}/api/content/store-products?_published=true`, + { next: { tag: "cms" } } + ); + const data = await res.json(); + if (!data.ok || !data.body) { + throw new Error(data.error); + } + if (data.body.length === 0) { + return []; + } + const parsedData = data.body.map((card) => { + return { + id: card._id, + imageUrl: card.image[0].src, + imageAlt: card.image_alt_text, + name: card.name, + price: card.price, + description: card.description, + similar_item_ids: card.similar_item_ids, + categories: card.categories, + }; + }); + return parsedData; + } catch (e) { + console.error(`Failed to fetch store-products: ${e.message}`); + return storeProductsFallbackData; } - }; +} - const handleCategoryChange = (category) => { - setSelectedCategory(category); - setCurrentPage(1); - router.push(`/store?category=${category}`); - }; - - const router = useRouter(); - - useEffect(() => { - setSelectedCategory(initialCategory); - }, [initialCategory]); +export default async function StoreListing() { + const products = await getProducts(); return ( -
-
-
-

Merch Store

- -
- - - - -
-
- -
- -
- {currentProducts.map((product) => ( - - {product.name} - -
-

{product.name}

-

{product.cost}

-
- - ))} -
- - {totalPages > 1 && ( -
-
- - - - {Array.from({ length: totalPages }, (_, i) => ( - - ))} - - - -
-
- )} -
- ); - } - + + ); +} \ No newline at end of file diff --git a/app/_data/alumni-cards.json b/app/_data/alumni-cards.json new file mode 100644 index 0000000..430a100 --- /dev/null +++ b/app/_data/alumni-cards.json @@ -0,0 +1,67 @@ +[ + { + "first_name": "Jack", + "last_name": "Hyslop", + "role1": "Founder, 2020–2022", + "role2": "", + "role3": "", + "description": "Jack started the club with his friends Maeve and Isabella in Winter Quarter 2020. He remained as primary leader until his graduation in Spring Quarter 2022.\n\nSince graduating, he has stayed creatively engaged. Recently, he has become a drummer in a band with his friends. He has taken sewing classes at a local city college and joined Radical Sewing Club (@radicalsewingclub) as a volunteer assisting folks with using a sewing machine to mend and alter their clothes. He rescued a kitten named Boots about two years ago.", + "imageUrl": "/AlumniPage/jack.png", + "instaHandle": "@radicalsewingclub", + "instaLink": "https://www.instagram.com/", + "linkedinLink": "https://www.linkedin.com/", + "linkedinHandle": "linkedin link" + }, + { + "first_name": "Leila", + "last_name": "Sanchez", + "role1": "President, 2020–2023", + "role2": "", + "role3": "", + "description": "After graduation in 2023, Leila Sanchez now works at the Social Security Administration while working on her writing career in Urban Fantasy and Horror. She self published the short story, The Challenge at the Carmichael Mansion in late 2023, and is currently on other writing projects, while breaking down tropes and providing her own analysis on shows and movies on her TikTok and Instagram, @Leilasstorycorner.", + "imageUrl": "/AlumniPage/Leila.jpg", + "instaHandle": "@Leilasstorycorner", + "instaLink": "https://www.instagram.com/", + "linkedinLink": "https://www.linkedin.com/", + "linkedinHandle": "linkedin link" + }, + { + "first_name": "Annie", + "last_name": "Tran", + "role1": "Vice President, 2022–2023", + "role2": "President, 2023-2024", + "role3": "", + "description": "Annie Tran joined the club in fall of 2020—during lockdown—serving as president in her final year at davis. Now having returned home, she is working in quality assurance and, in her leisure time, writing a fantasy novel.", + "imageUrl": "/AlumniPage/Annie.jpg", + "instaHandle": "@instagram", + "instaLink": "https://www.instagram.com/", + "linkedinLink": "https://www.linkedin.com/", + "linkedinHandle": "linkedin link" + }, + { + "first_name": "Jakob", + "last_name": "Stanton", + "role1": "Vice President, 2023–2024", + "role2": "", + "role3": "", + "description": "Jakob Stanton was the secondary leader of Aggie Fiction from 2023-2024. He graduated 2024 with a bachelor's degree in History and a minor in sociology. He researched and wrote an honors thesis about aeronautical culture in Germany from 1872-1929 titled We Dead Fliers. He is currently pursuing a masters in Screenwriting at Chapman University in Orange CA where he is trying to solve the puzzle that is screenwriting with two sweet black cats named Bucket and Boba.", + "imageUrl": "/AlumniPage/Jakob.png", + "instaHandle": "@instagram", + "instaLink": "https://www.instagram.com/", + "linkedinLink": "https://www.linkedin.com/", + "linkedinHandle": "linkedin link" + }, + { + "first_name": "Erika", + "last_name": "DiMaano", + "role1": "Publicity Chair, 2022–2024", + "role2": "", + "role3": "", + "description": "Erika DiMaano is an interaction designer who's been working on various interactive digital projects and design work. She's recently been branching out to web development and learning new skills to bring her work further.", + "imageUrl": "/AlumniPage/Erika.png", + "instaHandle": "@instagram", + "instaLink": "https://www.instagram.com/", + "linkedinLink": "https://www.linkedin.com/", + "linkedinHandle": "linkedin link" + } +] diff --git a/app/_data/board-members.json b/app/_data/board-members.json new file mode 100644 index 0000000..2b1cc2b --- /dev/null +++ b/app/_data/board-members.json @@ -0,0 +1,57 @@ +[ + { + "name": "Claudia Colorado", + "role": "President", + "imageUrl": "/aboutUsImages/president.png", + "imageAlt": "Claudia Colorda, the Aggie Fiction president", + "majors": ["Managerial Economics", "English"], + "minors": [], + "grade": "Junior" + }, + { + "name": "Yuri Nishii", + "role": "Vice-President", + "imageUrl": "/aboutUsImages/vp.png", + "imageAlt": "Yuri Nishii, the Aggie Fiction Vice President", + "majors": ["Communications", "English"], + "minors": ["Technology Management"], + "grade": "Senior" + }, + { + "name": "Chris Calub", + "role": "Treasurer", + "imageUrl": "/aboutUsImages/treasurer.png", + "imageAlt": "Chris Calub, the Aggie Fiction Treasurer", + "majors": ["English", "Communications"], + "minors": [], + "grade": "Junior" + }, + { + "name": "Audrey Zhang", + "role": "Outreach Chair", + "imageUrl": "/aboutUsImages/outreach.png", + "imageAlt": "Audrey Zhang, the Aggie Fiction Outreach Chair", + "majors": ["Economics", "English"], + "minors": [], + "grade": "Junior" + }, + { + "name": "Nea Le", + "role": "Publicity Chair", + "imageUrl": "/aboutUsImages/publicity.png", + "imageAlt": "Nea Le, the Aggie Fiction Publicity Chair", + "majors": ["Comparative Literature"], + "minors": ["Japanese"], + "grade": "Sophomore" + }, + { + "name": "Ri Herrera", + "role": "Design Lead", + "imageUrl": "/aboutUsImages/design.png", + "imageAlt": "Ri Herrera, the Design Lead", + "majors": ["Sociology & English"], + "minors": ["History"], + "grade": "Sophomore" + } +] + \ No newline at end of file diff --git a/app/_data/store-products.json b/app/_data/store-products.json new file mode 100644 index 0000000..a960599 --- /dev/null +++ b/app/_data/store-products.json @@ -0,0 +1,74 @@ +[ + { + "id": 1, + "name": "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", + "price": "$16.00", + "imageUrl": "/TestImages/AgFiTote.png", + "imageAlt": "Aggie Fiction Tote Bag", + "similar": [1, 2, 5] + }, + { + "id": 2, + "name": "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", + "price": "$16.00", + "imageUrl": "/TestImages/JackieOnMoonTote.jpg", + "imageAlt": "Jackie on Moon tote bag", + "similar": [1, 2, 3] + }, + { + "id": 3, + "name": "Jackie Tote Bag (2nd Edition; Spring Quarter 2024)", + "price": "$16.00", + "imageUrl": "/TestImages/JackieTote.png", + "imageAlt": "Jackie Tote Bag", + "similar": [4, 5, 6] + }, + { + "id": 4, + "name": "Colonizer Jackie Sticker", + "price": "$16.00", + "imageUrl": "/TestImages/Jackie1.jpg", + "imageAlt": "Colonizer Jackie Sticker", + "similar": [7, 8, 9] + }, + { + "id": 5, + "name": "Coquette Jackie Sticker", + "price": "$16.00", + "imageUrl": "/TestImages/Jackie2.jpg", + "imageAlt": "Coquette Jackie Sticker", + "similar": [1, 2, 6] + }, + { + "id": 6, + "name": "Boba Jackie Sticker", + "price": "$16.00", + "imageUrl": "/TestImages/JackieBoba.png", + "imageAlt": "Boba Jackie Sticker", + "similar": [1, 2, 6] + }, + { + "id": 7, + "name": "Eepy Jackie Sticker", + "price": "$16.00", + "imageUrl": "/TestImages/EepyJackie.jpg", + "imageAlt": "Eepy Jackie Sticker", + "similar": [1, 2, 6] + }, + { + "id": 8, + "name": "Santa Jackie Sticker", + "price": "$16.00", + "imageUrl": "/TestImages/SantaJackie.jpg", + "imageAlt": "Santa Jackie Sticker", + "similar": [1, 2, 6] + }, + { + "id": 9, + "name": "Jackie Maud'dib Sticker", + "price": "$16.00", + "imageUrl": "/TestImages/JackieMaud'dib.jpg", + "imageAlt": "Jackie Maud'dib Sticker", + "similar": [1, 2, 6] + } +] \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index d5456a1..e5c553d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,16 @@ /** @type {import('next').NextConfig} */ + const nextConfig = { reactStrictMode: true, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "res.cloudinary.com", + pathname: "/**", + }, + ], + }, }; export default nextConfig;