peer-at-code-web/components/ui/Puzzle.tsx
2023-07-05 18:48:34 +02:00

192 lines
5.5 KiB
TypeScript

'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import cookies from 'js-cookie';
import { notFound, useRouter } from 'next/navigation';
import { useContext, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useSWRConfig } from 'swr';
import * as z from 'zod';
import { usePuzzle } from '@/lib/hooks/use-puzzles';
import { getURL } from '@/lib/utils';
import { Button } from '@/components/ui/Button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/Form';
import { Input } from '@/components/ui/Input';
import ToHTML from '@/components/ui/ToHTML';
import { UserContext } from '@/context/user';
import { useToast } from '@/lib/hooks/use-toast';
type PuzzleData = {
answer: string;
// filename: string;
// code_file: File[];
};
type Granted = {
tries: number | null;
score?: number | null;
message?: string | null;
success?: boolean | null;
};
export default function Puzzle({ token, id }: { token: string; id: number }) {
const { data: me } = useContext(UserContext);
const [granted, setGranted] = useState<Granted | null>(null);
const { data: puzzle, isLoading } = usePuzzle({ token, id });
const { mutate } = useSWRConfig();
const router = useRouter();
const { register, handleSubmit } = useForm<PuzzleData>({
defaultValues: {
answer: ''
// filename: '',
// code_file: []
}
});
async function onSubmit(data: PuzzleData) {
const formData = new FormData();
// if (data.code_file[0].size > 16 * 1024 * 1024) {
// alert('Fichier trop volumineux');
// return;
// }
formData.append('answer', data.answer);
// formData.append('filename', 'placeholder');
// formData.append('code_file', new Blob(), 'placeholder');
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/puzzleResponse/${puzzle!.id}`, {
method: 'POST',
body: formData,
headers: {
Authorization: `Bearer ${cookies.get('token')}}`
}
});
if (res.ok || res.status === 403 || res.status === 406 || res.status === 423) {
const data = res.ok || res.status === 406 ? ((await res.json()) as Granted) : null;
if (data && data.score) {
mutate(`puzzles/${puzzle?.id}`);
} else if (data && data.tries) setGranted(data);
else if (res.ok && data?.success)
setGranted({ tries: null, score: null, message: 'Réponse correcte' });
else if (res.status === 423)
setGranted({ tries: null, score: null, message: 'Réponse incorrecte' });
else if (res.status === 403) mutate(`puzzles/${puzzle?.id}`);
}
}
if (!puzzle && isLoading) {
return <></>;
}
if (!puzzle) {
notFound();
}
// TODO : add a check to see if the user is in the group of the puzzle
if (me?.groups.length === 0) {
router.push('/dashboard/puzzles');
}
return (
<div className="flex h-full w-full flex-col justify-between space-y-4">
<h1 className="text-2xl font-bold sm:text-3xl md:text-4xl">
{puzzle.name}{' '}
<span className="text-xl text-highlight-secondary">({puzzle.scoreMax} points)</span>
</h1>
<div className="flex h-screen w-full overflow-y-auto">
<ToHTML className="font-code text-xs sm:text-base" data={puzzle.content} />
</div>
{!puzzle.score ? (
<InputForm />
) : (
<div className="flex items-center justify-between">
<div className="items-center gap-x-2">
<p>
Tentative{puzzle.tries && puzzle.tries > 1 ? 's' : ''} :{' '}
<span className="text-brand-accent">{puzzle.tries}</span>
</p>
<p>
Score : <span className="text-brand-accent">{puzzle.score}</span>
</p>
</div>
<Button type="button" onClick={() => router.push(getURL(`/dashboard/puzzles`))}>
Retour aux puzzles
</Button>
</div>
)}
</div>
);
}
const InputFormSchema = z.object({
answer: z.string().nonempty().trim(),
code_file: z.any().nullable()
});
function InputForm() {
const form = useForm<z.infer<typeof InputFormSchema>>({
resolver: zodResolver(InputFormSchema),
defaultValues: {
answer: '',
code_file: null
}
});
function onSubmit(data: z.infer<typeof InputFormSchema>) {
console.log(data);
form.reset();
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col items-end justify-between gap-4 sm:flex-row"
encType="multipart/form-data"
>
<div className="flex w-full flex-col gap-2 sm:flex-row sm:gap-4">
<FormField
control={form.control}
name="answer"
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="answer">Réponse</FormLabel>
<FormControl>
<Input placeholder="CAPTAIN, LOOK !" autoComplete="off" {...field} />
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="code_file"
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="code_file">Fichier</FormLabel>
<FormControl>
<Input {...field} type="file" />
</FormControl>
</FormItem>
)}
/>
</div>
<Button className="w-full sm:w-44" variant="brand">
Envoyer
</Button>
</form>
</Form>
);
}