diff --git a/backend/controllers/EventController.ts b/backend/controllers/EventController.ts index 3cb59e2..1f310ab 100644 --- a/backend/controllers/EventController.ts +++ b/backend/controllers/EventController.ts @@ -1,20 +1,38 @@ import { Request, Response } from "express"; -import Event from "../models/Event"; +import { Client } from "@notionhq/client"; +import { EventRow, eventRowsStructured } from "../utils/BackendTypes"; +import { config } from "dotenv"; +config(); + +const notionSecret = process.env.NOTION_SECRET; +const eventID = process.env.EVENT_DB_ID; + +const notion = new Client({ + auth: notionSecret, +}); const getEvents = async (req: Request, res: Response) => { - res.json({msg: "Implement GET endpoint"}); -} + if (!notionSecret || !eventID) { + throw new Error("Missing creds"); + } + + const query = await notion.databases.query({ + database_id: eventID, + }); + + // @ts-ignore + const rows = query.results.map((res) => res.properties) as EventRow[]; -const createEvent = async (req: Request, res: Response) => { - res.json({msg: "Implement POST endpoint"}); -} + const rowsStructured: eventRowsStructured = rows.map((row) => ({ + name: row.Name.title[0].text.content, + date: row.Date.date.start, + description: row.Description.rich_text[0].text.content, + image: row.Image.files[0].file.url, + })); -const deleteEvent = async (req: Request, res: Response) => { - res.json({msg: "Implement DELETE endpoint"}); -} + const orderedRowsStructured = rowsStructured.reverse(); -const updateEvent = async (req: Request, res: Response) => { - res.json({msg: "Implement PATCH endpoint"}); -} + res.status(200).json(orderedRowsStructured); +}; -export {getEvents, createEvent, deleteEvent, updateEvent}; \ No newline at end of file +export { getEvents }; diff --git a/backend/controllers/ExecController.ts b/backend/controllers/ExecController.ts new file mode 100644 index 0000000..046363d --- /dev/null +++ b/backend/controllers/ExecController.ts @@ -0,0 +1,37 @@ +import { Request, Response } from "express"; +import { Client } from "@notionhq/client"; +import { ExecRow, execRowsStructured } from "../utils/BackendTypes"; +import { config } from "dotenv"; +config(); + +const notionSecret = process.env.NOTION_SECRET; +const execID = process.env.EXEC_DB_ID; + +const notion = new Client({ + auth: notionSecret, +}); + +const getExecs = async (req: Request, res: Response) => { + if (!notionSecret || !execID) { + throw new Error("Missing creds"); + } + + const query = await notion.databases.query({ + database_id: execID, + }); + + // @ts-ignore + const rows = query.results.map((res) => res.properties) as ExecRow[]; + + const rowsStructured: execRowsStructured = rows.map((row) => ({ + name: row.Name.title[0].text.content, + role: row.Role.rich_text[0].text.content, + image: row.Image.files[0].file.url, + })); + + const orderedRowsStructured = rowsStructured.reverse(); + + res.status(200).json(orderedRowsStructured); +}; + +export { getExecs }; diff --git a/backend/controllers/SocialController.ts b/backend/controllers/SocialController.ts new file mode 100644 index 0000000..5a366d8 --- /dev/null +++ b/backend/controllers/SocialController.ts @@ -0,0 +1,36 @@ +import { Request, Response } from "express"; +import { Client } from "@notionhq/client"; +import { SocialRow, socialRowsStructured } from "../utils/BackendTypes"; +import { config } from "dotenv"; +config(); + +const notionSecret = process.env.NOTION_SECRET; +const socialID = process.env.SOCIAL_DB_ID; + +const notion = new Client({ + auth: notionSecret, +}); + +const getSocials = async (req: Request, res: Response) => { + if (!notionSecret || !socialID) { + throw new Error("Missing creds"); + } + + const query = await notion.databases.query({ + database_id: socialID, + }); + + // @ts-ignore + const rows = query.results.map((res) => res.properties) as SocialRow[]; + + const rowsStructured: socialRowsStructured = rows.map((row) => ({ + name: row.Name.title[0].text.content, + link: row.Link.url, + })); + + const orderedRowsStructured = rowsStructured.reverse(); + + res.status(200).json(orderedRowsStructured); +}; + +export { getSocials }; diff --git a/backend/controllers/SponsorController.ts b/backend/controllers/SponsorController.ts index 433f91d..ec925f2 100644 --- a/backend/controllers/SponsorController.ts +++ b/backend/controllers/SponsorController.ts @@ -1,20 +1,37 @@ import { Request, Response } from "express"; -import Sponsor from "../models/Sponsor"; +import { Client } from "@notionhq/client"; +import { SponsorRow, sponsorRowsStructured } from "../utils/BackendTypes"; +import { config } from "dotenv"; +config(); + +const notionSecret = process.env.NOTION_SECRET; +const sponsorID = process.env.SPONSOR_DB_ID; + +const notion = new Client({ + auth: notionSecret, +}); const getSponsors = async (req: Request, res: Response) => { - res.json({msg: "Implement GET endpoint"}); -} + if (!notionSecret || !sponsorID) { + throw new Error("Missing creds"); + } + + const query = await notion.databases.query({ + database_id: sponsorID, + }); + + // @ts-ignore + const rows = query.results.map((res) => res.properties) as SponsorRow[]; -const createSponsor = async (req: Request, res: Response) => { - res.json({msg: "Implement POST endpoint"}); -} + const rowsStructured: sponsorRowsStructured = rows.map((row) => ({ + name: row.Name.title[0].text.content, + description: row.Description.rich_text[0].text.content, + image: row.Image.files[0].file.url, + })); -const deleteSponsor = async (req: Request, res: Response) => { - res.json({msg: "Implement DELETE endpoint"}); -} + const orderedRowsStructured = rowsStructured.reverse(); -const updateSponsor = async (req: Request, res: Response) => { - res.json({msg: "Implement PATCH endpoint"}); -} + res.status(200).json(orderedRowsStructured); +}; -export {getSponsors, createSponsor, deleteSponsor, updateSponsor}; \ No newline at end of file +export { getSponsors }; diff --git a/backend/index.ts b/backend/index.ts index 8b531c4..ea6b0ac 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -1,25 +1,23 @@ -import express, { json } from 'express'; -import cors from 'cors'; -import { connect } from 'mongoose'; -import { config } from 'dotenv'; +import express, { json } from "express"; +import cors from "cors"; +import router from "./routes/routes"; +import { config } from "dotenv"; +config(); -// Import Routers -import router from './routes/routes'; +// Sets our port to the PORT .env value or 4000 by default if .env is not configured +const PORT = process.env.PORT ?? 4000; +// Creates the express server const app = express(); -config(); - -// const databaseUrl: string = process.env.DATABASE_URL!; -// connect(databaseUrl); +// Express middleware app.use(json()); app.use(cors()); -app.use(express.static('public')); +app.use(express.static("public")); // Routes -app.use('/', router); +app.use("/", router); -const port = Number.parseInt(process.env.PORT || '3000'); -app.listen(port, () => { - console.log(`Listening on port ${port}`); +app.listen(PORT, () => { + console.log(`Listening on port ${PORT}`); }); diff --git a/backend/models/Event.ts b/backend/models/Event.ts deleted file mode 100644 index f72ca6e..0000000 --- a/backend/models/Event.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Schema, model } from "mongoose"; - -const eventSchema = new Schema( - { - eventTitle: { - type: String, - required: true, - }, - eventDescription: { - type: String, - required: true, - }, - dateOfEvent: { - type: Date, - required: true, - }, - eventImage: { - type: String, - }, - eventLocation: { - type: String, - required: true, - }, - }, - { - timestamps: {}, - } -); - -const Event = model("Event", eventSchema); -export default Event; diff --git a/backend/models/Sponsor.ts b/backend/models/Sponsor.ts deleted file mode 100644 index 66f6600..0000000 --- a/backend/models/Sponsor.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Schema, model } from "mongoose"; - -const sponsorSchema = new Schema( - { - image: { - type: String, - required: true, - }, - name: { - type: String, - required: true, - }, - sponsorDescription: { - type: String, - }, - discountDescription: { - type: String, - required: true, - }, - sponsorWebsite: { - type: String, - }, - }, - { - timestamps: {}, - } -); - -const Sponsor = model("Sponsor", sponsorSchema); - -export default Sponsor; diff --git a/backend/package.json b/backend/package.json index b33778f..4b42f50 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,6 +21,7 @@ "typescript": "^5.3.3" }, "dependencies": { + "@notionhq/client": "^2.2.15", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/nodemailer": "^6.4.14", diff --git a/backend/routes/eventRoutes.ts b/backend/routes/eventRoutes.ts index 96b88be..e4383a7 100644 --- a/backend/routes/eventRoutes.ts +++ b/backend/routes/eventRoutes.ts @@ -1,18 +1,9 @@ -import { Router } from "express" -import { getEvents, createEvent, deleteEvent, updateEvent } from "../controllers/EventController"; +import { Router } from "express"; +import { getEvents } from "../controllers/EventController"; const eventRoutes = Router(); -// GET all users -eventRoutes.get('/', getEvents); +// Get all Events +eventRoutes.get("/", getEvents); -// CREATE new user -eventRoutes.post('/', createEvent); - -// DELETE a user -eventRoutes.delete('/:id', deleteEvent); - -// Update a user -eventRoutes.patch('/:id', updateEvent); - -export default eventRoutes; \ No newline at end of file +export default eventRoutes; diff --git a/backend/routes/execRoutes.ts b/backend/routes/execRoutes.ts new file mode 100644 index 0000000..819cd17 --- /dev/null +++ b/backend/routes/execRoutes.ts @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { getExecs } from "../controllers/ExecController"; + +const execRoutes = Router(); + +// Get all Execs +execRoutes.get("/", getExecs); + +export default execRoutes; diff --git a/backend/routes/hello.ts b/backend/routes/hello.ts deleted file mode 100644 index fd60113..0000000 --- a/backend/routes/hello.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - Example Route File -*/ -import { Router } from 'express'; -import type { Request, Response } from 'express'; -import { z } from 'zod'; - -const helloRoutes = Router(); - -helloRoutes.get('/:name', async (req: Request, res: Response) => { - const Name = z.object({ - name: z.string(), - }); - - const result = Name.safeParse(req.params); - if (!result.success) return res.status(400).send(result.error); - - const { name }: z.infer = result.data; - - return res.status(200).send(`Kia Ora ${name}`); -}); - -export default helloRoutes; diff --git a/backend/routes/routes.ts b/backend/routes/routes.ts index 9ef7909..db74148 100644 --- a/backend/routes/routes.ts +++ b/backend/routes/routes.ts @@ -1,6 +1,8 @@ import express from "express"; import eventRoutes from "./eventRoutes"; import sponsorRoutes from "./sponsorRoutes"; +import execRoutes from "./execRoutes"; +import socialRoutes from "./socialRoutes"; const router = express.Router(); @@ -10,4 +12,10 @@ router.use("/api/events", eventRoutes); // All sponsor routes router.use("/api/sponsors", sponsorRoutes); +// All exec routes +router.use("/api/execs", execRoutes); + +// All exec routes +router.use("/api/socials", socialRoutes); + export default router; diff --git a/backend/routes/socialRoutes.ts b/backend/routes/socialRoutes.ts new file mode 100644 index 0000000..4b53070 --- /dev/null +++ b/backend/routes/socialRoutes.ts @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { getSocials } from "../controllers/SocialController"; + +const socialRoutes = Router(); + +// Get all Socials +socialRoutes.get("/", getSocials); + +export default socialRoutes; diff --git a/backend/routes/sponsorRoutes.ts b/backend/routes/sponsorRoutes.ts index 46477dc..f788918 100644 --- a/backend/routes/sponsorRoutes.ts +++ b/backend/routes/sponsorRoutes.ts @@ -1,18 +1,9 @@ -import { Router } from "express" -import { getSponsors, createSponsor, deleteSponsor, updateSponsor } from "../controllers/SponsorController"; +import { Router } from "express"; +import { getSponsors } from "../controllers/SponsorController"; const sponsorRoutes = Router(); -// GET all users -sponsorRoutes.get('/', getSponsors); +// Get all Sponsors +sponsorRoutes.get("/", getSponsors); -// CREATE new user -sponsorRoutes.post('/', createSponsor); - -// DELETE a user -sponsorRoutes.delete('/:id', deleteSponsor); - -// Update a user -sponsorRoutes.patch('/:id', updateSponsor); - -export default sponsorRoutes; \ No newline at end of file +export default sponsorRoutes; diff --git a/backend/utils/BackendTypes.ts b/backend/utils/BackendTypes.ts new file mode 100644 index 0000000..e599b4c --- /dev/null +++ b/backend/utils/BackendTypes.ts @@ -0,0 +1,125 @@ +export type EventRow = { + Name: { + id: string; + title: { + text: { + content: string; + }; + }[]; + }; + Date: { + id: string; + date: { + start: string; + }; + }; + Description: { + id: string; + rich_text: { + text: { + content: string; + }; + }[]; + }; + Image: { + id: string; + files: { + file: { + url: string; + }; + }[]; + }; +} + +export type eventRowsStructured = { + name: string; + date: string; + description: string; + image: string; +}[]; + +export type ExecRow = { + Name: { + id: string; + title: { + text: { + content: string; + }; + }[]; + }; + Role: { + id: string; + rich_text: { + text: { + content: string; + }; + }[]; + }; + Image: { + id: string; + files: { + file: { + url: string; + }; + }[]; + }; +} + +export type execRowsStructured = { + name: string; + role: string; + image: string; +}[]; + +export type SocialRow = { + Name: { + id: string; + title: { + text: { + content: string; + }; + }[]; + }; + Link: { + id: string; + url: string; + }; +} + +export type socialRowsStructured = { + name: string; + link: string; +}[]; + +export type SponsorRow = { + Name: { + id: string; + title: { + text: { + content: string; + }; + }[]; + }; + Description: { + id: string; + rich_text: { + text: { + content: string; + }; + }[]; + }; + Image: { + id: string; + files: { + file: { + url: string; + }; + }[]; + }; +} + +export type sponsorRowsStructured = { + name: string; + description: string; + image: string; +}[]; \ No newline at end of file diff --git a/backend/yarn.lock b/backend/yarn.lock index 22f7633..f969801 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -34,6 +34,14 @@ dependencies: sparse-bitfield "^3.0.3" +"@notionhq/client@^2.2.15": + version "2.2.15" + resolved "https://registry.yarnpkg.com/@notionhq/client/-/client-2.2.15.tgz#739fc8edb1357a2e2e35d026571fafe17c089606" + integrity sha512-XhdSY/4B1D34tSco/GION+23GMjaS9S2zszcqYkMHo8RcWInymF6L1x+Gk7EmHdrSxNFva2WM8orhC4BwQCwgw== + dependencies: + "@types/node-fetch" "^2.5.10" + node-fetch "^2.6.1" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -118,6 +126,14 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/node-fetch@^2.5.10": + version "2.6.11" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" + integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node@*": version "20.11.16" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" @@ -245,6 +261,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -328,6 +349,13 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -397,6 +425,11 @@ define-data-property@^1.1.2: gopd "^1.0.1" has-property-descriptors "^1.0.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -511,6 +544,15 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -752,7 +794,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -833,6 +875,13 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-fetch@^2.6.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + nodemon@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.3.tgz#244a62d1c690eece3f6165c6cdb0db03ebd80b76" @@ -1070,6 +1119,11 @@ tr46@^4.1.1: dependencies: punycode "^2.3.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -1132,6 +1186,11 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -1145,6 +1204,14 @@ whatwg-url@^13.0.0: tr46 "^4.1.1" webidl-conversions "^7.0.0" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/web/package.json b/web/package.json index 8997a78..bf3f095 100644 --- a/web/package.json +++ b/web/package.json @@ -30,6 +30,7 @@ "@testing-library/dom": "^10.1.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^16.0.0", + "@types/jest": "^29.5.12", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^6.14.0", @@ -41,6 +42,7 @@ "eslint": "^8.55.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "jest": "^29.7.0", "jsdom": "^24.1.0", "postcss": "^8.4.34", "tailwindcss": "^3.4.1", diff --git a/web/src/components/EventCard.tsx b/web/src/components/EventCard.tsx index b5b9904..1ca6928 100644 --- a/web/src/components/EventCard.tsx +++ b/web/src/components/EventCard.tsx @@ -1,22 +1,10 @@ -import backgroundImage from "../assets/event.jpg"; - -export interface EventType { - id: number; - date: string; - month: string; - title: string; - description: string; -} - -interface EventCardProps { - event: EventType; -} +import { EventCardProps } from "../utils/FrontendTypes"; export default function EventCard({ event }: EventCardProps) { - const { date, month, title, description } = event; + const { name, date, description, image } = event; // Create a date object for the event - const eventDate = new Date(`${month} ${date}, ${new Date().getFullYear()}`); + const eventDate = new Date(date); const currentDate = new Date(); // Determine if the event is upcoming, happening today, or past @@ -24,11 +12,15 @@ export default function EventCard({ event }: EventCardProps) { const isUpcoming = eventDate > currentDate; const statusText = isToday ? "Happening Today" : isUpcoming ? "Upcoming Event" : "Passed"; + // Format the event date + const eventMonth = eventDate.toLocaleString("default", { month: "long" }); + const eventDay = eventDate.getDate(); + return ( <>
- Background Image + Background Image
{statusText}
@@ -36,12 +28,12 @@ export default function EventCard({ event }: EventCardProps) {
-

{month}

-

{date}

+

{eventMonth}

+

{eventDay}

-
-

{title}

+
+

{name}

{description}

diff --git a/web/src/components/ExecCard.tsx b/web/src/components/ExecCard.tsx index 153622c..18e1a61 100644 --- a/web/src/components/ExecCard.tsx +++ b/web/src/components/ExecCard.tsx @@ -1,21 +1,23 @@ -export interface ExecDataType { - name: string; - role: string; -} - -interface ExecCardProps { - exec: ExecDataType; -} +import { ExecCardProps } from "../utils/FrontendTypes"; export default function ExecCard({ exec }: ExecCardProps) { + const { name, role, image } = exec; + return ( - <> -
-
-

{exec.role}

-

{exec.name}

-
-
- + <> +
+
+

{role}

+

{name}

+
+
+ ); -} \ No newline at end of file +} diff --git a/web/src/components/Socials.tsx b/web/src/components/Socials.tsx index e3ddecd..d2ec5c9 100644 --- a/web/src/components/Socials.tsx +++ b/web/src/components/Socials.tsx @@ -1,47 +1,58 @@ import { Link } from "react-router-dom"; import { FaFacebookF, FaInstagram, FaDiscord, FaTiktok } from "react-icons/fa"; import { IoMdMail } from "react-icons/io"; - -interface SocialsProps { - background: string; - hoverBackground: string; - iconColor: string; - hoverIconColor: string; -} +import { useEffect, useState } from "react"; +import axios from "axios"; +import { SocialType, SocialsProps } from "../utils/FrontendTypes"; export default function Socials({ background, hoverBackground, iconColor, hoverIconColor }: SocialsProps) { - return ( - <> -
-
- - - -
+ const [socials, setSocials] = useState([]); -
- - - -
+ useEffect(() => { + async function fetchSocials() { + try { + const response = await axios.get("http://localhost:4000/api/socials/"); + setSocials(response.data); + } catch (error) { + console.error("Error fetching social data", error); + } + } -
- - - -
+ fetchSocials(); + }, []); -
- - - -
+ const renderIcon = (name: string) => { + switch (name) { + case "Facebook": + return ; + case "Instagram": + return ; + case "Discord": + return ; + case "TikTok": + return ; + case "Email": + return ; + default: + return null; + } + }; -
- - - -
+ return ( + <> +
+ {socials.slice(0, -1).map((social, index) => ( +
+ + {renderIcon(social.name)} + +
+ ))}
); diff --git a/web/src/components/SponsorCard.tsx b/web/src/components/SponsorCard.tsx index d3f56da..5181e52 100644 --- a/web/src/components/SponsorCard.tsx +++ b/web/src/components/SponsorCard.tsx @@ -1,15 +1,6 @@ +import { SponsorCardProps } from "../utils/FrontendTypes"; import background from "../assets/pink_sponsor_background.png"; -export interface SponsorType { - name: string; - description: string; - image: string; -} - -interface SponsorCardProps { - sponsor: SponsorType; -} - export default function SponsorCard({ sponsor }: SponsorCardProps) { const { name, description, image } = sponsor; diff --git a/web/src/components/__tests__/EventCard.test.tsx b/web/src/components/__tests__/EventCard.test.tsx index 58c5935..fbc1205 100644 --- a/web/src/components/__tests__/EventCard.test.tsx +++ b/web/src/components/__tests__/EventCard.test.tsx @@ -1,22 +1,23 @@ import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; -import EventCard, { EventType } from "../EventCard"; +import EventCard from "../EventCard"; +import eventBackground from "../../assets/event.jpg"; +import { EventType } from "../../utils/FrontendTypes"; describe("Event Card Component", () => { const event: EventType = { - id: 1, - date: "4", - month: "April", - title: "Meet and Greet", - description: "Come and meet your fellow peers and connect with each other", + name: "Event 1", + date: "2024-01-01", + description: "Event 1 Description", + image: eventBackground, }; it("Renders the EventCard component with correct details", () => { render(); - expect(screen.getByText("4")).toBeInTheDocument(); - expect(screen.getByText("April")).toBeInTheDocument(); - expect(screen.getByText("Meet and Greet")).toBeInTheDocument(); - expect(screen.getByText("Come and meet your fellow peers and connect with each other")).toBeInTheDocument(); + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("January")).toBeInTheDocument(); + expect(screen.getByText("Event 1")).toBeInTheDocument(); + expect(screen.getByText("Event 1 Description")).toBeInTheDocument(); }); }); diff --git a/web/src/components/__tests__/ExecCard.test.tsx b/web/src/components/__tests__/ExecCard.test.tsx index 60a6071..54dc268 100644 --- a/web/src/components/__tests__/ExecCard.test.tsx +++ b/web/src/components/__tests__/ExecCard.test.tsx @@ -1,11 +1,14 @@ import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; -import ExecCard, { ExecDataType } from "../ExecCard"; +import ExecCard from "../ExecCard"; +import execImage from "../../assets/placeholder.png" +import { ExecType } from "../../utils/FrontendTypes"; describe("Exec Card Component", () => { - const exec: ExecDataType = { + const exec: ExecType = { name: "Kai Hirafune", role: "President", + image: execImage }; it("Renders the ExecCard component with correct name and role", () => { diff --git a/web/src/components/__tests__/Socials.test.tsx b/web/src/components/__tests__/Socials.test.tsx index 9a3784d..4711e46 100644 --- a/web/src/components/__tests__/Socials.test.tsx +++ b/web/src/components/__tests__/Socials.test.tsx @@ -1,19 +1,36 @@ -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import Socials from "../Socials"; import "@testing-library/jest-dom"; +import axios from "axios"; +import { vi } from "vitest"; + +vi.mock("axios"); + +const mockSocials = [ + { name: "Facebook", link: "https://www.facebook.com/uoadessertsociety" }, + { name: "Instagram", link: "https://www.instagram.com/uoadessertsociety/" }, + { name: "Discord", link: "https://discord.gg/dFuwHuU8FT" }, + { name: "TikTok", link: "https://www.tiktok.com/@uoadessertsociety?_t=8mQ3asFY7Pz&_r=1" }, + { name: "Email", link: "uoadessertsociety@gmail.com" }, + { name: "Sign Up Form", link: "https://docs.google.com/forms/d/e/1FAIpQLSek62CaGuEBxPexG06yTCAziVe2vWMVA9jHPC0uJTQdugK8rA/viewform?embedded=true" }, +]; describe("Socials Component", () => { - beforeEach(() => { + beforeEach(async () => { + (axios.get as jest.Mock).mockResolvedValue({ data: mockSocials }); + render( ); + + await waitFor(() => expect(axios.get).toHaveBeenCalled()); }); - it("Should render the Facebook icon and link correctly", () => { - const facebookLink = screen.getByRole("link", { name: /facebook/i }); + it("Should render the Facebook icon and link correctly", async () => { + const facebookLink = await screen.findByRole("link", { name: /facebook/i }); expect(facebookLink).toBeInTheDocument(); expect(facebookLink).toHaveAttribute("href", "https://www.facebook.com/uoadessertsociety"); @@ -21,8 +38,8 @@ describe("Socials Component", () => { expect(facebookIcon).toBeInTheDocument(); }); - it("Should render the Instagram icon and link correctly", () => { - const instagramLink = screen.getByRole("link", { name: /instagram/i }); + it("Should render the Instagram icon and link correctly", async () => { + const instagramLink = await screen.findByRole("link", { name: /instagram/i }); expect(instagramLink).toBeInTheDocument(); expect(instagramLink).toHaveAttribute("href", "https://www.instagram.com/uoadessertsociety/"); @@ -30,8 +47,8 @@ describe("Socials Component", () => { expect(instagramIcon).toBeInTheDocument(); }); - it("Should render the Discord icon and link correctly", () => { - const discordLink = screen.getByRole("link", { name: /discord/i }); + it("Should render the Discord icon and link correctly", async () => { + const discordLink = await screen.findByRole("link", { name: /discord/i }); expect(discordLink).toBeInTheDocument(); expect(discordLink).toHaveAttribute("href", "https://discord.gg/dFuwHuU8FT"); @@ -39,8 +56,8 @@ describe("Socials Component", () => { expect(discordIcon).toBeInTheDocument(); }); - it("Should render the TikTok icon and link correctly", () => { - const tiktokLink = screen.getByRole("link", { name: /tiktok/i }); + it("Should render the TikTok icon and link correctly", async () => { + const tiktokLink = await screen.findByRole("link", { name: /tiktok/i }); expect(tiktokLink).toBeInTheDocument(); expect(tiktokLink).toHaveAttribute("href", "https://www.tiktok.com/@uoadessertsociety?_t=8mQ3asFY7Pz&_r=1"); @@ -48,12 +65,12 @@ describe("Socials Component", () => { expect(tiktokIcon).toBeInTheDocument(); }); - it("Should render the Email icon and link correctly", () => { - const emailLink = screen.getByRole("link", { name: /email/i }); + it("Should render the Email icon and link correctly", async () => { + const emailLink = await screen.findByRole("link", { name: /email/i }); expect(emailLink).toBeInTheDocument(); expect(emailLink).toHaveAttribute("href", "mailto:uoadessertsociety@gmail.com"); const emailIcon = screen.getByTestId("emailLogo"); expect(emailIcon).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/web/src/pages/About.tsx b/web/src/pages/About.tsx index 7b7493b..7e7e63c 100644 --- a/web/src/pages/About.tsx +++ b/web/src/pages/About.tsx @@ -1,39 +1,51 @@ import icecream from "../assets/ice-cream.svg"; import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; -import ExecCard, { ExecDataType } from "../components/ExecCard"; - -const execData: ExecDataType[] = [ - { name: "Kai", role: "President" }, - { name: "Alex", role: "Vice President" }, - { name: "Sam", role: "Secretary" }, - { name: "Jordan", role: "Treasurer" }, - { name: "Kai", role: "President" }, - { name: "Alex", role: "Vice President" }, - { name: "Sam", role: "Secretary" }, - { name: "Jordan", role: "Treasurer" }, -]; +import ExecCard from "../components/ExecCard"; +import { useState, useEffect } from "react"; +import axios from "axios"; +import { ExecType } from "../utils/FrontendTypes"; export default function About() { + const [execs, setExecs] = useState([]); + + useEffect(() => { + async function fetchExecs() { + try { + const response = await axios.get("http://localhost:4000/api/execs/"); + setExecs(response.data); + } catch (error) { + console.error("Error fetching exec data", error); + } + } + fetchExecs(); + }, []); + return ( <>
-
+

About Us

Welcome to the sweetest corner of the University of Auckland – the Dessert Society!


Whether you're a baker, connoisseur, or simply love sweets, you'll find a home with us.


-

Explore classic recipes to innovative creations through our events, workshops, and gatherings. Join us on a delightful journey through dessert-making, from perfect bakes to annual dessert crawls.

+

+ Explore classic recipes to innovative creations through our events, workshops, and gatherings. Join us on a delightful journey through + dessert-making, from perfect bakes to annual dessert crawls. +


No matter your skill level or background, everyone is welcome at the University of Auckland Dessert Society!


-
+
ice-cream photo
@@ -41,8 +53,12 @@ export default function About() {

The Executive Team

- {execData.map((exec, index) => ( -
+ {execs.map((exec, index) => ( +

{exec.role}

{exec.name}

@@ -52,7 +68,7 @@ export default function About() {
- {execData.map((exec, index) => ( + {execs.map((exec, index) => (
diff --git a/web/src/pages/Event.tsx b/web/src/pages/Event.tsx index 8b74f39..dac7f55 100644 --- a/web/src/pages/Event.tsx +++ b/web/src/pages/Event.tsx @@ -1,57 +1,35 @@ import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import cupcake from "../assets/cupcake.svg"; -import EventCard, { EventType } from "../components/EventCard"; - -const eventsData: EventType[] = [ - { - id: 1, - date: "4", - month: "April", - title: "Meet and Greet", - description: "Come and meet your fellow peers and connect with each other", - }, - { - id: 2, - date: "5", - month: "May", - title: "Event 2", - description: "Event 2 Description", - }, - { - id: 3, - date: "17", - month: "June", - title: "June Party", - description: "UADS Party in June", - }, - { - id: 4, - date: "6", - month: "July", - title: "Event 4", - description: "Description for Event 4", - }, - { - id: 5, - date: "6", - month: "August", - title: "Event 5", - description: "Description for Event 5", - }, -]; +import EventCard from "../components/EventCard"; +import axios from "axios"; +import { EventType } from "../utils/FrontendTypes"; export default function Event() { const [searchQuery, setSearchQuery] = useState(""); - const [displayedEvents, setDisplayedEvents] = useState(eventsData); + const [events, setEvents] = useState([]); + const [displayedEvents, setDisplayedEvents] = useState([]); + + useEffect(() => { + async function fetchEvents() { + try { + const response = await axios.get("http://localhost:4000/api/events/"); + setEvents(response.data); + setDisplayedEvents(response.data); + } catch (error) { + console.error("Error fetching event data", error); + } + } + fetchEvents(); + }, []); const handleSearchChange = (e: React.ChangeEvent) => { const query = e.target.value.toLowerCase(); setSearchQuery(query); - // Filter eventsData based on the search query - const filteredEvents = eventsData.filter((event) => event.title.toLowerCase().includes(query)); + // Filter events based on the search query + const filteredEvents = events.filter((event) => event.name.toLowerCase().includes(query)); setDisplayedEvents(filteredEvents); }; @@ -62,7 +40,12 @@ export default function Event() {
-

Events

+

+ Events +

Cupcake @@ -70,19 +53,27 @@ export default function Event() {
- +
{displayedEvents.length > 0 ? ( - displayedEvents.map((event) => ( -
+ displayedEvents.map((event, index) => ( +
)) ) : ( -

Sorry, no events found for "{searchQuery}"

+

+ Sorry, no events found for "{searchQuery}" +

)}
diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 838b277..f155167 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -2,7 +2,7 @@ import { NavLink } from "react-router-dom"; import uadslogo from "../assets/uads_logo_brown.svg"; import creamcup from "../assets/cupcake.svg"; import sundae from "../assets/sundae.svg"; -import EventCard, { EventType } from "../components/EventCard"; +import EventCard from "../components/EventCard"; import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; import Socials from "../components/Socials"; @@ -11,78 +11,37 @@ import "swiper/css"; import "swiper/css/pagination"; import "swiper/css/navigation"; import { Autoplay, Pagination } from "swiper/modules"; -import SponsorCard, { SponsorType } from "../components/SponsorCard"; -import placeholder from "../assets/download.jpg"; +import SponsorCard from "../components/SponsorCard"; +import axios from "axios"; +import { useEffect, useState } from "react"; +import { EventType, SponsorType } from "../utils/FrontendTypes"; -// Temp Sponsors data -const sponsorData: SponsorType[] = [ - { - name: "Sponsor 1", - description: "Sponsor 1 Description", - image: placeholder, - }, - { - name: "Sponsor 2", - description: "Sponsor 2 Description", - image: placeholder, - }, - { - name: "Sponsor 3", - description: "Sponsor 3 Description", - image: placeholder, - }, - { - name: "Sponsor 4", - description: "Sponsor 4 Description", - image: placeholder, - }, - { - name: "Sponsor 5", - description: "Sponsor 5 Description", - image: placeholder, - }, -]; +export default function Home() { + const [events, setEvents] = useState([]); + const [sponsors, setSponsors] = useState([]); -// Temp events data -const eventsData: EventType[] = [ - { - id: 1, - date: "4", - month: "April", - title: "Meet and Greet", - description: "Come and meet your fellow peers and connect with each other", - }, - { - id: 2, - date: "5", - month: "May", - title: "Event 2", - description: "Event 2 Description", - }, - { - id: 3, - date: "17", - month: "June", - title: "June Party", - description: "UADS Party in June", - }, - { - id: 4, - date: "6", - month: "July", - title: "Event 4", - description: "Description for Event 4", - }, - { - id: 5, - date: "6", - month: "August", - title: "Event 5", - description: "Description for Event 5", - }, -]; + useEffect(() => { + async function fetchEvents() { + try { + const response = await axios.get("http://localhost:4000/api/events/"); + setEvents(response.data); + } catch (error) { + console.error("Error fetching event data", error); + } + } + async function fetchSponsors() { + try { + const response = await axios.get("http://localhost:4000/api/sponsors/"); + setSponsors(response.data); + } catch (error) { + console.error("Error fetching sponsor data", error); + } + } + + fetchSponsors(); + fetchEvents(); + }, []); -export default function Home() { return ( <> @@ -140,9 +99,9 @@ export default function Home() { }} className="mySwiper px-0 md:px-10 py-10" > - {sponsorData.map((sponsor) => { + {sponsors.map((sponsor, index) => { return ( - + ); @@ -192,9 +151,9 @@ export default function Home() { }} className="mySwiper px-0 md:px-10 py-10" > - {eventsData.map((event) => { + {events.map((event, index) => { return ( - + ); diff --git a/web/src/pages/SignUp.tsx b/web/src/pages/SignUp.tsx index 258f06b..17573e1 100644 --- a/web/src/pages/SignUp.tsx +++ b/web/src/pages/SignUp.tsx @@ -1,12 +1,39 @@ import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; +import { useEffect, useState } from "react"; +import axios from "axios"; export default function SignUp() { + const [signUpFormLink, setSignUpFormLink] = useState(""); + + useEffect(() => { + async function fetchSocials() { + try { + const response = await axios.get("http://localhost:4000/api/socials/"); + // Sign Up Link is the last index of the response body + setSignUpFormLink(response.data[response.data.length - 1].link); + } catch (error) { + console.error("Error fetching social data", error); + } + } + + fetchSocials(); + }, []); + return ( -
+ <> -

Sign Up Page

+ +
+

Join Us Now

+
+ +
+
+
-
+ ); } diff --git a/web/src/pages/Sponsor.tsx b/web/src/pages/Sponsor.tsx index 3585744..75287d2 100644 --- a/web/src/pages/Sponsor.tsx +++ b/web/src/pages/Sponsor.tsx @@ -1,90 +1,86 @@ import donut from "../assets/half_donut_jelly.svg"; import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; -import { useState } from "react"; -import SponsorCard, { SponsorType } from "../components/SponsorCard"; -import placeholder from "../assets/download.jpg"; - -const sponsorData: SponsorType[] = [ - { - name: "Sponsor 1", - description: "Sponsor 1 Description", - image: placeholder, - }, - { - name: "Sponsor 2", - description: "Sponsor 2 Description", - image: placeholder, - }, - { - name: "Sponsor 3", - description: "Sponsor 3 Description", - image: placeholder, - }, -]; +import { useState, useEffect } from "react"; +import SponsorCard from "../components/SponsorCard"; +import axios from "axios"; +import { SponsorType } from "../utils/FrontendTypes"; export default function Sponsor() { - const [searchQuery, setSearchQuery] = useState(""); - const [displayedSponsors, setDisplayedSponsors] = - useState(sponsorData); + const [searchQuery, setSearchQuery] = useState(""); + const [sponsors, setSponsors] = useState([]); + const [displayedSponsors, setDisplayedSponsors] = useState([]); + + useEffect(() => { + async function fetchSponsors() { + try { + const response = await axios.get("http://localhost:4000/api/sponsors/"); + setSponsors(response.data); + setDisplayedSponsors(response.data); + } catch (error) { + console.error("Error fetching sponsor data", error); + } + } + fetchSponsors(); + }, []); - const handleSearchChange = (e: React.ChangeEvent) => { - const query = e.target.value.toLowerCase(); - setSearchQuery(query); + const handleSearchChange = (e: React.ChangeEvent) => { + const query = e.target.value.toLowerCase(); + setSearchQuery(query); - // Filter sponsorData based on the search query - const filteredSponsors = sponsorData.filter((sponsor) => - sponsor.name.toLowerCase().includes(query) - ); - setDisplayedSponsors(filteredSponsors); - }; + // Filter sponsorData based on the search query + const filteredSponsors = sponsors.filter((sponsor) => + sponsor.name.toLowerCase().includes(query) + ); + setDisplayedSponsors(filteredSponsors); + }; - return ( - <> - + return ( + <> + -
-
-
-

- Our Sponsors -

+
+
+
+

+ Our Sponsors +

-
- Donut -
-
+
+ Donut +
+
-
- -
-
+
+ +
+
-
- {displayedSponsors.length > 0 ? ( - displayedSponsors.map((sponsor, index) => ( -
- -
- )) - ) : ( -

- Sorry, no sponsor found for "{searchQuery}" -

- )} -
-
+
+ {displayedSponsors.length > 0 ? ( + displayedSponsors.map((sponsor, index) => ( +
+ +
+ )) + ) : ( +

+ Sorry, no sponsor found for "{searchQuery}" +

+ )} +
+
-