Fix mutate, added 404 & puzzle double sort

This commit is contained in:
Théo 2023-04-23 15:40:57 +02:00
parent f654936e45
commit e962fab69b
6 changed files with 155 additions and 52 deletions

View file

@ -3,10 +3,20 @@ import Image from 'next/image';
import error404 from '@/public/assets/404.png'; import error404 from '@/public/assets/404.png';
export default function NotFound() { export default function NotFound() {
const random = Math.floor(Math.random() * 100);
if (random > 50) {
return ( return (
<div className="m-auto flex h-screen flex-col items-center justify-center space-y-6"> <div className="m-auto flex h-screen flex-col items-center justify-center space-y-6">
<h2 className="text-6xl">Oh non! Un François 404</h2> <h1 className="text-4xl">Oh non! Un François 404</h1>
<Image priority src={error404} alt="François 404" width={1000} height={1000} /> <Image priority src={error404} alt="François 404" width={800} height={800} />
</div>
);
} else {
return (
<div className="m-auto flex h-screen flex-col items-center justify-center space-y-6">
<h1 className="text-4xl">Oh non! Ce puzzle est introuvable</h1>
</div> </div>
); );
} }
}

View file

@ -17,7 +17,15 @@ export async function generateMetadata({ params }: { params: { id: number } }):
const puzzle = await getPuzzle({ token, id }); const puzzle = await getPuzzle({ token, id });
if (!puzzle) { if (!puzzle) {
notFound(); return {
title: 'Puzzle introuvable',
openGraph: {
title: 'Puzzle introuvable',
type: 'website',
url: getURL(`/dashboard/puzzles/${id}`)
// IMAGES WITH OG IMAGE
}
};
} }
return { return {

View file

@ -5,12 +5,12 @@ export const getChapters = async ({ token }: { token: string }): Promise<Chapter
} }
}); });
const chapters = (await res.json()) as Chapter[];
if (!res.ok) { if (!res.ok) {
throw new Error('Failed to fetch puzzles'); return [];
} }
const chapters = (await res.json()) as Chapter[];
if (!chapters) { if (!chapters) {
return []; return [];
} }
@ -31,11 +31,10 @@ export const getChapter = async ({
} }
}); });
const chapter = (await res.json()) as Chapter;
if (!res.ok) { if (!res.ok) {
throw new Error('Failed to fetch puzzles'); return null;
} }
const chapter = (await res.json()) as Chapter;
if (!chapter) { if (!chapter) {
return null; return null;
@ -74,12 +73,12 @@ export const getPuzzle = async ({
} }
}); });
const puzzle = (await res.json()) as Puzzle;
if (!res.ok) { if (!res.ok) {
throw new Error('Failed to fetch puzzle'); return null;
} }
const puzzle = (await res.json()) as Puzzle;
if (!puzzle) { if (!puzzle) {
return null; return null;
} }

View file

