diff --git a/web/__test__/components/ExecCard.test.tsx b/web/__test__/components/ExecCard.test.tsx index 60ef42bb..d32d4ecb 100644 --- a/web/__test__/components/ExecCard.test.tsx +++ b/web/__test__/components/ExecCard.test.tsx @@ -2,8 +2,9 @@ import { describe, expect, it } from "vitest"; import { render, screen } from "@testing-library/react"; import ExecCard from "../../src/components/ExecCard"; import React from "react"; +import { Exec } from "../../src/types/types"; -const mockExec = { +const mockExec: Exec = { id: 1, image: "/uploads/john_doe.jpg", position: "President", diff --git a/web/__test__/components/PartnerCard.test.tsx b/web/__test__/components/PartnerCard.test.tsx new file mode 100644 index 00000000..3aa2a774 --- /dev/null +++ b/web/__test__/components/PartnerCard.test.tsx @@ -0,0 +1,45 @@ +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import PartnerCard from "../../src/components/PartnerCard"; +import React from "react"; +import { Partner } from "../../src/types/types"; + +const mockPartner: Partner = { + id: 1, + type: "Gold", + name: "The Kebab and Chicken House", + location: "17 Mount Street", + description: "20% off Everything", + image: "/uploads/kebab.jpg", +}; + +describe("PartnerCard", () => { + it("renders PartnerCard with correct data", () => { + render(); + + // Check if the elements are rendered correctly + expect(screen.getByAltText("Partner Image")).toBeInTheDocument(); + const partnerImage = screen.getByAltText("Partner Image"); + expect(partnerImage).toHaveAttribute("src", mockPartner.image); + expect(screen.getByText("The Kebab and Chicken House")).toBeInTheDocument(); + expect(screen.getByText("17 Mount Street")).toBeInTheDocument(); + expect(screen.getByText("20% off Everything")).toBeInTheDocument(); + }); + + it("opens Google Maps with the correct query when 'View' button is clicked", () => { + render(); + + const originalOpen = window.open; + window.open = vi.fn(); + + const viewButton = screen.getByText("View On Map"); + fireEvent.click(viewButton); + + expect(window.open).toHaveBeenCalledWith( + "https://www.google.com/maps/search/?api=1&query=17%20Mount%20Street", + "_blank" + ); + + window.open = originalOpen; // Restore original window.open + }); +}); diff --git a/web/__test__/screens/PartnerScreen.test.tsx b/web/__test__/screens/PartnerScreen.test.tsx new file mode 100644 index 00000000..25d5a225 --- /dev/null +++ b/web/__test__/screens/PartnerScreen.test.tsx @@ -0,0 +1,222 @@ +import { MockedProvider } from "@apollo/client/testing"; +import { GET_PARTNERS } from "../../src/graphql/queries"; +import { describe, expect, it } from "vitest"; +import { render, screen } from "@testing-library/react"; +import PartnersScreen from "../../src/screens/PartnersScreen"; +import React from "react"; +import { GraphQLError } from "graphql"; +import "@testing-library/jest-dom"; +import { MemoryRouter } from "react-router"; + +const partnersMock = { + request: { + query: GET_PARTNERS, + }, + result: { + data: { + partners: { + data: [ + { + id: 1, + attributes: { + Type: "Gold", + Name: "The Kebab and Chicken House", + Location: "17 Mount Street", + Description: "20% off Everything", + Image: { + data: { + attributes: { + url: "/uploads/kebab.jpg", + }, + }, + }, + }, + }, + { + id: 2, + attributes: { + Type: "Silver", + Name: "Subi's Desserts", + Location: "128 White Swan Road, Mount Roskil", + Description: "15% off Everything", + Image: { + data: { + attributes: { + url: "/uploads/subi.jpg", + }, + }, + }, + }, + }, + { + id: 3, + attributes: { + Type: "Bronze", + Name: "Beso Cafe and Kitchen", + Location: "256 Manukau Road, Epsom, Auckland", + Description: "10% off Everything", + Image: { + data: { + attributes: { + url: "/uploads/beso.jpg", + }, + }, + }, + }, + }, + ], + }, + }, + }, +}; + +const noPartnersMock = { + request: { + query: GET_PARTNERS, + }, + result: { + data: { + partners: { + data: [], + }, + }, + }, +}; + +const mocks = [partnersMock]; +const noDataMocks = [noPartnersMock]; + +describe("PartnersScreen", () => { + it("renders loading spinner initially", () => { + render( + + + + + + ); + + expect(screen.getByTestId("loading-spinner")).toBeInTheDocument(); + }); + + it("renders error message when query fails", async () => { + const errorMocks = [ + { + request: { + query: GET_PARTNERS, + }, + error: new GraphQLError("Error!"), + }, + ]; + + render( + + + + + + ); + + expect(await screen.findByText("CMS Offline")).toBeInTheDocument(); + }); + + it("renders current gold partners correctly", async () => { + render( + + + + + + ); + + expect( + await screen.findByText("The Kebab and Chicken House") + ).toBeInTheDocument(); + expect(await screen.findByText("17 Mount Street")).toBeInTheDocument(); + expect(await screen.findByText("20% off Everything")).toBeInTheDocument(); + + // Find the image element with alt text "Partner Image" and specific src + const goldPartnerImages = screen.getAllByAltText( + "Partner Image" + ) as HTMLImageElement[]; + const goldPartnerImage = goldPartnerImages.find((img) => + img.src.includes("/uploads/kebab.jpg") + ); + + expect(goldPartnerImage).toBeInTheDocument(); + }); + + it("renders current silver partners correctly", async () => { + render( + + + + + + ); + + expect(await screen.findByText("Subi's Desserts")).toBeInTheDocument(); + expect( + await screen.findByText("128 White Swan Road, Mount Roskil") + ).toBeInTheDocument(); + expect(await screen.findByText("15% off Everything")).toBeInTheDocument(); + + // Find the image element with alt text "Partner Image" and specific src + const silverPartnerImages = screen.getAllByAltText( + "Partner Image" + ) as HTMLImageElement[]; + const silverPartnerImage = silverPartnerImages.find((img) => + img.src.includes("/uploads/subi.jpg") + ); + + expect(silverPartnerImage).toBeInTheDocument(); + }); + + it("renders current bronze partners correctly", async () => { + render( + + + + + + ); + + expect( + await screen.findByText("Beso Cafe and Kitchen") + ).toBeInTheDocument(); + expect( + await screen.findByText("256 Manukau Road, Epsom, Auckland") + ).toBeInTheDocument(); + expect(await screen.findByText("10% off Everything")).toBeInTheDocument(); + + // Find the image element with alt text "Partner Image" and specific src + const bronzePartnerImages = screen.getAllByAltText( + "Partner Image" + ) as HTMLImageElement[]; + const bronzePartnerImage = bronzePartnerImages.find((img) => + img.src.includes("/uploads/beso.jpg") + ); + + expect(bronzePartnerImage).toBeInTheDocument(); + }); + + it("renders no data from cms", async () => { + render( + + + + + + ); + + expect( + await screen.findByText("No gold partners to display") + ).toBeInTheDocument(); + expect( + await screen.findByText("No silver partners to display") + ).toBeInTheDocument(); + expect( + await screen.findByText("No bronze partners to display") + ).toBeInTheDocument(); + }); +}); diff --git a/web/src/components/PartnerCard.tsx b/web/src/components/PartnerCard.tsx new file mode 100644 index 00000000..8e325bdb --- /dev/null +++ b/web/src/components/PartnerCard.tsx @@ -0,0 +1,58 @@ +import { PartnerCardProps } from "../types/types"; + +export default function PartnerCard({ partner, colour }: PartnerCardProps) { + // Function to convert hex to RGBA + const hexToRgba = (hex: string, alpha: number) => { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + }; + + const bgColorWithOpacity = hexToRgba(colour, 0.2); // 0.2 for 20% opacity + + // Function to handle the button click + const handleViewOnMapClick = () => { + const googleMapsUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(partner.location)}`; + window.open(googleMapsUrl, "_blank"); + }; + + return ( + <> +
+
+ Partner Image +
+ +

+ {partner.name} +

+ +
+

Benefits Provided

+

{partner.description}

+
+ +
+

Location

+

{partner.location}

+
+ + +
+ + ); +} diff --git a/web/src/main.tsx b/web/src/main.tsx index 0b43d717..a6c8a213 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -22,6 +22,7 @@ import { graphqlClient } from "./graphql/client.ts"; import CreditsScreen from "./screens/CreditsScreen.tsx"; import SignInScreen from "./screens/SignInScreen.tsx"; import EventScreen from "./screens/EventScreen.tsx"; +import PartnersScreen from "./screens/PartnersScreen.tsx"; //Add any routes for screens below const router = createBrowserRouter( @@ -31,6 +32,7 @@ const router = createBrowserRouter( } /> } /> } /> + } /> } /> } /> } /> diff --git a/web/src/screens/PartnersScreen.tsx b/web/src/screens/PartnersScreen.tsx new file mode 100644 index 00000000..3234a753 --- /dev/null +++ b/web/src/screens/PartnersScreen.tsx @@ -0,0 +1,146 @@ +import { useState, useEffect } from "react"; +import type { Partner } from "../types/types"; +import { useQuery } from "@apollo/client"; +import { GET_PARTNERS } from "../graphql/queries"; +import { Mapper } from "../utils/Mapper"; +import LoadingSpinner from "../components/LoadingSpinner"; +import Header from "../components/Header"; +import PartnerCard from "../components/PartnerCard"; + +export default function PartnersScreen() { + const { + loading: partnersLoading, + data: partnersData, + error: partnersError, + } = useQuery(GET_PARTNERS); + + const [partners, setPartners] = useState([]); + const [noPartners, setNoPartners] = useState(false); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (partnersData) { + try { + const sponsors = Mapper.mapToPartner(partnersData); + setPartners(sponsors); + } catch (error) { + setNoPartners(true); + } + } + }, [partnersData]); + + useEffect(() => { + if (!partnersLoading) { + setLoading(false); + } + }); + + if (partnersError) { + return
CMS Offline
; + } + + // Filtering the partners based on their type + const goldPartners = partners.filter((partner) => partner.type === "Gold"); + const silverPartners = partners.filter( + (partner) => partner.type === "Silver" + ); + const bronzePartners = partners.filter( + (partner) => partner.type === "Bronze" + ); + + return ( + <> + {loading ? ( + + ) : ( + <> +
+
+
+
+

Our Partners

+

+ Collaborating with amazing partners to enhance your AUIS + experience +

+
+
+
+ {/* Gold Partners */} + {noPartners ? ( +
+ No gold partners to display +
+ ) : ( + <> +

+ Gold Partners +

+
+ {goldPartners.map((e) => ( +
+ +
+ ))} +
+ + )} + + {/* Silver Partners */} + {noPartners ? ( +
+ No silver partners to display +
+ ) : ( + <> +

+ Silver Partners +

+
+ {silverPartners.map((e) => ( +
+ +
+ ))} +
+ + )} + + {/* Bronze Partners */} + {noPartners ? ( +
+ No bronze partners to display +
+ ) : ( + <> +

+ Bronze Partners +

+
+ {bronzePartners.map((e) => ( +
+ +
+ ))} +
+ + )} +
+ +
+

+ Want To Partner With Us? +

+ + Contact Us Now! + +
+
+ + )} + + ); +} diff --git a/web/src/types/types.ts b/web/src/types/types.ts index 7dfaa4e0..bc808a46 100644 --- a/web/src/types/types.ts +++ b/web/src/types/types.ts @@ -24,6 +24,11 @@ export interface Partner { location: string; } +export interface PartnerCardProps { + colour: string; + partner: Partner; +} + export interface Social { id: number; type: string;