Added group check
This commit is contained in:
parent
82fbf1ecba
commit
d475a08790
1 changed files with 268 additions and 65 deletions
333
ui/Puzzles.tsx
333
ui/Puzzles.tsx
|
@ -1,23 +1,40 @@
|
|||
'use client';
|
||||
|
||||
import { UserContext } from '@/context/user';
|
||||
import { useGroups } from '@/lib/hooks/use-groups';
|
||||
import { usePuzzles } from '@/lib/hooks/use-puzzles';
|
||||
import { type Chapter } from '@/lib/puzzles';
|
||||
import type { Chapter, Puzzle } from '@/lib/puzzles';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import AppLink from './AppLink';
|
||||
import Button from './Button';
|
||||
import Dialog from './Dialog';
|
||||
import Icon from './Icon';
|
||||
import Input from './Input';
|
||||
import Select from './Select';
|
||||
|
||||
export default function Puzzles({ token }: { token: string }) {
|
||||
const { data: me } = useContext(UserContext);
|
||||
const { data, isLoading } = usePuzzles({ token });
|
||||
|
||||
// SOme chapters have a start date and a end date (for example, the first chapter is only available for 2 weeks), I want to want to lock the chapter if the current date is not between the start and end date
|
||||
// I want to display a message to the user if the chapter is locked
|
||||
const [isOpen, setIsOpen] = useState<boolean[]>([]);
|
||||
|
||||
function isChapterLocked(chapter: Chapter) {
|
||||
function handleClick(index: number) {
|
||||
setIsOpen((prevState) => {
|
||||
const newState = [...prevState];
|
||||
newState[index] = !newState[index];
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
console.log(me);
|
||||
|
||||
function isInEventGroup(chapter: Chapter) {
|
||||
return (
|
||||
chapter.startDay &&
|
||||
chapter.endDay &&
|
||||
new Date() > new Date(chapter.startDay) &&
|
||||
new Date() < new Date(chapter.endDay)
|
||||
chapter.startDate &&
|
||||
chapter.endDate &&
|
||||
me?.groups?.some((group) => group.chapter && group.chapter === chapter.id)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -26,65 +43,63 @@ export default function Puzzles({ token }: { token: string }) {
|
|||
{(!isLoading &&
|
||||
data?.map((chapter) => (
|
||||
<div key={chapter.id} className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xl font-semibold sm:text-2xl">
|
||||
Chapitre {chapter.id} - {chapter.name}{' '}
|
||||
</h3>
|
||||
<div className="h-1 w-1/4 rounded-lg bg-gray-200">
|
||||
<div className="h-1 w-1/2 rounded-lg bg-gradient-to-tl from-brand to-brand-accent" />
|
||||
<div className="flex flex-col justify-between lg:flex-row lg:items-center">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<h3 className="text-xl font-semibold sm:text-2xl">
|
||||
Chapitre {chapter.id} - {chapter.name}{' '}
|
||||
</h3>
|
||||
{!isInEventGroup(chapter) && (
|
||||
<Dialog
|
||||
key={chapter.id}
|
||||
title={chapter.name}
|
||||
tooltip="Select Hogwarts Level"
|
||||
open={isOpen[chapter.id]}
|
||||
onOpenChange={() => handleClick(chapter.id)}
|
||||
trigger={
|
||||
<button className="flex items-center gap-x-2 text-sm font-semibold text-muted hover:text-brand">
|
||||
<Icon name="group-line" />
|
||||
Rejoindre un groupe
|
||||
</button>
|
||||
}
|
||||
className="right-96 p-4"
|
||||
>
|
||||
<GroupForm chapter={chapter} token={token} />
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
{chapter.startDate && chapter.endDate ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Icon name="calendar-line" className="text-sm text-muted" />
|
||||
<span className="text-sm text-muted">
|
||||
{new Date(chapter.startDate).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
hour: 'numeric'
|
||||
})}{' '}
|
||||
-{' '}
|
||||
{new Date(chapter.endDate).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
hour: 'numeric'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-1 w-1/4 rounded-lg bg-gray-200">
|
||||
<div className="h-1 w-1/2 rounded-lg bg-gradient-to-tl from-brand to-brand-accent" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ul
|
||||
className={cn('flex flex-col space-y-4', {
|
||||
// If the chapter is locked i want to add a class to li children to make them unclickable
|
||||
'pointer-events-none': isChapterLocked(chapter)
|
||||
})}
|
||||
>
|
||||
{chapter.puzzles &&
|
||||
chapter.puzzles.map((puzzle) => (
|
||||
<AppLink key={puzzle.id} href={`/dashboard/puzzles/${puzzle.id}`}>
|
||||
<li
|
||||
className={cn(
|
||||
'group flex items-center justify-between rounded-md border-2 bg-primary-700 p-4 font-code hover:bg-primary-600',
|
||||
{
|
||||
'border-green-600/30': puzzle.tags
|
||||
?.map((tag) => tag.name.toLowerCase())
|
||||
.includes('easy'),
|
||||
'border-yellow-600/30': puzzle.tags
|
||||
?.map((tag) => tag.name.toLowerCase())
|
||||
.includes('medium'),
|
||||
'border-red-600/30': puzzle.tags
|
||||
?.map((tag) => tag.name.toLowerCase())
|
||||
.includes('hard'),
|
||||
'border-highlight-secondary/30': !puzzle.tags?.length
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-x-2">
|
||||
<span className="text-base font-semibold">{puzzle.name}</span>
|
||||
{puzzle.tags?.length && (
|
||||
<div className="flex gap-x-2 text-sm text-muted">
|
||||
{puzzle.tags
|
||||
.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name))
|
||||
.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={cn('inline-block rounded-md bg-primary-900 px-2 py-1')}
|
||||
>
|
||||
{tag.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Icon
|
||||
className="-translate-x-2 transform-gpu duration-300 group-hover:translate-x-0"
|
||||
name="arrow-right-line"
|
||||
/>
|
||||
</li>
|
||||
</AppLink>
|
||||
))}
|
||||
</ul>
|
||||
{isInEventGroup(chapter) && (
|
||||
<ul className="flex flex-col space-y-4">
|
||||
{chapter.puzzles &&
|
||||
chapter.puzzles.map((puzzle) => (
|
||||
<PuzzleProp key={puzzle.id} puzzle={puzzle} chapter={chapter} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
))) || (
|
||||
<div className="flex flex-col space-y-6">
|
||||
|
@ -119,3 +134,191 @@ export default function Puzzles({ token }: { token: string }) {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
|
||||
const isPuzzleAvailable = (chapter: Chapter) => {
|
||||
const today = new Date();
|
||||
const startDate = new Date(chapter.startDate!);
|
||||
const endDate = new Date(chapter.endDate!);
|
||||
return (
|
||||
(chapter.startDate && chapter.endDate && today >= startDate && today <= endDate) ||
|
||||
(!chapter.startDate && !chapter.endDate)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<li
|
||||
className={cn(
|
||||
'group relative flex h-full w-full rounded-md border-2 bg-primary-700 font-code hover:bg-primary-600',
|
||||
{
|
||||
'border-green-600/30': puzzle.tags?.map((tag) => tag.name.toLowerCase()).includes('easy'),
|
||||
'border-yellow-600/30': puzzle.tags
|
||||
?.map((tag) => tag.name.toLowerCase())
|
||||
.includes('medium'),
|
||||
'border-red-600/30': puzzle.tags?.map((tag) => tag.name.toLowerCase()).includes('hard'),
|
||||
'border-highlight-primary': !puzzle.tags?.length,
|
||||
'cursor-not-allowed': !isPuzzleAvailable(chapter)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{isPuzzleAvailable(chapter) ? (
|
||||
<AppLink
|
||||
className="flex h-full w-full items-center justify-between p-4"
|
||||
key={puzzle.id}
|
||||
href={`/dashboard/puzzles/${puzzle.id}`}
|
||||
>
|
||||
<div className="flex gap-x-2">
|
||||
<span className="text-base font-semibold">{puzzle.name}</span>
|
||||
{puzzle.tags?.length && (
|
||||
<div className="flex gap-x-2 text-sm text-muted">
|
||||
{puzzle.tags
|
||||
.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name))
|
||||
.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={cn('inline-block rounded-md bg-primary-900 px-2 py-1')}
|
||||
>
|
||||
{tag.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Icon
|
||||
className="-translate-x-2 transform-gpu duration-300 group-hover:translate-x-0"
|
||||
name="arrow-right-line"
|
||||
/>
|
||||
</AppLink>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-between p-4 opacity-50">
|
||||
<div className="flex gap-x-2">
|
||||
<span className="text-base font-semibold">{puzzle.name}</span>
|
||||
{puzzle.tags?.length && (
|
||||
<div className="flex gap-x-2 text-sm text-muted">
|
||||
{puzzle.tags
|
||||
.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name))
|
||||
.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={cn('inline-block rounded-md bg-primary-900 px-2 py-1')}
|
||||
>
|
||||
{tag.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
type GroupData = {
|
||||
name?: string;
|
||||
chapter?: number;
|
||||
puzzle?: number;
|
||||
};
|
||||
|
||||
function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) {
|
||||
const [isJoining, setIsJoining] = useState(false);
|
||||
|
||||
const { data: groups } = useGroups({ token });
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setError,
|
||||
reset
|
||||
} = useForm<GroupData>({
|
||||
defaultValues: {
|
||||
name: undefined,
|
||||
chapter: chapter.id,
|
||||
puzzle: undefined
|
||||
}
|
||||
});
|
||||
|
||||
async function onSubmit(data: GroupData) {
|
||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/${isJoining ? 'groupJoin' : 'groupCreate'}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsJoining(false);
|
||||
reset();
|
||||
}}
|
||||
className={cn('rounded-lg p-2 font-semibold', {
|
||||
'bg-primary-500': !isJoining
|
||||
})}
|
||||
>
|
||||
Créer un groupe
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsJoining(true);
|
||||
reset();
|
||||
}}
|
||||
className={cn('rounded-lg p-2 font-semibold', {
|
||||
'bg-primary-500': isJoining
|
||||
})}
|
||||
>
|
||||
Rejoindre un groupe
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-2 py-4">
|
||||
<hr className="border-primary-600" />
|
||||
</div>
|
||||
<form
|
||||
className="flex w-full flex-col justify-between"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
encType="multipart/form-data"
|
||||
>
|
||||
<div className="flex w-56 flex-col space-y-4">
|
||||
{!isJoining ? (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<Input
|
||||
className="w-full"
|
||||
label="Nom du groupe"
|
||||
type="text"
|
||||
placeholder="Terre en vue mon capitaine !"
|
||||
required
|
||||
{...register('name')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Select
|
||||
className="w-full"
|
||||
label="Groupes"
|
||||
required
|
||||
{...register('name')}
|
||||
options={
|
||||
groups
|
||||
?.filter((group) => group.chapter === chapter.id)
|
||||
.map((group) => ({
|
||||
title: group.name,
|
||||
value: group.name
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button kind="brand" type="submit">
|
||||
{isJoining ? 'Rejoindre' : 'Créer'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue