Merge pull request #8 from Fayorg/dev
Using NextAuth for authentication and Server actions
This commit is contained in:
commit
0a6313ed00
|
@ -34,7 +34,7 @@ npm run start
|
||||||
|
|
||||||
Designed & made with love by [Elie Baier](https://github.com/fayorg), [Tim Haller](https://github.com/timhaller) & [Michal Polka](https://github.com/michalpolka).
|
Designed & made with love by [Elie Baier](https://github.com/fayorg), [Tim Haller](https://github.com/timhaller) & [Michal Polka](https://github.com/michalpolka).
|
||||||
|
|
||||||
We'd like to thanks Ms. Tixhon for the good idea and the great gifts!
|
We'd like to thanks Mrs. Tixhon for the good idea and the great gifts!
|
||||||
|
|
||||||
Don't forget : FUCK MATH!
|
Don't forget : FUCK MATH!
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export async function getGrade(testId: number, userId: number) {
|
||||||
|
const grade = await prisma.grade.findFirst({
|
||||||
|
select: {
|
||||||
|
grade: true,
|
||||||
|
id: true,
|
||||||
|
testId: true,
|
||||||
|
note: true,
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
testId: testId,
|
||||||
|
userId: userId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return grade;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addGrade(testId: number, userId: number, grade: number, note: string | null) {
|
||||||
|
const newGrade = await prisma.grade.create({
|
||||||
|
data: {
|
||||||
|
grade: grade,
|
||||||
|
note: note,
|
||||||
|
testId: testId,
|
||||||
|
userId: userId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newGrade;
|
||||||
|
}
|
|
@ -10,3 +10,65 @@ export async function setTestActive(id: number, active: boolean) {
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getActiveTest(date: Date) {
|
||||||
|
return await prisma.test.findFirst({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isActive: true,
|
||||||
|
isPassed: true,
|
||||||
|
testOf: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
isTeacher: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: true,
|
||||||
|
testOn: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
isPassed: false,
|
||||||
|
testOn: new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + (date.getDate().toString().length === 1 ? '0' + date.getDate() : date.getDate())),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getActiveTestWithGrade(date: Date, userId: number) {
|
||||||
|
return await prisma.test.findFirst({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isActive: true,
|
||||||
|
isPassed: true,
|
||||||
|
testOf: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
isTeacher: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grades: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
grade: true,
|
||||||
|
note: true,
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
take: 1,
|
||||||
|
where: {
|
||||||
|
userId: userId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createdAt: true,
|
||||||
|
testOn: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
isPassed: false,
|
||||||
|
testOn: new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + (date.getDate().toString().length === 1 ? '0' + date.getDate() : date.getDate())),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import NextAuth from "next-auth";
|
||||||
|
|
||||||
|
import { authOptions } from "@lib/authenticate";
|
||||||
|
|
||||||
|
const handler = NextAuth(authOptions);
|
||||||
|
export { handler as GET, handler as POST };
|
|
@ -1,93 +0,0 @@
|
||||||
import {NextRequest, NextResponse} from "next/server";
|
|
||||||
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
interface IBody {
|
|
||||||
key: string
|
|
||||||
grade: number
|
|
||||||
testId: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: Request){
|
|
||||||
const body: IBody = await req.json();
|
|
||||||
|
|
||||||
const test = await prisma.test.findFirst({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
testOn: true,
|
|
||||||
testOf: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
isTeacher: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grades: {
|
|
||||||
select: {
|
|
||||||
note: true,
|
|
||||||
grade: true,
|
|
||||||
createdAt: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
user: {
|
|
||||||
key: body.key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
id: body.testId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!test){
|
|
||||||
return NextResponse.json({error: "Test not found"}, {status: 404});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(test.grades.length > 0){
|
|
||||||
return NextResponse.json({error: "You have already voted"}, {status: 403});
|
|
||||||
}
|
|
||||||
|
|
||||||
const grade = await prisma.grade.create({
|
|
||||||
data: {
|
|
||||||
note: "",
|
|
||||||
grade: body.grade,
|
|
||||||
user: {
|
|
||||||
connect: {
|
|
||||||
key: body.key
|
|
||||||
}
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
connect: {
|
|
||||||
id: test.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
id: test.id,
|
|
||||||
testOn: test.testOn,
|
|
||||||
testOf: {
|
|
||||||
firstName: test.testOf.firstName,
|
|
||||||
lastName: test.testOf.lastName,
|
|
||||||
isTeacher: test.testOf.isTeacher
|
|
||||||
},
|
|
||||||
vote: {
|
|
||||||
hasVoted: test.grades?.length > 0,
|
|
||||||
grade: grade.grade,
|
|
||||||
note: grade.note,
|
|
||||||
createdAt: grade.createdAt
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest){
|
|
||||||
const key = req.nextUrl.searchParams.get("key");
|
|
||||||
|
|
||||||
if(!key) return NextResponse.json({error: "No key provided"}, {status: 400});
|
|
||||||
|
|
||||||
const grades = await prisma.grade.findMany({})
|
|
||||||
|
|
||||||
return NextResponse.json(grades, {status: 200});
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
import {NextRequest, NextResponse} from "next/server";
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest){
|
|
||||||
const key = req.nextUrl.searchParams.get("key");
|
|
||||||
|
|
||||||
if(!key) return NextResponse.json({error: "No key provided"}, {status: 400});
|
|
||||||
|
|
||||||
const user = await prisma.users.findUnique({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
isTeacher: true,
|
|
||||||
test: {
|
|
||||||
select: {
|
|
||||||
testOn: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
key
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!user) return NextResponse.json({error: "Key not found"}, {status: 404});
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
id: user.id,
|
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
|
||||||
isTeacher: user.isTeacher,
|
|
||||||
testOn: user.test?.testOn
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
import {NextRequest, NextResponse} from "next/server";
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest){
|
|
||||||
const date = req.nextUrl.searchParams.get("date");
|
|
||||||
const key = req.nextUrl.searchParams.get("key");
|
|
||||||
|
|
||||||
const usableDate = new Date(date || new Date());
|
|
||||||
|
|
||||||
const test = await prisma.test.findFirst({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
testOn: true,
|
|
||||||
testOf: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
isTeacher: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grades: {
|
|
||||||
select: {
|
|
||||||
note: true,
|
|
||||||
grade: true,
|
|
||||||
createdAt: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
user: {
|
|
||||||
key: key || ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
testOn: new Date(usableDate.getFullYear() + "-" + (usableDate.getMonth() + 1) + "-" + (usableDate.getDate().toString().length === 1 ? "0" + usableDate.getDate() : usableDate.getDate()))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!test){
|
|
||||||
return NextResponse.json({error: "Test not found"}, {status: 404});
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
id: test.id,
|
|
||||||
testOn: test.testOn,
|
|
||||||
testOf: {
|
|
||||||
firstName: test.testOf.firstName,
|
|
||||||
lastName: test.testOf.lastName,
|
|
||||||
isTeacher: test.testOf.isTeacher
|
|
||||||
},
|
|
||||||
vote: {
|
|
||||||
hasVoted: test.grades?.length > 0,
|
|
||||||
grade: test.grades[0]?.grade,
|
|
||||||
note: test.grades[0]?.note,
|
|
||||||
createdAt: test.grades[0]?.createdAt
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,7 +1,14 @@
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
import ActiveCard from './ActiveCard';
|
import ActiveCard from './ActiveCard';
|
||||||
|
import { getAuthServerSession } from '@/lib/authenticate';
|
||||||
|
import { red } from 'next/dist/lib/picocolors';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
export default async function Dashboard() {
|
export default async function Dashboard() {
|
||||||
|
const authSession = await getAuthServerSession();
|
||||||
|
console.log(authSession);
|
||||||
|
if (!authSession || !authSession.user.isTeacher) return redirect('/');
|
||||||
|
|
||||||
const tests = await prisma.test.findMany({ select: { isActive: true, isPassed: true, id: true, testOf: { select: { id: true, firstName: true, lastName: true, isTeacher: true } } } });
|
const tests = await prisma.test.findMany({ select: { isActive: true, isPassed: true, id: true, testOf: { select: { id: true, firstName: true, lastName: true, isTeacher: true } } } });
|
||||||
|
|
||||||
const activeTests = tests.filter((test) => test.isActive);
|
const activeTests = tests.filter((test) => test.isActive);
|
||||||
|
|
44
app/page.tsx
44
app/page.tsx
|
@ -1,42 +1,20 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { Poppins } from 'next/font/google'
|
|
||||||
import { Check } from 'lucide-react'
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
import logo from '../images/logo.svg';
|
import logo from '../images/logo.svg';
|
||||||
import Image from "next/image";
|
import Image from 'next/image';
|
||||||
import {Input} from "@components/ui/input";
|
import { LoginForm } from '@/components/forms/LoginForm';
|
||||||
import {white} from "next/dist/lib/picocolors";
|
import { getAuthServerSession } from '@/lib/authenticate';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
export default function Home() {
|
export default async function Home() {
|
||||||
const [password, setPassword] = useState('');
|
const session = await getAuthServerSession();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (session != null) {
|
||||||
if (localStorage.getItem('@password')) {
|
redirect('/play');
|
||||||
router.push('/play');
|
|
||||||
}
|
|
||||||
}, [router]);
|
|
||||||
|
|
||||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
localStorage.setItem('@password', password);
|
|
||||||
router.push('/play');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"w-full h-screen text-[#F0F0F0] bg-black p-12 flex flex-col items-center justify-center gap-y-28"}>
|
<div className={'w-full h-screen text-[#F0F0F0] bg-black p-12 flex flex-col items-center justify-center gap-y-28'}>
|
||||||
<Image src={logo} alt={"Logo"} width={100} height={200} className={"mx-auto w-full md:w-[400px]"}/>
|
<Image src={logo} alt={'Logo'} width={100} height={200} className={'mx-auto w-full md:w-[400px]'} />
|
||||||
<form onSubmit={handleSubmit} className={"flex flex-row gap-4 w-full md:w-[400px]"}>
|
<LoginForm />
|
||||||
<Input type="password" placeholder="Mot de passe" id="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)}></Input>
|
|
||||||
<input type="submit" value="Submit" id="submit" className={"hidden"} />
|
|
||||||
<label htmlFor="submit" className={"w-[54px] h-[54px] bg-secondary rounded-[20px] contents-none grid place-content-center"}>
|
|
||||||
<Check width={24} height={24} color={"white"}/>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { getActiveTestWithGrade } from '@/actions/mangeTest';
|
||||||
|
import { TestCard } from '@/components/custom';
|
||||||
|
import { Session } from 'next-auth';
|
||||||
|
import { signOut } from 'next-auth/react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import LogOut from '@images/logout.svg';
|
||||||
|
import { GradingForm } from '@/components/forms/GradingForm';
|
||||||
|
import YourGrade from '@images/your-grade.svg';
|
||||||
|
|
||||||
|
export function TodayTest({ session }: { session: Session }) {
|
||||||
|
const [activeTest, setActiveTest] = useState<{ data: any | null; error: Error | null; isLoading: boolean }>({ isLoading: true, data: null, error: null });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getActiveTestWithGrade(new Date(), session.user.id)
|
||||||
|
.catch((err) => setActiveTest({ data: null, error: err, isLoading: false }))
|
||||||
|
.then((data) => setActiveTest({ data, error: null, isLoading: false }));
|
||||||
|
}, [session.user.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TestCard data={activeTest.data} error={activeTest.error} isLoading={activeTest.isLoading} />
|
||||||
|
{activeTest.data &&
|
||||||
|
(activeTest.data.grades[0] ? (
|
||||||
|
<div className={'w-full md:w-[400px]'}>
|
||||||
|
<div className={'flex flex-row justify-evenly'}>
|
||||||
|
<Image src={YourGrade} alt={'Your Grade'} width={100} />
|
||||||
|
<span className={'w-[54px] h-[54px] bg-accent rounded-[20px] contents-none grid place-content-center'}>{activeTest.data.grades[0].grade}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<GradingForm session={session} testId={activeTest.data.id} />
|
||||||
|
))}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
await signOut();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image src={LogOut} alt={'Logo'} width={50} height={50} className={'mx-auto w-full md:w-[400px]'} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { getAuthServerSession } from '@/lib/authenticate';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
const session = await getAuthServerSession();
|
||||||
|
|
||||||
|
if (session == null) {
|
||||||
|
redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
|
@ -1,43 +1,35 @@
|
||||||
'use client';
|
import Image from 'next/image';
|
||||||
|
import Logo from '/images/logo.svg';
|
||||||
|
import { getAuthServerSession } from '@/lib/authenticate';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
import { getActiveTest } from '@/actions/mangeTest';
|
||||||
|
import { TodayTest } from './TodayTest';
|
||||||
|
|
||||||
import { GradingForm, TestCard } from '@/components/custom';
|
export default async function Play() {
|
||||||
import { useRouter } from 'next/navigation';
|
const session = await getAuthServerSession();
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import { fetcher } from '@/lib/fetcher';
|
|
||||||
import Image from "next/image";
|
|
||||||
import Logo from "/images/logo.svg";
|
|
||||||
import LogOut from "@images/logout.svg";
|
|
||||||
|
|
||||||
export default function Play() {
|
if (session == null) {
|
||||||
const router = useRouter();
|
redirect('/');
|
||||||
const [password, setPassword] = useState<string | null>('');
|
|
||||||
|
|
||||||
const { data, error, isLoading } = useSWR('/api/test?key=' + password, fetcher);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const pass = localStorage.getItem('@password');
|
|
||||||
setPassword(pass);
|
|
||||||
if (!pass) {
|
|
||||||
router.push('/');
|
|
||||||
}
|
}
|
||||||
}, [router]);
|
|
||||||
|
const now = new Date();
|
||||||
|
const todayTest = await getActiveTest(now);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"w-full h-[100vh] text-[#F0F0F0] bg-black p-12 flex flex-col items-center justify-center gap-y-28"}>
|
<div className={'w-full h-[100vh] text-[#F0F0F0] bg-black p-12 flex flex-col items-center justify-center gap-y-28'}>
|
||||||
<Image src={Logo} alt={"Logo"} width={100} height={200} className={"mx-auto w-full md:w-[400px]"}/>
|
<Image src={Logo} alt={'Logo'} width={100} height={200} className={'mx-auto w-full md:w-[400px]'} />
|
||||||
<TestCard data={data} error={error} isLoading={isLoading} />
|
<TodayTest session={session} />
|
||||||
{data && data.status == 200 && password && <GradingForm password={password} data={data} />}
|
{/* <TestCard data={data} error={error} isLoading={isLoading} /> */}
|
||||||
<div>
|
{/* {data && data.status == 200 && password && <GradingForm password={password} data={data} />} */}
|
||||||
|
{/* <div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
localStorage.clear();
|
await signOut();
|
||||||
router.push('/');
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image src={LogOut} alt={"Logo"} width={50} height={50} className={"mx-auto w-full md:w-[400px]"}/>
|
<Image src={LogOut} alt={'Logo'} width={50} height={50} className={'mx-auto w-full md:w-[400px]'} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import YourGrade from '@images/your-grade.svg';
|
|
||||||
import {Check} from "lucide-react";
|
|
||||||
|
|
||||||
export function GradingForm({ password, data }: { password: string; data: any }) {
|
|
||||||
const [grade, setGrade] = useState<number>(0);
|
|
||||||
const [hasVoted, setHasVoted] = useState<boolean>(false);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const response = await fetch('/api/grade', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ grade, key: password, testId: data.id }),
|
|
||||||
});
|
|
||||||
const json = await response.json();
|
|
||||||
if (response.status == 200) {
|
|
||||||
setHasVoted(true);
|
|
||||||
} else {
|
|
||||||
alert(json.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.vote.hasVoted || hasVoted) {
|
|
||||||
return (
|
|
||||||
<div className={"w-full md:w-[400px]"}>
|
|
||||||
<div className={"flex flex-row justify-evenly"}>
|
|
||||||
<Image src={YourGrade} alt={YourGrade} width={100}/>
|
|
||||||
<span className={"w-[54px] h-[54px] bg-accent rounded-[20px] contents-none grid place-content-center"}>{data.vote.hasVoted ? data.vote.grade : grade}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"w-full md:w-[400px]"}>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="2"
|
|
||||||
max="12"
|
|
||||||
id="gradeSelector"
|
|
||||||
className={"w-full h-4 bg-white range range:bg-white text-white"}
|
|
||||||
onChange={(e) => setGrade(Number(e.target.value) / 2)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={"flex flex-row justify-evenly"}>
|
|
||||||
<Image src={YourGrade} alt={YourGrade} width={100}/>
|
|
||||||
<span className={"w-[54px] h-[54px] bg-accent rounded-[20px] contents-none grid place-content-center"}>{grade}</span>
|
|
||||||
<input type="submit" value={'Envoyer'} id="submit" className={"hidden"}></input>
|
|
||||||
<label htmlFor="submit" className={"w-[54px] h-[54px] bg-secondary rounded-[20px] contents-none grid place-content-center"}>
|
|
||||||
<Check width={24} height={24} color={"white"}/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
export { DayCard as DayCard } from './dayCard';
|
export { DayCard as DayCard } from './dayCard';
|
||||||
export { TestCard as TestCard } from './testCard';
|
export { TestCard as TestCard } from './testCard';
|
||||||
export { GradingForm } from "./gradingForm";
|
|
||||||
export { Santa } from "./santa";
|
export { Santa } from "./santa";
|
||||||
|
|
|
@ -1,49 +1,48 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Calendar from "../../images/date.svg";
|
import Calendar from '../../images/date.svg';
|
||||||
import Image from "next/image";
|
import Image from 'next/image';
|
||||||
|
|
||||||
import { Ban, Snowflake, CircleOff } from "lucide-react";
|
import { Ban, Snowflake, CircleOff } from 'lucide-react';
|
||||||
|
|
||||||
export function TestCard({ data, error, isLoading }: { data: any; error: any; isLoading: boolean }) {
|
export function TestCard({ data, error, isLoading }: { data: any; error: any; isLoading: boolean }) {
|
||||||
console.log(data);
|
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return (
|
return (
|
||||||
<div className={"w-fit h-fit relative"}>
|
<div className={'w-fit h-fit relative'}>
|
||||||
<Image src={Calendar} alt={"Calendrier absolute"} />
|
<Image src={Calendar} alt={'Calendrier absolute'} />
|
||||||
<div className={"w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center"}>
|
<div className={'w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center'}>
|
||||||
<Snowflake width={36} height={36} color={"#000000"} className={"animate-spin place-self-center"} />
|
<Snowflake width={36} height={36} color={'#000000'} className={'animate-spin place-self-center'} />
|
||||||
<span className={"text-black text-xs"}>Chargement</span>
|
<span className={'text-black text-xs'}>Chargement</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (error)
|
if (error)
|
||||||
return (
|
return (
|
||||||
<div className={"w-fit h-fit relative"}>
|
<div className={'w-fit h-fit relative'}>
|
||||||
<Image src={Calendar} alt={"Calendrier absolute"} />
|
<Image src={Calendar} alt={'Calendrier absolute'} />
|
||||||
<div className={"w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center"}>
|
<div className={'w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center'}>
|
||||||
<Ban width={36} height={36} color={"#000000"} className={"place-self-center"}/>
|
<Ban width={36} height={36} color={'#000000'} className={'place-self-center'} />
|
||||||
<span className={"text-black text-xs"}>Erreur</span>
|
<span className={'text-black text-xs'}>Erreur</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (data.status == 404)
|
if (!data)
|
||||||
return (
|
return (
|
||||||
<div className={"w-fit h-fit relative"}>
|
<div className={'w-fit h-fit relative'}>
|
||||||
<Image src={Calendar} alt={"Calendrier absolute"} />
|
<Image src={Calendar} alt={'Calendrier absolute'} />
|
||||||
<div className={"w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center"}>
|
<div className={'w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center'}>
|
||||||
<CircleOff width={36} height={36} color={"#000000"} className={"place-self-center"}/>
|
<CircleOff width={36} height={36} color={'#000000'} className={'place-self-center'} />
|
||||||
<span className={"text-black text-xs"}>Pas de test</span>
|
<span className={'text-black text-xs'}>Pas de test</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"w-fit h-fit relative"}>
|
<div className={'w-fit h-fit relative'}>
|
||||||
<Image src={Calendar} alt={"Calendrier absolute"} />
|
<Image src={Calendar} alt={'Calendrier absolute'} />
|
||||||
<div className={"w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center"}>
|
<div className={'w-full h-full absolute top-0 left-0 p-4 flex flex-col place-content-end text-center'}>
|
||||||
<span className={"text-black text-[36px] font-medium"}>{new Date().getDate()}</span>
|
<span className={'text-black text-[36px] font-medium'}>{new Date().getDate()}</span>
|
||||||
<span className={"text-black text-xs"}>{data.testOf.firstName}</span>
|
<span className={'text-black text-xs'}>{data.testOf.firstName}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import YourGrade from '@images/your-grade.svg';
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
import { Session } from 'next-auth';
|
||||||
|
import { addGrade } from '@/actions/grades';
|
||||||
|
|
||||||
|
export function GradingForm({ session, testId }: { session: Session; testId: number }) {
|
||||||
|
const [grade, setGrade] = useState<number>(0);
|
||||||
|
const [hasVoted, setHasVoted] = useState<boolean>(false);
|
||||||
|
|
||||||
|
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const result = await addGrade(testId, session.user.id, grade, null);
|
||||||
|
if (result.id) {
|
||||||
|
console.log('ok');
|
||||||
|
setHasVoted(true);
|
||||||
|
setGrade(result.grade);
|
||||||
|
} else {
|
||||||
|
console.log('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasVoted && grade != 0) {
|
||||||
|
return (
|
||||||
|
<div className={'w-full md:w-[400px]'}>
|
||||||
|
<div className={'flex flex-row justify-evenly'}>
|
||||||
|
<Image src={YourGrade} alt={YourGrade} width={100} />
|
||||||
|
<span className={'w-[54px] h-[54px] bg-accent rounded-[20px] contents-none grid place-content-center'}>{grade}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'w-full md:w-[400px]'}>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div>
|
||||||
|
<input type="range" min="2" max="12" id="gradeSelector" className={'w-full h-4 bg-white range range:bg-white text-white'} onChange={(e) => setGrade(Number(e.target.value) / 2)} />
|
||||||
|
</div>
|
||||||
|
<div className={'flex flex-row justify-evenly'}>
|
||||||
|
<Image src={YourGrade} alt={YourGrade} width={100} />
|
||||||
|
<span className={'w-[54px] h-[54px] bg-accent rounded-[20px] contents-none grid place-content-center'}>{grade}</span>
|
||||||
|
<input type="submit" value={'Envoyer'} id="submit" className={'hidden'}></input>
|
||||||
|
<label htmlFor="submit" className={'w-[54px] h-[54px] bg-secondary rounded-[20px] contents-none grid place-content-center'}>
|
||||||
|
<Check width={24} height={24} color={'white'} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { signIn } from 'next-auth/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Input } from '../ui/input';
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
|
||||||
|
export function LoginForm() {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
await signIn('credentials', {
|
||||||
|
key: password,
|
||||||
|
callbackUrl: '/play',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className={'flex flex-row gap-4 w-full md:w-[400px]'}>
|
||||||
|
<Input type="password" placeholder="Mot de passe" id="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)}></Input>
|
||||||
|
<input type="submit" value="Submit" id="submit" className={'hidden'} />
|
||||||
|
<label htmlFor="submit" className={'w-[54px] h-[54px] bg-secondary rounded-[20px] contents-none grid place-content-center'}>
|
||||||
|
<Check width={24} height={24} color={'white'} />
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import Credentials from "next-auth/providers/credentials";
|
||||||
|
import prisma from "./prisma";
|
||||||
|
import { getServerSession, RequestInternal, type NextAuthOptions, User } from "next-auth";
|
||||||
|
import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
|
export async function authenticate(key: string) {
|
||||||
|
const user = await prisma.users.findUnique({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
isTeacher: true
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
key: key
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!user) return null;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
session: {
|
||||||
|
strategy: "jwt",
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, user }) {
|
||||||
|
if (user) {
|
||||||
|
token.userId = parseInt(user.id as string);
|
||||||
|
token.firstName = user.firstName;
|
||||||
|
token.lastName = user.lastName;
|
||||||
|
token.isTeacher = user.isTeacher;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
async session({ session, token, user }) {
|
||||||
|
if(token) {
|
||||||
|
session.user.id = token.userId;
|
||||||
|
session.user.firstName = token.firstName;
|
||||||
|
session.user.lastName = token.lastName;
|
||||||
|
session.user.isTeacher = token.isTeacher;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: '/',
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
name: "Credentials",
|
||||||
|
credentials: {
|
||||||
|
key: { label: "Key", type: "password" }
|
||||||
|
},
|
||||||
|
async authorize(credentials: Record<"key", string> | undefined, req: Pick<RequestInternal, "body" | "query" | "headers" | "method">) {
|
||||||
|
const { key } = credentials as {
|
||||||
|
key: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const user = await authenticate(key) as User | null
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use it in server contexts
|
||||||
|
export function auth(...args: [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]] | [NextApiRequest, NextApiResponse] | []) {
|
||||||
|
return getServerSession(...args, authOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAuthServerSession = () => getServerSession(authOptions);
|
|
@ -19,6 +19,7 @@
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
|
"next-auth": "^4.24.5",
|
||||||
"prisma": "^5.6.0",
|
"prisma": "^5.6.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
|
@ -426,6 +427,14 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@panva/hkdf": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@prisma/client": {
|
"node_modules/@prisma/client": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.6.0.tgz",
|
||||||
|
@ -3499,6 +3508,14 @@
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "4.15.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz",
|
||||||
|
"integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -3650,7 +3667,6 @@
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
@ -3816,6 +3832,41 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-auth": {
|
||||||
|
"version": "4.24.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz",
|
||||||
|
"integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.13",
|
||||||
|
"@panva/hkdf": "^1.0.2",
|
||||||
|
"cookie": "^0.5.0",
|
||||||
|
"jose": "^4.11.4",
|
||||||
|
"oauth": "^0.9.15",
|
||||||
|
"openid-client": "^5.4.0",
|
||||||
|
"preact": "^10.6.3",
|
||||||
|
"preact-render-to-string": "^5.1.19",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "^12.2.5 || ^13 || ^14",
|
||||||
|
"nodemailer": "^6.6.5",
|
||||||
|
"react": "^17.0.2 || ^18",
|
||||||
|
"react-dom": "^17.0.2 || ^18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-auth/node_modules/cookie": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
|
@ -3866,6 +3917,11 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oauth": {
|
||||||
|
"version": "0.9.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||||
|
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
@ -3991,6 +4047,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oidc-token-hash": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^10.13.0 || >=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
@ -3999,6 +4063,28 @@
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openid-client": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"jose": "^4.15.1",
|
||||||
|
"lru-cache": "^6.0.0",
|
||||||
|
"object-hash": "^2.2.0",
|
||||||
|
"oidc-token-hash": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/openid-client/node_modules/object-hash": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.3",
|
"version": "0.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||||
|
@ -4268,6 +4354,26 @@
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.19.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz",
|
||||||
|
"integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/preact-render-to-string": {
|
||||||
|
"version": "5.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
|
||||||
|
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
|
||||||
|
"dependencies": {
|
||||||
|
"pretty-format": "^3.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
@ -4277,6 +4383,11 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pretty-format": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
|
||||||
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz",
|
||||||
|
@ -5325,6 +5436,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
@ -5472,8 +5591,7 @@
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
|
"next-auth": "^4.24.5",
|
||||||
"prisma": "^5.6.0",
|
"prisma": "^5.6.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import NextAuth, { DefaultSession, DefaultJWT, DefaultUser } from "next-auth";
|
||||||
|
import { JWT } from "next-auth/jwt";
|
||||||
|
|
||||||
|
declare module "next-auth" {
|
||||||
|
interface Session {
|
||||||
|
user: {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string
|
||||||
|
isTeacher: boolean;
|
||||||
|
} & DefaultSession["user"];
|
||||||
|
}
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string
|
||||||
|
isTeacher: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "next-auth/jwt" {
|
||||||
|
interface JWT {
|
||||||
|
userId: number;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string
|
||||||
|
isTeacher: boolean;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue