From 4fa733754fbb2c4b3100937a2a6417bdc4295e73 Mon Sep 17 00:00:00 2001 From: marcin Date: Tue, 8 Apr 2025 08:07:00 -0500 Subject: [PATCH 1/2] complete assessnment --- api.ts | 1 + app/api/grades/route.tsx | 82 ++ app/api/numbers/route.tsx | 62 ++ app/grades/page.tsx | 197 ++++ app/numbers/page.tsx | 166 ++++ app/page.tsx | 120 +-- components/Navbar.tsx | 135 +++ lib/prisma.ts | 6 + next.config.ts | 18 +- package.json | 13 +- pnpm-lock.yaml | 859 +++++++++++++++++- .../20250408064336_init/migration.sql | 7 + .../migration.sql | 12 + .../migration.sql | 11 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 26 + 16 files changed, 1599 insertions(+), 119 deletions(-) create mode 100644 api.ts create mode 100644 app/api/grades/route.tsx create mode 100644 app/api/numbers/route.tsx create mode 100644 app/grades/page.tsx create mode 100644 app/numbers/page.tsx create mode 100644 components/Navbar.tsx create mode 100644 lib/prisma.ts create mode 100644 prisma/migrations/20250408064336_init/migration.sql create mode 100644 prisma/migrations/20250408070012_change_primary_key/migration.sql create mode 100644 prisma/migrations/20250408084120_added_grade_schema/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma diff --git a/api.ts b/api.ts new file mode 100644 index 0000000..afb9e04 --- /dev/null +++ b/api.ts @@ -0,0 +1 @@ +export const BASE_URL="http://localhost:3000/" \ No newline at end of file diff --git a/app/api/grades/route.tsx b/app/api/grades/route.tsx new file mode 100644 index 0000000..30f5d0e --- /dev/null +++ b/app/api/grades/route.tsx @@ -0,0 +1,82 @@ +import prisma from "@/lib/prisma"; +import { NextResponse } from "next/server"; + + +export const GET = async (request: Request) => { + try { + const { searchParams } = new URL(request.url); + const filter = searchParams.get("filter"); + + let result; + + switch (filter) { + case "averages": + result = await prisma.$queryRaw` + SELECT class, ROUND(AVG(grade), 2) as grade + FROM "Grades" + GROUP BY class + `; + break; + case "passing": + result = await prisma.$queryRaw` + SELECT class, ROUND(AVG(grade), 2) as grade + FROM "Grades" + GROUP BY class + HAVING AVG(grade) > 55 + `; + break; + case "highperforming": + result = await prisma.$queryRaw` + SELECT class, ROUND(AVG(grade), 2) as grade + FROM "Grades" + GROUP BY class + HAVING AVG(grade) > 70 + `; + break; + case "all": + default: + result = await prisma.$queryRaw` + SELECT * FROM "Grades" + `; + break; + } + + return NextResponse.json(result, { status: 200 }); + } catch (err) { + return NextResponse.json( + { message: "Failed to fetch grades", error: String(err) }, + { status: 500 } + ); + } +}; + + +export const POST = async (request: Request) => { + try { + const body = await request.json(); + const { sub, grade } = body; + + const createdGrade = { + class: sub, + grade: grade, + } + await prisma.$queryRaw` + INSERT INTO "Grades" (class, grade) + VALUES (${sub}::"Class", ${grade})` + + return new NextResponse( + JSON.stringify({ message: "Grade is created", createdGrade }), + { status: 201 } + ); + } catch (error) { + return new NextResponse( + JSON.stringify({ + message: "Error in creating grade", + error: error instanceof Error ? error.message : error, + }), + { + status: 500, + } + ); + } +}; diff --git a/app/api/numbers/route.tsx b/app/api/numbers/route.tsx new file mode 100644 index 0000000..ee6fa93 --- /dev/null +++ b/app/api/numbers/route.tsx @@ -0,0 +1,62 @@ +import prisma from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export const GET = async () => { + try { + const getAllNubers: any = await prisma.$queryRaw`select * from "Numbers" n` + console.log(typeof getAllNubers, "getAllNubers") + + const resultArray: any = [] + + getAllNubers.forEach((item: any, index: any) => { + resultArray.push( + { + ID1: item.id, + number1: item.value, + ID2: getAllNubers[index + 1] && getAllNubers[index + 1]['id'] ? getAllNubers[index + 1]['id'] : null, + number2: getAllNubers[index + 1] && getAllNubers[index + 1]['value'] ? getAllNubers[index + 1]['value'] : null, + sum: item.value + (getAllNubers[index + 1] && getAllNubers[index + 1]['value'] ? getAllNubers[index + 1]['value'] : 0) + } + ) + }); + + + if(resultArray.length > 1) {resultArray.pop()} + + return new NextResponse(JSON.stringify(resultArray), { status: 200 }); + } catch (error) { + return new NextResponse("Error in fetching users" + error, { status: 500 }); + } +}; + +export const POST = async (request: Request) => { + try { + const body = await request.json(); + const number = body['number'] + + const getLatestId: any = await prisma.$queryRaw`select id from "Numbers" n order by id desc limit 1` + console.log(getLatestId, getLatestId) + const latestId = getLatestId.length == 0 ? 1 : getLatestId[0]['id'] + 1 + + console.log(latestId, "latestId") + const createNumber = await prisma.$queryRaw` + INSERT INTO "Numbers" (id, value) + VALUES (${latestId}, ${number}) + `; + + return new NextResponse( + JSON.stringify({ message: "Number is created" }), + { status: 201 } + ); + } catch (error) { + return new NextResponse( + JSON.stringify({ + message: "Error in creating user", + error, + }), + { + status: 500, + } + ); + } +}; \ No newline at end of file diff --git a/app/grades/page.tsx b/app/grades/page.tsx new file mode 100644 index 0000000..ce5a80f --- /dev/null +++ b/app/grades/page.tsx @@ -0,0 +1,197 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { + Box, + CssBaseline, + Toolbar, + Typography, + Button, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Stack, + TextField, + MenuItem, + FormControl, + Select, + InputLabel, + Alert, +} from "@mui/material"; +import Navbar from "@/components/Navbar"; +import { BASE_URL } from "@/api"; + +interface Grade { + id?: number; + class: string; + grade: number; +} + +const Page = () => { + const [grades, setGrades] = useState([]); + const [filter, setFilter] = useState("all"); + + const [newClass, setNewClass] = useState(""); + const [newGrade, setNewGrade] = useState(""); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); + + const classOptions = ["Math", "Science", "History"]; + + const fetchGrades = async (type: string) => { + try { + const res = await fetch(`${BASE_URL}api/grades?filter=${type}`); + const data = await res.json(); + setGrades(data); + setFilter(type); + } catch (err) { + console.error("Failed to fetch grades:", err); + } + }; + + const handleSubmit = async () => { + + + setError(""); + setSuccess(""); + + const numericGrade = Number(newGrade); + + if (!newClass || !classOptions.includes(newClass)) { + setError("Please select a valid class."); + return; + } + + if (newGrade.trim() === "" || isNaN(numericGrade) || numericGrade < 0 || numericGrade > 100) { + setError("Grade must be a number between 0 and 100."); + return; + } + + try { + const res = await fetch(`${BASE_URL}api/grades`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sub: newClass, grade: numericGrade }), + }); + + if (!res.ok) throw new Error("Failed to add grade"); + + setSuccess("Grade added successfully."); + setNewClass(""); + setNewGrade(""); + fetchGrades(filter); + } catch (err) { + setError("Error adding grade. Please try again."); + console.error(err); + } + }; + + useEffect(() => { + fetchGrades("all"); + }, []); + + return ( + + + + + + + + + Add New Grade + + + + + Class + + + + setNewGrade(e.target.value)} + inputProps={{ min: 0, max: 100 }} + sx={{ width: 250 }} + error={!!error && (newGrade.trim() === "" || isNaN(Number(newGrade)) || Number(newGrade) < 0 || Number(newGrade) > 100)} + + /> + + + + + + {error && {error}} + {success && {success}} + + + Welcome to Grades Screen + + + + + + + + + + + + + + + {filter === "all" && ID} + Class + Grade + + + + {grades.map((row, idx) => ( + + {filter === "all" && {row.id}} + {row.class} + {row.grade} + + ))} + {grades.length === 0 && ( + + + No data found + + + )} + +
+
+
+
+ ); +}; + +export default Page; diff --git a/app/numbers/page.tsx b/app/numbers/page.tsx new file mode 100644 index 0000000..793eb4f --- /dev/null +++ b/app/numbers/page.tsx @@ -0,0 +1,166 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { + Box, + CssBaseline, + Toolbar, + Typography, + Button, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Stack, + TextField, + Alert, +} from "@mui/material"; +import Navbar from "@/components/Navbar"; +import { BASE_URL } from "@/api"; + +interface PairData { + ID1: number; + number1: number; + ID2: number; + number2: number; + sum: number; +} + +const Page = () => { + const [pairs, setPairs] = useState([]); + const [inputValue, setInputValue] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [inputError, setInputError] = useState(false); + + const fetchPairs = async () => { + try { + const res = await fetch(`${BASE_URL}api/numbers`); + if (!res.ok) throw new Error("Failed to fetch numbers"); + const data = await res.json(); + setPairs(data); + } catch (err) { + console.error(err); + setError("Could not load numbers from the database."); + } + }; + + + const handleSubmit = async () => { + setError(null); + setInputError(false); + + if (inputValue.trim() === "") { + setInputError(true); + return; + } + + const value = parseInt(inputValue); + if (isNaN(value)) { + setError("Please enter a valid number."); + return; + } + + setLoading(true); + try { + const res = await fetch(`${BASE_URL}api/numbers`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ number: value }), + }); + + if (!res.ok) throw new Error("Failed to save number."); + setInputValue(""); + fetchPairs(); + } catch (err) { + console.error(err); + setError("Error saving the number."); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchPairs(); + }, []); + + return ( + + + + + + + Numbers Page + + + + { + setInputValue(e.target.value); + setInputError(false); + }} + size="small" + required + error={inputError} + helperText={inputError ? "Number is required" : ""} + /> + + + + + {error && {error}} + + + Adjacent Pairs and Their Sums + + + + + + + ID 1 + Number 1 + ID 2 + Number 2 + Sum + + + + {pairs.length === 0 ? ( + + + No data available + + + ) : ( + pairs.map((pair, index) => ( + + {pair?.ID1} + {pair?.number1} + {pair?.ID2} + {pair?.number2} + {pair?.sum} + + )) + )} + +
+
+
+
+ ); +}; + +export default Page; diff --git a/app/page.tsx b/app/page.tsx index e974b4c..f39c0d1 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,97 +1,31 @@ -import Image from "next/image"; -import styles from "./page.module.css"; +import * as React from 'react'; +import Box from '@mui/material/Box'; +import CssBaseline from '@mui/material/CssBaseline'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import Navbar from '@/components/Navbar'; -export default function Home() { +export default function Page() { return ( -
-

Home

- -
- Next.js logo -
    -
  1. - Get started by editing app/page.tsx. -
  2. -
  3. Save and see your changes instantly.
  4. -
- - -
- -
+ + + + + + + Welcome to Full Stack Application + + + ); } diff --git a/components/Navbar.tsx b/components/Navbar.tsx new file mode 100644 index 0000000..3f0fe55 --- /dev/null +++ b/components/Navbar.tsx @@ -0,0 +1,135 @@ +"use client"; +import * as React from 'react'; +import { useRouter, usePathname } from 'next/navigation'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Divider from '@mui/material/Divider'; +import Drawer from '@mui/material/Drawer'; +import IconButton from '@mui/material/IconButton'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import MenuIcon from '@mui/icons-material/Menu'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; + +// interface Props { +// window?: () => Window; +// } + +const drawerWidth = 240; + +const navItems = [ + // { label: 'Home', path: '/' }, + { label: 'Numbers', path: '/numbers' }, + { label: 'Grades', path: '/grades' }, +]; + +const Navbar = () => { + // const { window } = props; + const [mobileOpen, setMobileOpen] = React.useState(false); + const router = useRouter(); + const pathname = usePathname(); + + const handleDrawerToggle = () => { + setMobileOpen((prevState) => !prevState); + }; + + const handleNavigation = (path: string) => { + router.push(path); + setMobileOpen(false); + }; + + const drawer = ( + + handleNavigation('/')} + > + Alison Full Stack Developer Assessment + + + + {navItems.map((item) => ( + + handleNavigation(item.path)} + > + + + + ))} + + + ); + + // const container = window !== undefined ? () => window().document.body : undefined; + + return ( + <> + + + + + + handleNavigation('/')} + sx={{ + flexGrow: 1, + display: { xs: 'none', sm: 'block' }, + cursor: 'pointer', + }} + > + Alison + + + {navItems.map((item) => ( + + ))} + + + + + + ); +}; + +export default Navbar; diff --git a/lib/prisma.ts b/lib/prisma.ts new file mode 100644 index 0000000..5fdd76a --- /dev/null +++ b/lib/prisma.ts @@ -0,0 +1,6 @@ +import { PrismaClient } from "@prisma/client"; + +let prisma: PrismaClient; +prisma = new PrismaClient(); + +export default prisma; diff --git a/next.config.ts b/next.config.ts index e9ffa30..beaf9bb 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,17 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - /* config options here */ +/** @type {import('next').NextConfig} */ +const nextConfig = { + env: { + RAZORPAY_KEY: process.env.DATABASE_URL, + }, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "http://localhost:3000", + pathname: "**", + }, + ], + }, }; export default nextConfig; diff --git a/package.json b/package.json index 500bff1..ec735e1 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,11 @@ "test": "jest" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.1", + "@mui/material": "^7.0.1", + "@prisma/client": "^6.5.0", "next": "15.2.0", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -27,8 +32,14 @@ "eslint-config-next": "15.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "prisma": "^6.5.0", "ts-node": "^10.9.2", "typescript": "^5.8.2" }, - "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b" + "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b", + "pnpm": { + "ignoredBuiltDependencies": [ + "@prisma/client" + ] + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17fb403..52b5a2d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,24 @@ importers: .: dependencies: + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': + specifier: ^11.14.0 + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@mui/icons-material': + specifier: ^7.0.1 + version: 7.0.1(@mui/material@7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@mui/material': + specifier: ^7.0.1 + version: 7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@prisma/client': + specifier: ^6.5.0 + version: 6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2) next: specifier: 15.2.0 - version: 15.2.0(@babel/core@7.26.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.2.0(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 @@ -50,10 +65,13 @@ importers: version: 15.2.0(eslint@9.21.0)(typescript@5.8.2) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + version: 29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 + prisma: + specifier: ^6.5.0 + version: 6.5.0(typescript@5.8.2) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.8)(typescript@5.8.2) @@ -220,6 +238,10 @@ packages: resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.26.9': resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} engines: {node: '>=6.9.0'} @@ -242,6 +264,210 @@ packages: '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.3.1': + resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.0': + resolution: {integrity: sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -496,6 +722,97 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mui/core-downloads-tracker@7.0.1': + resolution: {integrity: sha512-T5DNVnSD9pMbj4Jk/Uphz+yvj9dfpl2+EqsOuJtG12HxEihNG5pd3qzX5yM1Id4dDwKRvM3dPVcxyzavTFhJeA==} + + '@mui/icons-material@7.0.1': + resolution: {integrity: sha512-x8Em7LISFQ6s/KeZj6ZKwJHq2WttRNe9KJLWFa72eQx7B53s/TzMKOEjGKB/YyhOx+bqqSv1pMvK373M4Xf07A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^7.0.1 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material@7.0.1': + resolution: {integrity: sha512-tQwjIIsn/UUSCHoCIQVkANuLua67h7Ro9M9gIHoGWaFbJFuF6cSO4Oda2olDVqIs4SWG+PaDChuu6SngxsaoyQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^7.0.1 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@7.0.1': + resolution: {integrity: sha512-1kQ7REYjjzDukuMfTbAjm3pLEhD7gUMC2bWhg9VD6f6sHzyokKzX0XHzlr3IdzNWBjPytGkzHpPIRQrUOoPLCQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@7.0.1': + resolution: {integrity: sha512-BeGe4xZmF7tESKhmctYrL54Kl25kGHPKVdZYM5qj5Xz76WM/poY+d8EmAqUesT6k2rbJWPp2gtOAXXinNCGunQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@7.0.1': + resolution: {integrity: sha512-pK+puz0hRPHEKGlcPd80mKYD3jpyi0uVIwWffox1WZgPTQMw2dCKLcD+9ndMDJADnrKzmKlpoH756PPFh2UvWA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.4.0': + resolution: {integrity: sha512-TxJ4ezEeedWHBjOmLtxI203a9DII9l4k83RXmz1PYSAmnyEcK2PglTNmJGxswC/wM5cdl9ap2h8lnXvt2swAGQ==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@7.0.1': + resolution: {integrity: sha512-SJKrrebNpmK9rJCnVL29nGPhPXQYtBZmb7Dsp0f58uIUhQfAKcBXHE4Kjs06SX4CwqeCuwEVgcHY+MgAO6XQ/g==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@next/env@15.2.0': resolution: {integrity: sha512-eMgJu1RBXxxqqnuRJQh5RozhskoNUDHBFybvi+Z+yK9qzKeG7dadhv/Vp1YooSZmCnegf7JxWuapV77necLZNA==} @@ -566,6 +883,39 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@prisma/client@6.5.0': + resolution: {integrity: sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.5.0': + resolution: {integrity: sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw==} + + '@prisma/debug@6.5.0': + resolution: {integrity: sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ==} + + '@prisma/engines-version@6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60': + resolution: {integrity: sha512-iK3EmiVGFDCmXjSpdsKGNqy9hOdLnvYBrJB61far/oP03hlIxrb04OWmDjNTwtmZ3UZdA5MCvI+f+3k2jPTflQ==} + + '@prisma/engines@6.5.0': + resolution: {integrity: sha512-FVPQYHgOllJklN9DUyujXvh3hFJCY0NX86sDmBErLvoZjy2OXGiZ5FNf3J/C4/RZZmCypZBYpBKEhx7b7rEsdw==} + + '@prisma/fetch-engine@6.5.0': + resolution: {integrity: sha512-3LhYA+FXP6pqY8FLHCjewyE8pGXXJ7BxZw2rhPq+CZAhvflVzq4K8Qly3OrmOkn6wGlz79nyLQdknyCG2HBTuA==} + + '@prisma/get-platform@6.5.0': + resolution: {integrity: sha512-xYcvyJwNMg2eDptBYFqFLUCfgi+wZLcj6HDMsj0Qw0irvauG4IKmkbywnqwok0B+k+W+p+jThM2DKTSmoPCkzw==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -671,11 +1021,22 @@ packages: '@types/node@22.13.8': resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.14': + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/react-dom@19.0.4': resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} peerDependencies: '@types/react': ^19.0.0 + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' + '@types/react@19.0.10': resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} @@ -870,6 +1231,10 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + babel-preset-current-node-syntax@1.1.0: resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} peerDependencies: @@ -962,6 +1327,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -990,9 +1359,16 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + create-jest@29.7.0: resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1117,6 +1493,9 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} @@ -1182,6 +1561,16 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1371,6 +1760,9 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1504,6 +1896,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-encoding-sniffer@3.0.0: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} @@ -2144,6 +2539,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2183,6 +2582,16 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + prisma@6.5.0: + resolution: {integrity: sha512-yUGXmWqv5F4PByMSNbYFxke/WbnyTLjnJ5bKr8fLkcnY7U5rU9rUTh/+Fja+gOrRxEgtCbCtca94IeITj4j/pg==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -2220,6 +2629,15 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.1.0: + resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -2372,6 +2790,10 @@ packages: source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -2458,6 +2880,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2674,6 +3099,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -2865,6 +3294,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.26.9': dependencies: '@babel/code-frame': 7.26.2 @@ -2899,6 +3332,164 @@ snapshots: tslib: 2.8.1 optional: true + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.26.9 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.3.1': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.9 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.9 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) + '@emotion/utils': 1.4.2 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@esbuild/aix-ppc64@0.25.2': + optional: true + + '@esbuild/android-arm64@0.25.2': + optional: true + + '@esbuild/android-arm@0.25.2': + optional: true + + '@esbuild/android-x64@0.25.2': + optional: true + + '@esbuild/darwin-arm64@0.25.2': + optional: true + + '@esbuild/darwin-x64@0.25.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.2': + optional: true + + '@esbuild/freebsd-x64@0.25.2': + optional: true + + '@esbuild/linux-arm64@0.25.2': + optional: true + + '@esbuild/linux-arm@0.25.2': + optional: true + + '@esbuild/linux-ia32@0.25.2': + optional: true + + '@esbuild/linux-loong64@0.25.2': + optional: true + + '@esbuild/linux-mips64el@0.25.2': + optional: true + + '@esbuild/linux-ppc64@0.25.2': + optional: true + + '@esbuild/linux-riscv64@0.25.2': + optional: true + + '@esbuild/linux-s390x@0.25.2': + optional: true + + '@esbuild/linux-x64@0.25.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.2': + optional: true + + '@esbuild/netbsd-x64@0.25.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.2': + optional: true + + '@esbuild/openbsd-x64@0.25.2': + optional: true + + '@esbuild/sunos-x64@0.25.2': + optional: true + + '@esbuild/win32-arm64@0.25.2': + optional: true + + '@esbuild/win32-ia32@0.25.2': + optional: true + + '@esbuild/win32-x64@0.25.2': + optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0)': dependencies: eslint: 9.21.0 @@ -3048,7 +3639,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -3062,7 +3653,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -3223,6 +3814,93 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@mui/core-downloads-tracker@7.0.1': {} + + '@mui/icons-material@7.0.1(@mui/material@7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/material': 7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + + '@mui/material@7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/core-downloads-tracker': 7.0.1 + '@mui/system': 7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@mui/types': 7.4.0(@types/react@19.0.10) + '@mui/utils': 7.0.1(@types/react@19.0.10)(react@19.0.0) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.12(@types/react@19.0.10) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-is: 19.1.0 + react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@types/react': 19.0.10 + + '@mui/private-theming@7.0.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/utils': 7.0.1(@types/react@19.0.10)(react@19.0.0) + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + + '@mui/styled-engine@7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + + '@mui/system@7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/private-theming': 7.0.1(@types/react@19.0.10)(react@19.0.0) + '@mui/styled-engine': 7.0.1(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0) + '@mui/types': 7.4.0(@types/react@19.0.10) + '@mui/utils': 7.0.1(@types/react@19.0.10)(react@19.0.0) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) + '@types/react': 19.0.10 + + '@mui/types@7.4.0(@types/react@19.0.10)': + dependencies: + '@babel/runtime': 7.27.0 + optionalDependencies: + '@types/react': 19.0.10 + + '@mui/utils@7.0.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/types': 7.4.0(@types/react@19.0.10) + '@types/prop-types': 15.7.14 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-is: 19.1.0 + optionalDependencies: + '@types/react': 19.0.10 + '@next/env@15.2.0': {} '@next/eslint-plugin-next@15.2.0': @@ -3267,6 +3945,41 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@popperjs/core@2.11.8': {} + + '@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)': + optionalDependencies: + prisma: 6.5.0(typescript@5.8.2) + typescript: 5.8.2 + + '@prisma/config@6.5.0': + dependencies: + esbuild: 0.25.2 + esbuild-register: 3.6.0(esbuild@0.25.2) + transitivePeerDependencies: + - supports-color + + '@prisma/debug@6.5.0': {} + + '@prisma/engines-version@6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60': {} + + '@prisma/engines@6.5.0': + dependencies: + '@prisma/debug': 6.5.0 + '@prisma/engines-version': 6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60 + '@prisma/fetch-engine': 6.5.0 + '@prisma/get-platform': 6.5.0 + + '@prisma/fetch-engine@6.5.0': + dependencies: + '@prisma/debug': 6.5.0 + '@prisma/engines-version': 6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60 + '@prisma/get-platform': 6.5.0 + + '@prisma/get-platform@6.5.0': + dependencies: + '@prisma/debug': 6.5.0 + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.10.5': {} @@ -3386,10 +4099,18 @@ snapshots: dependencies: undici-types: 6.20.0 + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.14': {} + '@types/react-dom@19.0.4(@types/react@19.0.10)': dependencies: '@types/react': 19.0.10 + '@types/react-transition-group@4.4.12(@types/react@19.0.10)': + dependencies: + '@types/react': 19.0.10 + '@types/react@19.0.10': dependencies: csstype: 3.1.3 @@ -3650,6 +4371,12 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.26.9 + cosmiconfig: 7.1.0 + resolve: 1.22.10 + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.9): dependencies: '@babel/core': 7.26.9 @@ -3756,6 +4483,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + co@4.6.0: {} collect-v8-coverage@1.0.2: {} @@ -3784,15 +4513,25 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} - create-jest@29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + create-jest@29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -3857,7 +4596,9 @@ snapshots: decimal.js@10.5.0: {} - dedent@1.5.3: {} + dedent@1.5.3(babel-plugin-macros@3.1.0): + optionalDependencies: + babel-plugin-macros: 3.1.0 deep-is@0.1.4: {} @@ -3896,6 +4637,11 @@ snapshots: dom-accessibility-api@0.6.3: {} + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.27.0 + csstype: 3.1.3 + domexception@4.0.0: dependencies: webidl-conversions: 7.0.0 @@ -4023,6 +4769,41 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild-register@3.6.0(esbuild@0.25.2): + dependencies: + debug: 4.4.0 + esbuild: 0.25.2 + transitivePeerDependencies: + - supports-color + + esbuild@0.25.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 + escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -4297,6 +5078,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-root@1.1.0: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -4433,6 +5216,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + html-encoding-sniffer@3.0.0: dependencies: whatwg-encoding: 2.0.0 @@ -4671,7 +5458,7 @@ snapshots: jest-util: 29.7.0 p-limit: 3.1.0 - jest-circus@29.7.0: + jest-circus@29.7.0(babel-plugin-macros@3.1.0): dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 @@ -4680,7 +5467,7 @@ snapshots: '@types/node': 22.13.8 chalk: 4.1.2 co: 4.6.0 - dedent: 1.5.3 + dedent: 1.5.3(babel-plugin-macros@3.1.0) is-generator-fn: 2.1.0 jest-each: 29.7.0 jest-matcher-utils: 29.7.0 @@ -4697,16 +5484,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): + jest-cli@29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + create-jest: 29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -4716,7 +5503,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): + jest-config@29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): dependencies: '@babel/core': 7.26.9 '@jest/test-sequencer': 29.7.0 @@ -4727,7 +5514,7 @@ snapshots: deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 - jest-circus: 29.7.0 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) jest-environment-node: 29.7.0 jest-get-type: 29.6.3 jest-regex-util: 29.6.3 @@ -4977,12 +5764,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): + jest@29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.13.8)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) + jest-cli: 29.7.0(@types/node@22.13.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.8)(typescript@5.8.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -5146,7 +5933,7 @@ snapshots: natural-compare@1.4.0: {} - next@15.2.0(@babel/core@7.26.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.2.0(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.2.0 '@swc/counter': 0.1.3 @@ -5156,7 +5943,7 @@ snapshots: postcss: 8.4.31 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - styled-jsx: 5.1.6(@babel/core@7.26.9)(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@19.0.0) optionalDependencies: '@next/swc-darwin-arm64': 15.2.0 '@next/swc-darwin-x64': 15.2.0 @@ -5288,6 +6075,8 @@ snapshots: path-parse@1.0.7: {} + path-type@4.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5322,6 +6111,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + prisma@6.5.0(typescript@5.8.2): + dependencies: + '@prisma/config': 6.5.0 + '@prisma/engines': 6.5.0 + optionalDependencies: + fsevents: 2.3.3 + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -5356,6 +6155,17 @@ snapshots: react-is@18.3.1: {} + react-is@19.1.0: {} + + react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.27.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react@19.0.0: {} redent@3.0.0: @@ -5551,6 +6361,8 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map@0.5.7: {} + source-map@0.6.1: {} sprintf-js@1.0.3: {} @@ -5640,12 +6452,15 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.26.9)(react@19.0.0): + styled-jsx@5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@19.0.0): dependencies: client-only: 0.0.1 react: 19.0.0 optionalDependencies: '@babel/core': 7.26.9 + babel-plugin-macros: 3.1.0 + + stylis@4.2.0: {} supports-color@7.2.0: dependencies: @@ -5887,6 +6702,8 @@ snapshots: yallist@3.1.1: {} + yaml@1.10.2: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/prisma/migrations/20250408064336_init/migration.sql b/prisma/migrations/20250408064336_init/migration.sql new file mode 100644 index 0000000..89b7576 --- /dev/null +++ b/prisma/migrations/20250408064336_init/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "Numbers" ( + "id" TEXT NOT NULL, + "value" INTEGER NOT NULL, + + CONSTRAINT "Numbers_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/migrations/20250408070012_change_primary_key/migration.sql b/prisma/migrations/20250408070012_change_primary_key/migration.sql new file mode 100644 index 0000000..1582cfa --- /dev/null +++ b/prisma/migrations/20250408070012_change_primary_key/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - The primary key for the `Numbers` table will be changed. If it partially fails, the table could be left without primary key constraint. + - The `id` column on the `Numbers` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- AlterTable +ALTER TABLE "Numbers" DROP CONSTRAINT "Numbers_pkey", +DROP COLUMN "id", +ADD COLUMN "id" SERIAL NOT NULL, +ADD CONSTRAINT "Numbers_pkey" PRIMARY KEY ("id"); diff --git a/prisma/migrations/20250408084120_added_grade_schema/migration.sql b/prisma/migrations/20250408084120_added_grade_schema/migration.sql new file mode 100644 index 0000000..2e672e6 --- /dev/null +++ b/prisma/migrations/20250408084120_added_grade_schema/migration.sql @@ -0,0 +1,11 @@ +-- CreateEnum +CREATE TYPE "Class" AS ENUM ('Math', 'Science', 'History'); + +-- CreateTable +CREATE TABLE "Grades" ( + "id" SERIAL NOT NULL, + "class" "Class" NOT NULL, + "grade" INTEGER NOT NULL, + + CONSTRAINT "Grades_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..648c57f --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..dd24523 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,26 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = process.env.DATABASE_URL +} + +model Numbers { + id Int @id @default(autoincrement()) + value Int +} + +model Grades { + id Int @id @default(autoincrement()) + class Class + grade Int +} + +enum Class { + Math + Science + History +} + From eb28060f847c54fad38e81e77eaf10780605659e Mon Sep 17 00:00:00 2001 From: marcin Date: Wed, 9 Apr 2025 08:26:20 -0500 Subject: [PATCH 2/2] Add test cases --- __tests__/Grades.test.tsx | 65 + __tests__/Numbers.test.tsx | 42 + app/grades/page.tsx | 21 +- components/Navbar.tsx | 10 +- jest.config.ts | 5 +- jest.setup.ts | 3 + package.json | 11 +- pnpm-lock.yaml | 1589 ++++++++++++++++--------- prisma/migrations/migration_lock.toml | 7 +- prisma/schema.prisma | 4 +- 10 files changed, 1138 insertions(+), 619 deletions(-) create mode 100644 __tests__/Grades.test.tsx create mode 100644 __tests__/Numbers.test.tsx diff --git a/__tests__/Grades.test.tsx b/__tests__/Grades.test.tsx new file mode 100644 index 0000000..d0b6eca --- /dev/null +++ b/__tests__/Grades.test.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import Page from '../app/grades/page'; + +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + usePathname: jest.fn(), +})); + +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve([]), + }) +) as jest.Mock; + +describe('Grades Page', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders the grade form and all buttons', async () => { + await act(async () => { + render(); + }); + + expect(screen.getByRole('button', { name: /show all data/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /class averages/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /passing average/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /high performing classes/i })).toBeInTheDocument(); + }); + + it('shows "No data found" when no grades are returned', async () => { + await act(async () => { + render(); + }); + + await waitFor(() => { + expect(screen.getByText('No data found')).toBeInTheDocument(); + }); + }); + + it('shows error when submitting with empty fields', async () => { + await act(async () => { + render(); + }); + + fireEvent.click(screen.getByRole('button', { name: /submit/i })); + + await waitFor(() => { + expect(screen.getByText('Please select a valid class.')).toBeInTheDocument(); + }); + }); + + it('allows typing a valid grade value', async () => { + await act(async () => { + render(); + }); + + const gradeInput = screen.getByLabelText('Grade') as HTMLInputElement; + fireEvent.change(gradeInput, { target: { value: '85' } }); + + expect(gradeInput.value).toBe('85'); + }); +}); diff --git a/__tests__/Numbers.test.tsx b/__tests__/Numbers.test.tsx new file mode 100644 index 0000000..f09ba3d --- /dev/null +++ b/__tests__/Numbers.test.tsx @@ -0,0 +1,42 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import Page from '../app/numbers/page'; + + +jest.mock('next/navigation', () => ({ + useRouter: () => ({ + push: jest.fn(), + replace: jest.fn(), + prefetch: jest.fn(), + back: jest.fn(), + }), + usePathname: () => '/mock-path', +})); + +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve([]), + }) +) as jest.Mock; + +describe('Page Component', () => { + it('renders the Numbers Page title and shows "No data available" when no pairs exist', async () => { + render(); + + await waitFor(() => { + expect(screen.getByText('Numbers Page')).toBeInTheDocument(); + }); + + expect(screen.getByText('Adjacent Pairs and Their Sums')).toBeInTheDocument(); + expect(screen.getByText('No data available')).toBeInTheDocument(); + }); + + it('shows error when submitting empty input', async () => { + render(); + + const submitButton = screen.getByRole('button', { name: /Submit/i }); + fireEvent.click(submitButton); + + expect(await screen.findByText('Number is required')).toBeInTheDocument(); + }); +}); diff --git a/app/grades/page.tsx b/app/grades/page.tsx index ce5a80f..6195aea 100644 --- a/app/grades/page.tsx +++ b/app/grades/page.tsx @@ -1,5 +1,4 @@ "use client"; - import React, { useEffect, useState } from "react"; import { Box, @@ -45,7 +44,7 @@ const Page = () => { const fetchGrades = async (type: string) => { try { const res = await fetch(`${BASE_URL}api/grades?filter=${type}`); - const data = await res.json(); + const data = await res?.json(); setGrades(data); setFilter(type); } catch (err) { @@ -54,14 +53,10 @@ const Page = () => { }; const handleSubmit = async () => { - - setError(""); setSuccess(""); - const numericGrade = Number(newGrade); - - if (!newClass || !classOptions.includes(newClass)) { + if (!newClass || !classOptions?.includes(newClass)) { setError("Please select a valid class."); return; } @@ -78,7 +73,7 @@ const Page = () => { body: JSON.stringify({ sub: newClass, grade: numericGrade }), }); - if (!res.ok) throw new Error("Failed to add grade"); + if (!res?.ok) throw new Error("Failed to add grade"); setSuccess("Grade added successfully."); setNewClass(""); @@ -129,9 +124,7 @@ const Page = () => { onChange={(e) => setNewGrade(e.target.value)} inputProps={{ min: 0, max: 100 }} sx={{ width: 250 }} - error={!!error && (newGrade.trim() === "" || isNaN(Number(newGrade)) || Number(newGrade) < 0 || Number(newGrade) > 100)} - - /> + error={!!error && (newGrade.trim() === "" || isNaN(Number(newGrade)) || Number(newGrade) < 0 || Number(newGrade) > 100)} />