From 1948d7a84c753cd2e69523fcce2415d45eba11fe Mon Sep 17 00:00:00 2001 From: Yrjar V Date: Fri, 20 Sep 2024 11:12:38 +0200 Subject: [PATCH 01/65] Replace "Profile" with first section of user name Related to #42 We risk issues when name is long or if the user has multiple first names ("Per Ole Hansen" will be shown as "Per" and not "Per Ole") --- app/components/Login/LoginButton.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index 83bb730..8574ea3 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -52,7 +52,12 @@ export default function LoginButton(props) { bgcolor: cybTheme.palette.primary.main, }; - + const buttonText = () => { + if (session.data != undefined) { + return session.data.user.name.split(" ")[0] + } + return "Login" + } return ( <> @@ -84,7 +89,7 @@ export default function LoginButton(props) { }} primary={ - {session.data != undefined ? "Profile" : "Login"} + {buttonText()} } /> @@ -109,7 +114,7 @@ export default function LoginButton(props) { // color={cybTheme.palette.text.secondary} sx={{ display: { xs: "none", md: "flex" } }} > - {session.data != undefined ? "Profile" : "Login"} + {buttonText()} {session.status == "authenticated" ? ( @@ -122,7 +127,7 @@ export default function LoginButton(props) { ) : ( Date: Fri, 20 Sep 2024 15:50:19 +0200 Subject: [PATCH 02/65] Allow width of LoginButton to be automatic Remove hard width limit for the LoginButton text, allow the Grid containing LoginButton to shrink to the appropriate size for the length of the text inside LoginButton --- app/components/Login/LoginButton.js | 1 - app/components/layout/AppBar.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index 8574ea3..1b184b6 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -110,7 +110,6 @@ export default function LoginButton(props) { > diff --git a/app/components/layout/AppBar.js b/app/components/layout/AppBar.js index 7c69662..ae41fea 100644 --- a/app/components/layout/AppBar.js +++ b/app/components/layout/AppBar.js @@ -83,7 +83,7 @@ export class NavBar extends Component { - + From 3ac806f408144634b40461affe7df408f3c4af4f Mon Sep 17 00:00:00 2001 From: Yrjar V Date: Mon, 23 Sep 2024 08:46:27 +0200 Subject: [PATCH 03/65] Enable shortening of first name in profile button --- app/components/Login/LoginButton.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index 1b184b6..4dc7f7a 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -54,7 +54,12 @@ export default function LoginButton(props) { const buttonText = () => { if (session.data != undefined) { - return session.data.user.name.split(" ")[0] + let firstName = session.data.user.name.split(" ")[0] + // Shorten the first name if it is longer than 25 characters + if (firstName.length > 25) { + firstName = firstName.slice(0, 22) + "..." + } + return firstName } return "Login" } From 9056baf5b94d0f11e83f0513484bea851d685303 Mon Sep 17 00:00:00 2001 From: Tepohe Date: Tue, 24 Sep 2024 02:01:15 +0200 Subject: [PATCH 04/65] Added force-graph logic --- app/components/RecruitmentGraph.js | 165 +++++++++++++++++++++++++++++ app/pages/main/board/page.js | 63 +++++++++++ package.json | 1 + 3 files changed, 229 insertions(+) create mode 100644 app/components/RecruitmentGraph.js create mode 100644 app/pages/main/board/page.js diff --git a/app/components/RecruitmentGraph.js b/app/components/RecruitmentGraph.js new file mode 100644 index 0000000..8fd56ad --- /dev/null +++ b/app/components/RecruitmentGraph.js @@ -0,0 +1,165 @@ + +import React, { useRef, useEffect } from "react"; +import * as d3 from "d3"; +import { cybTheme } from "./themeCYB"; +import { Box } from "@mui/material"; +import { getUserInitials } from "@/app/components/textUtil" + +const ForceGraph = ({ data }) => { + const svgRef = useRef(); + + useEffect(() => { + + const svg = d3.select(svgRef.current); + const containerRect = svgRef.current.getBoundingClientRect(); + const height = containerRect.height; + const width = containerRect.width; + + const labelColour = "white"; + const linkColour = "white"; + const nodeColour = cybTheme.palette.primary.main; + const nodeStrokeColour = "black"; + + const node_radius = 24; + const arrowPath = "M 4 -2 L 10 1 L 4 4"; + + const nodes = data.nodes.map((e) => ({ ...e, name: getUserInitials(e) })); + const links = data.links.map((e) => ({ + source: e.source.id, + target: e.target.id, + })); + + // Clear the previous graph + svg.selectAll("*").remove(); + + // Set up the simulation + const simulation = d3 + .forceSimulation(nodes) + .force( + "link", + d3 + .forceLink(links) + .distance(100) + .id((d) => d.id) + ) + .force("charge", d3.forceManyBody().strength(-400)) + .force("x", d3.forceX(width / 2)) + .force("y", d3.forceY(height / 2)) + .force("collide", d3.forceCollide(node_radius + 5)); + + // Add links + const link = svg + .append("g") + .selectAll("line") + .data(links) + .enter() + .append("line") + .attr("marker-end", "url(#arrowhead-not-highlighted)") + .attr("stroke", linkColour) + .attr("stroke-opacity", 0.6) + .attr("stroke-width", 1.5) + + // arrow + svg + .append("marker") + .attr("id", "arrowhead-not-highlighted") + .attr("viewBox", "-0 -5 10 10") + .attr("refX", node_radius - 2) + .attr("refY", 1) + .attr("orient", "auto") + .attr("markerWidth", 13) + .attr("markerHeight", 13) + .append("svg:path") + .attr("d", arrowPath) + .attr("xoverflow", "visible") + .attr("fill", linkColour); + + // Add nodes + const node = svg + .append("g") + .selectAll("circle") + .data(nodes) + .enter() + .append("circle") + .attr("r", node_radius) + .attr("stroke-width", 2) + .attr("stroke", nodeStrokeColour) + .attr("fill", nodeColour) + .call( + d3 + .drag() + .on("start", (event, d) => { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + }) + .on("drag", (event, d) => { + d.fx = event.x; + d.fy = event.y; + }) + .on("end", (event, d) => { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + }) + ); + + // Add labels + const label = svg + .append("g") + .selectAll("text") + .data(nodes) + .enter() + .append("text") + .attr("x", 12) + .attr("y", 3) + .text((d) => d.name) + .attr("font-size", 14) + .attr("text-anchor", "middle") + .attr("dominant-baseline", "central") + .style("fill", labelColour) + .style("user-select", "none") // Make text unselectable + .style("-webkit-user-select", "none") // For Safari + .style("-moz-user-select", "none") // For Firefox + .style("-ms-user-select", "none") // For IE/Edge + .call( + d3 + .drag() + .on("start", (event, d) => { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + }) + .on("drag", (event, d) => { + d.fx = event.x; + d.fy = event.y; + }) + .on("end", (event, d) => { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + }) + ); + + // Update positions on each tick + simulation.on("tick", () => { + + link + .attr("x1", (d) => d.source.x) + .attr("y1", (d) => d.source.y) + .attr("x2", (d) => d.target.x) + .attr("y2", (d) => d.target.y); + + node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); + label.attr("x", (d) => d.x).attr("y", (d) => d.y); + }); + }, [data]); + + return ( + + + + ); +}; + +export default ForceGraph; diff --git a/app/pages/main/board/page.js b/app/pages/main/board/page.js new file mode 100644 index 0000000..eabf00f --- /dev/null +++ b/app/pages/main/board/page.js @@ -0,0 +1,63 @@ + +"use client" + +import { PageHeader } from "@/app/components/sanity/PageBuilder"; +import prismaRequest from "@/app/middleware/prisma/prismaRequest"; +import { Box, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import Forcegraph from "@/app/components/RecruitmentGraph" + +export default function BoardPage() { + + const [data, setData] = useState({ nodes: [], links: [] }); + + useEffect(() => { + prismaRequest({ + model: "User", + method: "find", + request: { + include: { + recruitedByUser: true, + recruitedUsers: true, + }, + where: { + OR: [ + { recruitedById: { not: null } }, + { recruitedUsers: { some: {} } } + ] + }, + }, + callback: (data) => { + + const links = data.data + .filter((element) => element.recruitedByUser) + .map((element) => ({ + source: element, + target: element.recruitedByUser, + })); + + const connectedNodes = new Set( + links.flatMap((link) => [link.source.id, link.target.id]) + ); + const filteredNodes = data.data.filter((node) => + connectedNodes.has(node.id) || connectedNodes.has(node.recruitedById) + ); + const filteredLinks = links.filter((link) => + connectedNodes.has(link.source.id) && connectedNodes.has(link.target.id) + ); + + const newData = { nodes: filteredNodes, links: filteredLinks }; + + setData(newData); + }, + }); + }, []); + + return ( + + + + + + ); +} \ No newline at end of file diff --git a/package.json b/package.json index 0f9a8c3..f3ad514 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@sanity/structure": "^2.36.2", "@sanity/vision": "^3.31.0", "@sanity/visual-editing": "^2.1.6", + "d3": "^7.9.0", "date-fns": "^3.6.0", "dotenv-cli": "^7.3.0", "mui-image": "^1.0.7", From 4e18084267d4eb2b47c4609a75549ab4e242e977 Mon Sep 17 00:00:00 2001 From: Yrjar V Date: Tue, 24 Sep 2024 12:46:12 +0200 Subject: [PATCH 05/65] Reduce max first name length in profile button to 15 --- app/components/Login/LoginButton.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index 4dc7f7a..57e0b7e 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -55,9 +55,9 @@ export default function LoginButton(props) { const buttonText = () => { if (session.data != undefined) { let firstName = session.data.user.name.split(" ")[0] - // Shorten the first name if it is longer than 25 characters - if (firstName.length > 25) { - firstName = firstName.slice(0, 22) + "..." + // Shorten the first name if it is longer than 15 characters + if (firstName.length > 15) { + firstName = firstName.slice(0, 12) + "..." } return firstName } From 216dbd2f9e01c47877bc3c24a947cf64b10b4776 Mon Sep 17 00:00:00 2001 From: Yrjar V Date: Tue, 24 Sep 2024 12:49:50 +0200 Subject: [PATCH 06/65] Reload page when user info is updated Awaits prismaRequest, then reloads the entire page. This 1) confirms to the user that something happened, and 2) updates AppBar to ensure the first name is always correct. I could not get router.refresh() to work, so window.location.reload() is the best replacement I could find. --- app/pages/main/profile/page.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/pages/main/profile/page.js b/app/pages/main/profile/page.js index 70e2831..e91c13f 100644 --- a/app/pages/main/profile/page.js +++ b/app/pages/main/profile/page.js @@ -112,9 +112,8 @@ function ProfilePage() { } }, [session]) - const handleUpdateData = () => { - - prismaRequest({ + const handleUpdateData = async () => { + await prismaRequest({ model: "user", method: "update", request: { @@ -126,8 +125,8 @@ function ProfilePage() { lastName: lastName, } } - }) - + }); + window.location.reload(); } const handleConfirmSelection = async () => { From 0b9a317512c1f7f28b3f6b7fec8b41d290123a26 Mon Sep 17 00:00:00 2001 From: Yrjar V Date: Tue, 24 Sep 2024 13:25:28 +0200 Subject: [PATCH 07/65] Replace hyphens in first name with non-breaking hyphen --- app/components/Login/LoginButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index 57e0b7e..ba93239 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -54,7 +54,7 @@ export default function LoginButton(props) { const buttonText = () => { if (session.data != undefined) { - let firstName = session.data.user.name.split(" ")[0] + let firstName = session.data.user.name.split(" ")[0].replace('-', '‑') // Shorten the first name if it is longer than 15 characters if (firstName.length > 15) { firstName = firstName.slice(0, 12) + "..." From 99e51906a7e0b1f9b3788fd8664050bb31b6a5dd Mon Sep 17 00:00:00 2001 From: Yrjar V Date: Fri, 27 Sep 2024 14:18:47 +0200 Subject: [PATCH 08/65] Manually replaces "About CYB" with "About" on mobile --- app/components/layout/AppBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/layout/AppBar.js b/app/components/layout/AppBar.js index ae41fea..58d84dc 100644 --- a/app/components/layout/AppBar.js +++ b/app/components/layout/AppBar.js @@ -163,7 +163,7 @@ function NavElementSmallScreen(item, index, iconProps, currentPath) { }} primary={ - {item.name} + {item.name == "About CYB" ? "About" : item.name} } /> From 2c516d4bd0ad55c3f657981297a9236e97306d76 Mon Sep 17 00:00:00 2001 From: Tepohe Date: Tue, 1 Oct 2024 14:07:06 +0200 Subject: [PATCH 09/65] Audited mui version --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f3ad514..8449be6 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "@emotion/styled": "^11.11.0", "@mdi/js": "^7.4.47", "@mdi/react": "^1.6.1", - "@mui/icons-material": "^5.15.6", - "@mui/material": "^5.15.6", + "@mui/icons-material": "^5.16.7", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@mui/x-date-pickers": "^7.9.0", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.9.0", From 15dc90b767e36252d1690286f354a7825947f972 Mon Sep 17 00:00:00 2001 From: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:12:20 +0200 Subject: [PATCH 10/65] Update development.yml --- .github/workflows/development.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index cd759a6..2b044bd 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -42,9 +42,7 @@ jobs: NEXT_PUBLIC_SANITY_DATASET = '${{ vars.NEXT_PUBLIC_SANITY_DATASET }}' NEXT_PUBLIC_SANITY_PROJECT_ID = '${{ vars.NEXT_PUBLIC_SANITY_PROJECT_ID }}' EOT - npm install -f - npm audit fix - npm run prismapull + npm install npx prisma generate npm run build npx pm2 restart ISV2_dev From bcbc8d151ac0b25749c0ef99f4dee78705a4b535 Mon Sep 17 00:00:00 2001 From: Tepohe Date: Thu, 10 Oct 2024 15:30:52 +0200 Subject: [PATCH 11/65] Squashed commit adding pr/53 commit 0f096c0f9a8d629f66f073f6fd612cd3e1715b44 Merge: a1ce2ec 4dfd6f8 Author: Tepohe Date: Thu Oct 10 14:42:19 2024 +0200 Merge branch 'main' into development commit a1ce2ec14eb8bafd7a5d9e5a9ce939a97c734f11 Merge: 4f7d813 f06a472 Author: Tepohe Date: Tue Oct 8 13:53:44 2024 +0200 Merge branch 'pr/53' into development commit f06a472fb08cbcc9989a93f1f8ad6497f666eb46 Author: Sebbben Date: Mon Oct 7 16:47:03 2024 +0200 Fixed wrong path for nav bar home from pointing to /home to pointing to / commit 0d154f5ef967dd55f6453abf93dead2504e34a47 Author: Sebbben Date: Thu Sep 26 14:40:51 2024 +0200 Renamed some directories to take advantage of nexts routing for more usable url paths Co-Authored-By: Sebbben <42118203+Sebbben@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/development.yml | 2 +- README.md | 2 +- .../main/home => (pages)/(main)/(home)}/layout.js | 0 .../main/home => (pages)/(main)/(home)}/page.js | 0 .../main => (pages)/(main)}/aboutCYB/layout.js | 0 .../main => (pages)/(main)}/aboutCYB/page.js | 0 .../main => (pages)/(main)}/aboutEscape/layout.js | 0 .../main => (pages)/(main)}/aboutEscape/page.js | 0 .../(main)}/aboutEscape/rentingEscape/layout.js | 0 .../(main)}/aboutEscape/rentingEscape/page.js | 0 .../main => (pages)/(main)}/admin/handleRoles.js | 0 .../(main)}/admin/handleWorkGroups.js | 0 app/{pages/main => (pages)/(main)}/admin/layout.js | 0 app/{pages/main => (pages)/(main)}/admin/page.js | 0 app/{pages/main => (pages)/(main)}/layout.js | 2 +- .../main => (pages)/(main)}/profile/layout.js | 0 app/{pages/main => (pages)/(main)}/profile/page.js | 0 .../main => (pages)/(main)}/unauthorized/layout.js | 0 .../main => (pages)/(main)}/unauthorized/page.js | 0 .../(main)}/volunteering/cafe/page.js | 0 .../(main)}/volunteering/economy/layout.js | 0 .../(main)}/volunteering/economy/page.js | 0 .../(main)}/volunteering/groups/layout.js | 0 .../(main)}/volunteering/groups/page.js | 0 .../main => (pages)/(main)}/volunteering/layout.js | 0 .../(main)}/volunteering/logs/layout.js | 0 .../(main)}/volunteering/logs/page.js | 0 .../(main)}/volunteering/logs/voucherLogInput.js | 0 .../(main)}/volunteering/logs/workLogInput.js | 0 .../(main)}/volunteering/membership/layout.js | 0 .../(main)}/volunteering/membership/page.js | 0 .../main => (pages)/(main)}/volunteering/page.js | 6 +++--- .../(main)}/volunteering/traditions/page.js | 0 app/{pages => (pages)}/auth/email.js | 0 app/{pages => (pages)}/auth/layout.js | 2 +- app/{pages => (pages)}/auth/register/layout.js | 0 app/{pages => (pages)}/auth/register/page.js | 2 +- app/{pages => (pages)}/auth/signIn/layout.js | 0 app/{pages => (pages)}/auth/signIn/page.js | 4 ++-- app/api/activate/[token]/route.js | 2 +- app/api/auth/[...nextauth]/route.js | 2 +- app/api/sendVerification/route.js | 2 +- app/components/Login/LoginButton.js | 4 ++-- app/components/layout/AppBar.js | 10 +++++----- app/components/layout/Breadcrumbs.js | 2 +- app/components/layout/Footer.js | 2 +- app/middleware/authWrapper.js | 2 +- app/sitemap.tsx | 10 +++++----- next.config.mjs | 14 ++++---------- 50 files changed, 33 insertions(+), 39 deletions(-) rename app/{pages/main/home => (pages)/(main)/(home)}/layout.js (100%) rename app/{pages/main/home => (pages)/(main)/(home)}/page.js (100%) rename app/{pages/main => (pages)/(main)}/aboutCYB/layout.js (100%) rename app/{pages/main => (pages)/(main)}/aboutCYB/page.js (100%) rename app/{pages/main => (pages)/(main)}/aboutEscape/layout.js (100%) rename app/{pages/main => (pages)/(main)}/aboutEscape/page.js (100%) rename app/{pages/main => (pages)/(main)}/aboutEscape/rentingEscape/layout.js (100%) rename app/{pages/main => (pages)/(main)}/aboutEscape/rentingEscape/page.js (100%) rename app/{pages/main => (pages)/(main)}/admin/handleRoles.js (100%) rename app/{pages/main => (pages)/(main)}/admin/handleWorkGroups.js (100%) rename app/{pages/main => (pages)/(main)}/admin/layout.js (100%) rename app/{pages/main => (pages)/(main)}/admin/page.js (100%) rename app/{pages/main => (pages)/(main)}/layout.js (96%) rename app/{pages/main => (pages)/(main)}/profile/layout.js (100%) rename app/{pages/main => (pages)/(main)}/profile/page.js (100%) rename app/{pages/main => (pages)/(main)}/unauthorized/layout.js (100%) rename app/{pages/main => (pages)/(main)}/unauthorized/page.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/cafe/page.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/economy/layout.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/economy/page.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/groups/layout.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/groups/page.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/layout.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/logs/layout.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/logs/page.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/logs/voucherLogInput.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/logs/workLogInput.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/membership/layout.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/membership/page.js (100%) rename app/{pages/main => (pages)/(main)}/volunteering/page.js (96%) rename app/{pages/main => (pages)/(main)}/volunteering/traditions/page.js (100%) rename app/{pages => (pages)}/auth/email.js (100%) rename app/{pages => (pages)}/auth/layout.js (97%) rename app/{pages => (pages)}/auth/register/layout.js (100%) rename app/{pages => (pages)}/auth/register/page.js (99%) rename app/{pages => (pages)}/auth/signIn/layout.js (100%) rename app/{pages => (pages)}/auth/signIn/page.js (97%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 644bdc0..259b4a2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -29,7 +29,7 @@ jobs: DATABASE_URL = 'mysql://${{ secrets.DATABASE_USER }}:${{ secrets.DATABASE_PASSWORD }}@localhost:3306/${{ secrets.DATABASE_NAME }}' NEXTAUTH_URL = '${{ vars.NEXTAUTH_URL }}' - NEXTAUTH_CALLBACK = '${{ secrets.NEXTAUTH_URL }}/pages/main/home' + NEXTAUTH_CALLBACK = '${{ secrets.NEXTAUTH_URL }}/' NEXTAUTH_SECRET = '${{ secrets.NEXTAUTH_SECRET }}' NEXTAUTH_GITHUB_CLIENT_ID = '${{ vars.NEXTAUTH_GITHUB_CLIENT_ID }}' diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 2b044bd..87a2e23 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -29,7 +29,7 @@ jobs: DATABASE_URL = 'mysql://${{ secrets.DATABASE_USER }}:${{ secrets.DATABASE_PASSWORD }}@localhost:3306/${{ secrets.DATABASE_NAME }}' NEXTAUTH_URL = '${{ vars.NEXTAUTH_URL }}' - NEXTAUTH_CALLBACK = '${{ secrets.NEXTAUTH_URL }}/pages/main/home' + NEXTAUTH_CALLBACK = '${{ secrets.NEXTAUTH_URL }}/' NEXTAUTH_SECRET = '${{ secrets.NEXTAUTH_SECRET }}' NEXTAUTH_GITHUB_CLIENT_ID = '${{ vars.NEXTAUTH_GITHUB_CLIENT_ID }}' diff --git a/README.md b/README.md index d2347ec..1078afd 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ DATABASE_SCHEMA = 'ISV2_development' DATABASE_URL = "mysql://${DATABASE_USER}:${DATABASE_PASS}@localhost:3307/${DATABASE_SCHEMA}" NEXTAUTH_URL = 'http://localhost:3005' -NEXTAUTH_CALLBACK = '/pages/main/home' +NEXTAUTH_CALLBACK = '/' NEXTAUTH_SECRET = 'QuTxFdD3yon9X+83rRaio/vSgkfhvnwUglcIrnUwTLA=' NODEMAILER_NOREPLY_USER = 'noreply@cyb.no' diff --git a/app/pages/main/home/layout.js b/app/(pages)/(main)/(home)/layout.js similarity index 100% rename from app/pages/main/home/layout.js rename to app/(pages)/(main)/(home)/layout.js diff --git a/app/pages/main/home/page.js b/app/(pages)/(main)/(home)/page.js similarity index 100% rename from app/pages/main/home/page.js rename to app/(pages)/(main)/(home)/page.js diff --git a/app/pages/main/aboutCYB/layout.js b/app/(pages)/(main)/aboutCYB/layout.js similarity index 100% rename from app/pages/main/aboutCYB/layout.js rename to app/(pages)/(main)/aboutCYB/layout.js diff --git a/app/pages/main/aboutCYB/page.js b/app/(pages)/(main)/aboutCYB/page.js similarity index 100% rename from app/pages/main/aboutCYB/page.js rename to app/(pages)/(main)/aboutCYB/page.js diff --git a/app/pages/main/aboutEscape/layout.js b/app/(pages)/(main)/aboutEscape/layout.js similarity index 100% rename from app/pages/main/aboutEscape/layout.js rename to app/(pages)/(main)/aboutEscape/layout.js diff --git a/app/pages/main/aboutEscape/page.js b/app/(pages)/(main)/aboutEscape/page.js similarity index 100% rename from app/pages/main/aboutEscape/page.js rename to app/(pages)/(main)/aboutEscape/page.js diff --git a/app/pages/main/aboutEscape/rentingEscape/layout.js b/app/(pages)/(main)/aboutEscape/rentingEscape/layout.js similarity index 100% rename from app/pages/main/aboutEscape/rentingEscape/layout.js rename to app/(pages)/(main)/aboutEscape/rentingEscape/layout.js diff --git a/app/pages/main/aboutEscape/rentingEscape/page.js b/app/(pages)/(main)/aboutEscape/rentingEscape/page.js similarity index 100% rename from app/pages/main/aboutEscape/rentingEscape/page.js rename to app/(pages)/(main)/aboutEscape/rentingEscape/page.js diff --git a/app/pages/main/admin/handleRoles.js b/app/(pages)/(main)/admin/handleRoles.js similarity index 100% rename from app/pages/main/admin/handleRoles.js rename to app/(pages)/(main)/admin/handleRoles.js diff --git a/app/pages/main/admin/handleWorkGroups.js b/app/(pages)/(main)/admin/handleWorkGroups.js similarity index 100% rename from app/pages/main/admin/handleWorkGroups.js rename to app/(pages)/(main)/admin/handleWorkGroups.js diff --git a/app/pages/main/admin/layout.js b/app/(pages)/(main)/admin/layout.js similarity index 100% rename from app/pages/main/admin/layout.js rename to app/(pages)/(main)/admin/layout.js diff --git a/app/pages/main/admin/page.js b/app/(pages)/(main)/admin/page.js similarity index 100% rename from app/pages/main/admin/page.js rename to app/(pages)/(main)/admin/page.js diff --git a/app/pages/main/layout.js b/app/(pages)/(main)/layout.js similarity index 96% rename from app/pages/main/layout.js rename to app/(pages)/(main)/layout.js index d92474f..825b193 100644 --- a/app/pages/main/layout.js +++ b/app/(pages)/(main)/layout.js @@ -18,7 +18,7 @@ import { cybTheme } from "@/app/components/themeCYB"; import LayoutFooter from "@/app/components/layout/Footer"; const NavItems = [ - { id: "home", path: "home", name: "Home", icon: }, + { id: "home", path: "", name: "Home", icon: }, { id: "aboutCYB", path: "aboutCYB", name: "About CYB", icon: }, { id: "volunteering", diff --git a/app/pages/main/profile/layout.js b/app/(pages)/(main)/profile/layout.js similarity index 100% rename from app/pages/main/profile/layout.js rename to app/(pages)/(main)/profile/layout.js diff --git a/app/pages/main/profile/page.js b/app/(pages)/(main)/profile/page.js similarity index 100% rename from app/pages/main/profile/page.js rename to app/(pages)/(main)/profile/page.js diff --git a/app/pages/main/unauthorized/layout.js b/app/(pages)/(main)/unauthorized/layout.js similarity index 100% rename from app/pages/main/unauthorized/layout.js rename to app/(pages)/(main)/unauthorized/layout.js diff --git a/app/pages/main/unauthorized/page.js b/app/(pages)/(main)/unauthorized/page.js similarity index 100% rename from app/pages/main/unauthorized/page.js rename to app/(pages)/(main)/unauthorized/page.js diff --git a/app/pages/main/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js similarity index 100% rename from app/pages/main/volunteering/cafe/page.js rename to app/(pages)/(main)/volunteering/cafe/page.js diff --git a/app/pages/main/volunteering/economy/layout.js b/app/(pages)/(main)/volunteering/economy/layout.js similarity index 100% rename from app/pages/main/volunteering/economy/layout.js rename to app/(pages)/(main)/volunteering/economy/layout.js diff --git a/app/pages/main/volunteering/economy/page.js b/app/(pages)/(main)/volunteering/economy/page.js similarity index 100% rename from app/pages/main/volunteering/economy/page.js rename to app/(pages)/(main)/volunteering/economy/page.js diff --git a/app/pages/main/volunteering/groups/layout.js b/app/(pages)/(main)/volunteering/groups/layout.js similarity index 100% rename from app/pages/main/volunteering/groups/layout.js rename to app/(pages)/(main)/volunteering/groups/layout.js diff --git a/app/pages/main/volunteering/groups/page.js b/app/(pages)/(main)/volunteering/groups/page.js similarity index 100% rename from app/pages/main/volunteering/groups/page.js rename to app/(pages)/(main)/volunteering/groups/page.js diff --git a/app/pages/main/volunteering/layout.js b/app/(pages)/(main)/volunteering/layout.js similarity index 100% rename from app/pages/main/volunteering/layout.js rename to app/(pages)/(main)/volunteering/layout.js diff --git a/app/pages/main/volunteering/logs/layout.js b/app/(pages)/(main)/volunteering/logs/layout.js similarity index 100% rename from app/pages/main/volunteering/logs/layout.js rename to app/(pages)/(main)/volunteering/logs/layout.js diff --git a/app/pages/main/volunteering/logs/page.js b/app/(pages)/(main)/volunteering/logs/page.js similarity index 100% rename from app/pages/main/volunteering/logs/page.js rename to app/(pages)/(main)/volunteering/logs/page.js diff --git a/app/pages/main/volunteering/logs/voucherLogInput.js b/app/(pages)/(main)/volunteering/logs/voucherLogInput.js similarity index 100% rename from app/pages/main/volunteering/logs/voucherLogInput.js rename to app/(pages)/(main)/volunteering/logs/voucherLogInput.js diff --git a/app/pages/main/volunteering/logs/workLogInput.js b/app/(pages)/(main)/volunteering/logs/workLogInput.js similarity index 100% rename from app/pages/main/volunteering/logs/workLogInput.js rename to app/(pages)/(main)/volunteering/logs/workLogInput.js diff --git a/app/pages/main/volunteering/membership/layout.js b/app/(pages)/(main)/volunteering/membership/layout.js similarity index 100% rename from app/pages/main/volunteering/membership/layout.js rename to app/(pages)/(main)/volunteering/membership/layout.js diff --git a/app/pages/main/volunteering/membership/page.js b/app/(pages)/(main)/volunteering/membership/page.js similarity index 100% rename from app/pages/main/volunteering/membership/page.js rename to app/(pages)/(main)/volunteering/membership/page.js diff --git a/app/pages/main/volunteering/page.js b/app/(pages)/(main)/volunteering/page.js similarity index 96% rename from app/pages/main/volunteering/page.js rename to app/(pages)/(main)/volunteering/page.js index 348a9a7..434f542 100644 --- a/app/pages/main/volunteering/page.js +++ b/app/(pages)/(main)/volunteering/page.js @@ -23,9 +23,9 @@ import { sanityClient } from "@/sanity/client"; const BUTTON_CONTENT_1 = [ // { title: "Economy", path: "volunteering/economy" }, - // { title: "Café shifts", path: "/pages/main/volunteering/cafe" }, - { title: "Vouchers", path: "/pages/main/volunteering/logs" }, - { title: "Membership", path: "/pages/main/volunteering/membership" }, + // { title: "Café shifts", path: "/volunteering/cafe" }, + { title: "Vouchers", path: "/volunteering/logs" }, + { title: "Membership", path: "/volunteering/membership" }, { title: "Website content", path: "/studio" }, ]; diff --git a/app/pages/main/volunteering/traditions/page.js b/app/(pages)/(main)/volunteering/traditions/page.js similarity index 100% rename from app/pages/main/volunteering/traditions/page.js rename to app/(pages)/(main)/volunteering/traditions/page.js diff --git a/app/pages/auth/email.js b/app/(pages)/auth/email.js similarity index 100% rename from app/pages/auth/email.js rename to app/(pages)/auth/email.js diff --git a/app/pages/auth/layout.js b/app/(pages)/auth/layout.js similarity index 97% rename from app/pages/auth/layout.js rename to app/(pages)/auth/layout.js index bff22ab..8fc91f1 100644 --- a/app/pages/auth/layout.js +++ b/app/(pages)/auth/layout.js @@ -17,7 +17,7 @@ export default function AuthLayout({ children }) { - + diff --git a/app/pages/auth/signIn/layout.js b/app/(pages)/auth/signIn/layout.js similarity index 100% rename from app/pages/auth/signIn/layout.js rename to app/(pages)/auth/signIn/layout.js diff --git a/app/pages/auth/signIn/page.js b/app/(pages)/auth/signIn/page.js similarity index 97% rename from app/pages/auth/signIn/page.js rename to app/(pages)/auth/signIn/page.js index c92716e..8d1e5d1 100644 --- a/app/pages/auth/signIn/page.js +++ b/app/(pages)/auth/signIn/page.js @@ -20,7 +20,7 @@ export default function SignInPage() { const router = useRouter() if (session.status == "authenticated") { - router.push("/pages/main/home"); + router.push("/"); return; } @@ -88,7 +88,7 @@ export default function SignInPage() { justifyContent="flex-end" > diff --git a/app/api/activate/[token]/route.js b/app/api/activate/[token]/route.js index 68c01c3..2b03656 100644 --- a/app/api/activate/[token]/route.js +++ b/app/api/activate/[token]/route.js @@ -45,5 +45,5 @@ export async function GET(request, {params}) { } }) - redirect("/pages/auth/signIn") + redirect("/auth/signIn") } \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.js b/app/api/auth/[...nextauth]/route.js index 53fb325..c636b52 100644 --- a/app/api/auth/[...nextauth]/route.js +++ b/app/api/auth/[...nextauth]/route.js @@ -84,7 +84,7 @@ const handler = NextAuth({ } }, pages: { - signIn: "/pages/auth/signIn" + signIn: "/auth/signIn" }, adapter: PrismaAdapter(prisma), }); diff --git a/app/api/sendVerification/route.js b/app/api/sendVerification/route.js index 3864ff0..9244664 100644 --- a/app/api/sendVerification/route.js +++ b/app/api/sendVerification/route.js @@ -1,5 +1,5 @@ -import { mailOptions, transporter } from "@/app/pages/auth/email"; +import { mailOptions, transporter } from "@/app/(pages)/auth/email"; import { NextResponse } from "next/server"; const NEXTAUTH_URL = process.env.NEXTAUTH_URL || ""; diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index ba93239..68a14b3 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -40,7 +40,7 @@ export default function LoginButton(props) { const handleClick = (event) => { console.log("CLICK") if (session.status == "authenticated") { - router.push("/pages/main/profile"); + router.push("/profile"); } else { signIn(); } @@ -88,7 +88,7 @@ export default function LoginButton(props) { key={`link_item_text_login`} sx={{ color: - currentPath == `/pages/main/profile` + currentPath == `/profile` ? cybTheme.palette.primary.main : cybTheme.palette.text.primary, }} diff --git a/app/components/layout/AppBar.js b/app/components/layout/AppBar.js index 58d84dc..3a7bc2e 100644 --- a/app/components/layout/AppBar.js +++ b/app/components/layout/AppBar.js @@ -63,7 +63,7 @@ export class NavBar extends Component { sx={{ display: { xs: "none", md: "flex" } }} > - + @@ -110,7 +110,7 @@ function NavElementLargeScreen(item, index, currentPath) { key={`link_nav${index}_itemtext`} sx={{ color: - currentPath == `/pages/main/${item.path}` + currentPath == `/${item.path}` ? cybTheme.palette.primary.main : cybTheme.palette.text.primary, }} @@ -135,7 +135,7 @@ function NavElementSmallScreen(item, index, iconProps, currentPath) { return ( @@ -157,7 +157,7 @@ function NavElementSmallScreen(item, index, iconProps, currentPath) { key={`link_snav${index}_itemtext`} sx={{ color: - currentPath == `/pages/main/${item.path}` + currentPath == `/${item.path}` ? cybTheme.palette.primary.main : cybTheme.palette.text.primary, }} diff --git a/app/components/layout/Breadcrumbs.js b/app/components/layout/Breadcrumbs.js index 1c17deb..23ee075 100644 --- a/app/components/layout/Breadcrumbs.js +++ b/app/components/layout/Breadcrumbs.js @@ -26,7 +26,7 @@ export default function WebsiteBreadcrumbs(path, navItems) { const path = `/${pathBits.slice(0, index + 1).join("/")}`; return ( - + Date: Mon, 14 Oct 2024 15:07:04 +0200 Subject: [PATCH 12/65] Squashed commit adding pr/67: commit d26bd3fa6f650a486f19b18daffc2c7778b62529 Merge: 0f096c0 c019c46 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 10 16:54:40 2024 +0200 Merge pull request #67 from Sebbben/fixNoResultMembershipSearch Fix no result membership search commit c019c466036d5168b50a953036050c7a0ae62021 Author: Sebbben Date: Sat Oct 5 16:20:01 2024 +0200 Made tables turn to first page when the table content changes with a search commit 0f096c0f9a8d629f66f073f6fd612cd3e1715b44 Merge: a1ce2ec 4dfd6f8 Author: Tepohe Date: Thu Oct 10 14:42:19 2024 +0200 Merge branch 'main' into development commit a1ce2ec14eb8bafd7a5d9e5a9ce939a97c734f11 Merge: 4f7d813 f06a472 Author: Tepohe Date: Tue Oct 8 13:53:44 2024 +0200 Merge branch 'pr/53' into development commit f06a472fb08cbcc9989a93f1f8ad6497f666eb46 Author: Sebbben Date: Mon Oct 7 16:47:03 2024 +0200 Fixed wrong path for nav bar home from pointing to /home to pointing to / commit 0d154f5ef967dd55f6453abf93dead2504e34a47 Author: Sebbben Date: Thu Sep 26 14:40:51 2024 +0200 Renamed some directories to take advantage of nexts routing for more usable url paths Co-Authored-By: Sebbben <42118203+Sebbben@users.noreply.github.com> --- app/components/CustomTable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/CustomTable.js b/app/components/CustomTable.js index bb2a6a0..5d5bc54 100644 --- a/app/components/CustomTable.js +++ b/app/components/CustomTable.js @@ -76,6 +76,7 @@ function CustomTable({ headers, data, defaultFilterBy }) { }, [data, sortBy, sortDirection]); const filteredData = useMemo(() => { + setPage(0); return sortedData.filter((row) => { if (selectedSearchColumn === null || selectedFilterOption === null) { return true; From 3a2cabc52b51d7da43a2d1fd02432ea3e3ce1f91 Mon Sep 17 00:00:00 2001 From: Tepohe Date: Thu, 17 Oct 2024 15:51:45 +0200 Subject: [PATCH 13/65] Squashed commit adding pr/75 commit f0bacbbc3673170802017986d8216aba779c2f60 Merge: d26bd3f d198b94 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 17 15:46:05 2024 +0200 Merge pull request #75 from Borgarsy/feature-halloween-theme Temporary Halloween theme commit d26bd3fa6f650a486f19b18daffc2c7778b62529 Merge: 0f096c0 c019c46 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 10 16:54:40 2024 +0200 Merge pull request #67 from Sebbben/fixNoResultMembershipSearch Fix no result membership search commit c019c466036d5168b50a953036050c7a0ae62021 Author: Sebbben Date: Sat Oct 5 16:20:01 2024 +0200 Made tables turn to first page when the table content changes with a search commit 0f096c0f9a8d629f66f073f6fd612cd3e1715b44 Merge: a1ce2ec 4dfd6f8 Author: Tepohe Date: Thu Oct 10 14:42:19 2024 +0200 Merge branch 'main' into development commit d198b9492b623e7959c640f04dc8a7199c84da0c Author: Borgar Date: Tue Oct 8 15:01:29 2024 +0200 adds halloween theme. commit a1ce2ec14eb8bafd7a5d9e5a9ce939a97c734f11 Merge: 4f7d813 f06a472 Author: Tepohe Date: Tue Oct 8 13:53:44 2024 +0200 Merge branch 'pr/53' into development commit f06a472fb08cbcc9989a93f1f8ad6497f666eb46 Author: Sebbben Date: Mon Oct 7 16:47:03 2024 +0200 Fixed wrong path for nav bar home from pointing to /home to pointing to / commit 0d154f5ef967dd55f6453abf93dead2504e34a47 Author: Sebbben Date: Thu Sep 26 14:40:51 2024 +0200 Renamed some directories to take advantage of nexts routing for more usable url paths Co-Authored-By: Borgarsy <181370696+Borgarsy@users.noreply.github.com> --- app/components/Login/LoginButton.js | 6 +- app/components/themeCYB.js | 2 +- package-lock.json | 1674 +++++++++++++++++++++++---- 3 files changed, 1440 insertions(+), 242 deletions(-) diff --git a/app/components/Login/LoginButton.js b/app/components/Login/LoginButton.js index 68a14b3..330312f 100644 --- a/app/components/Login/LoginButton.js +++ b/app/components/Login/LoginButton.js @@ -21,7 +21,7 @@ import { Person } from "@mui/icons-material"; import { cybTheme } from "../themeCYB"; import { useRouter } from "next/navigation"; -import { mdiPenguin } from '@mdi/js'; +import { mdiGhost } from '@mdi/js'; import Icon from "@mdi/react"; import Link from "next/link"; // import { useEffect, useState } from "react"; @@ -76,7 +76,7 @@ export default function LoginButton(props) { {session.status == "authenticated" ? ( ) : ( @@ -124,7 +124,7 @@ export default function LoginButton(props) { {session.status == "authenticated" ? ( diff --git a/app/components/themeCYB.js b/app/components/themeCYB.js index 04df972..676ffed 100644 --- a/app/components/themeCYB.js +++ b/app/components/themeCYB.js @@ -5,7 +5,7 @@ export const cybTheme = createTheme({ palette: { mode: "dark", primary: { - main: "#d2a30e", + main: "#E66C2C", }, secondary: { main: "#ac0bce", diff --git a/package-lock.json b/package-lock.json index a01a7ce..9f40264 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,18 +10,29 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", - "@mui/icons-material": "^5.15.6", - "@mui/material": "^5.15.6", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", + "@mui/icons-material": "^5.16.7", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", + "@mui/x-date-pickers": "^7.9.0", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.9.0", - "@sanity/client": "^6.15.1", + "@sanity/client": "^6.20.1", "@sanity/image-url": "^1.0.2", "@sanity/orderable-document-list": "^1.2.1", + "@sanity/react-loader": "^1.10.4", + "@sanity/structure": "^2.36.2", "@sanity/vision": "^3.31.0", + "@sanity/visual-editing": "^2.1.6", + "d3": "^7.9.0", + "date-fns": "^3.6.0", "dotenv-cli": "^7.3.0", + "mui-image": "^1.0.7", "next": "14.1.0", "next-auth": "^4.24.5", "next-sanity": "^7.1.4", + "nodemailer": "^6.9.14", "pm2": "^5.3.1", "react": "^18", "react-dom": "^18", @@ -350,9 +361,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1224,50 +1236,36 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz", - "integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==", + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", + "license": "Apache-2.0" + }, + "node_modules/@mdi/react": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz", + "integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "prop-types": "^15.7.2" } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.11.tgz", - "integrity": "sha512-JVrJ9Jo4gyU707ujnRzmE8ABBWpXd6FwL9GYULmwZRtfPg89ggXs/S3MStQkpJ1JRWfdLL6S5syXmgQGq5EDAw==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.10", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.10.tgz", - "integrity": "sha512-9cF8oUHZKo9oQ7EQ3pxPELaZuZVmphskU4OI6NiJNDVN7zcuvrEsuWjYo1Zh4fLiC39Nrvm30h/B51rcUjvSGA==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", + "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -1290,21 +1288,22 @@ } }, "node_modules/@mui/material": { - "version": "5.15.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.10.tgz", - "integrity": "sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", + "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.36", - "@mui/core-downloads-tracker": "^5.15.10", - "@mui/system": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/core-downloads-tracker": "^5.16.7", + "@mui/system": "^5.16.7", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, "engines": { @@ -1334,12 +1333,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz", - "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.11", + "@mui/utils": "^5.16.6", "prop-types": "^15.8.1" }, "engines": { @@ -1360,9 +1360,10 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz", - "integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", @@ -1391,15 +1392,16 @@ } }, "node_modules/@mui/system": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.9.tgz", - "integrity": "sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.9", - "@mui/styled-engine": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1430,11 +1432,12 @@ } }, "node_modules/@mui/types": { - "version": "7.2.13", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", - "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", + "version": "7.2.17", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz", + "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==", + "license": "MIT", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1443,14 +1446,17 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz", - "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^18.3.1" }, "engines": { "node": ">=12.0.0" @@ -1469,6 +1475,92 @@ } } }, + "node_modules/@mui/x-date-pickers": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.19.0.tgz", + "integrity": "sha512-OIQ+IxgL2Si7DP68sw1ImcHXZtAmklHcyo/oqP4HuJZ2lVnP5sJkoXrksfumL1wjWKJkecONFz3unAqViKXzCQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/utils": "^5.16.6", + "@mui/x-internals": "7.18.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.18.0.tgz", + "integrity": "sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/utils": "^5.16.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@next-auth/prisma-adapter": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.7.tgz", @@ -1975,6 +2067,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -2220,13 +2313,13 @@ } }, "node_modules/@sanity/client": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@sanity/client/-/client-6.15.1.tgz", - "integrity": "sha512-WA85uX8FX7PbWyDyQiTsRn75OLt1GNtQPTzlHyNymAfItDgVmbP7Cwk5aH2B+7gMSkAK7wuK9Ea2YyHl7Kl2WA==", + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/@sanity/client/-/client-6.22.1.tgz", + "integrity": "sha512-2F/byHTbA91iS2YuJA/LmuJbmh4prqMYqhMSQ2PGFBPU2z/trBOByuuYrHAQfjmETu95n1N24t5A8XjqzjQZTQ==", + "license": "MIT", "dependencies": { - "@sanity/eventsource": "^5.0.0", - "@vercel/stega": "0.1.0", - "get-it": "^8.4.10", + "@sanity/eventsource": "^5.0.2", + "get-it": "^8.6.5", "rxjs": "^7.0.0" }, "engines": { @@ -2238,6 +2331,18 @@ "resolved": "https://registry.npmjs.org/@sanity/color/-/color-2.2.5.tgz", "integrity": "sha512-tTi22KoKuER3sldXYl4c1Dq2zU7tMLDkljFiaUKVkBbu4PBvRGCFw75kXZnD2b4Bsp6vin+7sI+AKdCKRhfRuw==" }, + "node_modules/@sanity/core-loader": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@sanity/core-loader/-/core-loader-1.6.23.tgz", + "integrity": "sha512-DrNadkbr2KlsZbUNir4cCkEHe1kICpDIOpY+W4nQJNVqb/WCVFQuaVrjulESSSFG7DRKT7sdPJOax4PC/MgIRg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@sanity/client": "^6.21.3" + } + }, "node_modules/@sanity/diff": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/@sanity/diff/-/diff-3.31.0.tgz", @@ -2258,12 +2363,13 @@ } }, "node_modules/@sanity/eventsource": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-5.0.1.tgz", - "integrity": "sha512-BFdRPTqVI76Nh18teu8850lV8DETdtJilFAlmQq/BdoXo88BSWBSTkIIi+H6AW1O9Nd7uT+9VRBqKuL2HKrYlA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-5.0.2.tgz", + "integrity": "sha512-/B9PMkUvAlUrpRq0y+NzXgRv5lYCLxZNsBJD2WXVnqZYOfByL9oQBV7KiTaARuObp5hcQYuPfOAVjgXe3hrixA==", + "license": "MIT", "dependencies": { - "@types/event-source-polyfill": "1.0.2", - "@types/eventsource": "1.1.12", + "@types/event-source-polyfill": "1.0.5", + "@types/eventsource": "1.1.15", "event-source-polyfill": "1.0.31", "eventsource": "2.0.2" } @@ -2368,6 +2474,258 @@ "react": "^16.9 || ^17 || ^18" } }, + "node_modules/@sanity/initial-value-templates": { + "version": "2.36.2", + "resolved": "https://registry.npmjs.org/@sanity/initial-value-templates/-/initial-value-templates-2.36.2.tgz", + "integrity": "sha512-QoJIkVyBtsU/76F/BfblLrnAg+v3EM3G30IGl9A8zjSB0eaw0o+ulQhbOnswCz8oTqf6DscVDfqFia9Wa6s1UA==", + "license": "MIT", + "dependencies": { + "@sanity/icons": "^1.3.4", + "@sanity/util": "2.36.2", + "@types/lodash": "4.14.149", + "lodash": "^4.17.15", + "oneline": "^1.0.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/@sanity/client": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@sanity/client/-/client-3.4.1.tgz", + "integrity": "sha512-WSvnroCHqboUeyY0nl71vDPKmfurXI0mtqdNDb5u8MW00CAHRyCt1+Sgy39D/g+6R35FYniV31vTSTo3ofim0A==", + "license": "MIT", + "dependencies": { + "@sanity/eventsource": "^4.0.0", + "get-it": "^6.1.1", + "make-error": "^1.3.0", + "object-assign": "^4.1.1", + "rxjs": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/@sanity/eventsource": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-4.1.1.tgz", + "integrity": "sha512-4RpexVqD+hbIXDgFLgWq/vSJWHNEBbc9eGa96ear/3ADFhMQx+QG4Q7r3QmQkB2t7LfUrrJfMQn9sMwaBTlurg==", + "license": "MIT", + "dependencies": { + "event-source-polyfill": "1.0.31", + "eventsource": "2.0.2" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/@sanity/icons": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@sanity/icons/-/icons-1.3.10.tgz", + "integrity": "sha512-5wVG/vIiGuGrSmq+Bl3PY7XDgQrGv0fyHdJI64FSulnr2wH3NMqZ6C59UFxnrZ93sr7kOt0zQFoNv2lkPBi0Cg==", + "license": "MIT", + "peerDependencies": { + "react": "^16.9 || ^17 || ^18" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/@sanity/types": { + "version": "2.36.2", + "resolved": "https://registry.npmjs.org/@sanity/types/-/types-2.36.2.tgz", + "integrity": "sha512-cg0kq2wq63e9HMP6oqk9rnI7AgxjkZlhb39xgcV9QH8FaWpYR6+H7uFBkIVsCCrgdmkLjMvF5z0dmXgQfBHUlA==", + "license": "MIT", + "dependencies": { + "@sanity/client": "^3.3.3", + "@sanity/color": "^2.1.14", + "@types/react": "^17.0.42", + "rxjs": "^6.5.3" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/@sanity/util": { + "version": "2.36.2", + "resolved": "https://registry.npmjs.org/@sanity/util/-/util-2.36.2.tgz", + "integrity": "sha512-dxR63aAwHQLC1An+FsnQQIypK20OKlrB+Vqlibwr2c9O0ta6b0SKHNTH8igt9SbHW24YCfFEg29NUNLJK956WQ==", + "license": "MIT", + "dependencies": { + "@sanity/types": "2.36.2", + "dotenv": "^8.2.0", + "fs-extra": "^7.0.0", + "get-random-values": "^1.2.2", + "lodash": "^4.17.15", + "moment": "^2.29.4", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "license": "MIT" + }, + "node_modules/@sanity/initial-value-templates/node_modules/@types/react": { + "version": "17.0.83", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", + "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/get-it": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/get-it/-/get-it-6.1.1.tgz", + "integrity": "sha512-2835L9lb4NAgjAbFOMMOm2XDSgj+lWmmCQv40A5rE7zZoIdM2+yk7Ie+sBD3T5lHW/Dw5IFFHyx16oQGpAo4hQ==", + "license": "MIT", + "dependencies": { + "@sanity/timed-out": "^4.0.2", + "create-error-class": "^3.0.2", + "debug": "^2.6.8", + "decompress-response": "^6.0.0", + "follow-redirects": "^1.2.4", + "form-urlencoded": "^2.0.7", + "into-stream": "^3.1.0", + "is-plain-object": "^2.0.4", + "is-retry-allowed": "^1.1.0", + "is-stream": "^1.1.0", + "nano-pubsub": "^1.0.2", + "object-assign": "^4.1.1", + "parse-headers": "^2.0.4", + "progress-stream": "^2.0.0", + "same-origin": "^0.1.1", + "simple-concat": "^1.0.1", + "tunnel-agent": "^0.6.0", + "url-parse": "^1.1.9" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@sanity/initial-value-templates/node_modules/nano-pubsub": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-pubsub/-/nano-pubsub-1.0.2.tgz", + "integrity": "sha512-HtPs1RbULM/z8wt3BbeeZlxVNiJbl+zQAwwrbc0KAq5NHaCG3MmffOVCpRhNTs+TK67MdN6aZ+5wzPtRZvME+w==", + "license": "MIT" + }, + "node_modules/@sanity/initial-value-templates/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@sanity/initial-value-templates/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@sanity/initial-value-templates/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@sanity/logos": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@sanity/logos/-/logos-2.1.6.tgz", @@ -2621,9 +2979,10 @@ } }, "node_modules/@sanity/preview-url-secret": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@sanity/preview-url-secret/-/preview-url-secret-1.6.4.tgz", - "integrity": "sha512-iOu6n5Zt7kuWoOYRliS2o/MDDBWcWkrnwwss2tglIBl744RPRkz0yi9qulnpAqMnWrZyIU8fV1yPCdg0RY+olw==", + "version": "1.6.21", + "resolved": "https://registry.npmjs.org/@sanity/preview-url-secret/-/preview-url-secret-1.6.21.tgz", + "integrity": "sha512-ZAIT4I5Nrsax+RBQVhSBY4PnnHgaI+eybSd5aHhjZi97+ziMTtTLnL1Hsg+ViLtPqJWwX0GiB0l8nSfGAt7+QA==", + "license": "MIT", "dependencies": { "@sanity/uuid": "3.0.2" }, @@ -2631,7 +2990,23 @@ "node": ">=18" }, "peerDependencies": { - "@sanity/client": "^6.14.4" + "@sanity/client": "^6.21.3" + } + }, + "node_modules/@sanity/react-loader": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@sanity/react-loader/-/react-loader-1.10.7.tgz", + "integrity": "sha512-YrVIuOHtVnQHW3Xzf9phxvy+HQD3v3VAw2me/Bkhi4csDpemTl7QZS8LlQw7bmXWKFLw1xCwrMO2g4YRk89eRw==", + "license": "MIT", + "dependencies": { + "@sanity/core-loader": "1.6.23" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@sanity/client": "^6.21.3", + "react": "^18.3 || >=19.0.0-rc" } }, "node_modules/@sanity/schema": { @@ -2648,6 +3023,184 @@ "object-inspect": "^1.6.0" } }, + "node_modules/@sanity/structure": { + "version": "2.36.2", + "resolved": "https://registry.npmjs.org/@sanity/structure/-/structure-2.36.2.tgz", + "integrity": "sha512-PYoWo0ppErINe3RC6i/NS1gpH+As2vNXPdEWk91PLIOmTLsQYXyWD7mthUAmzEUTIDdP0bxJb5+YwXYLZMErHg==", + "license": "MIT", + "dependencies": { + "@sanity/client": "^3.3.3", + "@sanity/icons": "^1.3.4", + "@sanity/initial-value-templates": "2.36.2", + "@types/lodash": "4.14.149", + "@types/memoize-one": "^3.1.1", + "lodash": "^4.17.15", + "memoize-one": "^3.1.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@sanity/structure/node_modules/@sanity/client": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@sanity/client/-/client-3.4.1.tgz", + "integrity": "sha512-WSvnroCHqboUeyY0nl71vDPKmfurXI0mtqdNDb5u8MW00CAHRyCt1+Sgy39D/g+6R35FYniV31vTSTo3ofim0A==", + "license": "MIT", + "dependencies": { + "@sanity/eventsource": "^4.0.0", + "get-it": "^6.1.1", + "make-error": "^1.3.0", + "object-assign": "^4.1.1", + "rxjs": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sanity/structure/node_modules/@sanity/eventsource": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-4.1.1.tgz", + "integrity": "sha512-4RpexVqD+hbIXDgFLgWq/vSJWHNEBbc9eGa96ear/3ADFhMQx+QG4Q7r3QmQkB2t7LfUrrJfMQn9sMwaBTlurg==", + "license": "MIT", + "dependencies": { + "event-source-polyfill": "1.0.31", + "eventsource": "2.0.2" + } + }, + "node_modules/@sanity/structure/node_modules/@sanity/icons": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@sanity/icons/-/icons-1.3.10.tgz", + "integrity": "sha512-5wVG/vIiGuGrSmq+Bl3PY7XDgQrGv0fyHdJI64FSulnr2wH3NMqZ6C59UFxnrZ93sr7kOt0zQFoNv2lkPBi0Cg==", + "license": "MIT", + "peerDependencies": { + "react": "^16.9 || ^17 || ^18" + } + }, + "node_modules/@sanity/structure/node_modules/@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "license": "MIT" + }, + "node_modules/@sanity/structure/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@sanity/structure/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sanity/structure/node_modules/get-it": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/get-it/-/get-it-6.1.1.tgz", + "integrity": "sha512-2835L9lb4NAgjAbFOMMOm2XDSgj+lWmmCQv40A5rE7zZoIdM2+yk7Ie+sBD3T5lHW/Dw5IFFHyx16oQGpAo4hQ==", + "license": "MIT", + "dependencies": { + "@sanity/timed-out": "^4.0.2", + "create-error-class": "^3.0.2", + "debug": "^2.6.8", + "decompress-response": "^6.0.0", + "follow-redirects": "^1.2.4", + "form-urlencoded": "^2.0.7", + "into-stream": "^3.1.0", + "is-plain-object": "^2.0.4", + "is-retry-allowed": "^1.1.0", + "is-stream": "^1.1.0", + "nano-pubsub": "^1.0.2", + "object-assign": "^4.1.1", + "parse-headers": "^2.0.4", + "progress-stream": "^2.0.0", + "same-origin": "^0.1.1", + "simple-concat": "^1.0.1", + "tunnel-agent": "^0.6.0", + "url-parse": "^1.1.9" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@sanity/structure/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sanity/structure/node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sanity/structure/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sanity/structure/node_modules/memoize-one": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-3.1.1.tgz", + "integrity": "sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==", + "license": "MIT" + }, + "node_modules/@sanity/structure/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@sanity/structure/node_modules/nano-pubsub": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-pubsub/-/nano-pubsub-1.0.2.tgz", + "integrity": "sha512-HtPs1RbULM/z8wt3BbeeZlxVNiJbl+zQAwwrbc0KAq5NHaCG3MmffOVCpRhNTs+TK67MdN6aZ+5wzPtRZvME+w==", + "license": "MIT" + }, + "node_modules/@sanity/structure/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@sanity/structure/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/@sanity/telemetry": { "version": "0.7.7", "resolved": "https://registry.npmjs.org/@sanity/telemetry/-/telemetry-0.7.7.tgz", @@ -2663,6 +3216,15 @@ "node": ">=16.0.0" } }, + "node_modules/@sanity/timed-out": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sanity/timed-out/-/timed-out-4.0.2.tgz", + "integrity": "sha512-NBDKGj14g9Z+bopIvZcQKWCzJq5JSrdmzRR1CS+iyA3Gm8SnIWBfZa7I3mTg2X6Nu8LQXG0EPKXdOGozLS4i3w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@sanity/types": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/@sanity/types/-/types-3.31.0.tgz", @@ -2829,32 +3391,52 @@ } }, "node_modules/@sanity/visual-editing": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@sanity/visual-editing/-/visual-editing-1.2.2.tgz", - "integrity": "sha512-iR1stYfPPhBJLjwKY3MBgr0DcRADgnRaTJvZ3nUJ2puGVniPoSxJ9Zj4PkspferU8997RKKtupN9m9spFYskXg==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@sanity/visual-editing/-/visual-editing-2.1.10.tgz", + "integrity": "sha512-dnr8Y6SjcbazEDpSsgRouU/aTHn2sUQXkamzAEzqnb/9JUQuoetOzZUTKrC0CQ33bwLjDLmjAjPNW5W/MRohuw==", + "license": "MIT", "dependencies": { - "@vercel/stega": "0.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-is": "18.2.0", - "scroll-into-view-if-needed": "^3.1.0" + "@sanity/preview-url-secret": "^1.6.21", + "@vercel/stega": "0.1.2", + "scroll-into-view-if-needed": "^3.1.0", + "valibot": "0.31.1" }, "engines": { "node": ">=18" }, "peerDependencies": { "@remix-run/react": ">= 2", - "next": ">= 13" + "@sanity/client": "^6.21.3", + "@sveltejs/kit": ">= 2", + "next": ">= 13 || >=14.3.0-canary.0 <14.3.0 || >=15.0.0-rc", + "react": "^18.3 || >=19.0.0-rc", + "react-dom": "^18.3 || >=19.0.0-rc", + "svelte": ">= 4" }, "peerDependenciesMeta": { "@remix-run/react": { "optional": true }, + "@sanity/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, "next": { "optional": true + }, + "svelte": { + "optional": true } } }, + "node_modules/@sanity/visual-editing/node_modules/@vercel/stega": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@vercel/stega/-/stega-0.1.2.tgz", + "integrity": "sha512-P7mafQXjkrsoyTRppnt0N21udKS9wUmLXHRyP9saLXLHw32j/FgUJ3FscSWgvSqRs4cj7wKZtwqJEvWJ2jbGmA==", + "license": "MPL-2.0" + }, "node_modules/@sanity/webhook": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sanity/webhook/-/webhook-4.0.0.tgz", @@ -2938,14 +3520,25 @@ } }, "node_modules/@types/event-source-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/event-source-polyfill/-/event-source-polyfill-1.0.2.tgz", - "integrity": "sha512-qE5zrFd73BRs5oSjVys6g/5GboqOMbzLRTUFPAhfULvvvbRAOXw9m4Wk+p1BtoZm4JgW7TljGGfVabBqvi3eig==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/event-source-polyfill/-/event-source-polyfill-1.0.5.tgz", + "integrity": "sha512-iaiDuDI2aIFft7XkcwMzDWLqo7LVDixd2sR6B4wxJut9xcp/Ev9bO4EFg4rm6S9QxATLBj5OPxdeocgmhjwKaw==", + "license": "MIT" }, "node_modules/@types/eventsource": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@types/eventsource/-/eventsource-1.1.12.tgz", - "integrity": "sha512-KlVguyxdoO8VkAhOMwOemK+NhFAg0gOwJHgimrWJUgM6LrdVW2nLa+d47WVWQcs8feRn0eeP+5yUDmDfzLBjRA==" + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@types/eventsource/-/eventsource-1.1.15.tgz", + "integrity": "sha512-XQmGcbnxUNa06HR3VBVkc9+A2Vpi9ZyLJcdS5dwaQQ/4ZMWFO+5c90FnMUpbtMZwB/FChoYHwuVg8TvkECacTA==", + "license": "MIT" + }, + "node_modules/@types/follow-redirects": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@types/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/glob": { "version": "7.2.0", @@ -2991,6 +3584,12 @@ "@types/lodash": "*" } }, + "node_modules/@types/memoize-one": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/memoize-one/-/memoize-one-3.1.2.tgz", + "integrity": "sha512-A5ydEHaD2xdUbrXlAjF2TifOkYq7TJXU+5t7rLqBGfispKdJJoZnWwPQPyvjh0exxGTTO1ugLB+5jL9P3DQh5w==", + "license": "MIT" + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -3014,10 +3613,20 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "node_modules/@types/progress-stream": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/progress-stream/-/progress-stream-2.0.5.tgz", + "integrity": "sha512-5YNriuEZkHlFHHepLIaxzq3atGeav1qCTGzB74HKWpo66qjfostF+rHc785YYYHeBytve8ZG3ejg42jEIfXNiQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.2.48", @@ -3046,9 +3655,10 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -3698,6 +4308,18 @@ } ] }, + "node_modules/capture-stack-trace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", + "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3958,9 +4580,10 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4300,117 +4923,539 @@ } ] }, - "node_modules/crc32-stream/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/crc32-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "license": "MIT", + "dependencies": { + "capture-stack-trace": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "dependencies": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, + "node_modules/croner": { + "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/culvert": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" + }, + "node_modules/cyclist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.2.tgz", + "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", "dependencies": { - "safe-buffer": "~5.2.0" + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/create-react-class": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", - "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", - "dependencies": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/croner": { - "version": "4.1.97", - "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", "dependencies": { - "tiny-invariant": "^1.0.6" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/css-to-react-native": { + "node_modules/d3-shape": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" + "d3-array": "2 - 3" }, "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": ">=12" } }, - "node_modules/cssstyle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", - "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", "dependencies": { - "rrweb-cssom": "^0.6.0" + "d3-time": "1 - 3" }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/culvert": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } }, - "node_modules/cyclist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.2.tgz", - "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==" + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } }, "node_modules/data-uri-to-buffer": { "version": "1.2.0", @@ -4435,18 +5480,13 @@ "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==" }, "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/date-now": { @@ -4672,6 +5712,15 @@ "node": ">= 14" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5202,15 +6251,16 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5259,6 +6309,12 @@ "node": ">= 6" } }, + "node_modules/form-urlencoded": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/form-urlencoded/-/form-urlencoded-2.0.9.tgz", + "integrity": "sha512-fWUzNiOnYa126vFAT6TFXd1mhJrvD8IqmQ9ilZPjkLYQfaRreBr5fIUoOpPlWtqaAG64nzoE7u5zSetifab9IA==", + "license": "MIT" + }, "node_modules/framer-motion": { "version": "10.18.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", @@ -5408,18 +6464,16 @@ } }, "node_modules/get-it": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/get-it/-/get-it-8.4.10.tgz", - "integrity": "sha512-yFcSKM8qyGvycYPcnoQGYC3mxeUxXnlQnA+2DF5TyrEHWN2G5pirXylJX+/AyCQrpctEWu/163fS0g0tFnhYcw==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/get-it/-/get-it-8.6.5.tgz", + "integrity": "sha512-o1hjPwrb/icm3WJbCweTSq8mKuDfJlqwbFauI+Pdgid99at/BFaBXFBJZE+uqvHyOVARE4z680S44vrDm8SsCw==", + "license": "MIT", "dependencies": { - "debug": "^4.3.4", + "@types/follow-redirects": "^1.14.4", + "@types/progress-stream": "^2.0.5", "decompress-response": "^7.0.0", - "follow-redirects": "^1.15.4", - "into-stream": "^6.0.0", - "is-plain-object": "^5.0.0", + "follow-redirects": "^1.15.6", "is-retry-allowed": "^2.2.0", - "is-stream": "^2.0.1", - "parse-headers": "^2.0.5", "progress-stream": "^2.0.0", "tunnel-agent": "^0.6.0" }, @@ -5881,19 +6935,26 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/into-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", - "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "license": "MIT", "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, "node_modules/ip-address": { @@ -6129,6 +7190,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -6408,6 +7478,12 @@ "semver": "bin/semver.js" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/md5-o-matic": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", @@ -6613,6 +7689,19 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/mui-image": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mui-image/-/mui-image-1.0.7.tgz", + "integrity": "sha512-U79TWMKMfMC1ZiGnv/M+SaVJeUpubEjXOdy7w53RsvidUAMZ+4nW+QmDG9yg5fgWeYy6YJgLHyI9BHSDw76iIg==", + "license": "ISC", + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "@mui/material": "^5.0.1", + "prop-types": "^15.7.2", + "react": "^17.0.2 || ^18.0.0" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -6780,6 +7869,40 @@ "styled-components": "^5.2 || ^6.0" } }, + "node_modules/next-sanity/node_modules/@sanity/visual-editing": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@sanity/visual-editing/-/visual-editing-1.2.2.tgz", + "integrity": "sha512-iR1stYfPPhBJLjwKY3MBgr0DcRADgnRaTJvZ3nUJ2puGVniPoSxJ9Zj4PkspferU8997RKKtupN9m9spFYskXg==", + "license": "MIT", + "dependencies": { + "@vercel/stega": "0.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-is": "18.2.0", + "scroll-into-view-if-needed": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@remix-run/react": ">= 2", + "next": ">= 13" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "next": { + "optional": true + } + } + }, + "node_modules/next-sanity/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "license": "MIT" + }, "node_modules/node-machine-id": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", @@ -6790,6 +7913,15 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, + "node_modules/nodemailer": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -6967,11 +8099,12 @@ } }, "node_modules/p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/p-limit": { @@ -7127,7 +8260,8 @@ "node_modules/parse-headers": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", - "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", + "license": "MIT" }, "node_modules/parse-json": { "version": "5.2.0", @@ -7849,9 +8983,10 @@ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -7883,15 +9018,16 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-fast-compare": { @@ -7943,9 +9079,10 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -8343,6 +9480,12 @@ "node": "*" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -8404,6 +9547,12 @@ } ] }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -8441,6 +9590,12 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/same-origin": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/same-origin/-/same-origin-0.1.1.tgz", + "integrity": "sha512-effkSW9cap879l6CVNdwL5iubVz8tkspqgfiqwgBgFQspV7152WHaLzr5590yR8oFgt7E1d4lO09uUhtAgUPoA==", + "license": "MIT" + }, "node_modules/sanity": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/sanity/-/sanity-3.31.0.tgz", @@ -8700,6 +9855,22 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/sanity/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/sanity/node_modules/framer-motion": { "version": "11.0.8", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.8.tgz", @@ -8767,9 +9938,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -8847,6 +10019,26 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/simple-wcswidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", @@ -9720,6 +10912,12 @@ "uuidv7": "cli.js" } }, + "node_modules/valibot": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.31.1.tgz", + "integrity": "sha512-2YYIhPrnVSz/gfT2/iXVTrSj92HwchCt9Cga/6hX4B26iCz9zkIsGTS0HjDYTZfTi1Un0X6aRvhBi1cfqs/i0Q==", + "license": "MIT" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", From ebe4c43df04a4147d25a195d90ffa2ebf8365bd4 Mon Sep 17 00:00:00 2001 From: Tepohe Date: Thu, 17 Oct 2024 16:26:53 +0200 Subject: [PATCH 14/65] Squashed commit adding pr/64 commit 8b529ef70214c7d3d66054181557f0594577a201 Author: Tepohe Date: Thu Oct 17 16:20:05 2024 +0200 Updated incorrect url-path commit 73999a883f499fe8ccf66bc8293f37a4d8f43fc4 Merge: f0bacbb 1861431 Author: Eric Svebakk Date: Thu Oct 17 16:11:36 2024 +0200 git push origin developmentMerge branch 'Sebbben-featureBetterRegisterFeedback' into development Handled merge-conflict for pr/64 commit 1861431483ec52ea0647b6cb912c27f54a7d767e Merge: f0bacbb cd922ac Author: Tepohe Date: Thu Oct 17 16:08:02 2024 +0200 Handled merge-conflict commit f0bacbbc3673170802017986d8216aba779c2f60 Merge: d26bd3f d198b94 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 17 15:46:05 2024 +0200 Merge pull request #75 from Borgarsy/feature-halloween-theme Temporary Halloween theme commit cd922ac9be0d937de54bcfd3a71cea81cab9c070 Author: Sebbben Date: Fri Oct 11 10:20:45 2024 +0200 made it so that pressing enter on signin and register page submits the forms commit 56dc5709ad033cc676d336540e5655d38e5a0095 Author: Sebbben Date: Fri Oct 11 10:16:44 2024 +0200 removed duplicate login button on successfull registration commit d26bd3fa6f650a486f19b18daffc2c7778b62529 Merge: 0f096c0 c019c46 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 10 16:54:40 2024 +0200 Merge pull request #67 from Sebbben/fixNoResultMembershipSearch Fix no result membership search commit c019c466036d5168b50a953036050c7a0ae62021 Author: Sebbben Date: Sat Oct 5 16:20:01 2024 +0200 Made tables turn to first page when the table content changes with a search commit 0f096c0f9a8d629f66f073f6fd612cd3e1715b44 Merge: a1ce2ec 4dfd6f8 Author: Tepohe Date: Thu Oct 10 14:42:19 2024 +0200 Merge branch 'main' into development commit d198b9492b623e7959c640f04dc8a7199c84da0c Author: Borgar Date: Tue Oct 8 15:01:29 2024 +0200 adds halloween theme. commit a1ce2ec14eb8bafd7a5d9e5a9ce939a97c734f11 Merge: 4f7d813 f06a472 Author: Tepohe Date: Tue Oct 8 13:53:44 2024 +0200 Merge branch 'pr/53' into development commit f06a472fb08cbcc9989a93f1f8ad6497f666eb46 Author: Sebbben Date: Mon Oct 7 16:47:03 2024 +0200 Fixed wrong path for nav bar home from pointing to /home to pointing to / commit a715facad10659dd7d7edc1ec45e6d6260fbd477 Author: Sebbben Date: Tue Oct 1 17:24:53 2024 +0200 added snackbar to singin page commit 6322071f60e41dc381c232f99c301017ba138342 Merge: 131cc76 ea38e61 Author: Sebbben Date: Tue Oct 1 17:08:07 2024 +0200 Merged features from featureBetterRegisterFeedback into changes made by pvk05's fixRegisterUserResponse commit 131cc76ce624229a1a2dc3d8c449367f6832fae2 Merge: 221a968 eb8b2f2 Author: Philip Knudsen <113419351+pvk05@users.noreply.github.com> Date: Tue Oct 1 16:32:52 2024 +0200 Merge branch 'cybernetisk:main' into fixRegisterUserResponse commit 221a968a368d5074d5d28f0610909321fb848da3 Author: pvk05 Date: Sat Sep 28 00:57:24 2024 +0200 moved register response to a snackbar for better visual feedback commit ea38e61f1bad4be9fc25b3f35d34acad48ddfa4e Author: Sebbben Date: Fri Sep 27 23:41:22 2024 +0200 added loading indicator to singin page as well commit 8a3d2de7c6304b6fa23dd3654866e056b0a95fdd Author: pvk05 Date: Fri Sep 27 23:14:48 2024 +0200 checks if email includes "@" before registering user commit 3f547cfd5af113371057a12bf598cde6a62850b7 Author: pvk05 Date: Fri Sep 27 22:44:11 2024 +0200 fixed response from sendMail function to send correct feedback when failing commit 0b33808f1dc512ba7bc27872ba1d69502eab7960 Author: Sebbben Date: Thu Sep 26 17:07:50 2024 +0200 visual queue when registering user commit 0d154f5ef967dd55f6453abf93dead2504e34a47 Author: Sebbben Date: Thu Sep 26 14:40:51 2024 +0200 Renamed some directories to take advantage of nexts routing for more usable url paths Co-Authored-By: Philip Knudsen <113419351+pvk05@users.noreply.github.com> Co-Authored-By: Sebbben <42118203+Sebbben@users.noreply.github.com> --- app/(pages)/auth/register/page.js | 134 ++++++++++++++++------- app/(pages)/auth/signIn/page.js | 51 +++++++-- app/api/sendVerification/route.js | 1 + app/components/feedback/snackbarAlert.js | 37 +++++++ 4 files changed, 178 insertions(+), 45 deletions(-) create mode 100644 app/components/feedback/snackbarAlert.js diff --git a/app/(pages)/auth/register/page.js b/app/(pages)/auth/register/page.js index 03eaded..b081c6f 100644 --- a/app/(pages)/auth/register/page.js +++ b/app/(pages)/auth/register/page.js @@ -4,25 +4,44 @@ import { cybTheme } from "@/app/components/themeCYB"; import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; import { normalizeEmail } from "@/app/components/Login/authUtil"; import Link from "next/link"; import { useState } from "react"; +import SnackbarAlert from "@/app/components/feedback/snackbarAlert"; export default function registerPage() { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); - const [email, setEmail] = useState("") - const [response, setResponse] = useState("") + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [severity, setSeverity] = useState("") + const [email, setEmail] = useState(""); + const [response, setResponse] = useState(""); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); const debug = true; const handleRegister = async () => { + setSnackbarOpen(false) + + if (!email.includes("@")) { + setResponse("Email is invalid") + setSeverity("error") + setSnackbarOpen(true) + return + } + + setLoading(true); const responseCUE = await checkUserExists(email, debug) if (!responseCUE.ok) { setResponse(responseCUE.error); + setSeverity("error") + setSnackbarOpen(true) + setLoading(false); return; } @@ -30,6 +49,9 @@ export default function registerPage() { if (!responseCU.ok) { setResponse(responseCU.error) + setSeverity("error") + setSnackbarOpen(true) + setLoading(false); return; } @@ -41,12 +63,40 @@ export default function registerPage() { if (!responseSVM.ok) { setResponse(responseSVM.error); + setSeverity("error") + setSnackbarOpen(true) + setLoading(false); return; } else { setResponse(`User created. Email sent to ${responseSVM.email}`); + setSeverity("success") + setSuccess(true); + setSnackbarOpen(true) + setLoading(false); + return; } } + + const CheckedTextField = (title, textValue, textCallback) => { + + return ( + + textCallback(event.target.value)} + InputLabelProps={{ shrink: true }} + onKeyUp={(e)=>{if(e.key==="Enter") handleRegister()}} + /> + + ); + + } + return ( handleRegister()} > Register + + {loading && ( + + )} - - - - - - Log in - - - + + {success ? + + + + + + : + + + + Log in + + + + } {response != "" ? ( - response + ) : ( { - - return ( - - textCallback(event.target.value)} - InputLabelProps={{ shrink: true }} - /> - - ); - -} // async function checkUserExists(email, debug) { @@ -202,7 +262,7 @@ async function sendVerificiationMail(newUser, activateToken, debug) { if (debug) console.log("sendVerificiationMail response:", response); if (!response.ok) { - return { ok: false, error: response.error }; + return { ok: false, error: response.statusText }; } const data = await response.json(); diff --git a/app/(pages)/auth/signIn/page.js b/app/(pages)/auth/signIn/page.js index 8d1e5d1..1a8d343 100644 --- a/app/(pages)/auth/signIn/page.js +++ b/app/(pages)/auth/signIn/page.js @@ -2,12 +2,14 @@ "use client" import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; import { useState } from "react"; import { cybTheme } from "./../../../components/themeCYB"; import { signIn, useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { normalizeEmail } from "./../../../components/Login/authUtil"; +import SnackbarAlert from "@/app/components/feedback/snackbarAlert"; export default function SignInPage() { @@ -15,6 +17,9 @@ export default function SignInPage() { const [email, setEmail] = useState("") const [response, setResponse] = useState("") const [error, setError] = useState(false) + const [loading, setLoading] = useState(false) + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [severity, setSeverity] = useState("") const session = useSession() const router = useRouter() @@ -25,10 +30,14 @@ export default function SignInPage() { } const handleLogin = async () => { + setLoading(true); if (email == "") { setError(true) + setSeverity("error") setResponse("Please fill in your email") + setLoading(false); + setSnackbarOpen(true); return } @@ -41,11 +50,16 @@ export default function SignInPage() { if (response.error == null) { setError(false) + setSeverity("success") setResponse(`Email sent to ${normalizedEmail}`); - } - else { + setLoading(false); + setSnackbarOpen(true); + } else { setError(true); + setSeverity("error") setResponse(response.error); + setLoading(false); + setSnackbarOpen(true); } } @@ -72,12 +86,29 @@ export default function SignInPage() { error={error} onChange={(event) => setEmail(event.target.value)} InputLabelProps={{ shrink: true }} + onKeyUp={(e) => {if(e.key==="Enter") handleLogin()}} /> - @@ -99,12 +130,16 @@ export default function SignInPage() { Register new user - - - - + + + {response != "" ? ( - response + ) : ( { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + return ( +
+ + + {response} + + +
+ ); +} \ No newline at end of file From 184fe7db7f92a800348e5897de9af5667920a98e Mon Sep 17 00:00:00 2001 From: Tepohe Date: Mon, 28 Oct 2024 16:30:48 +0100 Subject: [PATCH 15/65] Squashed commit adding pr/82 commit cbe876a9553c82fc07d740edb5db998a1b0773a4 Merge: 8b529ef ac17a6e Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Mon Oct 28 16:24:24 2024 +0100 Merge pull request #82 from Sebbben/moveBoardPage Move board page commit ac17a6e51d1159e50b2c21280f92243ec7de5c63 Author: Sebbben Date: Fri Oct 25 10:54:13 2024 +0200 moved pages/main/board/* to (pages)/(main)/board commit 8b529ef70214c7d3d66054181557f0594577a201 Author: Tepohe Date: Thu Oct 17 16:20:05 2024 +0200 Updated incorrect url-path commit 73999a883f499fe8ccf66bc8293f37a4d8f43fc4 Merge: f0bacbb 1861431 Author: Eric Svebakk Date: Thu Oct 17 16:11:36 2024 +0200 git push origin developmentMerge branch 'Sebbben-featureBetterRegisterFeedback' into development Handled merge-conflict for pr/64 commit 1861431483ec52ea0647b6cb912c27f54a7d767e Merge: f0bacbb cd922ac Author: Tepohe Date: Thu Oct 17 16:08:02 2024 +0200 Handled merge-conflict commit f0bacbbc3673170802017986d8216aba779c2f60 Merge: d26bd3f d198b94 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 17 15:46:05 2024 +0200 Merge pull request #75 from Borgarsy/feature-halloween-theme Temporary Halloween theme commit cd922ac9be0d937de54bcfd3a71cea81cab9c070 Author: Sebbben Date: Fri Oct 11 10:20:45 2024 +0200 made it so that pressing enter on signin and register page submits the forms commit 56dc5709ad033cc676d336540e5655d38e5a0095 Author: Sebbben Date: Fri Oct 11 10:16:44 2024 +0200 removed duplicate login button on successfull registration commit d26bd3fa6f650a486f19b18daffc2c7778b62529 Merge: 0f096c0 c019c46 Author: Eric Svebakk <47322476+EricSvebakk@users.noreply.github.com> Date: Thu Oct 10 16:54:40 2024 +0200 Merge pull request #67 from Sebbben/fixNoResultMembershipSearch Fix no result membership search commit c019c466036d5168b50a953036050c7a0ae62021 Author: Sebbben Date: Sat Oct 5 16:20:01 2024 +0200 Made tables turn to first page when the table content changes with a search commit 0f096c0f9a8d629f66f073f6fd612cd3e1715b44 Merge: a1ce2ec 4dfd6f8 Author: Tepohe Date: Thu Oct 10 14:42:19 2024 +0200 Merge branch 'main' into development commit d198b9492b623e7959c640f04dc8a7199c84da0c Author: Borgar Date: Tue Oct 8 15:01:29 2024 +0200 adds halloween theme. commit a1ce2ec14eb8bafd7a5d9e5a9ce939a97c734f11 Merge: 4f7d813 f06a472 Author: Tepohe Date: Tue Oct 8 13:53:44 2024 +0200 Merge branch 'pr/53' into development commit f06a472fb08cbcc9989a93f1f8ad6497f666eb46 Author: Sebbben Date: Mon Oct 7 16:47:03 2024 +0200 Fixed wrong path for nav bar home from pointing to /home to pointing to / commit a715facad10659dd7d7edc1ec45e6d6260fbd477 Author: Sebbben Date: Tue Oct 1 17:24:53 2024 +0200 added snackbar to singin page commit 6322071f60e41dc381c232f99c301017ba138342 Merge: 131cc76 ea38e61 Author: Sebbben Date: Tue Oct 1 17:08:07 2024 +0200 Merged features from featureBetterRegisterFeedback into changes made by pvk05's fixRegisterUserResponse commit 131cc76ce624229a1a2dc3d8c449367f6832fae2 Merge: 221a968 eb8b2f2 Author: Philip Knudsen <113419351+pvk05@users.noreply.github.com> Date: Tue Oct 1 16:32:52 2024 +0200 Merge branch 'cybernetisk:main' into fixRegisterUserResponse commit 221a968a368d5074d5d28f0610909321fb848da3 Author: pvk05 Date: Sat Sep 28 00:57:24 2024 +0200 moved register response to a snackbar for better visual feedback commit ea38e61f1bad4be9fc25b3f35d34acad48ddfa4e Author: Sebbben Date: Fri Sep 27 23:41:22 2024 +0200 added loading indicator to singin page as well commit 8a3d2de7c6304b6fa23dd3654866e056b0a95fdd Author: pvk05 Date: Fri Sep 27 23:14:48 2024 +0200 checks if email includes "@" before registering user commit 3f547cfd5af113371057a12bf598cde6a62850b7 Author: pvk05 Date: Fri Sep 27 22:44:11 2024 +0200 fixed response from sendMail function to send correct feedback when failing commit 0b33808f1dc512ba7bc27872ba1d69502eab7960 Author: Sebbben Date: Thu Sep 26 17:07:50 2024 +0200 visual queue when registering user commit 0d154f5ef967dd55f6453abf93dead2504e34a47 Author: Sebbben Date: Thu Sep 26 14:40:51 2024 +0200 Renamed some directories to take advantage of nexts routing for more usable url paths Co-Authored-By: Sebbben <42118203+Sebbben@users.noreply.github.com> --- app/{pages/main => (pages)/(main)}/board/page.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/{pages/main => (pages)/(main)}/board/page.js (100%) diff --git a/app/pages/main/board/page.js b/app/(pages)/(main)/board/page.js similarity index 100% rename from app/pages/main/board/page.js rename to app/(pages)/(main)/board/page.js From 944012391c25cc56cf531470ae54ef93fb8f46b6 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 29 Oct 2024 08:00:50 +0100 Subject: [PATCH 16/65] updated api to include api version in path --- app/api/{ => v1}/activate/[token]/route.js | 0 app/api/{ => v1}/auth/[...nextauth]/route.js | 0 app/api/{ => v1}/createUser/route.js | 0 app/api/{ => v1}/data/prisma/route.js | 0 app/api/{ => v1}/data/setRoles/route.js | 0 app/api/{ => v1}/data/updateORCreateShift/route.js | 0 app/api/{ => v1}/sendVerification/route.js | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename app/api/{ => v1}/activate/[token]/route.js (100%) rename app/api/{ => v1}/auth/[...nextauth]/route.js (100%) rename app/api/{ => v1}/createUser/route.js (100%) rename app/api/{ => v1}/data/prisma/route.js (100%) rename app/api/{ => v1}/data/setRoles/route.js (100%) rename app/api/{ => v1}/data/updateORCreateShift/route.js (100%) rename app/api/{ => v1}/sendVerification/route.js (100%) diff --git a/app/api/activate/[token]/route.js b/app/api/v1/activate/[token]/route.js similarity index 100% rename from app/api/activate/[token]/route.js rename to app/api/v1/activate/[token]/route.js diff --git a/app/api/auth/[...nextauth]/route.js b/app/api/v1/auth/[...nextauth]/route.js similarity index 100% rename from app/api/auth/[...nextauth]/route.js rename to app/api/v1/auth/[...nextauth]/route.js diff --git a/app/api/createUser/route.js b/app/api/v1/createUser/route.js similarity index 100% rename from app/api/createUser/route.js rename to app/api/v1/createUser/route.js diff --git a/app/api/data/prisma/route.js b/app/api/v1/data/prisma/route.js similarity index 100% rename from app/api/data/prisma/route.js rename to app/api/v1/data/prisma/route.js diff --git a/app/api/data/setRoles/route.js b/app/api/v1/data/setRoles/route.js similarity index 100% rename from app/api/data/setRoles/route.js rename to app/api/v1/data/setRoles/route.js diff --git a/app/api/data/updateORCreateShift/route.js b/app/api/v1/data/updateORCreateShift/route.js similarity index 100% rename from app/api/data/updateORCreateShift/route.js rename to app/api/v1/data/updateORCreateShift/route.js diff --git a/app/api/sendVerification/route.js b/app/api/v1/sendVerification/route.js similarity index 100% rename from app/api/sendVerification/route.js rename to app/api/v1/sendVerification/route.js From fad4284fe239426c2355379b267dcd681bf86abe Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 29 Oct 2024 08:02:51 +0100 Subject: [PATCH 17/65] updated all links to use new api path --- app/(pages)/(main)/admin/page.js | 2 +- app/(pages)/(main)/volunteering/cafe/page.js | 4 ++-- app/(pages)/auth/register/page.js | 4 ++-- app/api/v1/sendVerification/route.js | 2 +- app/middleware/prisma/prismaRequest.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/(pages)/(main)/admin/page.js b/app/(pages)/(main)/admin/page.js index e8bd419..0b2461f 100644 --- a/app/(pages)/(main)/admin/page.js +++ b/app/(pages)/(main)/admin/page.js @@ -217,7 +217,7 @@ function roleSettings( ) { const handleSave = async () => { - const response = await fetch("/api/data/setRoles", { + const response = await fetch("/api/v1/data/setRoles", { method: "post", mode: "cors", headers: { diff --git a/app/(pages)/(main)/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js index 87a278e..c1b73f8 100644 --- a/app/(pages)/(main)/volunteering/cafe/page.js +++ b/app/(pages)/(main)/volunteering/cafe/page.js @@ -88,7 +88,7 @@ function CafePage() { const manageShift = async () => { const sendRequest = async (date, id) => { - const response = await fetch("/api/data/updateORCreateShift", { + const response = await fetch("/api/v1/data/updateORCreateShift", { method: "post", mode: "cors", headers: { @@ -124,7 +124,7 @@ function CafePage() { } const manageShiftClear = async () => { - const response = await fetch("/api/data/updateORCreateShift", { + const response = await fetch("/api/v1/data/updateORCreateShift", { method: "post", mode: "cors", headers: { diff --git a/app/(pages)/auth/register/page.js b/app/(pages)/auth/register/page.js index b081c6f..49ae359 100644 --- a/app/(pages)/auth/register/page.js +++ b/app/(pages)/auth/register/page.js @@ -218,7 +218,7 @@ async function createUser(firstName, lastName, email, debug) { const normalizedEmail = normalizeEmail(email); - const response = await fetch("/api/createUser/", { + const response = await fetch("/api/v1/createUser/", { method: "post", mode: "cors", headers: { @@ -247,7 +247,7 @@ async function createUser(firstName, lastName, email, debug) { // async function sendVerificiationMail(newUser, activateToken, debug) { - const response = await fetch("/api/sendVerification/", { + const response = await fetch("/api/v1/sendVerification/", { method: "post", mode: "cors", headers: { diff --git a/app/api/v1/sendVerification/route.js b/app/api/v1/sendVerification/route.js index 0cb66e9..0073ece 100644 --- a/app/api/v1/sendVerification/route.js +++ b/app/api/v1/sendVerification/route.js @@ -10,7 +10,7 @@ export async function POST(req) { const args = await req.json(); const { user, activateToken } = args - const link = `${NEXTAUTH_URL}/api/activate/${activateToken.token}`; + const link = `${NEXTAUTH_URL}/api/v1/activate/${activateToken.token}`; const html = ` Hello ${user.firstName} ${user.lastName}, You have successfully created a user-account at ${NEXTAUTH_URL}.

diff --git a/app/middleware/prisma/prismaRequest.js b/app/middleware/prisma/prismaRequest.js index 604d30f..31f75ce 100644 --- a/app/middleware/prisma/prismaRequest.js +++ b/app/middleware/prisma/prismaRequest.js @@ -8,7 +8,7 @@ export default async function prismaRequest({ debug = false, }) { - const response = await fetch("/api/data/prisma", { + const response = await fetch("/api/v1/data/prisma", { method: "post", mode: "cors", headers: { From c77a94a63c754fc6cf3d0c18c2cf9b8cadb653c7 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 29 Oct 2024 15:53:28 +0100 Subject: [PATCH 18/65] beginning of api rewrite --- app/(pages)/(main)/layout.js | 2 +- app/(pages)/(main)/volunteering/logs/page.js | 39 +-- .../(main)/volunteering/membership/page.js | 32 +- app/(pages)/(main)/volunteering/page.js | 16 +- app/(pages)/auth/layout.js | 2 +- app/api/v2/auth/[...nextauth]/route.js | 92 ++++++ app/api/v2/memberships/route.js | 38 +++ app/api/v2/semester/route.js | 33 +++ app/api/v2/users/[userID]/route.js | 20 ++ app/api/v2/users/route.js | 63 ++++ app/api/v2/voucherLogs/route.js | 40 +++ app/api/v2/workLogs/route.js | 41 +++ app/auth/email.js | 24 ++ app/auth/layout.js | 62 ++++ app/auth/register/layout.js | 9 + app/auth/register/page.js | 273 ++++++++++++++++++ app/auth/signIn/layout.js | 9 + app/auth/signIn/page.js | 155 ++++++++++ app/components/layout/AppBar.js | 4 +- 19 files changed, 886 insertions(+), 68 deletions(-) create mode 100644 app/api/v2/auth/[...nextauth]/route.js create mode 100644 app/api/v2/memberships/route.js create mode 100644 app/api/v2/semester/route.js create mode 100644 app/api/v2/users/[userID]/route.js create mode 100644 app/api/v2/users/route.js create mode 100644 app/api/v2/voucherLogs/route.js create mode 100644 app/api/v2/workLogs/route.js create mode 100644 app/auth/email.js create mode 100644 app/auth/layout.js create mode 100644 app/auth/register/layout.js create mode 100644 app/auth/register/page.js create mode 100644 app/auth/signIn/layout.js create mode 100644 app/auth/signIn/page.js diff --git a/app/(pages)/(main)/layout.js b/app/(pages)/(main)/layout.js index 825b193..05577d8 100644 --- a/app/(pages)/(main)/layout.js +++ b/app/(pages)/(main)/layout.js @@ -61,7 +61,7 @@ export default function AppLayout({ children }) { }} > - {children} + {children} diff --git a/app/(pages)/(main)/volunteering/logs/page.js b/app/(pages)/(main)/volunteering/logs/page.js index e8f4928..51dfb47 100644 --- a/app/(pages)/(main)/volunteering/logs/page.js +++ b/app/(pages)/(main)/volunteering/logs/page.js @@ -76,35 +76,18 @@ function LogsPage() { }, []); useEffect(() => { - prismaRequest({ - model: "workLog", - method: "find", - request: { - include: { - LoggedByUser: true, - LoggedForUser: true, - }, - where: { - semesterId: session.data.semester.id, - }, - }, - callback: (data) => handleWorkLogs(data.data, session, setWorkLogs, setVouchersEarned) - }); + fetch("/api/v2/workLogs") + .then(res => res.json()) + .then(resData => { + handleWorkLogs(resData.workLogs, session, setWorkLogs, setVouchersEarned) + }) - prismaRequest({ - model: "voucherLog", - method: "find", - request: { - include: { - LoggedForUser: true, - }, - where: { - semesterId: session.data.semester.id, - }, - }, - callback: (data) => handleVoucherLogs(data.data, session, setVoucherLogs, setVouchersUsed) - }); - }, [refresh]); + fetch("/api/v2/voucherLogs") + .then(res => res.json()) + .then(voucherLog => { + handleVoucherLogs(voucherLog.voucherLogs, session, setVoucherLogs, setVouchersUsed) + }) + }, [refresh]) const worklogInputLayout = worklogInput( session, diff --git a/app/(pages)/(main)/volunteering/membership/page.js b/app/(pages)/(main)/volunteering/membership/page.js index 345954f..4c78a1a 100644 --- a/app/(pages)/(main)/volunteering/membership/page.js +++ b/app/(pages)/(main)/volunteering/membership/page.js @@ -45,7 +45,7 @@ const MEMBERSHIP_TABLE_HEADERS = [ flex: 3, }, { - id: "comment", + id: "comments", type: "string", name: "Comment", flex: 2, @@ -64,28 +64,12 @@ function MembershipPage() { const [refresh, setRefresh] = useState(false); useEffect(() => { - prismaRequest({ - model: "userMembership", - method: "find", - request: { - where: { - OR: [ - { - semester_id: session.data.semester.id, - }, - { - honorary: true, - }, - { - lifetime: true, - }, - ], - }, - }, - callback: (data) => { + fetch("/api/v2/memberships").then(res => { + res.json().then((data) => { + console.log(data) if (data.length == 0) return; - const newMembershipData = data.data.map((e) => { + const newMembershipData = data.memberships.map((e) => { return { ...e, date_joined_num: parseISO(e.date_joined).getTime(), @@ -96,7 +80,7 @@ function MembershipPage() { }; }); - const newNumSpecialMembers = data.data.filter((e) => { + const newNumSpecialMembers = data.memberships.filter((e) => { return e.honorary || e.lifetime; }).length; @@ -104,8 +88,8 @@ function MembershipPage() { setTableData(newMembershipData); setNumSpecialMembers(newNumSpecialMembers); - }, - }); + }) + }) }, [refresh]); const addNewMember = async () => { diff --git a/app/(pages)/(main)/volunteering/page.js b/app/(pages)/(main)/volunteering/page.js index 434f542..f85995e 100644 --- a/app/(pages)/(main)/volunteering/page.js +++ b/app/(pages)/(main)/volunteering/page.js @@ -63,18 +63,10 @@ function VolunteeringPage(params) { useEffect(() => { - prismaRequest({ - model: "semester", - method: "find", - request: { - orderBy: { - id: "desc" - } - }, - callback: (data) => { - if (data.length == 0) return; - setSemester(data.data[0]) - } + fetch("/api/v2/semester") + .then(res => res.json) + .then(data => { + setSemester(data.semester) }); prismaRequest({ diff --git a/app/(pages)/auth/layout.js b/app/(pages)/auth/layout.js index 8fc91f1..aa9acde 100644 --- a/app/(pages)/auth/layout.js +++ b/app/(pages)/auth/layout.js @@ -50,7 +50,7 @@ export default function AuthLayout({ children }) { sx={{ minHeight: "100vh" }} > - {children} + {children} diff --git a/app/api/v2/auth/[...nextauth]/route.js b/app/api/v2/auth/[...nextauth]/route.js new file mode 100644 index 0000000..c636b52 --- /dev/null +++ b/app/api/v2/auth/[...nextauth]/route.js @@ -0,0 +1,92 @@ + + +import NextAuth from "next-auth"; +import Email from "next-auth/providers/email"; +import { PrismaAdapter } from "@next-auth/prisma-adapter"; +import prisma from "@/prisma/prismaClient"; + +const handler = NextAuth({ + providers: [ + Email({ + server: { + host: "smtp.gmail.com", + port: 587, + auth: { + user: process.env.NODEMAILER_NOREPLY_USER, + pass: process.env.NODEMAILER_NOREPLY_PASSWORD, + }, + }, + from: process.env.NODEMAILER_NOREPLY_USER + }), + ], + callbacks: { + async signIn({ user }) { + + const cybUser = await prisma.user.findFirst({ + where: { + email: user.email + } + }) + + if (!cybUser) { + throw "No user found" + } + + if (!cybUser.active) { + throw "Email has not been verified" + } + + return true + }, + async session({ session }) { + + const cybUser = await prisma.user.findFirst({ + where: { + email: session.user.email, + }, + include: { + recruitedByUser: true, + recruitedUsers: true, + roles: { + include: { + role: true, + }, + }, + }, + }); + + const semesters = await prisma.semester.findMany({ + orderBy: { + id: "desc" + }, + take: 1, + }) + const currentSemester = await semesters[0]; + + if (cybUser) { + session.user.name = `${cybUser.firstName} ${cybUser.lastName}` + delete session.user.name; + + session.user = { + ...session.user, + ...cybUser, + roles: cybUser.roles.map((e) => e.role), + name: `${cybUser.firstName} ${cybUser.lastName ? cybUser.lastName : ""}` + } + } + + // console.log(currentSemester); + if (currentSemester) { + session.semester = currentSemester; + } + + return session + } + }, + pages: { + signIn: "/auth/signIn" + }, + adapter: PrismaAdapter(prisma), +}); + +export { handler as GET, handler as POST }; \ No newline at end of file diff --git a/app/api/v2/memberships/route.js b/app/api/v2/memberships/route.js new file mode 100644 index 0000000..771a92a --- /dev/null +++ b/app/api/v2/memberships/route.js @@ -0,0 +1,38 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; +import { randomBytes } from "crypto"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const semester = await prisma.UserMembership.findMany({ + select: { + lifetime: true, + honorary: true, + name: true, + comments: true, + date_joined: true + } + }); + + return NextResponse.json({ memberships: semester }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file diff --git a/app/api/v2/semester/route.js b/app/api/v2/semester/route.js new file mode 100644 index 0000000..1fbde26 --- /dev/null +++ b/app/api/v2/semester/route.js @@ -0,0 +1,33 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const semester = await prisma.semester.findFirst({ + orderBy: { + year: "desc" + } + }); + + return NextResponse.json({ semester: semester }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file diff --git a/app/api/v2/users/[userID]/route.js b/app/api/v2/users/[userID]/route.js new file mode 100644 index 0000000..e04b32b --- /dev/null +++ b/app/api/v2/users/[userID]/route.js @@ -0,0 +1,20 @@ + +import { NextResponse } from "next/server"; +import { useRouter } from 'next/navigation' + +export default function GET(req) { + const router = useRouter() + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + return NextResponse.json( + { error: `frefew: ${router.query.userID}` }, + { status: 200 } + ); + +} \ No newline at end of file diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js new file mode 100644 index 0000000..a00fe0d --- /dev/null +++ b/app/api/v2/users/route.js @@ -0,0 +1,63 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; +import { randomBytes } from "crypto"; + +export async function POST(req) { + + if (req.method != "POST") { + return NextResponse.json( + { error: `Method '${req.method}' does not match POST` }, + { status: 405 } + ); + } + + const args = await req.json(); + const { email, firstName, lastName } = args; + + if (!email || !firstName || !lastName) { + return NextResponse.json( + { error: "Missing required fields" }, + { status: 400 } + ); + } + + try { + const result = await prisma.$transaction(async (prisma) => { + const newUser = await prisma.user.create({ + data: { + email: email, + firstName: firstName, + lastName: lastName, + }, + }); + + const activateToken = await prisma.activateToken.create({ + data: { + token: `${randomBytes(32).toString("hex")}`, + userId: newUser.id, + }, + }); + + return { newUser, activateToken }; + }); + + return NextResponse.json({ data: result }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} + +export function GET(req) { + return NextResponse.json( + req.json(), + {status: 200} + ) +} \ No newline at end of file diff --git a/app/api/v2/voucherLogs/route.js b/app/api/v2/voucherLogs/route.js new file mode 100644 index 0000000..3fdc828 --- /dev/null +++ b/app/api/v2/voucherLogs/route.js @@ -0,0 +1,40 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const semester = await prisma.Semester.findFirst({select: {id:true}, orderBy: {year: "desc"}}) + const voucherLogs = await prisma.voucherLog.findMany({ + select: { + LoggedForUser: {select: {firstName: true, lastName: true, id: true}}, + usedAt: true, + amount: true, + description: true + }, + where: { + semesterId: semester.id, + } + }); + + return NextResponse.json({ voucherLogs: voucherLogs }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file diff --git a/app/api/v2/workLogs/route.js b/app/api/v2/workLogs/route.js new file mode 100644 index 0000000..5a9c609 --- /dev/null +++ b/app/api/v2/workLogs/route.js @@ -0,0 +1,41 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const semester = await prisma.Semester.findFirst({select: {id:true}, orderBy: {year: "desc"}}) + const workLogs = await prisma.WorkLog.findMany({ + select: { + LoggedByUser: {select: {firstName: true, lastName: true, id: true}}, + LoggedForUser: {select: {firstName: true, lastName: true, id: true}}, + workedAt: true, + duration: true, + description: true + }, + where: { + semesterId: semester.id, + } + }); + + return NextResponse.json({ workLogs: workLogs }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file diff --git a/app/auth/email.js b/app/auth/email.js new file mode 100644 index 0000000..045d31f --- /dev/null +++ b/app/auth/email.js @@ -0,0 +1,24 @@ + +import nodemailer from "nodemailer"; + +export const transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 587, + secure: false, // Use `true` for port 465, `false` for all other ports + auth: { + user: process.env.NODEMAILER_NOREPLY_USER, + pass: process.env.NODEMAILER_NOREPLY_PASSWORD, + }, +}); + +export const mailOptions = (receiver, html) => { + return { + from: { + name: "CYB Email Verification", + address: process.env.NODEMAILER_NOREPLY_USER, + }, + to: [receiver], + subject: "Please verify your CYB Email", + html: html, + } +}; diff --git a/app/auth/layout.js b/app/auth/layout.js new file mode 100644 index 0000000..8fc91f1 --- /dev/null +++ b/app/auth/layout.js @@ -0,0 +1,62 @@ + +"use client" + +import { Avatar, Box, CssBaseline, Paper, Stack, ThemeProvider } from "@mui/material"; +import { SessionProvider } from "next-auth/react"; +import { cybTheme } from "@/app/components/themeCYB"; +import Link from "next/link"; +import cybLogo from "./../../icon.png"; +import Image from "next/image"; + +export default function AuthLayout({ children }) { + + + + return ( + + + + + + + cyb logo + + + + + + + + + + {children} + + + + + + + + ); +} \ No newline at end of file diff --git a/app/auth/register/layout.js b/app/auth/register/layout.js new file mode 100644 index 0000000..6e2ed2d --- /dev/null +++ b/app/auth/register/layout.js @@ -0,0 +1,9 @@ + +export const metadata = { + title: "Register new user", + description: "registration page for cyb.no", +}; + +export default function Layout({ children }) { + return children; +} \ No newline at end of file diff --git a/app/auth/register/page.js b/app/auth/register/page.js new file mode 100644 index 0000000..49ae359 --- /dev/null +++ b/app/auth/register/page.js @@ -0,0 +1,273 @@ + +"use client" + +import { cybTheme } from "@/app/components/themeCYB"; +import prismaRequest from "@/app/middleware/prisma/prismaRequest"; +import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; +import { normalizeEmail } from "@/app/components/Login/authUtil"; +import Link from "next/link"; +import { useState } from "react"; +import SnackbarAlert from "@/app/components/feedback/snackbarAlert"; + +export default function registerPage() { + + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [severity, setSeverity] = useState("") + const [email, setEmail] = useState(""); + const [response, setResponse] = useState(""); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + + const debug = true; + + const handleRegister = async () => { + setSnackbarOpen(false) + + if (!email.includes("@")) { + setResponse("Email is invalid") + setSeverity("error") + setSnackbarOpen(true) + return + } + + setLoading(true); + + const responseCUE = await checkUserExists(email, debug) + + if (!responseCUE.ok) { + setResponse(responseCUE.error); + setSeverity("error") + setSnackbarOpen(true) + setLoading(false); + return; + } + + const responseCU = await createUser(firstName, lastName, email, debug); + + if (!responseCU.ok) { + setResponse(responseCU.error) + setSeverity("error") + setSnackbarOpen(true) + setLoading(false); + return; + } + + const responseSVM = await sendVerificiationMail( + responseCU.data.newUser, + responseCU.data.activateToken, + debug + ); + + if (!responseSVM.ok) { + setResponse(responseSVM.error); + setSeverity("error") + setSnackbarOpen(true) + setLoading(false); + return; + } else { + setResponse(`User created. Email sent to ${responseSVM.email}`); + setSeverity("success") + setSuccess(true); + setSnackbarOpen(true) + setLoading(false); + return; + } + } + + + const CheckedTextField = (title, textValue, textCallback) => { + + return ( + + textCallback(event.target.value)} + InputLabelProps={{ shrink: true }} + onKeyUp={(e)=>{if(e.key==="Enter") handleRegister()}} + /> + + ); + + } + + return ( + + + + Register new user + + + {CheckedTextField("First name", firstName, setFirstName)} + {CheckedTextField("Last name", lastName, setLastName)} + {CheckedTextField("Email", email, setEmail)} + + + + + {success ? + + + + + + : + + + + Log in + + + + } + + + + {response != "" ? ( + + ) : ( + + )} + + + + + ); + +} + + +// +async function checkUserExists(email, debug) { + + const normalizedEmail = normalizeEmail(email); + + const response = await prismaRequest({ + model: "user", + method: "find", + request: { + where: { + email: normalizedEmail, + }, + }, + debug: debug, + }); + + if (!response.ok) { + return { ok: false, error: "Unable to connect to database" }; + } else if (response.data.length > 0) { + return { ok: false, error: `${normalizedEmail} already exists in database` }; + } + + return { + ok: true + }; +} + +// +async function createUser(firstName, lastName, email, debug) { + + const normalizedEmail = normalizeEmail(email); + + const response = await fetch("/api/v1/createUser/", { + method: "post", + mode: "cors", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + firstName: firstName, + lastName: lastName, + email: normalizedEmail, + }), + }); + + if (debug) console.log("createUser response:", response); + + if (!response.ok) { + return { ok: false, error: response.error }; + } + + const data = await response.json(); + + if (debug) console.log("createUser:", data); + + return { ok: true, ...data }; +} + +// +async function sendVerificiationMail(newUser, activateToken, debug) { + + const response = await fetch("/api/v1/sendVerification/", { + method: "post", + mode: "cors", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user: newUser, + activateToken: activateToken, + }), + }) + + if (debug) console.log("sendVerificiationMail response:", response); + + if (!response.ok) { + return { ok: false, error: response.statusText }; + } + + const data = await response.json(); + + if (debug) console.log("sendVerificiationMail:", data); + + return { ok: true, ...data }; +} \ No newline at end of file diff --git a/app/auth/signIn/layout.js b/app/auth/signIn/layout.js new file mode 100644 index 0000000..fa9d18b --- /dev/null +++ b/app/auth/signIn/layout.js @@ -0,0 +1,9 @@ + +export const metadata = { + title: "sign In", + description: "sign-in page for cyb.no", +}; + +export default function Layout({ children }) { + return children; +} \ No newline at end of file diff --git a/app/auth/signIn/page.js b/app/auth/signIn/page.js new file mode 100644 index 0000000..9033acb --- /dev/null +++ b/app/auth/signIn/page.js @@ -0,0 +1,155 @@ + +"use client" + +import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; +import CircularProgress from '@mui/material/CircularProgress'; +import { useState } from "react"; +import { cybTheme } from "../../components/themeCYB"; +import { signIn, useSession } from "next-auth/react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { normalizeEmail } from "../../components/Login/authUtil"; +import SnackbarAlert from "@/app/components/feedback/snackbarAlert"; + + +export default function SignInPage() { + + const [email, setEmail] = useState("") + const [response, setResponse] = useState("") + const [error, setError] = useState(false) + const [loading, setLoading] = useState(false) + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [severity, setSeverity] = useState("") + + const session = useSession() + const router = useRouter() + + if (session.status == "authenticated") { + router.push("/"); + return; + } + + const handleLogin = async () => { + setLoading(true); + + if (email == "") { + setError(true) + setSeverity("error") + setResponse("Please fill in your email") + setLoading(false); + setSnackbarOpen(true); + return + } + + const normalizedEmail = normalizeEmail(email); + + const response = await signIn("email", { + email: normalizedEmail, + redirect: false, + }); + + if (response.error == null) { + setError(false) + setSeverity("success") + setResponse(`Email sent to ${normalizedEmail}`); + setLoading(false); + setSnackbarOpen(true); + } else { + setError(true); + setSeverity("error") + setResponse(response.error); + setLoading(false); + setSnackbarOpen(true); + } + } + + return ( + + + + Log in + + + + setEmail(event.target.value)} + InputLabelProps={{ shrink: true }} + onKeyUp={(e) => {if(e.key==="Enter") handleLogin()}} + /> + + + + + + + + + + Register new user + + + + + + {response != "" ? ( + + ) : ( + + )} + + + + + ); +} diff --git a/app/components/layout/AppBar.js b/app/components/layout/AppBar.js index 3a7bc2e..27a47ba 100644 --- a/app/components/layout/AppBar.js +++ b/app/components/layout/AppBar.js @@ -48,7 +48,7 @@ export class NavBar extends Component { ))} - + @@ -84,7 +84,7 @@ export class NavBar extends Component {
- + From 9e9ae13df4b0721226437a8da5658cb79f4084b4 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 29 Oct 2024 20:26:37 +0100 Subject: [PATCH 19/65] logs --- app/(pages)/(main)/volunteering/logs/page.js | 10 +++---- app/api/v2/users/route.js | 15 ++++++++-- app/api/v2/voucherLogs/route.js | 4 +-- app/api/v2/workGroups/route.js | 29 ++++++++++++++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 app/api/v2/workGroups/route.js diff --git a/app/(pages)/(main)/volunteering/logs/page.js b/app/(pages)/(main)/volunteering/logs/page.js index 51dfb47..10f44fc 100644 --- a/app/(pages)/(main)/volunteering/logs/page.js +++ b/app/(pages)/(main)/volunteering/logs/page.js @@ -68,11 +68,11 @@ function LogsPage() { ), }); - prismaRequest({ - model: "workGroup", - method: "find", - callback: (data) => setWorkGroups(data.data), - }); + fetch("/api/v2/workGroups") + .then(res => res.json()) + .then(groups => { + setWorkGroups(groups.groups) + }) }, []); useEffect(() => { diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js index a00fe0d..0e4b505 100644 --- a/app/api/v2/users/route.js +++ b/app/api/v2/users/route.js @@ -55,9 +55,20 @@ export async function POST(req) { } -export function GET(req) { +export async function GET(req) { + const params = req.nextUrl.searchParams + if (params.get("active") === "true") { + const res = await prisma.user.findMany({ + where: { + active: true + } + }) + return NextResponse.json( + res, + {status: 200} + ) + } return NextResponse.json( - req.json(), {status: 200} ) } \ No newline at end of file diff --git a/app/api/v2/voucherLogs/route.js b/app/api/v2/voucherLogs/route.js index 3fdc828..4dee76c 100644 --- a/app/api/v2/voucherLogs/route.js +++ b/app/api/v2/voucherLogs/route.js @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; + export async function GET(req) { if (req.method != "GET") { @@ -35,6 +36,5 @@ export async function GET(req) { { error: `something went wrong: ${error}` }, { status: 500 } ); - } - + } } \ No newline at end of file diff --git a/app/api/v2/workGroups/route.js b/app/api/v2/workGroups/route.js new file mode 100644 index 0000000..791cca4 --- /dev/null +++ b/app/api/v2/workGroups/route.js @@ -0,0 +1,29 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const groups = await prisma.WorkGroup.findMany(); + + return NextResponse.json({ groups: groups }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file From fa721045e6f9cd226b24a9be2afcb8c896e53531 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Wed, 30 Oct 2024 11:30:02 +0100 Subject: [PATCH 20/65] admin page converted to new api --- app/(pages)/(main)/admin/page.js | 135 ++++++++------------ app/api/v2/roles/route.js | 33 +++++ app/api/v2/users/[userID]/[action]/route.js | 92 +++++++++++++ app/api/v2/users/[userID]/route.js | 20 --- app/api/v2/users/route.js | 14 +- app/middleware.js | 4 + 6 files changed, 189 insertions(+), 109 deletions(-) create mode 100644 app/api/v2/roles/route.js create mode 100644 app/api/v2/users/[userID]/[action]/route.js delete mode 100644 app/api/v2/users/[userID]/route.js create mode 100644 app/middleware.js diff --git a/app/(pages)/(main)/admin/page.js b/app/(pages)/(main)/admin/page.js index 0b2461f..6c87654 100644 --- a/app/(pages)/(main)/admin/page.js +++ b/app/(pages)/(main)/admin/page.js @@ -44,69 +44,66 @@ function AdminPage(params) { const [roleChangeResponse, setRoleChangeResponse] = useState(""); useEffect(() => { - - prismaRequest({ - model: "role", - method: "find", - callback: (data) => setRoles(data.data), - }); + fetch("/api/v2/roles") + .then(res => res.json()) + .then(roles => { + setRoles(roles.roles) + }) - prismaRequest({ - model: "user", - method: "find", - request: { - include: { - roles: { - include: { - role: true, - }, - }, - }, - }, - callback: (data) => { - const users = data.data.map((e) => { - return { - ...e, - name: `${e.firstName} ${e.lastName ? e.lastName : ""}`, - }; - }) - setUsers(users) - }, - }); + fetch("/api/v2/users") + .then(res => res.json()) + .then((data) => { + const users = data.users.map((e) => { + return { + ...e, + name: `${e.firstName} ${e.lastName ? e.lastName : ""}`, + }; + }) + setUsers(users) + }) }, []); useEffect(() => { - if (selectedUser && showLogSettings) { - prismaRequest({ - model: "workLog", - method: "find", - request: { - where: { loggedFor: selectedUser.id }, - include: { LoggedByUser: true }, - }, - callback: (data) => { - const newLogs = data.data.map((e) => { - const user = e.LoggedByUser; - const name = user ? `${user.firstName} ${user.lastName}` : null; - return { - ...e, - loggedBy: name, - workedAt_num: parseISO(e.workedAt).getTime(), - workedAt: format( - parseISO(e.workedAt), - "dd MMM 'kl.'HH:mm" - ).toLowerCase(), - }; - }); - - setLogs(newLogs); - } + if (selectedUser && showLogSettings) { + fetch(`/api/v2/users/${selectedUser.id}/workLogs`) + .then(res => res.json()) + .then(data => { + const newLogs = data.workLogs.map((e) => { + const user = e.LoggedByUser; + const name = user ? `${user.firstName} ${user.lastName}` : null; + return { + ...e, + loggedBy: name, + workedAt_num: parseISO(e.workedAt).getTime(), + workedAt: format( + parseISO(e.workedAt), + "dd MMM 'kl.'HH:mm" + ).toLowerCase(), + }; + }); - }); + setLogs(newLogs); + }) } }, [selectedUser, showLogSettings]) + function handleChangeUser(user) { + setSelectedUser(user); + + if (user !== null) { + + fetch(`/api/v2/users/${user.id}/roles`) + .then(res => res.json()) + .then(data => { + setAssignedRoles(data.userRoles) + setAvailableRoles(roles.filter(e => !data.userRoles.includes(e))) + if (roleChangeResponse !== "") setRoleChangeResponse(""); + }) + + } + } + return ( @@ -120,30 +117,7 @@ function AdminPage(params) { { - setSelectedUser(data); - - if (data !== null) { - const roleIds = - data.roles.length !== 0 - ? data.roles.map((e) => e.role.id) - : []; - const newAvailableRoles = roles.filter( - (e) => !roleIds.includes(e.id) && e.id !== "hihih" - ); - - const newAssignedRoles = data.roles - .filter((e) => e.role.id !== "hihih") - .map((e) => e.role); - - console.log(newAvailableRoles, newAssignedRoles); - - setAvailableRoles(newAvailableRoles); - setAssignedRoles(newAssignedRoles); - - if (roleChangeResponse !== "") setRoleChangeResponse(""); - } - }} + callback={handleChangeUser} data={users} dataLabel="name" subDataLabel="email" @@ -206,6 +180,7 @@ function AdminPage(params) { ); } + function roleSettings( selectedUser, availableRoles, @@ -270,7 +245,7 @@ function roleSettings( "&:hover": { color: cybTheme.palette.primary.main }, }} > - {e.name} + {e} ); @@ -304,7 +279,7 @@ function roleSettings( "&:hover": { color: cybTheme.palette.primary.main }, }} > - {e.name} + {e} ); diff --git a/app/api/v2/roles/route.js b/app/api/v2/roles/route.js new file mode 100644 index 0000000..d9f4708 --- /dev/null +++ b/app/api/v2/roles/route.js @@ -0,0 +1,33 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const roles = await prisma.role.findMany({ + select: { + name: true + } + }); + + return NextResponse.json({ roles: roles.map(e => e.name) }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file diff --git a/app/api/v2/users/[userID]/[action]/route.js b/app/api/v2/users/[userID]/[action]/route.js new file mode 100644 index 0000000..66d5f27 --- /dev/null +++ b/app/api/v2/users/[userID]/[action]/route.js @@ -0,0 +1,92 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; +import { duration } from "@mui/material"; + + +export async function GET(req, {params}) { + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + await params + const userID = params.userID + const action = params.action + + if (userID) { + switch (action) { + case "roles": + return handleGetRoles(userID) + case "workLogs": + return handleGetWorkLogs(userID) + default: + return handleGetUser(req) + } + } + return NextResponse.json( + { error: `${userID}, ${action}` }, + { status: 200 } + ); + +} + +async function handleGetWorkLogs(userID) { + const workLogs = await prisma.workLog.findMany({ + where: { + loggedFor: userID + }, + select: { + duration: true, + workedAt: true, + description: true, + LoggedByUser: { + select: { + firstName: true, + lastName: true + } + }, + } + }) + return NextResponse.json( + {workLogs: workLogs}, + {status: 200} + ) +} + +async function handleGetRoles(userID) { + const userRoleIds = await prisma.user.findFirst({ + where: { + id: userID + }, + select: { + roles: { + select: { + roleId: true + } + } + } + }) + + const rolesList = await prisma.role.findMany({ + select: { + name: true, + id: true + } + }) + + const roles = {} + + for (let role of rolesList) { + roles[role.id] = role.name + } + + const userRoles = userRoleIds.roles.map(e => roles[e.roleId]) + + return NextResponse.json( + {userRoles: userRoles}, + {status: 200} + ) +} \ No newline at end of file diff --git a/app/api/v2/users/[userID]/route.js b/app/api/v2/users/[userID]/route.js deleted file mode 100644 index e04b32b..0000000 --- a/app/api/v2/users/[userID]/route.js +++ /dev/null @@ -1,20 +0,0 @@ - -import { NextResponse } from "next/server"; -import { useRouter } from 'next/navigation' - -export default function GET(req) { - const router = useRouter() - - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } - - return NextResponse.json( - { error: `frefew: ${router.query.userID}` }, - { status: 200 } - ); - -} \ No newline at end of file diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js index 0e4b505..7c7e764 100644 --- a/app/api/v2/users/route.js +++ b/app/api/v2/users/route.js @@ -57,18 +57,14 @@ export async function POST(req) { export async function GET(req) { const params = req.nextUrl.searchParams + + const queryParams = {} if (params.get("active") === "true") { - const res = await prisma.user.findMany({ - where: { - active: true - } - }) - return NextResponse.json( - res, - {status: 200} - ) + queryParams.update({where: {active: true}}) } + const res = await prisma.user.findMany(queryParams) return NextResponse.json( + {users: res}, {status: 200} ) } \ No newline at end of file diff --git a/app/middleware.js b/app/middleware.js new file mode 100644 index 0000000..a72da61 --- /dev/null +++ b/app/middleware.js @@ -0,0 +1,4 @@ +export default function middleware(req) { + console.log("Middleware ran") + return req +} \ No newline at end of file From ab2adb6cfdf118e08ca259465b9494c7c0143a45 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Thu, 31 Oct 2024 15:22:03 +0100 Subject: [PATCH 21/65] middleware proof of concept --- app/(pages)/(main)/admin/middleware.js | 6 +++++ app/api/middleware.js | 6 +++++ app/middleware.js | 4 --- middleware.js | 36 ++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 app/(pages)/(main)/admin/middleware.js create mode 100644 app/api/middleware.js delete mode 100644 app/middleware.js create mode 100644 middleware.js diff --git a/app/(pages)/(main)/admin/middleware.js b/app/(pages)/(main)/admin/middleware.js new file mode 100644 index 0000000..c806ec6 --- /dev/null +++ b/app/(pages)/(main)/admin/middleware.js @@ -0,0 +1,6 @@ +import { NextResponse } from "next/server"; + +export function middleware(req) { + console.log("Admin middleware") + return NextResponse.next() +} \ No newline at end of file diff --git a/app/api/middleware.js b/app/api/middleware.js new file mode 100644 index 0000000..842c482 --- /dev/null +++ b/app/api/middleware.js @@ -0,0 +1,6 @@ +import { NextResponse } from "next/server"; + +export function middleware(req) { + console.log("API middleware") + return NextResponse.next() +} \ No newline at end of file diff --git a/app/middleware.js b/app/middleware.js deleted file mode 100644 index a72da61..0000000 --- a/app/middleware.js +++ /dev/null @@ -1,4 +0,0 @@ -export default function middleware(req) { - console.log("Middleware ran") - return req -} \ No newline at end of file diff --git a/middleware.js b/middleware.js new file mode 100644 index 0000000..ef59e6a --- /dev/null +++ b/middleware.js @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server" +import { middleware as AdminHandler } from "@/app/(pages)/(main)/admin/middleware" +import { middleware as ApiHandler } from "@/app/api/middleware" + +const middlewareMap = { + "/admin": AdminHandler, + "/api": ApiHandler +} + +const allowList = new Set([ + "/olejohan.svg", + "/icon.png" +]) + +export async function middleware(req) { + // Prevent infinate redirect to unauthorized + const path = req.nextUrl.pathname; + if (path === "/unauthorized") return NextResponse.next() + console.log(path) + if (allowList.has(path)) return NextResponse.next() + + for (let route of Object.keys(middlewareMap)) { + if (path.indexOf(route) === 0) return middlewareMap[route](req) + } + + // Redirect to authorized if no page has allowed the request through + return NextResponse.redirect(new URL("/unauthorized", req.url)) +} + +export const config = { + matcher: [ + { + source: "/((?!_next).*)", + } + ], +} \ No newline at end of file From 123b6572a5bd99f6f81338cf6702542a629c76f9 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Fri, 1 Nov 2024 08:41:55 +0100 Subject: [PATCH 22/65] further middleware implementation --- app/(pages)/(main)/admin/middleware.js | 3 +- app/(pages)/(main)/admin/page.js | 6 +- app/(pages)/(main)/layout.js | 4 +- app/(pages)/(main)/middleware.js | 6 + app/api/v1/auth/[...nextauth]/route.js | 3 +- app/auth/email.js | 24 --- app/auth/layout.js | 62 ------ app/auth/register/layout.js | 9 - app/auth/register/page.js | 273 ------------------------- app/auth/signIn/layout.js | 9 - app/auth/signIn/page.js | 155 -------------- app/middleware/authWrapper.js | 1 - middleware.js | 26 ++- 13 files changed, 30 insertions(+), 551 deletions(-) create mode 100644 app/(pages)/(main)/middleware.js delete mode 100644 app/auth/email.js delete mode 100644 app/auth/layout.js delete mode 100644 app/auth/register/layout.js delete mode 100644 app/auth/register/page.js delete mode 100644 app/auth/signIn/layout.js delete mode 100644 app/auth/signIn/page.js diff --git a/app/(pages)/(main)/admin/middleware.js b/app/(pages)/(main)/admin/middleware.js index c806ec6..ff6d052 100644 --- a/app/(pages)/(main)/admin/middleware.js +++ b/app/(pages)/(main)/admin/middleware.js @@ -1,6 +1,5 @@ import { NextResponse } from "next/server"; -export function middleware(req) { - console.log("Admin middleware") +export async function middleware(req) { return NextResponse.next() } \ No newline at end of file diff --git a/app/(pages)/(main)/admin/page.js b/app/(pages)/(main)/admin/page.js index 6c87654..f455705 100644 --- a/app/(pages)/(main)/admin/page.js +++ b/app/(pages)/(main)/admin/page.js @@ -12,7 +12,6 @@ import { Typography, } from "@mui/material"; import { useEffect, useState } from "react"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import authWrapper from "@/app/middleware/authWrapper"; import CustomAutoComplete from "@/app/components/input/CustomAutocomplete"; import Link from "next/link"; @@ -29,8 +28,9 @@ const TABLE_HEADERS_LOGS = [ { id: "description", name: "Description" }, ]; -function AdminPage(params) { - +function AdminPage({ props }) { + + const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); const [logs, setLogs] = useState([]); diff --git a/app/(pages)/(main)/layout.js b/app/(pages)/(main)/layout.js index 05577d8..2cebf2a 100644 --- a/app/(pages)/(main)/layout.js +++ b/app/(pages)/(main)/layout.js @@ -11,7 +11,7 @@ import { ThemeProvider, Paper, } from "@mui/material"; -import { SessionProvider } from "next-auth/react"; +import { SessionProvider, useSession } from "next-auth/react"; import { usePathname } from "next/navigation"; import { NavBar } from "@/app/components/layout/AppBar"; import { cybTheme } from "@/app/components/themeCYB"; @@ -28,7 +28,7 @@ const NavItems = [ }, ]; -export default function AppLayout({ children }) { +export default async function AppLayout({ children }) { const pathname = usePathname(); diff --git a/app/(pages)/(main)/middleware.js b/app/(pages)/(main)/middleware.js new file mode 100644 index 0000000..4332077 --- /dev/null +++ b/app/(pages)/(main)/middleware.js @@ -0,0 +1,6 @@ +import { NextResponse } from "next/server"; + +export function middleware(req) { + console.log("Main middleware") + return NextResponse.next() +} \ No newline at end of file diff --git a/app/api/v1/auth/[...nextauth]/route.js b/app/api/v1/auth/[...nextauth]/route.js index c636b52..00a9810 100644 --- a/app/api/v1/auth/[...nextauth]/route.js +++ b/app/api/v1/auth/[...nextauth]/route.js @@ -89,4 +89,5 @@ const handler = NextAuth({ adapter: PrismaAdapter(prisma), }); -export { handler as GET, handler as POST }; \ No newline at end of file +// export { handler as GET, handler as POST }; +export { handler } \ No newline at end of file diff --git a/app/auth/email.js b/app/auth/email.js deleted file mode 100644 index 045d31f..0000000 --- a/app/auth/email.js +++ /dev/null @@ -1,24 +0,0 @@ - -import nodemailer from "nodemailer"; - -export const transporter = nodemailer.createTransport({ - host: "smtp.gmail.com", - port: 587, - secure: false, // Use `true` for port 465, `false` for all other ports - auth: { - user: process.env.NODEMAILER_NOREPLY_USER, - pass: process.env.NODEMAILER_NOREPLY_PASSWORD, - }, -}); - -export const mailOptions = (receiver, html) => { - return { - from: { - name: "CYB Email Verification", - address: process.env.NODEMAILER_NOREPLY_USER, - }, - to: [receiver], - subject: "Please verify your CYB Email", - html: html, - } -}; diff --git a/app/auth/layout.js b/app/auth/layout.js deleted file mode 100644 index 8fc91f1..0000000 --- a/app/auth/layout.js +++ /dev/null @@ -1,62 +0,0 @@ - -"use client" - -import { Avatar, Box, CssBaseline, Paper, Stack, ThemeProvider } from "@mui/material"; -import { SessionProvider } from "next-auth/react"; -import { cybTheme } from "@/app/components/themeCYB"; -import Link from "next/link"; -import cybLogo from "./../../icon.png"; -import Image from "next/image"; - -export default function AuthLayout({ children }) { - - - - return ( - - - - - - - cyb logo - - - - - - - - - - {children} - - - - - - - - ); -} \ No newline at end of file diff --git a/app/auth/register/layout.js b/app/auth/register/layout.js deleted file mode 100644 index 6e2ed2d..0000000 --- a/app/auth/register/layout.js +++ /dev/null @@ -1,9 +0,0 @@ - -export const metadata = { - title: "Register new user", - description: "registration page for cyb.no", -}; - -export default function Layout({ children }) { - return children; -} \ No newline at end of file diff --git a/app/auth/register/page.js b/app/auth/register/page.js deleted file mode 100644 index 49ae359..0000000 --- a/app/auth/register/page.js +++ /dev/null @@ -1,273 +0,0 @@ - -"use client" - -import { cybTheme } from "@/app/components/themeCYB"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; -import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; -import CircularProgress from '@mui/material/CircularProgress'; -import { normalizeEmail } from "@/app/components/Login/authUtil"; -import Link from "next/link"; -import { useState } from "react"; -import SnackbarAlert from "@/app/components/feedback/snackbarAlert"; - -export default function registerPage() { - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [severity, setSeverity] = useState("") - const [email, setEmail] = useState(""); - const [response, setResponse] = useState(""); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - - const debug = true; - - const handleRegister = async () => { - setSnackbarOpen(false) - - if (!email.includes("@")) { - setResponse("Email is invalid") - setSeverity("error") - setSnackbarOpen(true) - return - } - - setLoading(true); - - const responseCUE = await checkUserExists(email, debug) - - if (!responseCUE.ok) { - setResponse(responseCUE.error); - setSeverity("error") - setSnackbarOpen(true) - setLoading(false); - return; - } - - const responseCU = await createUser(firstName, lastName, email, debug); - - if (!responseCU.ok) { - setResponse(responseCU.error) - setSeverity("error") - setSnackbarOpen(true) - setLoading(false); - return; - } - - const responseSVM = await sendVerificiationMail( - responseCU.data.newUser, - responseCU.data.activateToken, - debug - ); - - if (!responseSVM.ok) { - setResponse(responseSVM.error); - setSeverity("error") - setSnackbarOpen(true) - setLoading(false); - return; - } else { - setResponse(`User created. Email sent to ${responseSVM.email}`); - setSeverity("success") - setSuccess(true); - setSnackbarOpen(true) - setLoading(false); - return; - } - } - - - const CheckedTextField = (title, textValue, textCallback) => { - - return ( - - textCallback(event.target.value)} - InputLabelProps={{ shrink: true }} - onKeyUp={(e)=>{if(e.key==="Enter") handleRegister()}} - /> - - ); - - } - - return ( - - - - Register new user - - - {CheckedTextField("First name", firstName, setFirstName)} - {CheckedTextField("Last name", lastName, setLastName)} - {CheckedTextField("Email", email, setEmail)} - - - - - {success ? - - - - - - : - - - - Log in - - - - } - - - - {response != "" ? ( - - ) : ( - - )} - - - - - ); - -} - - -// -async function checkUserExists(email, debug) { - - const normalizedEmail = normalizeEmail(email); - - const response = await prismaRequest({ - model: "user", - method: "find", - request: { - where: { - email: normalizedEmail, - }, - }, - debug: debug, - }); - - if (!response.ok) { - return { ok: false, error: "Unable to connect to database" }; - } else if (response.data.length > 0) { - return { ok: false, error: `${normalizedEmail} already exists in database` }; - } - - return { - ok: true - }; -} - -// -async function createUser(firstName, lastName, email, debug) { - - const normalizedEmail = normalizeEmail(email); - - const response = await fetch("/api/v1/createUser/", { - method: "post", - mode: "cors", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - firstName: firstName, - lastName: lastName, - email: normalizedEmail, - }), - }); - - if (debug) console.log("createUser response:", response); - - if (!response.ok) { - return { ok: false, error: response.error }; - } - - const data = await response.json(); - - if (debug) console.log("createUser:", data); - - return { ok: true, ...data }; -} - -// -async function sendVerificiationMail(newUser, activateToken, debug) { - - const response = await fetch("/api/v1/sendVerification/", { - method: "post", - mode: "cors", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user: newUser, - activateToken: activateToken, - }), - }) - - if (debug) console.log("sendVerificiationMail response:", response); - - if (!response.ok) { - return { ok: false, error: response.statusText }; - } - - const data = await response.json(); - - if (debug) console.log("sendVerificiationMail:", data); - - return { ok: true, ...data }; -} \ No newline at end of file diff --git a/app/auth/signIn/layout.js b/app/auth/signIn/layout.js deleted file mode 100644 index fa9d18b..0000000 --- a/app/auth/signIn/layout.js +++ /dev/null @@ -1,9 +0,0 @@ - -export const metadata = { - title: "sign In", - description: "sign-in page for cyb.no", -}; - -export default function Layout({ children }) { - return children; -} \ No newline at end of file diff --git a/app/auth/signIn/page.js b/app/auth/signIn/page.js deleted file mode 100644 index 9033acb..0000000 --- a/app/auth/signIn/page.js +++ /dev/null @@ -1,155 +0,0 @@ - -"use client" - -import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; -import CircularProgress from '@mui/material/CircularProgress'; -import { useState } from "react"; -import { cybTheme } from "../../components/themeCYB"; -import { signIn, useSession } from "next-auth/react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { normalizeEmail } from "../../components/Login/authUtil"; -import SnackbarAlert from "@/app/components/feedback/snackbarAlert"; - - -export default function SignInPage() { - - const [email, setEmail] = useState("") - const [response, setResponse] = useState("") - const [error, setError] = useState(false) - const [loading, setLoading] = useState(false) - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [severity, setSeverity] = useState("") - - const session = useSession() - const router = useRouter() - - if (session.status == "authenticated") { - router.push("/"); - return; - } - - const handleLogin = async () => { - setLoading(true); - - if (email == "") { - setError(true) - setSeverity("error") - setResponse("Please fill in your email") - setLoading(false); - setSnackbarOpen(true); - return - } - - const normalizedEmail = normalizeEmail(email); - - const response = await signIn("email", { - email: normalizedEmail, - redirect: false, - }); - - if (response.error == null) { - setError(false) - setSeverity("success") - setResponse(`Email sent to ${normalizedEmail}`); - setLoading(false); - setSnackbarOpen(true); - } else { - setError(true); - setSeverity("error") - setResponse(response.error); - setLoading(false); - setSnackbarOpen(true); - } - } - - return ( - - - - Log in - - - - setEmail(event.target.value)} - InputLabelProps={{ shrink: true }} - onKeyUp={(e) => {if(e.key==="Enter") handleLogin()}} - /> - - - - - - - - - - Register new user - - - - - - {response != "" ? ( - - ) : ( - - )} - - - - - ); -} diff --git a/app/middleware/authWrapper.js b/app/middleware/authWrapper.js index 071d1e0..0196d97 100644 --- a/app/middleware/authWrapper.js +++ b/app/middleware/authWrapper.js @@ -1,7 +1,6 @@ import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { useEffect } from "react"; import { PageBuilderSkeleton } from "../components/sanity/PageBuilder"; export default function authWrapper(WrappedComponent, requiredRole="", redirect="/unauthorized") { diff --git a/middleware.js b/middleware.js index ef59e6a..4164eb6 100644 --- a/middleware.js +++ b/middleware.js @@ -1,36 +1,42 @@ import { NextResponse } from "next/server" import { middleware as AdminHandler } from "@/app/(pages)/(main)/admin/middleware" import { middleware as ApiHandler } from "@/app/api/middleware" +import { middleware as MainHandler } from "@/app/(pages)/(main)/middleware" const middlewareMap = { "/admin": AdminHandler, - "/api": ApiHandler + "/api": ApiHandler, + "/": MainHandler } const allowList = new Set([ "/olejohan.svg", - "/icon.png" + "/icon.png", + "/aboutCYB" ]) export async function middleware(req) { + + // return NextResponse.next() + // Prevent infinate redirect to unauthorized const path = req.nextUrl.pathname; if (path === "/unauthorized") return NextResponse.next() - console.log(path) if (allowList.has(path)) return NextResponse.next() - + for (let route of Object.keys(middlewareMap)) { if (path.indexOf(route) === 0) return middlewareMap[route](req) } - + + console.log(path) // Redirect to authorized if no page has allowed the request through return NextResponse.redirect(new URL("/unauthorized", req.url)) } export const config = { - matcher: [ - { - source: "/((?!_next).*)", - } - ], +// matcher: [ +// { +// source: "/((?!_next).*)", +// } +// ], } \ No newline at end of file From 9745bd6e2fb6322a19cc8496e241b3ad541e5f14 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Fri, 1 Nov 2024 10:37:18 +0100 Subject: [PATCH 23/65] turned off middleware after realising it wont work for auth --- middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware.js b/middleware.js index 4164eb6..d1745df 100644 --- a/middleware.js +++ b/middleware.js @@ -17,7 +17,7 @@ const allowList = new Set([ export async function middleware(req) { - // return NextResponse.next() + return NextResponse.next() // Prevent infinate redirect to unauthorized const path = req.nextUrl.pathname; From 5a899b8617db66df9e8cdf8045e5547c39fbd7dc Mon Sep 17 00:00:00 2001 From: Sebbben Date: Fri, 1 Nov 2024 10:37:47 +0100 Subject: [PATCH 24/65] more REST api implementation --- app/(pages)/(main)/admin/handleRoles.js | 99 ---------------- app/(pages)/(main)/admin/handleWorkGroups.js | 117 ------------------- app/(pages)/(main)/board/page.js | 6 + app/api/v2/recruitGraph/route.js | 33 ++++++ 4 files changed, 39 insertions(+), 216 deletions(-) delete mode 100644 app/(pages)/(main)/admin/handleRoles.js delete mode 100644 app/(pages)/(main)/admin/handleWorkGroups.js create mode 100644 app/api/v2/recruitGraph/route.js diff --git a/app/(pages)/(main)/admin/handleRoles.js b/app/(pages)/(main)/admin/handleRoles.js deleted file mode 100644 index 3060d7d..0000000 --- a/app/(pages)/(main)/admin/handleRoles.js +++ /dev/null @@ -1,99 +0,0 @@ - - -import { useState } from "react"; -import { Autocomplete, Button, Stack, TextField, Typography } from "@mui/material"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; - -export default function handleRoles(users, roles, setRoles) { - - const [selectedUser, setSelectedUser] = useState(null) - const [selectedRole, setSelectedRole] = useState(null) - - // const [name, setName] = useState("") - // const [description, setDescription] = useState("") - - return ( - - Set role - - e.firstName)} - onChange={(e, v) => setSelectedUser(users.filter((e) => e.firstName == v)[0]) } - renderInput={(params) => ( - - )} - /> - e.name)} - onChange={(e, v) => setSelectedRole(roles.filter((e) => e.name == v)[0]) } - renderInput={(params) => ( - - )} - /> - - - - ); - -} - -// async function handleAddition(name, description, refresh, setRefresh) { - -// const data = await prismaRequest({ -// model: "workGroup", -// method: "create", -// request: { -// data: { -// name: name, -// description: description, -// leaderTitle: "", -// } -// }, -// }); - -// setRefresh(!refresh); -// } - -// async function handleDeletion(group, refresh, setRefresh) { -// console.log(group) - -// const data = await prismaRequest({ -// model: "workGroup", -// method: "delete", -// request: { -// where: { -// id: group.id -// }, -// }, -// }); - -// setRefresh(!refresh); -// } \ No newline at end of file diff --git a/app/(pages)/(main)/admin/handleWorkGroups.js b/app/(pages)/(main)/admin/handleWorkGroups.js deleted file mode 100644 index d722125..0000000 --- a/app/(pages)/(main)/admin/handleWorkGroups.js +++ /dev/null @@ -1,117 +0,0 @@ - -import { useState } from "react"; -import { Autocomplete, Button, Stack, TextField, Typography } from "@mui/material"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; - -export default function handleWorkGroups(groups, setGroups) { - - const [name, setName] = useState("") - const [description, setDescription] = useState("") - const [selectedGroup, setSelectedGroup] = useState(null) - - return ( - - Work Groups - setName(event.target.value)} - InputLabelProps={{ shrink: true }} - /> - setDescription(event.target.value)} - InputLabelProps={{ shrink: true }} - /> - - - e.name)} - onChange={(e, v) => { - setSelectedGroup(groups.filter((e) => e.name == v)[0]); - // console.log(v, selectedGroup) - }} - renderInput={(params) => ( - - )} - /> - - - ); - -} - -// async function handleAddition(name, description, refresh, setRefresh) { - -// const data = await prismaRequest({ -// model: "workGroup", -// method: "create", -// request: { -// data: { -// name: name, -// description: description, -// leaderTitle: "", -// } -// }, -// }); - -// setRefresh(!refresh); -// } - -// async function handleDeletion(group, refresh, setRefresh) { -// console.log(group) - -// const data = await prismaRequest({ -// model: "workGroup", -// method: "delete", -// request: { -// where: { -// id: group.id -// }, -// }, -// }); - -// setRefresh(!refresh); -// } \ No newline at end of file diff --git a/app/(pages)/(main)/board/page.js b/app/(pages)/(main)/board/page.js index eabf00f..8d336d6 100644 --- a/app/(pages)/(main)/board/page.js +++ b/app/(pages)/(main)/board/page.js @@ -12,6 +12,12 @@ export default function BoardPage() { const [data, setData] = useState({ nodes: [], links: [] }); useEffect(() => { + fetch(`/api/v2/recruitsGraph`) + .then(res => res.json()) + .then(data => { + + }) + prismaRequest({ model: "User", method: "find", diff --git a/app/api/v2/recruitGraph/route.js b/app/api/v2/recruitGraph/route.js new file mode 100644 index 0000000..d9f4708 --- /dev/null +++ b/app/api/v2/recruitGraph/route.js @@ -0,0 +1,33 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const roles = await prisma.role.findMany({ + select: { + name: true + } + }); + + return NextResponse.json({ roles: roles.map(e => e.name) }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file From 2de5d3eaea900be9606bddae726a7b44112a97df Mon Sep 17 00:00:00 2001 From: Sebbben Date: Sun, 3 Nov 2024 20:21:02 +0100 Subject: [PATCH 25/65] board page --- app/(pages)/(main)/board/page.js | 45 ++---------------------------- app/api/v2/recruitGraph/route.js | 26 +++++++++++++++-- app/components/RecruitmentGraph.js | 12 ++++---- 3 files changed, 32 insertions(+), 51 deletions(-) diff --git a/app/(pages)/(main)/board/page.js b/app/(pages)/(main)/board/page.js index 8d336d6..2a562bf 100644 --- a/app/(pages)/(main)/board/page.js +++ b/app/(pages)/(main)/board/page.js @@ -9,54 +9,15 @@ import Forcegraph from "@/app/components/RecruitmentGraph" export default function BoardPage() { - const [data, setData] = useState({ nodes: [], links: [] }); + const [data, setData] = useState({ nodes: [], edges: [] }); useEffect(() => { - fetch(`/api/v2/recruitsGraph`) + fetch(`/api/v2/recruitGraph`) .then(res => res.json()) .then(data => { - + setData({ nodes: data.nodes, edges: data.edges}) }) - prismaRequest({ - model: "User", - method: "find", - request: { - include: { - recruitedByUser: true, - recruitedUsers: true, - }, - where: { - OR: [ - { recruitedById: { not: null } }, - { recruitedUsers: { some: {} } } - ] - }, - }, - callback: (data) => { - - const links = data.data - .filter((element) => element.recruitedByUser) - .map((element) => ({ - source: element, - target: element.recruitedByUser, - })); - - const connectedNodes = new Set( - links.flatMap((link) => [link.source.id, link.target.id]) - ); - const filteredNodes = data.data.filter((node) => - connectedNodes.has(node.id) || connectedNodes.has(node.recruitedById) - ); - const filteredLinks = links.filter((link) => - connectedNodes.has(link.source.id) && connectedNodes.has(link.target.id) - ); - - const newData = { nodes: filteredNodes, links: filteredLinks }; - - setData(newData); - }, - }); }, []); return ( diff --git a/app/api/v2/recruitGraph/route.js b/app/api/v2/recruitGraph/route.js index d9f4708..4f5a2ab 100644 --- a/app/api/v2/recruitGraph/route.js +++ b/app/api/v2/recruitGraph/route.js @@ -13,13 +13,33 @@ export async function GET(req) { try { - const roles = await prisma.role.findMany({ + const users = await prisma.user.findMany({ select: { - name: true + firstName: true, + lastName: true, + id: true, + recruitedById: true } }); - return NextResponse.json({ roles: roles.map(e => e.name) }); + const userIdToId = {} + for (const [i, user] of users.entries()) { + userIdToId[user.id] = i + } + + const nodes = users.map(user => { + return { + id: userIdToId[user.id], + firstName: user.firstName, + lastName: user.lastName + } + }) + + const edges = users.filter(user => user.recruitedById).map(user => { + return [userIdToId[user.id], userIdToId[user.recruitedById]] + }) + + return NextResponse.json({ nodes: nodes, edges: edges }); } catch (error) { diff --git a/app/components/RecruitmentGraph.js b/app/components/RecruitmentGraph.js index 8fd56ad..fd20747 100644 --- a/app/components/RecruitmentGraph.js +++ b/app/components/RecruitmentGraph.js @@ -23,10 +23,10 @@ const ForceGraph = ({ data }) => { const node_radius = 24; const arrowPath = "M 4 -2 L 10 1 L 4 4"; - const nodes = data.nodes.map((e) => ({ ...e, name: getUserInitials(e) })); - const links = data.links.map((e) => ({ - source: e.source.id, - target: e.target.id, + const nodes = data.nodes.map((e) => ({ id: e.id, name: getUserInitials(e) })); + const edges = data.edges.map((e) => ({ + source: e[0], + target: e[1], })); // Clear the previous graph @@ -38,7 +38,7 @@ const ForceGraph = ({ data }) => { .force( "link", d3 - .forceLink(links) + .forceLink(edges) .distance(100) .id((d) => d.id) ) @@ -51,7 +51,7 @@ const ForceGraph = ({ data }) => { const link = svg .append("g") .selectAll("line") - .data(links) + .data(edges) .enter() .append("line") .attr("marker-end", "url(#arrowhead-not-highlighted)") From 5612c7f4d6e289d20e335623db052fc30e36f17e Mon Sep 17 00:00:00 2001 From: Sebbben Date: Sun, 3 Nov 2024 21:09:53 +0100 Subject: [PATCH 26/65] profile page --- app/(pages)/(main)/profile/page.js | 56 +++------------------ app/api/v2/users/[userID]/[action]/route.js | 42 ++++++++++++++++ 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/app/(pages)/(main)/profile/page.js b/app/(pages)/(main)/profile/page.js index e91c13f..5b5c4d0 100644 --- a/app/(pages)/(main)/profile/page.js +++ b/app/(pages)/(main)/profile/page.js @@ -16,7 +16,6 @@ import { PageHeader } from "@/app/components/sanity/PageBuilder" import authWrapper from "@/app/middleware/authWrapper" import prismaRequest from "@/app/middleware/prisma/prismaRequest" import CustomAutoComplete from "@/app/components/input/CustomAutocomplete"; -import CustomTable from "@/app/components/CustomTable"; import { signOut, useSession } from "next-auth/react" import { redirect, useRouter } from "next/navigation" import { useEffect, useState } from "react" @@ -52,55 +51,16 @@ function ProfilePage() { useEffect(() => { if (!recruiter) { - prismaRequest({ - model: "user", - method: "find", - callback: (data) => setUsers(data.data), - }); + fetch("/api/v2/users") + .then(res => res.json()) + .then(data => setUsers(data.users)) } - prismaRequest({ - model: "user", - method: "find", - request: { - where: { - recruitedById: session.data.user.id, - }, - include: { - LoggedForUser: { - include: { - LoggedForUser: true, - LoggedByUser: true, - }, - }, - }, - }, - callback: (data) => { - let newLogs = []; - - data.data.forEach((e) => { - e.LoggedForUser.forEach((f) => { - const p1 = f.LoggedByUser; - const p2 = f.LoggedForUser; - const p1name = p1 ? `${p1.firstName} ${p1.lastName}` : ""; - const p2name = p2 ? `${p2.firstName} ${p2.lastName}` : ""; - ; - newLogs.push({ - ...f, - loggedBy: getInitials(p1name), - loggedFor: getInitials(p2name), - workedAt_num: parseISO(f.workedAt).getTime(), - workedAt: format( - parseISO(f.workedAt), - "dd.MM HH:mm" - ).toLowerCase(), - }); - }); - }); - setRecruitHours(newLogs.reduce((sum, next) => sum += next.duration, 0)) - setRecruitLogs(newLogs); - }, - }); + fetch(`/api/v2/users/${session.data.user.id}/recruitInfo`) + .then(res => res.json()) + .then(data => { + setRecruitHours(data.recruitHours) + }) }, []); // queries database for user data diff --git a/app/api/v2/users/[userID]/[action]/route.js b/app/api/v2/users/[userID]/[action]/route.js index 66d5f27..6cfa9e5 100644 --- a/app/api/v2/users/[userID]/[action]/route.js +++ b/app/api/v2/users/[userID]/[action]/route.js @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; import { duration } from "@mui/material"; +import { inc } from "sanity"; export async function GET(req, {params}) { @@ -22,6 +23,8 @@ export async function GET(req, {params}) { return handleGetRoles(userID) case "workLogs": return handleGetWorkLogs(userID) + case "recruitInfo": + return handleGetRecruitInfo(userID) default: return handleGetUser(req) } @@ -89,4 +92,43 @@ async function handleGetRoles(userID) { {userRoles: userRoles}, {status: 200} ) +} + +async function handleGetRecruitInfo(userID) { + const numRecruits = (await prisma.User.findFirst({ + where: { + id: userID + }, + select: { + _count: { + include: { + recruitedUsers: true + } + } + } + }))._count.recruitedUsers + + const recruitLogs = await prisma.User.findFirst({ + where: { + id: userID + }, + select: { + recruitedUsers: { + select: { + LoggedForUser: { + select: { + duration: true + } + } + } + } + } + }) + + const recruitHours = recruitLogs.recruitedUsers.flatMap(user => user.LoggedForUser).reduce((sum, log) => sum + log.duration, 0) + + return NextResponse.json({ + numRecruits: numRecruits, + recruitHours: recruitHours + }) } \ No newline at end of file From d7a4473b4b749284b24c463c06034f8c35457faf Mon Sep 17 00:00:00 2001 From: Sebbben Date: Sun, 3 Nov 2024 22:09:24 +0100 Subject: [PATCH 27/65] volunteering page --- app/(pages)/(main)/volunteering/page.js | 84 +++++++---------------- app/api/v2/semesterVolunteerInfo/route.js | 71 +++++++++++++++++++ 2 files changed, 96 insertions(+), 59 deletions(-) create mode 100644 app/api/v2/semesterVolunteerInfo/route.js diff --git a/app/(pages)/(main)/volunteering/page.js b/app/(pages)/(main)/volunteering/page.js index f85995e..d8f6a24 100644 --- a/app/(pages)/(main)/volunteering/page.js +++ b/app/(pages)/(main)/volunteering/page.js @@ -50,11 +50,13 @@ async function sanityFetch(setPages) { function VolunteeringPage(params) { const [paidMemberships, setPaidMemberships] = useState([]); - const [voucherLogs, setVoucherLogs] = useState([]); - const [workLogs, setWorkLogs] = useState([]); const [semester, setSemester] = useState(null); const [pages, setPages] = useState(null); - const [numVolunteers, setNumVolunteers] = useState(null); + const [numVolunteers, setNumVolunteers] = useState(0); + const [volunteerHours, setVolunteerHours] = useState(0); + const [vouchersEarned, setVouchersEarned] = useState(0); + const [vouchersUsed, setVouchersUsed] = useState(0); + useEffect(() => { sanityFetch(setPages); @@ -64,58 +66,32 @@ function VolunteeringPage(params) { useEffect(() => { fetch("/api/v2/semester") - .then(res => res.json) + .then(res => res.json()) .then(data => { setSemester(data.semester) }); - prismaRequest({ - model: "workLog", - method: "find", - callback: (data) => setWorkLogs(data.data) - }); - - prismaRequest({ - model: "voucherLog", - method: "find", - callback: (data) => setVoucherLogs(data.data), - }); - - prismaRequest({ - model: "userToWorkGroup", - method: "find", - callback: (data) => { - if (data.length == 0) return; - setNumVolunteers(data.data); - } + fetch("/api/v2/semesterVolunteerInfo") + .then(res => res.json()) + .then(data => { + setPaidMemberships(data.membershipsPaid) + setNumVolunteers(data.numberVolunteers) + setVolunteerHours(data.volunteerHours) + setVouchersEarned(data.vouchersEarned) + setVouchersUsed(data.vouchersUsed) + }) }, []) // Semester-based data - useEffect(() => { - if (semester == null) return; - - prismaRequest({ - model: "userMembership", - method: "find", - request: { - where: { - semester_id: semester.id, - }, - }, - callback: (data) => setPaidMemberships(data.data), - }); - - }, [semester]) - return ( - {createNavigation(semester, paidMemberships, workLogs, voucherLogs, numVolunteers)} + {createNavigation(semester, paidMemberships, vouchersEarned, vouchersUsed, numVolunteers, volunteerHours)} @@ -141,17 +117,9 @@ function VolunteeringPage(params) { ); } -function createNavigation(semester, paidMemberships, workLogs, voucherLogs, numVolunteers) { - - const buttonGroup1 = createButtons(BUTTON_CONTENT_1); - - const currentSemester = semester != undefined ? semester.semester + " " + semester.year : null - const membershipsPaid = paidMemberships.length.toLocaleString(); - const totalWorkHours = workLogs.length != 0 ? workLogs.reduce((tot, cur) => tot += cur.duration,0) : 0; - const TWHString = totalWorkHours.toLocaleString(); - const totalVouchersUsed = voucherLogs.length != 0 ? voucherLogs.reduce((tot, cur) => tot += cur.amount,0) : 0; - const totalNumVolunteers = numVolunteers ? numVolunteers.length : 0; +function createNavigation(semester, paidMemberships, vouchersEarned, vouchersUsed, numVolunteers, volunteerHours) { + const buttonGroup1 = createButtons(BUTTON_CONTENT_1); return ( @@ -166,14 +134,12 @@ function createNavigation(semester, paidMemberships, workLogs, voucherLogs, numV - {currentSemester ? ( - <> + {semester ? ( - ) : ( )} @@ -181,21 +147,21 @@ function createNavigation(semester, paidMemberships, workLogs, voucherLogs, numV Memberships paid - {membershipsPaid} + {paidMemberships} Number volunteers - {totalNumVolunteers} + {numVolunteers} Total volunteer hours - {TWHString} + {volunteerHours} Total vouchers used - {totalVouchersUsed.toFixed(1)} /{" "} - {(totalWorkHours * 0.5).toLocaleString()} + {vouchersUsed} /{" "} + {vouchersEarned} diff --git a/app/api/v2/semesterVolunteerInfo/route.js b/app/api/v2/semesterVolunteerInfo/route.js new file mode 100644 index 0000000..a5e2915 --- /dev/null +++ b/app/api/v2/semesterVolunteerInfo/route.js @@ -0,0 +1,71 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +const VOUCHER_MODIFIER = 0.5 + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const currentSemester = (await prisma.Semester.findFirst({ + select: { + id: true + }, + orderBy: { + id: "desc" + } + })).id + + const membershipsPaid = (await prisma.UserMembership.aggregate({ + where: { + semester_id: currentSemester + }, + _count: true + }))._count + + const numberVolunteers = (await prisma.workLog.findMany({ + distinct: ["loggedFor"], + select: { + loggedFor: true, + } + })).length + + const totVolunteerHours = (await prisma.workLog.aggregate({ + _sum: { + duration: true + } + }))._sum.duration + + const vouchersUsed = (await prisma.VoucherLog.aggregate({ + _sum: { + amount: true + } + }))._sum.amount + + + return NextResponse.json({ + membershipsPaid: membershipsPaid, + numberVolunteers: numberVolunteers, + totVolunteerHours: totVolunteerHours, + vouchersEarned: totVolunteerHours * VOUCHER_MODIFIER, + vouchersUsed: vouchersUsed + }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file From d8dbc356899c96a9f9dc29a322aa80ae81887432 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Sun, 3 Nov 2024 22:11:51 +0100 Subject: [PATCH 28/65] fixed typo --- app/api/v2/semesterVolunteerInfo/route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/v2/semesterVolunteerInfo/route.js b/app/api/v2/semesterVolunteerInfo/route.js index a5e2915..fff1854 100644 --- a/app/api/v2/semesterVolunteerInfo/route.js +++ b/app/api/v2/semesterVolunteerInfo/route.js @@ -54,7 +54,7 @@ export async function GET(req) { return NextResponse.json({ membershipsPaid: membershipsPaid, numberVolunteers: numberVolunteers, - totVolunteerHours: totVolunteerHours, + volunteerHours: totVolunteerHours, vouchersEarned: totVolunteerHours * VOUCHER_MODIFIER, vouchersUsed: vouchersUsed }); From 025025ade4d77bd46f2394647f8e4e3d09643f06 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Mon, 4 Nov 2024 09:20:56 +0100 Subject: [PATCH 29/65] log and cafe pages --- app/(pages)/(main)/board/page.js | 3 +- app/(pages)/(main)/volunteering/cafe/page.js | 53 +++++++------------- app/(pages)/(main)/volunteering/logs/page.js | 27 ++++------ app/(pages)/(main)/volunteering/page.js | 1 - app/api/v2/shifts/route.js | 37 ++++++++++++++ 5 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 app/api/v2/shifts/route.js diff --git a/app/(pages)/(main)/board/page.js b/app/(pages)/(main)/board/page.js index 2a562bf..cee4831 100644 --- a/app/(pages)/(main)/board/page.js +++ b/app/(pages)/(main)/board/page.js @@ -2,8 +2,7 @@ "use client" import { PageHeader } from "@/app/components/sanity/PageBuilder"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; -import { Box, Typography } from "@mui/material"; +import { Box } from "@mui/material"; import { useEffect, useState } from "react"; import Forcegraph from "@/app/components/RecruitmentGraph" diff --git a/app/(pages)/(main)/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js index c1b73f8..295b0ed 100644 --- a/app/(pages)/(main)/volunteering/cafe/page.js +++ b/app/(pages)/(main)/volunteering/cafe/page.js @@ -44,44 +44,29 @@ function CafePage() { const [numberRepeats, setNumberRepeats] = useState(0); useEffect(() => { - prismaRequest({ - model: "user", - method: "find", - callback: (data) => { - if (data.data.length == 0) return; - setUsers(data.data); - } - }) + fetch("/api/v2/users") + .then(res => res.json()) + .then(data => {setUsers(data.users)}) }, []) useEffect(() => { - prismaRequest({ - model: "shiftCafe", - method: "find", - request: { - include: { - UserForShiftManager: true, - UserForShiftWorker1: true, - UserForShiftWorker2: true, + fetch("/api/v2/shifts") + .then(res => res.json()) + .then(data => { + const newShifts = data.shifts.map((e) => { + return { + ...e, + isReal: true, + startAt: new Date(Date.parse(e.startAt)), + shiftManager: e.UserForShiftManager, + shiftWorker1: e.UserForShiftWorker1, + shiftWorker2: e.UserForShiftWorker2, } - }, - callback: (data) => { - if (data.data.length == 0) return; - - const newShifts = data.data.map((e) => { - return { - ...e, - isReal: true, - startAt: new Date(Date.parse(e.startAt)), - shiftManager: e.UserForShiftManager, - shiftWorker1: e.UserForShiftWorker1, - shiftWorker2: e.UserForShiftWorker2, - } - }) - - // console.log(newShifts); - setShifts(newShifts); - } + }) + + // console.log(newShifts); + setShifts(newShifts); + }) }, [refresh]); diff --git a/app/(pages)/(main)/volunteering/logs/page.js b/app/(pages)/(main)/volunteering/logs/page.js index 10f44fc..1096105 100644 --- a/app/(pages)/(main)/volunteering/logs/page.js +++ b/app/(pages)/(main)/volunteering/logs/page.js @@ -16,7 +16,6 @@ import { PageHeader } from "@/app/components/sanity/PageBuilder"; import { getUserInitials, getUserName } from "@/app/components/textUtil"; import { cybTheme } from "@/app/components/themeCYB"; import authWrapper from "@/app/middleware/authWrapper"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import CustomTable from "@/app/components/CustomTable"; import worklogInput from "./workLogInput"; import voucherLogInput from "./voucherLogInput"; @@ -51,22 +50,16 @@ function LogsPage() { const session = useSession(); useEffect(() => { - prismaRequest({ - model: "user", - method: "find", - request: { - where: { - active: true, - }, - }, - callback: (data) => - setUsers( - data.data.map((e) => ({ - ...e, - name: `${e.firstName} ${e.lastName}` - })) - ), - }); + fetch("/api/v2/users") + .then(res => res.json()) + .then(data => { + setUsers( + data.users.map((e) => ({ + ...e, + name: `${e.firstName} ${e.lastName}` + })) + ) + }) fetch("/api/v2/workGroups") .then(res => res.json()) diff --git a/app/(pages)/(main)/volunteering/page.js b/app/(pages)/(main)/volunteering/page.js index d8f6a24..87ef651 100644 --- a/app/(pages)/(main)/volunteering/page.js +++ b/app/(pages)/(main)/volunteering/page.js @@ -17,7 +17,6 @@ import { } from "@/app/components/sanity/PageBuilder"; import { useEffect, useState } from "react"; import { cybTheme } from "@/app/components/themeCYB"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import Link from "next/link"; import { sanityClient } from "@/sanity/client"; diff --git a/app/api/v2/shifts/route.js b/app/api/v2/shifts/route.js new file mode 100644 index 0000000..751ab71 --- /dev/null +++ b/app/api/v2/shifts/route.js @@ -0,0 +1,37 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + +export async function GET(req) { + + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + + try { + const shifts = await prisma.shiftCafe.findMany({ + select: { + shiftPosition: true, + startAt: true, + UserForShiftManager: {select: {_count:true}}, + UserForShiftWorker1: {select: {_count:true}}, + UserForShiftWorker2: {select: {_count:true}} + } + }) + + return NextResponse.json({ shifts: shifts }); + } + + catch (error) { + console.error(error); + return NextResponse.json( + { error: `something went wrong: ${error}` }, + { status: 500 } + ); + } + +} \ No newline at end of file From 535d39df07e1443801179a286e19dedb427683d6 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Mon, 4 Nov 2024 10:45:31 +0100 Subject: [PATCH 30/65] fixed stuff that broke with shift scheduler --- app/(pages)/(main)/volunteering/cafe/page.js | 1 - app/api/v2/shifts/route.js | 6 +++--- app/components/calendar/CafeShiftScheduler.js | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/(pages)/(main)/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js index 295b0ed..6699863 100644 --- a/app/(pages)/(main)/volunteering/cafe/page.js +++ b/app/(pages)/(main)/volunteering/cafe/page.js @@ -64,7 +64,6 @@ function CafePage() { } }) - // console.log(newShifts); setShifts(newShifts); }) diff --git a/app/api/v2/shifts/route.js b/app/api/v2/shifts/route.js index 751ab71..44dc6d5 100644 --- a/app/api/v2/shifts/route.js +++ b/app/api/v2/shifts/route.js @@ -17,9 +17,9 @@ export async function GET(req) { select: { shiftPosition: true, startAt: true, - UserForShiftManager: {select: {_count:true}}, - UserForShiftWorker1: {select: {_count:true}}, - UserForShiftWorker2: {select: {_count:true}} + UserForShiftManager: {select: {firstName: true, lastName: true}}, + UserForShiftWorker1: {select: {firstName: true, lastName: true}}, + UserForShiftWorker2: {select: {firstName: true, lastName: true}} } }) diff --git a/app/components/calendar/CafeShiftScheduler.js b/app/components/calendar/CafeShiftScheduler.js index 962fe92..ab94a22 100644 --- a/app/components/calendar/CafeShiftScheduler.js +++ b/app/components/calendar/CafeShiftScheduler.js @@ -214,6 +214,7 @@ export default function CafeShiftScheduler(props) { shift.shiftWorker2 ] + return ( - {s ? getUserInitials(s.firstName) : "X"} + {s ? getUserInitials(s) : "X"} ))} From 77b9ad2599afb68218ab616491e75064cb725860 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Mon, 4 Nov 2024 13:23:01 +0100 Subject: [PATCH 31/65] patch request for profile page --- app/(pages)/(main)/profile/page.js | 41 +++++++++---------- app/api/v2/users/[userID]/route.js | 66 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 app/api/v2/users/[userID]/route.js diff --git a/app/(pages)/(main)/profile/page.js b/app/(pages)/(main)/profile/page.js index 5b5c4d0..ceb7224 100644 --- a/app/(pages)/(main)/profile/page.js +++ b/app/(pages)/(main)/profile/page.js @@ -73,35 +73,32 @@ function ProfilePage() { }, [session]) const handleUpdateData = async () => { - await prismaRequest({ - model: "user", - method: "update", - request: { - where: { - email: session.data.user.email - }, - data: { - firstName: firstName, - lastName: lastName, - } - } + + await fetch(`/api/v2/users/${session.data.user.id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + firstName: firstName, + lastName: lastName, + }), }); + window.location.reload(); } const handleConfirmSelection = async () => { - await prismaRequest({ - model: "user", - method: "update", - request: { - where: { - email: session.data.user.email, - }, - data: { - recruitedById: selectedRecruiter.id, - }, + await fetch(`/api/v2/users/${session.data.user.id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", }, + body: JSON.stringify({ + recruitedById: selectedRecruiter.id, + }), }); + window.location.reload() } diff --git a/app/api/v2/users/[userID]/route.js b/app/api/v2/users/[userID]/route.js new file mode 100644 index 0000000..6ccad88 --- /dev/null +++ b/app/api/v2/users/[userID]/route.js @@ -0,0 +1,66 @@ + +import { NextResponse } from "next/server"; +import prisma from "@/prisma/prismaClient"; + + +export async function GET(req, {params}) { + if (req.method != "GET") { + return NextResponse.json( + { error: `Invalid method '${req.method}'` }, + { status: 405 } + ); + } + + await params + const userID = params.userID + + + return NextResponse.json( + { error: `${userID}` }, + { status: 200 } + ); + +} + +export async function PATCH(req, {params}) { + await params + const userID = params.userID + const args = await req.json() + + const result = {} + + for (let [arg, val] of Object.entries(args)) { + result[arg] = false + switch (arg) { + case "firstName": + case "lastName": + case "recruitedById": + let res = await updateField(arg, val, userID) + if (res) + result[arg] = true + break + default: continue + } + } + + return NextResponse.json( + result, + { status: 200 } + ); + +} + +async function updateField(fieldName, val, userID) { + + const data = {} + data[fieldName] = val + + let res = await prisma.User.update({ + where: { + id: userID + }, + data: data + }) + + return res +} \ No newline at end of file From 467ad3d464c71e28423aaafce0e3b4ed9e6bfeaa Mon Sep 17 00:00:00 2001 From: Sebbben Date: Mon, 4 Nov 2024 14:02:45 +0100 Subject: [PATCH 32/65] voucher and work logs posts --- app/(pages)/(main)/profile/page.js | 5 +--- app/(pages)/(main)/volunteering/cafe/page.js | 1 - .../volunteering/logs/voucherLogInput.js | 28 +++++++++--------- .../(main)/volunteering/logs/workLogInput.js | 29 +++++++++---------- app/api/v2/voucherLogs/route.js | 25 ++++++++++++++++ app/api/v2/workLogs/route.js | 27 +++++++++++++++++ 6 files changed, 80 insertions(+), 35 deletions(-) diff --git a/app/(pages)/(main)/profile/page.js b/app/(pages)/(main)/profile/page.js index ceb7224..d33c238 100644 --- a/app/(pages)/(main)/profile/page.js +++ b/app/(pages)/(main)/profile/page.js @@ -14,13 +14,10 @@ import { } from "@mui/material"; import { PageHeader } from "@/app/components/sanity/PageBuilder" import authWrapper from "@/app/middleware/authWrapper" -import prismaRequest from "@/app/middleware/prisma/prismaRequest" import CustomAutoComplete from "@/app/components/input/CustomAutocomplete"; import { signOut, useSession } from "next-auth/react" -import { redirect, useRouter } from "next/navigation" +import { useRouter } from "next/navigation" import { useEffect, useState } from "react" -import { format, parseISO } from "date-fns"; -import { getInitials } from "@/app/components/calendar/schedulerUtils"; const RECRUIT_TABLE_HEADERS = [ { id: "workedAt", name: "work date", sortBy: "workedAt_num", flex: 2 }, diff --git a/app/(pages)/(main)/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js index 6699863..2dc021e 100644 --- a/app/(pages)/(main)/volunteering/cafe/page.js +++ b/app/(pages)/(main)/volunteering/cafe/page.js @@ -17,7 +17,6 @@ import CustomAutoComplete from "@/app/components/input/CustomAutocomplete"; import CustomNumberInput from "@/app/components/input/CustomNumberInput"; import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import { PageHeader } from "@/app/components/sanity/PageBuilder"; import authWrapper from "@/app/middleware/authWrapper"; import { useEffect, useState } from "react"; diff --git a/app/(pages)/(main)/volunteering/logs/voucherLogInput.js b/app/(pages)/(main)/volunteering/logs/voucherLogInput.js index 3981da4..6c88682 100644 --- a/app/(pages)/(main)/volunteering/logs/voucherLogInput.js +++ b/app/(pages)/(main)/volunteering/logs/voucherLogInput.js @@ -2,7 +2,6 @@ import { Box, Button, Grid, Skeleton, Stack, TextField, Typography } from "@mui/material"; import { useState } from "react"; import CustomNumberInput from "@/app/components/input/CustomNumberInput"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; export default function voucherLogInput( session, @@ -31,23 +30,22 @@ export default function voucherLogInput( if (isInvalid) return; - const response = await prismaRequest({ - model: "voucherLog", - method: "create", - request: { - data: { - loggedFor: session.data.user.id, - amount: numVouchers, - description: descriptionVoucher, - semesterId: session.data.semester.id, - }, + const response = await fetch("/api/v2/voucherLogs", { + method: "POST", + headers: { + "content-type": "application/json" }, - callback: (data) => { + body: JSON.stringify({ + loggedFor: session.data.user.id, + amount: numVouchers, + description: descriptionVoucher, + semesterId: session.data.semester.id, + }) + }).then(res => { setNumVouchers(0); setDescriptionVoucher(""); - setRefresh(data); - }, - }); + setRefresh(); + }) if (!response.ok) { setRequestResponse("Failed to use voucher. Please try again."); diff --git a/app/(pages)/(main)/volunteering/logs/workLogInput.js b/app/(pages)/(main)/volunteering/logs/workLogInput.js index 84abb73..3f93380 100644 --- a/app/(pages)/(main)/volunteering/logs/workLogInput.js +++ b/app/(pages)/(main)/volunteering/logs/workLogInput.js @@ -44,27 +44,26 @@ export default function workLogInput( if (isInvalid) return; - const response = await prismaRequest({ - model: "workLog", - method: "create", - request: { - data: { - loggedBy: session.data.user.id, - loggedFor: registeredFor.id, - workedAt: selectedDateTime.toISOString(), - duration: hours, - description: description, - semesterId: session.data.semester.id, - }, + const response = await fetch("/api/v2/workLogs", { + method: "POST", + headers: { + "content-type": "application/json" }, - callback: (data) => { + body: JSON.stringify({ + loggedBy: session.data.user.id, + loggedFor: registeredFor.id, + workedAt: selectedDateTime.toISOString(), + duration: hours, + description: description, + semesterId: session.data.semester.id, + }), + }).then(res => { setRegisteredFor(null); setSelectedGroup(null); setHours(0); setDescription(""); setRefresh(data); - }, - }); + }); if (!response.ok) { setRequestResponse("Failed to register work. Please try again."); diff --git a/app/api/v2/voucherLogs/route.js b/app/api/v2/voucherLogs/route.js index 4dee76c..692d120 100644 --- a/app/api/v2/voucherLogs/route.js +++ b/app/api/v2/voucherLogs/route.js @@ -37,4 +37,29 @@ export async function GET(req) { { status: 500 } ); } +} + +export async function POST(req) { + const args = await req.json() + + if ( + !(args.hasOwnProperty("loggedFor") && + args.hasOwnProperty("amount") && + args.hasOwnProperty("description") && + args.hasOwnProperty("semesterId")) + ) return NextResponse.json({error: "Malformed request"}, {status: 400}) + + let res = await prisma.voucherLog.create({ + data: { + loggedFor: args.loggedFor, + amount: args.amount, + description: args.description, + semesterId: args.semesterId + } + }) + + if (res) { + return NextResponse.json({status: 200}) + } + } \ No newline at end of file diff --git a/app/api/v2/workLogs/route.js b/app/api/v2/workLogs/route.js index 5a9c609..cb1bd1f 100644 --- a/app/api/v2/workLogs/route.js +++ b/app/api/v2/workLogs/route.js @@ -38,4 +38,31 @@ export async function GET(req) { ); } +} + +export async function POST(req) { + const args = await req.json() + + if (!( + args.hasOwnProperty("loggedBy") && + args.hasOwnProperty("loggedFor") && + args.hasOwnProperty("workedAt") && + args.hasOwnProperty("duration") && + args.hasOwnProperty("description") && + args.hasOwnProperty("semesterId") + )) return NextResponse.json({error: "Malformed request"}, {status: 400}) + + const res = await prisma.workLog.create({ + data: { + loggedBy: args.loggedBy, + loggedFor: args.loggedFor, + workedAt: args.workedAt, + duration: args.duration, + description: args.description, + semesterId: args.semesterId + } + }) + + if (res) + return NextResponse.json({status: 200}) } \ No newline at end of file From 2edd6955174365c4bcaf79b34dae6048c66e567b Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 5 Nov 2024 08:46:04 +0100 Subject: [PATCH 33/65] user to workgroup post --- .../(main)/volunteering/logs/workLogInput.js | 27 +++++++---------- app/api/v2/workGroups/route.js | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/app/(pages)/(main)/volunteering/logs/workLogInput.js b/app/(pages)/(main)/volunteering/logs/workLogInput.js index 3f93380..3c3820c 100644 --- a/app/(pages)/(main)/volunteering/logs/workLogInput.js +++ b/app/(pages)/(main)/volunteering/logs/workLogInput.js @@ -44,7 +44,7 @@ export default function workLogInput( if (isInvalid) return; - const response = await fetch("/api/v2/workLogs", { + fetch("/api/v2/workLogs", { method: "POST", headers: { "content-type": "application/json" @@ -62,24 +62,19 @@ export default function workLogInput( setSelectedGroup(null); setHours(0); setDescription(""); - setRefresh(data); + setRefresh(""); }); - if (!response.ok) { - setRequestResponse("Failed to register work. Please try again."); - return; - } - - prismaRequest({ - model: "userToWorkGroup", - method: "create", - request: { - data: { - userId: registeredFor.id, - workGroupId: selectedGroup.id, - }, + fetch("/api/v2/workGroups", { + method: "POST", + headers: { + "content-type": "application/json" }, - }); + body: JSON.stringify({ + userId: registeredFor.id, + workGroupId: selectedGroup.id + }) + }) setRegisteredFor(null); setRequestResponse("Work registered."); diff --git a/app/api/v2/workGroups/route.js b/app/api/v2/workGroups/route.js index 791cca4..4cbfa57 100644 --- a/app/api/v2/workGroups/route.js +++ b/app/api/v2/workGroups/route.js @@ -26,4 +26,33 @@ export async function GET(req) { ); } +} + +export async function POST(req) { + const args = await req.json() + + if (!( + args.hasOwnProperty("userId") && + args.hasOwnProperty("workGroupId") + )) return NextResponse.json({error: "Malformed request"}, {status: 400}) + + const userId = args.userId + const workGroupId = args.workGroupId + + try { + const res = await prisma.userToWorkGroup.create({ + data: { + userId: userId, + workGroupId: workGroupId + } + }) + + if (res) + return NextResponse.json({status: 200}) + + } catch (error) { + console.log("User to workgroup already exist in database") + return NextResponse.json({status: 200}) + } + } \ No newline at end of file From 9ca89f6b3eea1d4c665a0afa6eeeec9df72b717b Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 5 Nov 2024 09:04:37 +0100 Subject: [PATCH 34/65] Add members --- .../(main)/volunteering/logs/workLogInput.js | 1 - .../(main)/volunteering/membership/page.js | 31 +++++++++---------- app/api/v2/memberships/route.js | 25 +++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/(pages)/(main)/volunteering/logs/workLogInput.js b/app/(pages)/(main)/volunteering/logs/workLogInput.js index 3c3820c..2599759 100644 --- a/app/(pages)/(main)/volunteering/logs/workLogInput.js +++ b/app/(pages)/(main)/volunteering/logs/workLogInput.js @@ -5,7 +5,6 @@ import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3"; import CustomAutoComplete from "@/app/components/input/CustomAutocomplete"; import CustomNumberInput from "@/app/components/input/CustomNumberInput"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import locale from "date-fns/locale/en-GB"; export default function workLogInput( diff --git a/app/(pages)/(main)/volunteering/membership/page.js b/app/(pages)/(main)/volunteering/membership/page.js index 4c78a1a..738702c 100644 --- a/app/(pages)/(main)/volunteering/membership/page.js +++ b/app/(pages)/(main)/volunteering/membership/page.js @@ -13,7 +13,6 @@ import { import { PageHeader } from "@/app/components/sanity/PageBuilder"; import CustomTable from "@/app/components/CustomTable"; import authWrapper from "@/app/middleware/authWrapper"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import { format, parseISO } from "date-fns"; import { useSession } from "next-auth/react"; import { useEffect, useState } from "react"; @@ -93,22 +92,22 @@ function MembershipPage() { }, [refresh]); const addNewMember = async () => { - const response = await prismaRequest({ - model: "userMembership", - method: "create", - request: { - data: { - name: newMemberName, - email: "", - comments: newMemberComment, - seller_id: session.data.user.id, - semester_id: session.data.semester.id, - }, + fetch("/api/v2/memberships", { + method: "POST", + headers: { + "content-type": "application/json" }, - callback: (data) => { - setRefresh(!refresh); - }, - }); + body: JSON.stringify({ + name: newMemberName, + email: "", + comments: newMemberComment, + seller_id: session.data.user.id, + semester_id: session.data.semester.id, + }) + }).then(res => { + setRefresh(!refresh) + }) + }; return ( diff --git a/app/api/v2/memberships/route.js b/app/api/v2/memberships/route.js index 771a92a..01c5679 100644 --- a/app/api/v2/memberships/route.js +++ b/app/api/v2/memberships/route.js @@ -35,4 +35,29 @@ export async function GET(req) { ); } +} + +export async function POST(req) { + const args = await req.json() + + if (!( + args.hasOwnProperty("name") && + args.hasOwnProperty("email") && + args.hasOwnProperty("comments") && + args.hasOwnProperty("seller_id") && + args.hasOwnProperty("semester_id") + )) return NextResponse.json({error: "Malformed reqeust"}, {status: 400}) + + const res = await prisma.UserMembership.create({ + data: { + name: args.name, + email: args.email, + comments: args.comments, + seller_id: args.seller_id, + semester_id: args.semester_id + } + }) + + if (res) + return NextResponse.json({status: 200}) } \ No newline at end of file From ef572bb15275df4f6e2a1393351801e36256a805 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 5 Nov 2024 11:19:54 +0100 Subject: [PATCH 35/65] Register user --- app/(pages)/auth/register/page.js | 87 ++----------------------------- app/api/v2/users/route.js | 83 +++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 105 deletions(-) diff --git a/app/(pages)/auth/register/page.js b/app/(pages)/auth/register/page.js index 49ae359..7ccfd01 100644 --- a/app/(pages)/auth/register/page.js +++ b/app/(pages)/auth/register/page.js @@ -2,7 +2,6 @@ "use client" import { cybTheme } from "@/app/components/themeCYB"; -import prismaRequest from "@/app/middleware/prisma/prismaRequest"; import { Box, Button, Grid, Skeleton, TextField, Typography } from "@mui/material"; import CircularProgress from '@mui/material/CircularProgress'; import { normalizeEmail } from "@/app/components/Login/authUtil"; @@ -34,16 +33,6 @@ export default function registerPage() { } setLoading(true); - - const responseCUE = await checkUserExists(email, debug) - - if (!responseCUE.ok) { - setResponse(responseCUE.error); - setSeverity("error") - setSnackbarOpen(true) - setLoading(false); - return; - } const responseCU = await createUser(firstName, lastName, email, debug); @@ -53,22 +42,8 @@ export default function registerPage() { setSnackbarOpen(true) setLoading(false); return; - } - - const responseSVM = await sendVerificiationMail( - responseCU.data.newUser, - responseCU.data.activateToken, - debug - ); - - if (!responseSVM.ok) { - setResponse(responseSVM.error); - setSeverity("error") - setSnackbarOpen(true) - setLoading(false); - return; } else { - setResponse(`User created. Email sent to ${responseSVM.email}`); + setResponse(`User created. Email sent to ${email}`); setSeverity("success") setSuccess(true); setSnackbarOpen(true) @@ -185,40 +160,12 @@ export default function registerPage() { } - -// -async function checkUserExists(email, debug) { - - const normalizedEmail = normalizeEmail(email); - - const response = await prismaRequest({ - model: "user", - method: "find", - request: { - where: { - email: normalizedEmail, - }, - }, - debug: debug, - }); - - if (!response.ok) { - return { ok: false, error: "Unable to connect to database" }; - } else if (response.data.length > 0) { - return { ok: false, error: `${normalizedEmail} already exists in database` }; - } - - return { - ok: true - }; -} - // async function createUser(firstName, lastName, email, debug) { const normalizedEmail = normalizeEmail(email); - const response = await fetch("/api/v1/createUser/", { + const response = await fetch("/api/v2/users", { method: "post", mode: "cors", headers: { @@ -241,33 +188,5 @@ async function createUser(firstName, lastName, email, debug) { if (debug) console.log("createUser:", data); - return { ok: true, ...data }; + return { ok: true }; } - -// -async function sendVerificiationMail(newUser, activateToken, debug) { - - const response = await fetch("/api/v1/sendVerification/", { - method: "post", - mode: "cors", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user: newUser, - activateToken: activateToken, - }), - }) - - if (debug) console.log("sendVerificiationMail response:", response); - - if (!response.ok) { - return { ok: false, error: response.statusText }; - } - - const data = await response.json(); - - if (debug) console.log("sendVerificiationMail:", data); - - return { ok: true, ...data }; -} \ No newline at end of file diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js index 7c7e764..c58c641 100644 --- a/app/api/v2/users/route.js +++ b/app/api/v2/users/route.js @@ -2,26 +2,33 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; import { randomBytes } from "crypto"; +import { mailOptions, transporter } from "@/app/(pages)/auth/email"; -export async function POST(req) { - - if (req.method != "POST") { - return NextResponse.json( - { error: `Method '${req.method}' does not match POST` }, - { status: 405 } - ); - } - - const args = await req.json(); - const { email, firstName, lastName } = args; + +const NEXTAUTH_URL = process.env.NEXTAUTH_URL || ""; + +async function sendVerificationEmail(user, activateToken) { + const link = `${NEXTAUTH_URL}/api/v1/activate/${activateToken.token}`; + const html = ` + Hello ${user.firstName} ${user.lastName}, You have successfully created a user-account at ${NEXTAUTH_URL}.

- if (!email || !firstName || !lastName) { - return NextResponse.json( - { error: "Missing required fields" }, - { status: 400 } - ); + Please verify your email by clicking the following link: ${link}

+ If you have not created a user, ignore this message.

+ You cannot reply to this email. + `; + + try { + await transporter.sendMail(mailOptions(user.email, html)); + } catch (error) { + console.log(error) + return {success: false} } + return {success: true} + +} + +async function createUser(email, firstName, lastName) { try { const result = await prisma.$transaction(async (prisma) => { const newUser = await prisma.user.create({ @@ -41,18 +48,52 @@ export async function POST(req) { return { newUser, activateToken }; }); + return result + } catch (error) { + console.log(error) + return false + } - return NextResponse.json({ data: result }); +} + +async function registerUser(email, firstName, lastName) { + if (!email || !firstName || !lastName) { + return NextResponse.json( + { error: "Missing required fields" }, + { status: 400 } + ); } + + const userRes = await createUser(email, firstName, lastName) + if (!userRes) + return NextResponse.json({ ok: false, error: "Could not create user" }, {status: 400}); + + const {newUser, activateToken} = userRes + + const res = await sendVerificationEmail(newUser, activateToken) + if (res.success) { + return NextResponse.json({ok: true}, {status: 200}) + } + + return NextResponse.json({ ok: false, error: "Could not send verification email" }, {status: 400}); + - catch (error) { - console.error(error); +} + +export async function POST(req) { + + if (req.method != "POST") { return NextResponse.json( - { error: `something went wrong: ${error}` }, - { status: 500 } + { error: `Method '${req.method}' does not match POST` }, + { status: 405 } ); } + const args = await req.json(); + const { email, firstName, lastName } = args; + + return await registerUser(email, firstName, lastName); + } export async function GET(req) { From 738ce30541106f508c5a7c08328d5fe987213c34 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 5 Nov 2024 14:59:31 +0100 Subject: [PATCH 36/65] cafe shift post --- app/(pages)/(main)/volunteering/cafe/page.js | 2 +- app/api/v2/shifts/route.js | 104 +++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/app/(pages)/(main)/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js index 2dc021e..43d1602 100644 --- a/app/(pages)/(main)/volunteering/cafe/page.js +++ b/app/(pages)/(main)/volunteering/cafe/page.js @@ -71,7 +71,7 @@ function CafePage() { const manageShift = async () => { const sendRequest = async (date, id) => { - const response = await fetch("/api/v1/data/updateORCreateShift", { + const response = await fetch("/api/v2/shifts", { method: "post", mode: "cors", headers: { diff --git a/app/api/v2/shifts/route.js b/app/api/v2/shifts/route.js index 44dc6d5..20d1c53 100644 --- a/app/api/v2/shifts/route.js +++ b/app/api/v2/shifts/route.js @@ -1,6 +1,8 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { getHours } from "date-fns"; + export async function GET(req) { @@ -34,4 +36,106 @@ export async function GET(req) { ); } +} + + +/** + * @param {*} req + * @returns {NextResponse} + */ +export async function POST(req) { + const args = await req.json(); + + if (!( + args.hasOwnProperty("selectedDay") && + args.hasOwnProperty("shiftManagerId") && + args.hasOwnProperty("shiftWorker1Id") && + args.hasOwnProperty("shiftWorker2Id") && + args.hasOwnProperty("comment") + )) return NextResponse.json({error: "Malformed request"}, {status: 400}) + + const { + selectedDay, + shiftManagerId, + shiftWorker1Id, + shiftWorker2Id, + comment, + } = args; + + + let title; + let shiftPosition; + const selectedStartTime = getHours(selectedDay); + + const hasWorkers = shiftManagerId || shiftWorker1Id || shiftWorker2Id; + + switch (selectedStartTime) { + case 10: + shiftPosition = 0; + break; + case 12: + shiftPosition = 1; + break; + case 14: + shiftPosition = 2; + break; + } + + try { + + + const shiftExists = await prisma.shiftCafe.findFirst({ + where: { + startAt: selectedDay, + }, + }); + + + let data; + + // shift and workers exists + if (hasWorkers && shiftExists) { + data = await prisma.shiftCafe.update({ + where: { + id: shiftExists.id, + }, + data: { + title: title, + comment: comment, + shiftPosition: shiftPosition, + shiftManager: shiftManagerId, + shiftWorker1: shiftWorker1Id, + shiftWorker2: shiftWorker2Id, + }, + }); + } + + // workers exist + else if (hasWorkers && !shiftExists) { + data = await prisma.shiftCafe.create({ + data: { + title: title, + comment: comment, + startAt: selectedDay, + shiftPosition: shiftPosition, + shiftManager: shiftManagerId, + shiftWorker1: shiftWorker1Id, + shiftWorker2: shiftWorker2Id, + }, + }); + } else if (shiftExists) { + data = await prisma.shiftCafe.delete({ + where: { + id: shiftExists.id, + }, + }); + } + + return NextResponse.json({}, {status: 200}); + } catch (error) { + const message = `Error with assigning roles: ${error}`; + + console.log(message); + return NextResponse.json({ error: message }, { status: 400 }); + } } \ No newline at end of file From 5a0b333bc7d40e15ded3c1870cb01106f08b1697 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Tue, 5 Nov 2024 15:20:29 +0100 Subject: [PATCH 37/65] admin setroles post --- app/(pages)/(main)/admin/page.js | 2 +- app/api/v2/users/[userID]/[action]/route.js | 54 ++++++++++++++++++--- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/app/(pages)/(main)/admin/page.js b/app/(pages)/(main)/admin/page.js index f455705..8b9f9da 100644 --- a/app/(pages)/(main)/admin/page.js +++ b/app/(pages)/(main)/admin/page.js @@ -192,7 +192,7 @@ function roleSettings( ) { const handleSave = async () => { - const response = await fetch("/api/v1/data/setRoles", { + const response = await fetch(`/api/v2/users/${selectedUser.id}/roles`, { method: "post", mode: "cors", headers: { diff --git a/app/api/v2/users/[userID]/[action]/route.js b/app/api/v2/users/[userID]/[action]/route.js index 6cfa9e5..fcc0c02 100644 --- a/app/api/v2/users/[userID]/[action]/route.js +++ b/app/api/v2/users/[userID]/[action]/route.js @@ -4,6 +4,18 @@ import prisma from "@/prisma/prismaClient"; import { duration } from "@mui/material"; import { inc } from "sanity"; +export async function POST(req, {params}) { + const args = await req.json() + const {userID, action} = await params + + if (userID) { + switch (action) { + case "roles": + return handlePostRoles(userID, args) + } + } + +} export async function GET(req, {params}) { if (req.method != "GET") { @@ -12,10 +24,7 @@ export async function GET(req, {params}) { { status: 405 } ); } - - await params - const userID = params.userID - const action = params.action + const {userID, action} = await params if (userID) { switch (action) { @@ -29,13 +38,42 @@ export async function GET(req, {params}) { return handleGetUser(req) } } - return NextResponse.json( - { error: `${userID}, ${action}` }, - { status: 200 } - ); + return NextResponse.json( + { error: `${userID}, ${action}` }, + { status: 200 } + ); } +async function handlePostRoles(userID, args) { + if (!( + args.hasOwnProperty("roles") + )) return NextResponse.json({error: "Malformed request"}, {status: 400}) + + const roles = args.roles; + + await prisma.UserRole.deleteMany({ + where: { + userId: userID + } + }) + + const roleList = await prisma.role.findMany({select: {id: true, name: true}}) + const roleMap = {} + for (let role of roleList) roleMap[role.name] = role.id + + for (let role of roles) { + await prisma.UserRole.create({ + data: { + userId: userID, + roleId: roleMap[role] + } + }) + } + + return NextResponse.json({}, {status: 200}) +} + async function handleGetWorkLogs(userID) { const workLogs = await prisma.workLog.findMany({ where: { From 5340df0f8fa52cef85a5be0b5da01943becadbb3 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Wed, 6 Nov 2024 12:18:40 +0100 Subject: [PATCH 38/65] api rework complete, but potentialy contain bugs --- app/(pages)/(main)/layout.js | 5 +- app/(pages)/(main)/volunteering/cafe/page.js | 3 +- app/api/v1/sendVerification/route.js | 2 +- app/api/v2/auth/activate/[token]/route.js | 49 ++++++++++++++++++++ app/api/v2/shifts/route.js | 3 +- app/api/v2/users/route.js | 2 +- 6 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 app/api/v2/auth/activate/[token]/route.js diff --git a/app/(pages)/(main)/layout.js b/app/(pages)/(main)/layout.js index 2cebf2a..cc92c7e 100644 --- a/app/(pages)/(main)/layout.js +++ b/app/(pages)/(main)/layout.js @@ -61,7 +61,10 @@ export default async function AppLayout({ children }) { }} > - {children} + {children}
diff --git a/app/(pages)/(main)/volunteering/cafe/page.js b/app/(pages)/(main)/volunteering/cafe/page.js index 43d1602..08ecaba 100644 --- a/app/(pages)/(main)/volunteering/cafe/page.js +++ b/app/(pages)/(main)/volunteering/cafe/page.js @@ -107,7 +107,7 @@ function CafePage() { } const manageShiftClear = async () => { - const response = await fetch("/api/v1/data/updateORCreateShift", { + const response = await fetch("/api/v2/shifts", { method: "post", mode: "cors", headers: { @@ -123,7 +123,6 @@ function CafePage() { if (response.ok) { const data = await response.json(); - // console.log(data); setSelectedShift(data.data); setRefresh(!refresh); } diff --git a/app/api/v1/sendVerification/route.js b/app/api/v1/sendVerification/route.js index 0073ece..2c70c17 100644 --- a/app/api/v1/sendVerification/route.js +++ b/app/api/v1/sendVerification/route.js @@ -10,7 +10,7 @@ export async function POST(req) { const args = await req.json(); const { user, activateToken } = args - const link = `${NEXTAUTH_URL}/api/v1/activate/${activateToken.token}`; + const link = `/api/v2/activate/${activateToken.token}`; const html = ` Hello ${user.firstName} ${user.lastName}, You have successfully created a user-account at ${NEXTAUTH_URL}.

diff --git a/app/api/v2/auth/activate/[token]/route.js b/app/api/v2/auth/activate/[token]/route.js new file mode 100644 index 0000000..f5f2f29 --- /dev/null +++ b/app/api/v2/auth/activate/[token]/route.js @@ -0,0 +1,49 @@ + +import prisma from "@/prisma/prismaClient"; +import { NextResponse } from "next/server"; + +export async function GET(req, {params}) { + + const {token} = params + + const aToken = await prisma.activateToken.findFirst({ + where: { + token: token, + activatedAt: null + } + }) + + if (!aToken) { + throw new Error("Invalid activate token") + } + + const user = await prisma.user.findFirst({ + where: { + id: aToken.userId + } + }) + + if (!user) { + throw new Error("No user related to activate token"); + } + + await prisma.user.update({ + where: { + id: user.id + }, + data: { + active: true + } + }) + + await prisma.activateToken.update({ + where: { + token + }, + data: { + activatedAt: new Date() + } + }) + + return NextResponse.redirect(new URL("/auth/signIn", req.url)) +} \ No newline at end of file diff --git a/app/api/v2/shifts/route.js b/app/api/v2/shifts/route.js index 20d1c53..dd388d5 100644 --- a/app/api/v2/shifts/route.js +++ b/app/api/v2/shifts/route.js @@ -50,8 +50,7 @@ export async function POST(req) { args.hasOwnProperty("selectedDay") && args.hasOwnProperty("shiftManagerId") && args.hasOwnProperty("shiftWorker1Id") && - args.hasOwnProperty("shiftWorker2Id") && - args.hasOwnProperty("comment") + args.hasOwnProperty("shiftWorker2Id") )) return NextResponse.json({error: "Malformed request"}, {status: 400}) const { diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js index c58c641..64c141f 100644 --- a/app/api/v2/users/route.js +++ b/app/api/v2/users/route.js @@ -8,7 +8,7 @@ import { mailOptions, transporter } from "@/app/(pages)/auth/email"; const NEXTAUTH_URL = process.env.NEXTAUTH_URL || ""; async function sendVerificationEmail(user, activateToken) { - const link = `${NEXTAUTH_URL}/api/v1/activate/${activateToken.token}`; + const link = `${NEXTAUTH_URL}/activate/${activateToken.token}`; const html = ` Hello ${user.firstName} ${user.lastName}, You have successfully created a user-account at ${NEXTAUTH_URL}.

From d96a721f378cc900a881e5182725e12614d75f93 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Wed, 6 Nov 2024 12:27:17 +0100 Subject: [PATCH 39/65] bug fixes --- .../volunteering/logs/voucherLogInput.js | 21 ++-- .../(main)/volunteering/membership/page.js | 2 + app/api/v1/activate/[token]/route.js | 49 -------- app/api/v1/auth/[...nextauth]/route.js | 93 -------------- app/api/v1/createUser/route.js | 56 --------- app/api/v1/data/prisma/route.js | 117 ------------------ app/api/v1/data/setRoles/route.js | 57 --------- app/api/v1/data/updateORCreateShift/route.js | 109 ---------------- app/api/v1/sendVerification/route.js | 43 ------- 9 files changed, 11 insertions(+), 536 deletions(-) delete mode 100644 app/api/v1/activate/[token]/route.js delete mode 100644 app/api/v1/auth/[...nextauth]/route.js delete mode 100644 app/api/v1/createUser/route.js delete mode 100644 app/api/v1/data/prisma/route.js delete mode 100644 app/api/v1/data/setRoles/route.js delete mode 100644 app/api/v1/data/updateORCreateShift/route.js delete mode 100644 app/api/v1/sendVerification/route.js diff --git a/app/(pages)/(main)/volunteering/logs/voucherLogInput.js b/app/(pages)/(main)/volunteering/logs/voucherLogInput.js index 6c88682..bde66a3 100644 --- a/app/(pages)/(main)/volunteering/logs/voucherLogInput.js +++ b/app/(pages)/(main)/volunteering/logs/voucherLogInput.js @@ -30,7 +30,7 @@ export default function voucherLogInput( if (isInvalid) return; - const response = await fetch("/api/v2/voucherLogs", { + fetch("/api/v2/voucherLogs", { method: "POST", headers: { "content-type": "application/json" @@ -44,19 +44,16 @@ export default function voucherLogInput( }).then(res => { setNumVouchers(0); setDescriptionVoucher(""); + if (!res.ok) { + setRequestResponse("Failed to use voucher. Please try again."); + return + } setRefresh(); + setRequestResponse("Voucher used."); + setTimeout(() => { + setRequestResponse(""); + }, 5000); }) - - if (!response.ok) { - setRequestResponse("Failed to use voucher. Please try again."); - return; - } - - setRequestResponse("Voucher used."); - setTimeout(() => { - setRequestResponse(""); - }, 5000); - }; return ( diff --git a/app/(pages)/(main)/volunteering/membership/page.js b/app/(pages)/(main)/volunteering/membership/page.js index 738702c..fe03ec5 100644 --- a/app/(pages)/(main)/volunteering/membership/page.js +++ b/app/(pages)/(main)/volunteering/membership/page.js @@ -105,6 +105,8 @@ function MembershipPage() { semester_id: session.data.semester.id, }) }).then(res => { + setNewMemberName("") + setNewMemberComment("") setRefresh(!refresh) }) diff --git a/app/api/v1/activate/[token]/route.js b/app/api/v1/activate/[token]/route.js deleted file mode 100644 index 2b03656..0000000 --- a/app/api/v1/activate/[token]/route.js +++ /dev/null @@ -1,49 +0,0 @@ - -import { redirect } from "next/navigation"; -import prisma from "@/prisma/prismaClient"; - -export async function GET(request, {params}) { - - const {token} = params - - const aToken = await prisma.activateToken.findFirst({ - where: { - token: token, - activatedAt: null - } - }) - - if (!aToken) { - throw new Error("Invalid activate token") - } - - const user = await prisma.user.findFirst({ - where: { - id: aToken.userId - } - }) - - if (!user) { - throw new Error("No user related to activate token"); - } - - await prisma.user.update({ - where: { - id: user.id - }, - data: { - active: true - } - }) - - await prisma.activateToken.update({ - where: { - token - }, - data: { - activatedAt: new Date() - } - }) - - redirect("/auth/signIn") -} \ No newline at end of file diff --git a/app/api/v1/auth/[...nextauth]/route.js b/app/api/v1/auth/[...nextauth]/route.js deleted file mode 100644 index 00a9810..0000000 --- a/app/api/v1/auth/[...nextauth]/route.js +++ /dev/null @@ -1,93 +0,0 @@ - - -import NextAuth from "next-auth"; -import Email from "next-auth/providers/email"; -import { PrismaAdapter } from "@next-auth/prisma-adapter"; -import prisma from "@/prisma/prismaClient"; - -const handler = NextAuth({ - providers: [ - Email({ - server: { - host: "smtp.gmail.com", - port: 587, - auth: { - user: process.env.NODEMAILER_NOREPLY_USER, - pass: process.env.NODEMAILER_NOREPLY_PASSWORD, - }, - }, - from: process.env.NODEMAILER_NOREPLY_USER - }), - ], - callbacks: { - async signIn({ user }) { - - const cybUser = await prisma.user.findFirst({ - where: { - email: user.email - } - }) - - if (!cybUser) { - throw "No user found" - } - - if (!cybUser.active) { - throw "Email has not been verified" - } - - return true - }, - async session({ session }) { - - const cybUser = await prisma.user.findFirst({ - where: { - email: session.user.email, - }, - include: { - recruitedByUser: true, - recruitedUsers: true, - roles: { - include: { - role: true, - }, - }, - }, - }); - - const semesters = await prisma.semester.findMany({ - orderBy: { - id: "desc" - }, - take: 1, - }) - const currentSemester = await semesters[0]; - - if (cybUser) { - session.user.name = `${cybUser.firstName} ${cybUser.lastName}` - delete session.user.name; - - session.user = { - ...session.user, - ...cybUser, - roles: cybUser.roles.map((e) => e.role), - name: `${cybUser.firstName} ${cybUser.lastName ? cybUser.lastName : ""}` - } - } - - // console.log(currentSemester); - if (currentSemester) { - session.semester = currentSemester; - } - - return session - } - }, - pages: { - signIn: "/auth/signIn" - }, - adapter: PrismaAdapter(prisma), -}); - -// export { handler as GET, handler as POST }; -export { handler } \ No newline at end of file diff --git a/app/api/v1/createUser/route.js b/app/api/v1/createUser/route.js deleted file mode 100644 index 2df4f75..0000000 --- a/app/api/v1/createUser/route.js +++ /dev/null @@ -1,56 +0,0 @@ - -import { NextResponse } from "next/server"; -import prisma from "@/prisma/prismaClient"; -import { randomBytes } from "crypto"; - -export async function POST(req) { - - if (req.method != "POST") { - return NextResponse.json( - { error: `Method '${req.method}' does not match POST` }, - { status: 405 } - ); - } - - const args = await req.json(); - const { email, firstName, lastName } = args; - - if (!email || !firstName || !lastName) { - return NextResponse.json( - { error: "Missing required fields" }, - { status: 400 } - ); - } - - try { - const result = await prisma.$transaction(async (prisma) => { - const newUser = await prisma.user.create({ - data: { - email: email, - firstName: firstName, - lastName: lastName, - }, - }); - - const activateToken = await prisma.activateToken.create({ - data: { - token: `${randomBytes(32).toString("hex")}`, - userId: newUser.id, - }, - }); - - return { newUser, activateToken }; - }); - - return NextResponse.json({ data: result }); - } - - catch (error) { - console.error(error); - return NextResponse.json( - { error: `something went wrong: ${error}` }, - { status: 500 } - ); - } - -} \ No newline at end of file diff --git a/app/api/v1/data/prisma/route.js b/app/api/v1/data/prisma/route.js deleted file mode 100644 index 1d7bfc1..0000000 --- a/app/api/v1/data/prisma/route.js +++ /dev/null @@ -1,117 +0,0 @@ - - - -import { NextResponse } from "next/server"; -import prisma from "@/prisma/prismaClient"; - -/** - * @param {*} req - * @returns {NextResponse} - */ -export async function POST(req) { - if (req.method == "POST") { - const args = await req.json(); - let data; - - if (!isValidRequest(args)) { - return NextResponse.json( - { error: `Request body ${args} is not correct` }, - { status: 405 } - ); - } - - if (args.debug) console.log("PMH Request", args); - - try { - switch (args.method) { - case "find": - data = await prisma[args.model].findMany(args.request); - break; - case "create": - data = await prisma[args.model].create(args.request); - break; - case "update": - data = await prisma[args.model].update(args.request); - break; - case "delete": - data = await handleDelete(args) - break; - default: - throw new Error(`Invalid method ${args.method} for request`); - } - - if (args.fields) { - return NextResponse.json({ - data: data, - fields: Object.keys(prisma[args.model].fields), - extra: prisma[args.model].fields, - }); - } - - return NextResponse.json({ data: data }); - - } catch (error) { - console.log(`uh oh, Error with model "${args.model}"`, error); - - return NextResponse.json( - { error: `Error with ${args.model}: ${error}` }, - { status: 400 } - ); - } - } else { - return NextResponse.json( - { error: `Method '${req.method}' does not match POST` }, - { status: 405 } - ); - } -} - -function isValidRequest(args) { - const check1 = Object.hasOwn(args, "model"); - const check2 = Object.hasOwn(args, "method"); - const check3 = Object.hasOwn(args, "request"); - return check1 & check2 & check3; -} - -async function handleDelete(args) { - - switch (args.model) { - case ("user"): - const user = await prisma.user.findUnique({ - ...args.request, - include: { - activateToken: true, - sessions: true - } - }); - - const data = await prisma.$transaction([ - prisma.activateToken.deleteMany({ - where: { - id: { - in: user.ActivateToken.map((e) => e.id), - }, - }, - }), - prisma.session.deleteMany({ - where: { - id: { - in: user.sessions.map((e) => e.id), - }, - }, - }), - prisma.user.delete({ - where: { - id: user.id, - }, - }), - ]); - - return data; - - default: - break; - } - - -} \ No newline at end of file diff --git a/app/api/v1/data/setRoles/route.js b/app/api/v1/data/setRoles/route.js deleted file mode 100644 index 7698324..0000000 --- a/app/api/v1/data/setRoles/route.js +++ /dev/null @@ -1,57 +0,0 @@ - -import prisma from "@/prisma/prismaClient"; -import { NextResponse } from "next/server"; - -/** - * @param {*} req - * @returns {NextResponse} - */ -export async function POST(req) { - if (req.method == "POST") { - - const args = await req.json(); - - const { user, roles } = args; - // console.log("post roles", user, roles); - - try { - const data = await prisma.$transaction(async (prisma) => { - - await prisma.userRole.deleteMany({ - where: { userId: user.id } - }); - - const userRoles = roles.map((role) => ({ - userId: user.id, - roleId: role.id - })) - - await prisma.userRole.createMany({ - data: userRoles - }) - }); - - return NextResponse.json({ data: data }); - } - - catch (error) { - const message = `Error with assigning roles: ${error}`; - - console.log(message) - return NextResponse.json( - { error: message }, - { status: 400 } - ); - } - } - - else { - const message = `Method '${req.method}' does not match POST`; - - console.log(message); - return NextResponse.json( - { error: message }, - { status: 405 } - ); - } -} \ No newline at end of file diff --git a/app/api/v1/data/updateORCreateShift/route.js b/app/api/v1/data/updateORCreateShift/route.js deleted file mode 100644 index bd6577f..0000000 --- a/app/api/v1/data/updateORCreateShift/route.js +++ /dev/null @@ -1,109 +0,0 @@ - - -import prisma from "@/prisma/prismaClient"; -import { getHours } from "date-fns"; -import { NextResponse } from "next/server"; - -/** - * @param {*} req - * @returns {NextResponse} - */ -export async function POST(req) { - if (req.method == "POST") { - const args = await req.json(); - - const { - selectedDay, - shiftManagerId, - shiftWorker1Id, - shiftWorker2Id, - comment, - } = args; - - // console.log(args); - - let title; - let shiftPosition; - const selectedStartTime = getHours(selectedDay); - - const hasWorkers = shiftManagerId || shiftWorker1Id || shiftWorker2Id; - - switch (selectedStartTime) { - case 10: - shiftPosition = 0; - break; - case 12: - shiftPosition = 1; - break; - case 14: - shiftPosition = 2; - break; - } - - try { - - - const shiftExists = await prisma.shiftCafe.findFirst({ - where: { - startAt: selectedDay, - }, - }); - - // console.log(shiftExists); - - let data; - - // shift and workers exists - if (hasWorkers && shiftExists) { - data = await prisma.shiftCafe.update({ - where: { - id: shiftExists.id, - // startAt: selectedDay, - }, - data: { - title: title, - comment: comment, - shiftPosition: shiftPosition, - shiftManager: shiftManagerId, - shiftWorker1: shiftWorker1Id, - shiftWorker2: shiftWorker2Id, - }, - }); - } - - // workers exist - else if (hasWorkers && !shiftExists) { - data = await prisma.shiftCafe.create({ - data: { - title: title, - comment: comment, - startAt: selectedDay, - shiftPosition: shiftPosition, - shiftManager: shiftManagerId, - shiftWorker1: shiftWorker1Id, - shiftWorker2: shiftWorker2Id, - }, - }); - } else if (shiftExists) { - data = await prisma.shiftCafe.delete({ - where: { - id: shiftExists.id, - // startAt: selectedDay, - }, - }); - } - - return NextResponse.json({ data: data }); - } catch (error) { - const message = `Error with assigning roles: ${error}`; - - console.log(message); - return NextResponse.json({ error: message }, { status: 400 }); - } - } else { - const message = `Method '${req.method}' does not match POST`; - - console.log(message); - return NextResponse.json({ error: message }, { status: 405 }); - } -} \ No newline at end of file diff --git a/app/api/v1/sendVerification/route.js b/app/api/v1/sendVerification/route.js deleted file mode 100644 index 2c70c17..0000000 --- a/app/api/v1/sendVerification/route.js +++ /dev/null @@ -1,43 +0,0 @@ - -import { mailOptions, transporter } from "@/app/(pages)/auth/email"; -import { NextResponse } from "next/server"; - -const NEXTAUTH_URL = process.env.NEXTAUTH_URL || ""; - -export async function POST(req) { - - if (req.method == "POST") { - - const args = await req.json(); - const { user, activateToken } = args - const link = `/api/v2/activate/${activateToken.token}`; - const html = ` - Hello ${user.firstName} ${user.lastName}, You have successfully created a user-account at ${NEXTAUTH_URL}.

- - Please verify your email by clicking the following link: ${link}

- If you have not created a user, ignore this message.

- You cannot reply to this email. - `; - - let success = false - - console.log(html) - - try { - await transporter.sendMail(mailOptions(user.email, html)); - success = true - - } catch (error) { - console.error("Error with sending email: ", error); - return NextResponse.json({ success: success, email: user.email, error: error }, { status: 400, statusText: `Can't send email to ${user.email}` }) - } - - return NextResponse.json({ success: success, email: user.email, link: link }, { status: 200 }); - } - - return NextResponse.json( - { error: `Method '${req.method}' does not match POST` }, - { status: 405 } - ); - -} \ No newline at end of file From ba102fa9c18041a54f83a9a8273b71a778b8b1c3 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Wed, 6 Nov 2024 21:11:08 +0100 Subject: [PATCH 40/65] first auth attempt --- app/api/utils/auth.js | 23 +++++++++++++++++++++++ app/api/v2/auth/[...nextauth]/route.js | 6 ++++-- app/api/v2/memberships/route.js | 22 +++++++++++++--------- 3 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 app/api/utils/auth.js diff --git a/app/api/utils/auth.js b/app/api/utils/auth.js new file mode 100644 index 0000000..041860a --- /dev/null +++ b/app/api/utils/auth.js @@ -0,0 +1,23 @@ +import { getServerSession } from "next-auth"; +import { authOptions } from "../v2/auth/[...nextauth]/route"; +import { NextResponse } from "next/server"; + + +/** + * This function takes roles need to access page. + * Returns either a redirect or error NextResponse if the user is not authenticated + * Returns null if user should be able to access page + * + * @param {string[] | null} requiredRoles A list of required roles to access page / route. Leave empty to only require user to be signed in to access page / route + * @returns {NextResponse | null} Returns null if user is authenticated and a redirect or error if not + */ +export async function auth(requiredRoles = null) { + const session = await getServerSession(authOptions) + +} + +export function authWrapper() { + return () => { + + } +} \ No newline at end of file diff --git a/app/api/v2/auth/[...nextauth]/route.js b/app/api/v2/auth/[...nextauth]/route.js index c636b52..9f2b85e 100644 --- a/app/api/v2/auth/[...nextauth]/route.js +++ b/app/api/v2/auth/[...nextauth]/route.js @@ -5,7 +5,7 @@ import Email from "next-auth/providers/email"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import prisma from "@/prisma/prismaClient"; -const handler = NextAuth({ +export const authOptions = { providers: [ Email({ server: { @@ -87,6 +87,8 @@ const handler = NextAuth({ signIn: "/auth/signIn" }, adapter: PrismaAdapter(prisma), -}); +} + +const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; \ No newline at end of file diff --git a/app/api/v2/memberships/route.js b/app/api/v2/memberships/route.js index 01c5679..8ca2641 100644 --- a/app/api/v2/memberships/route.js +++ b/app/api/v2/memberships/route.js @@ -1,16 +1,18 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; -import { randomBytes } from "crypto"; +import { auth } from "../../utils/auth"; + export async function GET(req) { - - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } + + const authDenied = auth({ + requiredRoles: [ + "intern" + ] + }) + + if (authDenied) return authDenied try { @@ -38,6 +40,7 @@ export async function GET(req) { } export async function POST(req) { + const args = await req.json() if (!( @@ -60,4 +63,5 @@ export async function POST(req) { if (res) return NextResponse.json({status: 200}) -} \ No newline at end of file +} + From e80c943198639ba9f663b9bdf5bda1bfefba5f4d Mon Sep 17 00:00:00 2001 From: Sebbben Date: Thu, 7 Nov 2024 12:50:43 +0100 Subject: [PATCH 41/65] second auth attempt and beginning of securing api --- app/api/utils/auth.js | 69 +++++++++++++++++++++----- app/api/v2/auth/[...nextauth]/route.js | 14 ++++-- app/api/v2/memberships/route.js | 25 ++++------ app/middleware/authWrapper.js | 2 +- 4 files changed, 76 insertions(+), 34 deletions(-) diff --git a/app/api/utils/auth.js b/app/api/utils/auth.js index 041860a..0e15431 100644 --- a/app/api/utils/auth.js +++ b/app/api/utils/auth.js @@ -1,23 +1,66 @@ import { getServerSession } from "next-auth"; import { authOptions } from "../v2/auth/[...nextauth]/route"; -import { NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; +const NOT_AUTHORIZED = NextResponse.json({error: "Not authorized"}, {status: 403}) +const NOT_SIGNED_IN = NextResponse.json({error: "Not logged in"}, {status: 401}) /** - * This function takes roles need to access page. - * Returns either a redirect or error NextResponse if the user is not authenticated - * Returns null if user should be able to access page + * This class is meant to be used in a chain-fassion + * 1. Make an instance of the class + * 2. Chain together checks + * 3. Check the failed attribute to check if the auth-check failed or not + * 4. Return the reponse attribute which contain a NextResponse if the checks failed * - * @param {string[] | null} requiredRoles A list of required roles to access page / route. Leave empty to only require user to be signed in to access page / route - * @returns {NextResponse | null} Returns null if user is authenticated and a redirect or error if not + * @param {NextRequest} req */ -export async function auth(requiredRoles = null) { - const session = await getServerSession(authOptions) +export class Auth { + constructor(req) { + this.response = null + this.failed = false + this.req = req + } -} + /** + * @param {string[]} requiredRoles List of roles required to access page, leave empty to require user to be logged in + * @returns {Auth} + */ + async requireRoles(requiredRoles) { + if (this.failed) return this + + this.session ??= await getServerSession(authOptions) + // If no roles are required, the user just needs to be logged in which can be checked with the existance of the session object + if (requiredRoles.length >= 0 && this.session === null) { + this.response = NOT_SIGNED_IN + this.failed = true + return this + } + + // Count the how many of the required roles the user has, if equal to length of requiredRoles, the user has all roles + if (requiredRoles.reduce((sum,role) => sum+this.session.user.roles.includes(role), 0) === requiredRoles.length) + return this + + this.response = NOT_AUTHORIZED + this.failed = true -export function authWrapper() { - return () => { - + return this } -} \ No newline at end of file + + /** + * @param {string[]} params Parameters required to access page + * @returns {Auth} + */ + async requireParams(params) { + const reqParams = await this.req.json() + for (const reqParam of reqParams){ + if (!params.contains(reqParam)){ + this.response = MISSING_PARAMS + this.failed = true + break + } + } + + return this + } + +} diff --git a/app/api/v2/auth/[...nextauth]/route.js b/app/api/v2/auth/[...nextauth]/route.js index 9f2b85e..f810e8d 100644 --- a/app/api/v2/auth/[...nextauth]/route.js +++ b/app/api/v2/auth/[...nextauth]/route.js @@ -44,12 +44,18 @@ export const authOptions = { where: { email: session.user.email, }, - include: { + select: { + firstName: true, + lastName: true, recruitedByUser: true, recruitedUsers: true, roles: { - include: { - role: true, + select: { + role: { + select: { + name: true + } + }, }, }, }, @@ -70,7 +76,7 @@ export const authOptions = { session.user = { ...session.user, ...cybUser, - roles: cybUser.roles.map((e) => e.role), + roles: cybUser.roles.map((e) => e.role.name), name: `${cybUser.firstName} ${cybUser.lastName ? cybUser.lastName : ""}` } } diff --git a/app/api/v2/memberships/route.js b/app/api/v2/memberships/route.js index 8ca2641..907ffaa 100644 --- a/app/api/v2/memberships/route.js +++ b/app/api/v2/memberships/route.js @@ -1,18 +1,15 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; -import { auth } from "../../utils/auth"; +import { Auth } from "../../utils/auth"; export async function GET(req) { - const authDenied = auth({ - requiredRoles: [ - "intern" - ] - }) + const authCheck = await new Auth(req) + .requireRoles(["intern"]) - if (authDenied) return authDenied + if (authCheck.failed) return authCheck.response try { @@ -41,15 +38,11 @@ export async function GET(req) { export async function POST(req) { - const args = await req.json() - - if (!( - args.hasOwnProperty("name") && - args.hasOwnProperty("email") && - args.hasOwnProperty("comments") && - args.hasOwnProperty("seller_id") && - args.hasOwnProperty("semester_id") - )) return NextResponse.json({error: "Malformed reqeust"}, {status: 400}) + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + .requireParams(["name", "email", "comments", "seller_id", "semester_id"]) + + if (authCheck.failed) return authCheck.response const res = await prisma.UserMembership.create({ data: { diff --git a/app/middleware/authWrapper.js b/app/middleware/authWrapper.js index 0196d97..21aa24b 100644 --- a/app/middleware/authWrapper.js +++ b/app/middleware/authWrapper.js @@ -19,7 +19,7 @@ export default function authWrapper(WrappedComponent, requiredRole="", redirect= } else if (status == "authenticated" && requiredRole != "") { - const userRoles = data.user.roles.map((e) => e.name) + const userRoles = data.user.roles const missingRole = !userRoles.includes(requiredRole) if (missingRole) { From c36efde84d525c7a967ca23eedacbe917ea6768f Mon Sep 17 00:00:00 2001 From: Sebbben Date: Thu, 7 Nov 2024 13:23:52 +0100 Subject: [PATCH 42/65] added x-auth-verified custom header to api responses that has been verified by the auth util --- app/api/utils/auth.js | 13 ++++++++++++- app/api/v2/memberships/route.js | 8 ++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/api/utils/auth.js b/app/api/utils/auth.js index 0e15431..acabfe8 100644 --- a/app/api/utils/auth.js +++ b/app/api/utils/auth.js @@ -62,5 +62,16 @@ export class Auth { return this } - + /** + * + * @param {NextResponse} res + * @returns {NextResponse} + */ + verify(res) { + if (this.failed) return this.response + + res.headers.set("X-Auth-Checked", "true") + + return res + } } diff --git a/app/api/v2/memberships/route.js b/app/api/v2/memberships/route.js index 907ffaa..9356013 100644 --- a/app/api/v2/memberships/route.js +++ b/app/api/v2/memberships/route.js @@ -23,15 +23,15 @@ export async function GET(req) { } }); - return NextResponse.json({ memberships: semester }); + return authCheck.verify(NextResponse.json({ memberships: semester })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } @@ -55,6 +55,6 @@ export async function POST(req) { }) if (res) - return NextResponse.json({status: 200}) + return authCheck.verify(NextResponse.json({status: 200})) } From b483e922cc6d55c0cada384418dca3833fb65aaf Mon Sep 17 00:00:00 2001 From: Sebbben Date: Thu, 7 Nov 2024 13:27:35 +0100 Subject: [PATCH 43/65] added custom header to api error responses --- app/api/utils/auth.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/api/utils/auth.js b/app/api/utils/auth.js index acabfe8..f595f21 100644 --- a/app/api/utils/auth.js +++ b/app/api/utils/auth.js @@ -4,6 +4,7 @@ import { NextRequest, NextResponse } from "next/server"; const NOT_AUTHORIZED = NextResponse.json({error: "Not authorized"}, {status: 403}) const NOT_SIGNED_IN = NextResponse.json({error: "Not logged in"}, {status: 401}) +const MISSING_PARAMS = NextResponse.json({error: "Malformed request, missing params"}, {status: 400}) /** * This class is meant to be used in a chain-fassion @@ -68,10 +69,13 @@ export class Auth { * @returns {NextResponse} */ verify(res) { - if (this.failed) return this.response + if (this.failed) { + const failedRes = this.response.clone() + failedRes.headers.set("X-Auth-Checked", "true") + return failedRes + } res.headers.set("X-Auth-Checked", "true") - return res } } From 5201382f751302ba5bac903bd6c6521f26b5e0d1 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Thu, 7 Nov 2024 13:54:28 +0100 Subject: [PATCH 44/65] role based and param based auth added to all api endpoints --- app/api/v2/recruitGraph/route.js | 19 ++++------ app/api/v2/roles/route.js | 18 ++++----- app/api/v2/semester/route.js | 19 +++++----- app/api/v2/semesterVolunteerInfo/route.js | 20 +++++----- app/api/v2/shifts/route.js | 41 +++++++++----------- app/api/v2/users/[userID]/[action]/route.js | 42 +++++++++++++-------- app/api/v2/users/[userID]/route.js | 28 +++++++++----- app/api/v2/users/route.js | 24 +++++++----- app/api/v2/voucherLogs/route.js | 33 ++++++++-------- app/api/v2/workGroups/route.js | 37 +++++++++--------- app/api/v2/workLogs/route.js | 38 +++++++++---------- 11 files changed, 164 insertions(+), 155 deletions(-) diff --git a/app/api/v2/recruitGraph/route.js b/app/api/v2/recruitGraph/route.js index 4f5a2ab..0cc0c0c 100644 --- a/app/api/v2/recruitGraph/route.js +++ b/app/api/v2/recruitGraph/route.js @@ -1,16 +1,13 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; export async function GET(req) { - - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } - + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { const users = await prisma.user.findMany({ @@ -39,15 +36,15 @@ export async function GET(req) { return [userIdToId[user.id], userIdToId[user.recruitedById]] }) - return NextResponse.json({ nodes: nodes, edges: edges }); + return authCheck.verify(NextResponse.json({ nodes: nodes, edges: edges })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } \ No newline at end of file diff --git a/app/api/v2/roles/route.js b/app/api/v2/roles/route.js index d9f4708..bb0adf4 100644 --- a/app/api/v2/roles/route.js +++ b/app/api/v2/roles/route.js @@ -1,16 +1,14 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; export async function GET(req) { - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } - + const authCheck = await new Auth(req) + .requireRoles([]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { const roles = await prisma.role.findMany({ @@ -19,15 +17,15 @@ export async function GET(req) { } }); - return NextResponse.json({ roles: roles.map(e => e.name) }); + return authCheck.verify(NextResponse.json({ roles: roles.map(e => e.name) })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } \ No newline at end of file diff --git a/app/api/v2/semester/route.js b/app/api/v2/semester/route.js index 1fbde26..c263a2b 100644 --- a/app/api/v2/semester/route.js +++ b/app/api/v2/semester/route.js @@ -1,16 +1,15 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; + export async function GET(req) { - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } - + const authCheck = await new Auth(req) + .requireRoles([]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { const semester = await prisma.semester.findFirst({ @@ -19,15 +18,15 @@ export async function GET(req) { } }); - return NextResponse.json({ semester: semester }); + return authCheck.verify(NextResponse.json({ semester: semester })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } \ No newline at end of file diff --git a/app/api/v2/semesterVolunteerInfo/route.js b/app/api/v2/semesterVolunteerInfo/route.js index fff1854..856423e 100644 --- a/app/api/v2/semesterVolunteerInfo/route.js +++ b/app/api/v2/semesterVolunteerInfo/route.js @@ -1,17 +1,17 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; + const VOUCHER_MODIFIER = 0.5 export async function GET(req) { - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } + const authCheck = await new Auth(req) + .requireRoles([]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { @@ -51,21 +51,21 @@ export async function GET(req) { }))._sum.amount - return NextResponse.json({ + return authCheck.verify(NextResponse.json({ membershipsPaid: membershipsPaid, numberVolunteers: numberVolunteers, volunteerHours: totVolunteerHours, vouchersEarned: totVolunteerHours * VOUCHER_MODIFIER, vouchersUsed: vouchersUsed - }); + })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } \ No newline at end of file diff --git a/app/api/v2/shifts/route.js b/app/api/v2/shifts/route.js index dd388d5..efe5fca 100644 --- a/app/api/v2/shifts/route.js +++ b/app/api/v2/shifts/route.js @@ -2,16 +2,15 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; import { getHours } from "date-fns"; +import { Auth } from "../../utils/auth"; + export async function GET(req) { - - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { @@ -25,33 +24,29 @@ export async function GET(req) { } }) - return NextResponse.json({ shifts: shifts }); + return authCheck.verify(NextResponse.json({ shifts: shifts })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } -/** - * @param {*} req - * @returns {NextResponse} - */ export async function POST(req) { - const args = await req.json(); - if (!( - args.hasOwnProperty("selectedDay") && - args.hasOwnProperty("shiftManagerId") && - args.hasOwnProperty("shiftWorker1Id") && - args.hasOwnProperty("shiftWorker2Id") - )) return NextResponse.json({error: "Malformed request"}, {status: 400}) + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + .requireParams(["selectedDay", "shiftManagerId", "shiftWorker1Id", "shiftWorker2Id"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + + const args = await req.json(); const { selectedDay, @@ -130,11 +125,11 @@ export async function POST(req) { }); } - return NextResponse.json({}, {status: 200}); + return authCheck.verify(NextResponse.json({}, {status: 200})); } catch (error) { const message = `Error with assigning roles: ${error}`; console.log(message); - return NextResponse.json({ error: message }, { status: 400 }); + return authCheck.verify(NextResponse.json({ error: message }, { status: 400 })); } } \ No newline at end of file diff --git a/app/api/v2/users/[userID]/[action]/route.js b/app/api/v2/users/[userID]/[action]/route.js index fcc0c02..c94633f 100644 --- a/app/api/v2/users/[userID]/[action]/route.js +++ b/app/api/v2/users/[userID]/[action]/route.js @@ -1,50 +1,62 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; -import { duration } from "@mui/material"; -import { inc } from "sanity"; +import { Auth } from "@/app/api/utils/auth"; export async function POST(req, {params}) { + + const authCheck = await new Auth(req) + .requireRoles(["admin"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + + const args = await req.json() const {userID, action} = await params if (userID) { switch (action) { case "roles": - return handlePostRoles(userID, args) + return authCheck.verify(handlePostRoles(userID, args)) } } } export async function GET(req, {params}) { - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } + + const authCheck = await new Auth(req) + .requireRoles([]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + const {userID, action} = await params if (userID) { + let res; switch (action) { case "roles": - return handleGetRoles(userID) + res = handleGetRoles(userID) case "workLogs": - return handleGetWorkLogs(userID) + res = handleGetWorkLogs(userID) case "recruitInfo": - return handleGetRecruitInfo(userID) + res = handleGetRecruitInfo(userID) default: - return handleGetUser(req) + res = handleGetUser(userID) } + return authCheck.verify(await res) } - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `${userID}, ${action}` }, { status: 200 } - ); + )); } +function handleGetUser(userID) { + return NextResponse.json({},{status:200}) +} + async function handlePostRoles(userID, args) { if (!( args.hasOwnProperty("roles") diff --git a/app/api/v2/users/[userID]/route.js b/app/api/v2/users/[userID]/route.js index 6ccad88..465d794 100644 --- a/app/api/v2/users/[userID]/route.js +++ b/app/api/v2/users/[userID]/route.js @@ -1,28 +1,36 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "@/app/api/utils/auth"; export async function GET(req, {params}) { - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } + + const authCheck = await new Auth(req) + .requireRoles([]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + await params const userID = params.userID - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `${userID}` }, { status: 200 } - ); + )); } export async function PATCH(req, {params}) { + + const authCheck = await new Auth(req) + .requireRoles([]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + + await params const userID = params.userID const args = await req.json() @@ -43,10 +51,10 @@ export async function PATCH(req, {params}) { } } - return NextResponse.json( + return authCheck.verify(NextResponse.json( result, { status: 200 } - ); + )); } diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js index 64c141f..2eb94bc 100644 --- a/app/api/v2/users/route.js +++ b/app/api/v2/users/route.js @@ -3,6 +3,7 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; import { randomBytes } from "crypto"; import { mailOptions, transporter } from "@/app/(pages)/auth/email"; +import { Auth } from "../../utils/auth"; const NEXTAUTH_URL = process.env.NEXTAUTH_URL || ""; @@ -81,22 +82,25 @@ async function registerUser(email, firstName, lastName) { } export async function POST(req) { - - if (req.method != "POST") { - return NextResponse.json( - { error: `Method '${req.method}' does not match POST` }, - { status: 405 } - ); - } + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) const args = await req.json(); const { email, firstName, lastName } = args; - return await registerUser(email, firstName, lastName); + return authCheck.verify(await registerUser(email, firstName, lastName)); } export async function GET(req) { + + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + const params = req.nextUrl.searchParams const queryParams = {} @@ -104,8 +108,8 @@ export async function GET(req) { queryParams.update({where: {active: true}}) } const res = await prisma.user.findMany(queryParams) - return NextResponse.json( + return authCheck.verify(NextResponse.json( {users: res}, {status: 200} - ) + )); } \ No newline at end of file diff --git a/app/api/v2/voucherLogs/route.js b/app/api/v2/voucherLogs/route.js index 692d120..763fdef 100644 --- a/app/api/v2/voucherLogs/route.js +++ b/app/api/v2/voucherLogs/route.js @@ -1,16 +1,16 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; export async function GET(req) { + + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } try { @@ -27,28 +27,27 @@ export async function GET(req) { } }); - return NextResponse.json({ voucherLogs: voucherLogs }); + return authCheck.verify(NextResponse.json({ voucherLogs: voucherLogs })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } export async function POST(req) { + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + .requireParams(["loggedFor", "amount", "description", "semesterId"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + const args = await req.json() - if ( - !(args.hasOwnProperty("loggedFor") && - args.hasOwnProperty("amount") && - args.hasOwnProperty("description") && - args.hasOwnProperty("semesterId")) - ) return NextResponse.json({error: "Malformed request"}, {status: 400}) - let res = await prisma.voucherLog.create({ data: { loggedFor: args.loggedFor, @@ -59,7 +58,7 @@ export async function POST(req) { }) if (res) { - return NextResponse.json({status: 200}) + return authCheck.verify(NextResponse.json({status: 200})) } } \ No newline at end of file diff --git a/app/api/v2/workGroups/route.js b/app/api/v2/workGroups/route.js index 4cbfa57..42bdba3 100644 --- a/app/api/v2/workGroups/route.js +++ b/app/api/v2/workGroups/route.js @@ -1,43 +1,44 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; export async function GET(req) { - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { const groups = await prisma.WorkGroup.findMany(); - return NextResponse.json({ groups: groups }); + return authCheck.verify(NextResponse.json({ groups: groups })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } export async function POST(req) { - const args = await req.json() + + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + .requireParams(["userId", "workGroupId"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) - if (!( - args.hasOwnProperty("userId") && - args.hasOwnProperty("workGroupId") - )) return NextResponse.json({error: "Malformed request"}, {status: 400}) - const userId = args.userId - const workGroupId = args.workGroupId + const args = await req.json() + + const { userId, workGroupId } = args; try { const res = await prisma.userToWorkGroup.create({ @@ -48,11 +49,11 @@ export async function POST(req) { }) if (res) - return NextResponse.json({status: 200}) + return authCheck.verify(NextResponse.json({status: 200})) } catch (error) { console.log("User to workgroup already exist in database") - return NextResponse.json({status: 200}) + return authCheck.verify(NextResponse.json({status: 200})) } } \ No newline at end of file diff --git a/app/api/v2/workLogs/route.js b/app/api/v2/workLogs/route.js index cb1bd1f..e6cb3a7 100644 --- a/app/api/v2/workLogs/route.js +++ b/app/api/v2/workLogs/route.js @@ -1,16 +1,14 @@ import { NextResponse } from "next/server"; import prisma from "@/prisma/prismaClient"; +import { Auth } from "../../utils/auth"; export async function GET(req) { - - if (req.method != "GET") { - return NextResponse.json( - { error: `Invalid method '${req.method}'` }, - { status: 405 } - ); - } - + + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) try { const semester = await prisma.Semester.findFirst({select: {id:true}, orderBy: {year: "desc"}}) @@ -27,30 +25,28 @@ export async function GET(req) { } }); - return NextResponse.json({ workLogs: workLogs }); + return authCheck.verify(NextResponse.json({ workLogs: workLogs })); } catch (error) { console.error(error); - return NextResponse.json( + return authCheck.verify(NextResponse.json( { error: `something went wrong: ${error}` }, { status: 500 } - ); + )); } } export async function POST(req) { - const args = await req.json() - if (!( - args.hasOwnProperty("loggedBy") && - args.hasOwnProperty("loggedFor") && - args.hasOwnProperty("workedAt") && - args.hasOwnProperty("duration") && - args.hasOwnProperty("description") && - args.hasOwnProperty("semesterId") - )) return NextResponse.json({error: "Malformed request"}, {status: 400}) + const authCheck = await new Auth(req) + .requireRoles(["intern"]) + .requireParams(["loggedBy", "loggedFor", "workedAt", "duration", "description", "semesterId"]) + + if (authCheck.failed) return authCheck.verify(authCheck.response) + + const args = await req.json() const res = await prisma.workLog.create({ data: { @@ -64,5 +60,5 @@ export async function POST(req) { }) if (res) - return NextResponse.json({status: 200}) + return authCheck.verify(NextResponse.json({status: 200})) } \ No newline at end of file From e40ccd61cbbfce8f1f7b59e073c725522390a001 Mon Sep 17 00:00:00 2001 From: Sebbben Date: Fri, 8 Nov 2024 09:15:40 +0100 Subject: [PATCH 45/65] workaround for chaining with async functions not working properly --- app/api/utils/auth.js | 25 +++++++++++++++++---- app/api/v2/auth/[...nextauth]/route.js | 1 + app/api/v2/memberships/route.js | 12 +++++----- app/api/v2/recruitGraph/route.js | 4 ++-- app/api/v2/roles/route.js | 4 ++-- app/api/v2/semester/route.js | 4 ++-- app/api/v2/semesterVolunteerInfo/route.js | 4 ++-- app/api/v2/shifts/route.js | 10 ++++----- app/api/v2/users/[userID]/[action]/route.js | 11 ++++----- app/api/v2/users/[userID]/route.js | 25 ++++++++++----------- app/api/v2/users/route.js | 8 +++---- app/api/v2/voucherLogs/route.js | 10 ++++----- app/api/v2/workGroups/route.js | 10 ++++----- app/api/v2/workLogs/route.js | 10 ++++----- 14 files changed, 79 insertions(+), 59 deletions(-) diff --git a/app/api/utils/auth.js b/app/api/utils/auth.js index f595f21..648d613 100644 --- a/app/api/utils/auth.js +++ b/app/api/utils/auth.js @@ -38,8 +38,9 @@ export class Auth { } // Count the how many of the required roles the user has, if equal to length of requiredRoles, the user has all roles - if (requiredRoles.reduce((sum,role) => sum+this.session.user.roles.includes(role), 0) === requiredRoles.length) + if (requiredRoles.every(role => this.session.user.roles.includes(role))) { return this + } this.response = NOT_AUTHORIZED this.failed = true @@ -52,9 +53,11 @@ export class Auth { * @returns {Auth} */ async requireParams(params) { - const reqParams = await this.req.json() - for (const reqParam of reqParams){ - if (!params.contains(reqParam)){ + if (this.failed) return this + + const givenParams = await this.req.json() + for (const reqParam of params){ + if (!(reqParam in givenParams)){ this.response = MISSING_PARAMS this.failed = true break @@ -63,6 +66,20 @@ export class Auth { return this } + + async requireOwnership(owner) { + if (this.failed) return this + + this.session ??= await getServerSession(authOptions) + + if (this.session.user.id !== owner) { + this.failed = true + this.response = NOT_AUTHORIZED + } + + return this + } + /** * * @param {NextResponse} res diff --git a/app/api/v2/auth/[...nextauth]/route.js b/app/api/v2/auth/[...nextauth]/route.js index f810e8d..0335384 100644 --- a/app/api/v2/auth/[...nextauth]/route.js +++ b/app/api/v2/auth/[...nextauth]/route.js @@ -45,6 +45,7 @@ export const authOptions = { email: session.user.email, }, select: { + id: true, firstName: true, lastName: true, recruitedByUser: true, diff --git a/app/api/v2/memberships/route.js b/app/api/v2/memberships/route.js index 9356013..b913720 100644 --- a/app/api/v2/memberships/route.js +++ b/app/api/v2/memberships/route.js @@ -6,8 +6,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.response @@ -38,12 +38,14 @@ export async function GET(req) { export async function POST(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) - .requireParams(["name", "email", "comments", "seller_id", "semester_id"]) + const authCheck = new Auth(req.clone()) + authCheck.requireRoles(["intern"]) + authCheck.requireParams(["name", "email", "comments", "seller_id", "semester_id"]) if (authCheck.failed) return authCheck.response + const args = await req.json() + const res = await prisma.UserMembership.create({ data: { name: args.name, diff --git a/app/api/v2/recruitGraph/route.js b/app/api/v2/recruitGraph/route.js index 0cc0c0c..544645b 100644 --- a/app/api/v2/recruitGraph/route.js +++ b/app/api/v2/recruitGraph/route.js @@ -4,8 +4,8 @@ import prisma from "@/prisma/prismaClient"; import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/roles/route.js b/app/api/v2/roles/route.js index bb0adf4..129231c 100644 --- a/app/api/v2/roles/route.js +++ b/app/api/v2/roles/route.js @@ -5,8 +5,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles([]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles([]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/semester/route.js b/app/api/v2/semester/route.js index c263a2b..af09fb5 100644 --- a/app/api/v2/semester/route.js +++ b/app/api/v2/semester/route.js @@ -6,8 +6,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles([]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles([]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/semesterVolunteerInfo/route.js b/app/api/v2/semesterVolunteerInfo/route.js index 856423e..526e2b4 100644 --- a/app/api/v2/semesterVolunteerInfo/route.js +++ b/app/api/v2/semesterVolunteerInfo/route.js @@ -8,8 +8,8 @@ const VOUCHER_MODIFIER = 0.5 export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles([]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles([]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/shifts/route.js b/app/api/v2/shifts/route.js index efe5fca..ad3efe1 100644 --- a/app/api/v2/shifts/route.js +++ b/app/api/v2/shifts/route.js @@ -7,8 +7,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) @@ -40,9 +40,9 @@ export async function GET(req) { export async function POST(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) - .requireParams(["selectedDay", "shiftManagerId", "shiftWorker1Id", "shiftWorker2Id"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) + authCheck.requireParams(["selectedDay", "shiftManagerId", "shiftWorker1Id", "shiftWorker2Id"]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/users/[userID]/[action]/route.js b/app/api/v2/users/[userID]/[action]/route.js index c94633f..5f7913e 100644 --- a/app/api/v2/users/[userID]/[action]/route.js +++ b/app/api/v2/users/[userID]/[action]/route.js @@ -5,8 +5,8 @@ import { Auth } from "@/app/api/utils/auth"; export async function POST(req, {params}) { - const authCheck = await new Auth(req) - .requireRoles(["admin"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["admin"]) if (authCheck.failed) return authCheck.verify(authCheck.response) @@ -24,13 +24,14 @@ export async function POST(req, {params}) { } export async function GET(req, {params}) { + const {userID, action} = await params - const authCheck = await new Auth(req) - .requireRoles([]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles([]) + authCheck.requireOwnership(userID) if (authCheck.failed) return authCheck.verify(authCheck.response) - const {userID, action} = await params if (userID) { let res; diff --git a/app/api/v2/users/[userID]/route.js b/app/api/v2/users/[userID]/route.js index 465d794..503c9c2 100644 --- a/app/api/v2/users/[userID]/route.js +++ b/app/api/v2/users/[userID]/route.js @@ -5,28 +5,27 @@ import { Auth } from "@/app/api/utils/auth"; export async function GET(req, {params}) { - - const authCheck = await new Auth(req) - .requireRoles([]) - - if (authCheck.failed) return authCheck.verify(authCheck.response) + await params + const userID = params.userID + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles([]) - await params - const userID = params.userID - + authCheckauthCheck.requireOwnership(userID) + + if (authCheck.failed) return authCheck.verify(authCheck.response) - return authCheck.verify(NextResponse.json( - { error: `${userID}` }, - { status: 200 } + return authCheck.verify(NextResponse.json( + { error: `${userID}` }, + { status: 200 } )); } export async function PATCH(req, {params}) { - const authCheck = await new Auth(req) - .requireRoles([]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles([]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/users/route.js b/app/api/v2/users/route.js index 2eb94bc..c93abff 100644 --- a/app/api/v2/users/route.js +++ b/app/api/v2/users/route.js @@ -82,8 +82,8 @@ async function registerUser(email, firstName, lastName) { } export async function POST(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) @@ -96,8 +96,8 @@ export async function POST(req) { export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/voucherLogs/route.js b/app/api/v2/voucherLogs/route.js index 763fdef..5fdcb35 100644 --- a/app/api/v2/voucherLogs/route.js +++ b/app/api/v2/voucherLogs/route.js @@ -6,8 +6,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) @@ -40,9 +40,9 @@ export async function GET(req) { } export async function POST(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) - .requireParams(["loggedFor", "amount", "description", "semesterId"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) + authCheck.requireParams(["loggedFor", "amount", "description", "semesterId"]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/workGroups/route.js b/app/api/v2/workGroups/route.js index 42bdba3..aa799f6 100644 --- a/app/api/v2/workGroups/route.js +++ b/app/api/v2/workGroups/route.js @@ -5,8 +5,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) @@ -29,9 +29,9 @@ export async function GET(req) { export async function POST(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) - .requireParams(["userId", "workGroupId"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) + authCheck.requireParams(["userId", "workGroupId"]) if (authCheck.failed) return authCheck.verify(authCheck.response) diff --git a/app/api/v2/workLogs/route.js b/app/api/v2/workLogs/route.js index e6cb3a7..7f72ec5 100644 --- a/app/api/v2/workLogs/route.js +++ b/app/api/v2/workLogs/route.js @@ -5,8 +5,8 @@ import { Auth } from "../../utils/auth"; export async function GET(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) if (authCheck.failed) return authCheck.verify(authCheck.response) @@ -40,9 +40,9 @@ export async function GET(req) { export async function POST(req) { - const authCheck = await new Auth(req) - .requireRoles(["intern"]) - .requireParams(["loggedBy", "loggedFor", "workedAt", "duration", "description", "semesterId"]) + const authCheck = await new Auth(req.clone()) + authCheck.requireRoles(["intern"]) + authCheck.requireParams(["loggedBy", "loggedFor", "workedAt", "duration", "description", "semesterId"]) if (authCheck.failed) return authCheck.verify(authCheck.response) From 22a378d1d39350397ecc7d7d749f52d32afe711e Mon Sep 17 00:00:00 2001 From: Sebbben Date: Fri, 8 Nov 2024 09:17:15 +0100 Subject: [PATCH 46/65] fixed roles not showing on profile page --- app/(pages)/(main)/profile/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(pages)/(main)/profile/page.js b/app/(pages)/(main)/profile/page.js index d33c238..adc4e2e 100644 --- a/app/(pages)/(main)/profile/page.js +++ b/app/(pages)/(main)/profile/page.js @@ -42,7 +42,7 @@ function ProfilePage() { ); const email = session.data.user.email; - const userRoles = session.data.user.roles.map((e) => e.name); + const userRoles = session.data.user.roles; const recruiter = session.data.user.recruitedByUser; const usersRecruited = session.data.user.recruitedUsers?.length; From 35962f6d3a8a9cc96e0c94ffc47b6f7c37f7a0cf Mon Sep 17 00:00:00 2001 From: Sebbben Date: Fri, 8 Nov 2024 09:19:20 +0100 Subject: [PATCH 47/65] fixed some roles but that was introduced with slimming of session object --- app/(pages)/(main)/profile/page.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(pages)/(main)/profile/page.js b/app/(pages)/(main)/profile/page.js index adc4e2e..f8777fe 100644 --- a/app/(pages)/(main)/profile/page.js +++ b/app/(pages)/(main)/profile/page.js @@ -276,7 +276,7 @@ function section3(props) { } const AdminRedirectButton = (session, router, buttonProps) => { - const userRoles = session.data.user.roles.map((e) => e.name) + const userRoles = session.data.user.roles; if (!userRoles.includes("admin")) return return (