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.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) => (
+
+ ))}
+
+ >
+ )}
+
+
+
+
+ >
+ )}
+ >
+ );
+}
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;