@ -62,8 +62,9 @@ export default function Puzzle({ token, id }: { token: string; id: number }) {
if (res.ok || res.status === 406 || res.status === 423) { if (res.ok || res.status === 406 || res.status === 423) {
const data = res.ok || res.status === 406 ? ((await res.json()) as Granted) : null; const data = res.ok || res.status === 406 ? ((await res.json()) as Granted) : null;
if (data && data.score) mutate(`puzzles/${puzzle?.id}`); if (data && data.score) {
else if (data && data.tries) setGranted(data); mutate([`puzzles/${puzzle?.id}`, 'me']);
} else if (data && data.tries) setGranted(data);
else if (res.ok && data?.success) else if (res.ok && data?.success)
setGranted({ tries: null, score: null, message: 'Réponse correcte' }); setGranted({ tries: null, score: null, message: 'Réponse correcte' });
else if (res.status === 423) else if (res.status === 423)

View file

@ -19,11 +19,20 @@ import type { Chapter, Puzzle } from '@/lib/puzzles';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import useLocalStorage from '@/lib/hooks/use-local-storage'; import useLocalStorage from '@/lib/hooks/use-local-storage';
const difficulty = [
{ value: 'easy', label: 'Facile' },
{ value: 'medium', label: 'Moyen' },
{ value: 'hard', label: 'Difficile' }
];
// TODO: REFACTOR FILTER TO AVOID WARNINGS
export default function Puzzles({ token }: { token: string }) { export default function Puzzles({ token }: { token: string }) {
const { data: me } = useContext(UserContext); const { data: me } = useContext(UserContext);
const { data, isLoading } = usePuzzles({ token }); const { data, isLoading } = usePuzzles({ token });
const [isOpen, setIsOpen] = useState<boolean[]>([]); const [isOpen, setIsOpen] = useState<boolean[]>([]);
const [filter, setFilter] = useState<string>(''); const [filterTags, setFilterTags] = useState<string>('');
const [filterDifficulty, setFilterDifficulty] = useState<string>('');
const [filterChapter, setFilterChapter] = useState<number>(); const [filterChapter, setFilterChapter] = useState<number>();
function handleClick(index: number) { function handleClick(index: number) {
@ -52,7 +61,7 @@ export default function Puzzles({ token }: { token: string }) {
if (now > startDate) { if (now > startDate) {
const minutes = 10 * 60 * 1000; const minutes = 10 * 60 * 1000;
if (startDate.getTime() + minutes < now.getTime()) { if (now.getTime() - startDate.getTime() < minutes) {
return true; return true;
} }
} }
@ -61,25 +70,39 @@ export default function Puzzles({ token }: { token: string }) {
} }
const filteredData = useMemo(() => { const filteredData = useMemo(() => {
if (filter && filterChapter) { if ((filterTags || filterDifficulty) && filterChapter) {
if (filter === 'completed' || filter === 'no-completed') {
return data return data
?.find((chapter) => chapter.id === filterChapter) ?.find((chapter) => chapter.id === filterChapter)
?.puzzles.filter((puzzle) => { ?.puzzles.filter((puzzle) => {
if (filter === 'completed') { if (!puzzle?.tags) return false;
return puzzle!.score; if (filterDifficulty && filterTags) {
if (filterTags === 'completed') {
return puzzle!.tags!.some((tag) => tag.name === filterDifficulty) && puzzle!.score;
} else if (filterTags === 'not-completed') {
return puzzle!.tags!.some((tag) => tag.name === filterDifficulty) && !puzzle!.score;
} }
return (
puzzle!.tags!.some((tag) => tag.name === filterTags) &&
puzzle!.tags!.some((tag) => tag.name === filterDifficulty)
);
}
if (filterDifficulty) {
return puzzle!.tags!.some((tag) => tag.name === filterDifficulty);
}
if (filterTags) {
if (filterTags === 'completed') {
return puzzle!.score;
} else if (filterTags === 'not-completed') {
return !puzzle!.score; return !puzzle!.score;
}
return puzzle!.tags!.some((tag) => tag.name === filterTags);
}
return puzzle;
}) })
.map((puzzle) => puzzle); .map((puzzle) => puzzle);
} }
return data
?.find((chapter) => chapter.id === filterChapter)
?.puzzles.filter((puzzle) => puzzle!.tags!.some((tag) => tag.name === filter))
.map((puzzle) => puzzle);
}
return data?.find((chapter) => chapter.id === filterChapter)?.puzzles; return data?.find((chapter) => chapter.id === filterChapter)?.puzzles;
}, [data, filter, filterChapter]); }, [data, filterTags, filterDifficulty, filterChapter]);
return ( return (
<> <>
@ -109,7 +132,7 @@ export default function Puzzles({ token }: { token: string }) {
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
{chapter.startDate && chapter.endDate ? ( {chapter.startDate && chapter.endDate ? (
<div className="flex items-center gap-x-2"> <div className="flex items-center justify-start gap-x-2 md:justify-end">
<Icon name="calendar-line" className="text-sm text-muted" /> <Icon name="calendar-line" className="text-sm text-muted" />
<span className="text-sm text-muted"> <span className="text-sm text-muted">
{new Date(chapter.startDate).toLocaleDateString('fr-FR', { {new Date(chapter.startDate).toLocaleDateString('fr-FR', {
@ -134,15 +157,24 @@ export default function Puzzles({ token }: { token: string }) {
<div className="h-1 w-1/2 rounded-lg bg-gradient-to-tl from-brand to-brand-accent" /> <div className="h-1 w-1/2 rounded-lg bg-gradient-to-tl from-brand to-brand-accent" />
</div> </div>
)} )}
<div className="mt-1 flex justify-end"> <div className="mt-1 flex justify-start gap-x-2 md:justify-end">
{isInEventGroup(chapter) && ( {isInEventGroup(chapter) && (
<FilterChapter <>
<FilterDifficulty
chapters={data} chapters={data}
chapter={chapter} chapter={chapter}
filter={filter} filter={filterDifficulty}
setFilter={setFilter} setFilter={setFilterDifficulty}
setFilterChapter={setFilterChapter} setFilterChapter={setFilterChapter}
/> />
<FilterTags
chapters={data}
chapter={chapter}
filter={filterTags}
setFilter={setFilterTags}
setFilterChapter={setFilterChapter}
/>
</>
)} )}
</div> </div>
</div> </div>
@ -202,11 +234,8 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
const now = new Date(); const now = new Date();
if (now > startDate) { if (now > startDate) {
const minutes = 10 * 60 * 1000;
if (startDate.getTime() + minutes < now.getTime()) {
return true; return true;
} }
}
return false; return false;
} }
@ -257,7 +286,10 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
<div className="flex h-full w-full items-center justify-between p-4 opacity-50"> <div className="flex h-full w-full items-center justify-between p-4 opacity-50">
<div className="flex gap-x-2"> <div className="flex gap-x-2">
<span className="text-base font-semibold"> <span className="text-base font-semibold">
{puzzle.name} ({puzzle.scoreMax}) {puzzle.name}{' '}
<span className="text-sm text-highlight-secondary">
({puzzle.score ? `${puzzle.score}` : '?'}/{puzzle.scoreMax} points)
</span>
</span> </span>
{puzzle.tags?.length && ( {puzzle.tags?.length && (
<div className="flex gap-x-2 text-sm text-muted"> <div className="flex gap-x-2 text-sm text-muted">
@ -280,7 +312,7 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
); );
} }
function FilterChapter({ function FilterDifficulty({
chapters, chapters,
chapter, chapter,
filter, filter,
@ -294,7 +326,7 @@ function FilterChapter({
setFilterChapter: (chapter: number) => void; setFilterChapter: (chapter: number) => void;
}) { }) {
const [stored, setStored] = useLocalStorage({ const [stored, setStored] = useLocalStorage({
key: 'puzzles-filter', key: 'filter-difficulty',
initialValue: '' initialValue: ''
}); });
@ -304,12 +336,65 @@ function FilterChapter({
.find((c) => c.id === chapter.id) .find((c) => c.id === chapter.id)
?.puzzles?.map((p) => p.tags) ?.puzzles?.map((p) => p.tags)
.flat() .flat()
.filter((tag) => difficulty.some((d) => tag?.name === d.value))
.filter((tag, index, self) => self.findIndex((t) => t!.name === tag!.name) === index) .filter((tag, index, self) => self.findIndex((t) => t!.name === tag!.name) === index)
.map((t) => { .map((t) => {
return { title: t!.name, value: t!.name }; return { title: t!.name, value: t!.name };
}) as { title: string; value: string }[]; }) as { title: string; value: string }[];
options?.unshift({ title: 'Pas encore terminé(s)', value: 'no-completed' }); options?.unshift({ title: 'Toutes les difficultés', value: '' });
setFilterChapter(chapter.id);
function handleChange(event: ChangeEvent<HTMLSelectElement>) {
setFilter(event.target.value);
// TODO OPTI
// @ts-ignore
setStored(event.target.value);
}
useEffect(() => {
if (stored) {
// TODO OPTI
// @ts-ignore
setFilter(stored);
}
}, [stored]);
return <Select className="w-44" options={options} value={filter} onChange={handleChange} />;
}
function FilterTags({
chapters,
chapter,
filter,
setFilter,
setFilterChapter
}: {
chapters: Chapter[];
chapter: Chapter;
filter: string;
setFilter: (filter: string) => void;
setFilterChapter: (chapter: number) => void;
}) {
const [stored, setStored] = useLocalStorage({
key: 'filter-tags',
initialValue: ''
});
let options = [] as { title: string; value: string }[];
options = chapters
.find((c) => c.id === chapter.id)
?.puzzles?.map((p) => p.tags)
.flat()
.filter((tag) => !difficulty.some((d) => tag?.name === d.value))
.filter((tag, index, self) => self.findIndex((t) => t!.name === tag!.name) === index)
.map((t) => {
return { title: t!.name, value: t!.name };
}) as { title: string; value: string }[];
options?.unshift({ title: 'Pas encore terminé(s)', value: 'not-completed' });
options?.unshift({ title: 'Terminé(s)', value: 'completed' }); options?.unshift({ title: 'Terminé(s)', value: 'completed' });
options?.unshift({ title: 'Tout les puzzles', value: '' }); options?.unshift({ title: 'Tout les puzzles', value: '' });

View file

@ -22,7 +22,7 @@ const Select = forwardRef<
ref={ref} ref={ref}
> >
{options.map((option) => ( {options.map((option) => (
<option key={option.value} value={option.value} selected={option.value === props.value}> <option key={option.value} value={option.value}>
{option.title} {option.title}
</option> </option>
))} ))}