feat: merge to chapters > id > puzzle > id
This commit is contained in:
parent
8702c8e790
commit
e6cd69f0ad
18 changed files with 508 additions and 46 deletions
39
src/lib/components/Chapter.svelte
Normal file
39
src/lib/components/Chapter.svelte
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/Utils';
|
||||||
|
import type { Chapter } from '$lib/types';
|
||||||
|
|
||||||
|
import ChevronRight from './Icons/ChevronRight.svelte';
|
||||||
|
|
||||||
|
export let chapter: Chapter;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class={cn(
|
||||||
|
'font-code group relative flex h-full w-full rounded-md bg-primary-700 transition-colors duration-150 hover:bg-primary-600'
|
||||||
|
// 'cursor-not-allowed': !isStarted(chapter)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<a class="flex h-full w-full items-center gap-4 p-4" href="/dashboard/chapters/{chapter.id}">
|
||||||
|
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
||||||
|
<h2 class="text-base font-semibold">
|
||||||
|
{chapter.name}
|
||||||
|
</h2>
|
||||||
|
<!-- <div class="flex items-center gap-x-6">
|
||||||
|
{#if puzzle.tags?.length}
|
||||||
|
<div class="flex gap-x-2 text-sm">
|
||||||
|
{#each puzzle.tags as tag}
|
||||||
|
<span
|
||||||
|
class="inline-block rounded-md bg-primary-800 px-2 py-1 text-highlight-secondary"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<span class="translate-x-0 transform-gpu duration-300 group-hover:translate-x-2">
|
||||||
|
<ChevronRight />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
19
src/lib/components/Icons/Discord.svelte
Normal file
19
src/lib/components/Icons/Discord.svelte
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib';
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={cn(className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
viewBox="0 0 640 512"
|
||||||
|
fill="currentColor"
|
||||||
|
><path
|
||||||
|
d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"
|
||||||
|
/></svg
|
||||||
|
>
|
23
src/lib/components/Icons/Git.svelte
Normal file
23
src/lib/components/Icons/Git.svelte
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib';
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={cn(className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
><path
|
||||||
|
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
|
||||||
|
/><path d="M9 18c-4.51 2-5-2-7-2" /></svg
|
||||||
|
>
|
23
src/lib/components/Icons/Help.svelte
Normal file
23
src/lib/components/Icons/Help.svelte
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib';
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={cn(className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
><circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><path
|
||||||
|
d="M12 17h.01"
|
||||||
|
/></svg
|
||||||
|
>
|
|
@ -12,7 +12,6 @@
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
|
|
25
src/lib/components/Icons/Mail.svelte
Normal file
25
src/lib/components/Icons/Mail.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib';
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={cn(className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
><path d="M22 10.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12.5" /><path
|
||||||
|
d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"
|
||||||
|
/><path d="M18 15.28c.2-.4.5-.8.9-1a2.1 2.1 0 0 1 2.6.4c.3.4.5.8.5 1.3 0 1.3-2 2-2 2" /><path
|
||||||
|
d="M20 22v.01"
|
||||||
|
/></svg
|
||||||
|
>
|
|
@ -7,7 +7,10 @@
|
||||||
export let isOpen: boolean;
|
export let isOpen: boolean;
|
||||||
|
|
||||||
$: user = $page.data.user;
|
$: user = $page.data.user;
|
||||||
$: segment = $page.url.pathname.slice(1).replaceAll('/', ' / ');
|
$: segments = $page.url.pathname.slice(1).split('/');
|
||||||
|
$: breadcrumb = segments.map((segment, index) => {
|
||||||
|
return { name: segment, href: '/' + segments.slice(0, index + 1).join('/') };
|
||||||
|
}) as { name: string; href: string }[];
|
||||||
|
|
||||||
function handleToggle() {
|
function handleToggle() {
|
||||||
isOpen = !isOpen;
|
isOpen = !isOpen;
|
||||||
|
@ -27,11 +30,24 @@
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if !isOpen && segment}
|
{#if !isOpen && segments.length}
|
||||||
|
<div class="flex items-center justify-center capitalize text-highlight-secondary">
|
||||||
|
{#each breadcrumb as segment}
|
||||||
|
{@const last = segment === breadcrumb[breadcrumb.length - 1]}
|
||||||
|
<a class="hover:underline hover:text-primary" href={segment.href}>
|
||||||
|
{segment.name}
|
||||||
|
</a>
|
||||||
|
{#if !last}
|
||||||
|
<span class="mx-1 text-highlight-secondary">/</span>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<!-- {#if !isOpen && segment}
|
||||||
<div class="flex items-center justify-center capitalize text-highlight-secondary">
|
<div class="flex items-center justify-center capitalize text-highlight-secondary">
|
||||||
{segment}
|
{segment}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if} -->
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<Avatar />
|
<Avatar />
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
import { cn } from '$lib/Utils';
|
import { cn } from '$lib/Utils';
|
||||||
import type { Puzzle } from '$lib/types';
|
import type { Puzzle } from '$lib/types';
|
||||||
import ChevronRight from './Icons/ChevronRight.svelte';
|
import ChevronRight from './Icons/ChevronRight.svelte';
|
||||||
|
|
||||||
export let puzzle: Puzzle;
|
export let puzzle: Puzzle;
|
||||||
|
|
||||||
|
const chapterId = $page.params.chapterId;
|
||||||
|
|
||||||
$: tags = puzzle.tags?.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name));
|
$: tags = puzzle.tags?.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -20,7 +23,10 @@
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<a class="flex h-full w-full items-center gap-4 p-4" href="/dashboard/puzzles/{puzzle.id}">
|
<a
|
||||||
|
class="flex h-full w-full items-center gap-4 p-4"
|
||||||
|
href="/dashboard/chapters/{chapterId}/puzzle/{puzzle.id}"
|
||||||
|
>
|
||||||
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
<div class="flex w-full flex-col justify-between gap-2 sm:flex-row">
|
||||||
<h2 class="text-base font-semibold">
|
<h2 class="text-base font-semibold">
|
||||||
{puzzle.name}
|
{puzzle.name}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import { cn } from '$lib/Utils';
|
import { cn } from '$lib/Utils';
|
||||||
|
|
||||||
import Badge from '$lib/components/Icons/Badge.svelte';
|
import Badge from '$lib/components/Icons/Badge.svelte';
|
||||||
|
@ -7,6 +8,10 @@
|
||||||
import Dashboard from '$lib/components/Icons/Dashboard.svelte';
|
import Dashboard from '$lib/components/Icons/Dashboard.svelte';
|
||||||
import Leaderboard from '$lib/components/Icons/Leaderboard.svelte';
|
import Leaderboard from '$lib/components/Icons/Leaderboard.svelte';
|
||||||
import Settings from '$lib/components/Icons/Settings.svelte';
|
import Settings from '$lib/components/Icons/Settings.svelte';
|
||||||
|
import Discord from './Icons/Discord.svelte';
|
||||||
|
import Git from './Icons/Git.svelte';
|
||||||
|
import Help from './Icons/Help.svelte';
|
||||||
|
import Mail from './Icons/Mail.svelte';
|
||||||
|
|
||||||
$: path = $page.url.pathname;
|
$: path = $page.url.pathname;
|
||||||
$: isActive = (slug: string) => path === slug;
|
$: isActive = (slug: string) => path === slug;
|
||||||
|
@ -25,8 +30,8 @@
|
||||||
icon: Leaderboard
|
icon: Leaderboard
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Puzzles',
|
name: 'Chapitres',
|
||||||
slug: '/dashboard/puzzles',
|
slug: '/dashboard/chapters',
|
||||||
icon: Code
|
icon: Code
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -83,7 +88,13 @@
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<svelte:component this={item.icon} />
|
<svelte:component
|
||||||
|
this={item.icon}
|
||||||
|
class={cn({
|
||||||
|
'stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0':
|
||||||
|
!isActive(item.slug)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
class={cn('hidden lg:block', {
|
class={cn('hidden lg:block', {
|
||||||
'block sm:hidden': isOpen,
|
'block sm:hidden': isOpen,
|
||||||
|
@ -106,28 +117,108 @@
|
||||||
<div class="px-4 pt-4">
|
<div class="px-4 pt-4">
|
||||||
<ul class="flex flex-col gap-4">
|
<ul class="flex flex-col gap-4">
|
||||||
<li>
|
<li>
|
||||||
<!-- <NavItem
|
<a
|
||||||
item={{
|
on:click={() => {
|
||||||
name: 'Discord',
|
isOpen = false;
|
||||||
slug: 'https://discord.gg/72vuHcwUkE',
|
}}
|
||||||
icon: Icons.Discord,
|
href="/dashboard/help"
|
||||||
disabled: false
|
class="group flex justify-center rounded-md px-3 py-3 text-sm transition-colors duration-150 hover:bg-primary-700 lg:justify-start"
|
||||||
}}
|
>
|
||||||
isOpen={isOpen}
|
<div class="flex items-center gap-2">
|
||||||
onClick={toggle}
|
<Help
|
||||||
/> -->
|
class="stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Aide
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<!-- <NavItem
|
<a
|
||||||
item={{
|
on:click={() => {
|
||||||
name: 'Git',
|
isOpen = false;
|
||||||
slug: 'https://git.peerat.dev/Peer-at-Code',
|
}}
|
||||||
icon: Github,
|
href="mailto:cyberbottle@peerat.dev"
|
||||||
disabled: false
|
class="group flex justify-center rounded-md px-3 py-3 text-sm transition-colors duration-150 hover:bg-primary-700 lg:justify-start"
|
||||||
}}
|
>
|
||||||
isOpen={isOpen}
|
<div class="flex items-center gap-2">
|
||||||
onClick={toggle}
|
<Mail
|
||||||
/> -->
|
class="stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Mail
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
href="//discord.gg/72vuHcwUkE"
|
||||||
|
class="group flex justify-center rounded-md px-3 py-3 text-sm transition-colors duration-150 hover:bg-primary-700 lg:justify-start"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Discord
|
||||||
|
class="fill-highlight-secondary transition-colors duration-150 group-hover:fill-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
href="//git.peerat.dev"
|
||||||
|
class="group flex justify-center rounded-md px-3 py-3 text-sm transition-colors duration-150 hover:bg-primary-700 lg:justify-start"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Git
|
||||||
|
class="stroke-highlight-secondary transition-colors duration-150 group-hover:stroke-primary-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'hidden text-highlight-secondary transition-colors duration-150 group-hover:text-primary lg:block',
|
||||||
|
{
|
||||||
|
'block sm:hidden': isOpen,
|
||||||
|
hidden: !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Git
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
29
src/routes/dashboard/chapters/+page.server.ts
Normal file
29
src/routes/dashboard/chapters/+page.server.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import type { Chapter } from '$lib/types';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, fetch, cookies }) => {
|
||||||
|
await parent();
|
||||||
|
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/chapters`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return {
|
||||||
|
chapters: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const chapters = (await res.json()) as Chapter[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
chapters
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
22
src/routes/dashboard/chapters/+page.svelte
Normal file
22
src/routes/dashboard/chapters/+page.svelte
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Chapter from '$lib/components/Chapter.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
$: chapters = data.chapters;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex w-full flex-col space-y-6">
|
||||||
|
<header class="sticky flex items-center justify-between">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="text-xl font-semibold">Chaptires</h1>
|
||||||
|
<p class="text-muted">
|
||||||
|
Les chapitres sont les différentes parties du jeu. Chaque chapitre est composé de plusieurs
|
||||||
|
puzzles.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{#each chapters as chapter}
|
||||||
|
<Chapter {chapter} />
|
||||||
|
{/each}
|
||||||
|
</section>
|
28
src/routes/dashboard/chapters/[chapterId]/+page.server.ts
Normal file
28
src/routes/dashboard/chapters/[chapterId]/+page.server.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import type { Chapter } from '$lib/types';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, fetch, cookies, params: { chapterId } }) => {
|
||||||
|
await parent();
|
||||||
|
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
const res = await fetch(`${API_URL}/chapter/${chapterId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw redirect(302, '/dashboard/chapters');
|
||||||
|
}
|
||||||
|
|
||||||
|
const chapter = (await res.json()) as Chapter;
|
||||||
|
|
||||||
|
return {
|
||||||
|
chapter
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
97
src/routes/dashboard/chapters/[chapterId]/+page.svelte
Normal file
97
src/routes/dashboard/chapters/[chapterId]/+page.svelte
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Puzzle from '$lib/components/Puzzle.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
$: chapter = data.chapter;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="flex w-full flex-col space-y-6">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex flex-col justify-between md:flex-row md:items-center">
|
||||||
|
<div class="flex items-center gap-x-2">
|
||||||
|
<h1 class="text-xl font-semibold">{chapter.name}</h1>
|
||||||
|
<!-- {!isInEventGroup(chapter) && isBeforeStart(chapter) && (
|
||||||
|
<Dialog
|
||||||
|
key={chapter.id}
|
||||||
|
title={chapter.name}
|
||||||
|
open={isOpen[chapter.id]}
|
||||||
|
onOpenChange={() => handleClick(chapter.id)}
|
||||||
|
trigger={
|
||||||
|
<button class="flex items-center gap-x-2 text-sm font-semibold text-muted hover:text-brand">
|
||||||
|
{/* <Icon name="group-line" /> */}
|
||||||
|
Rejoindre un groupe
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
class="right-96 p-4"
|
||||||
|
>
|
||||||
|
<GroupForm chapter={chapter} token={token} />
|
||||||
|
</Dialog>
|
||||||
|
)} -->
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
{#if chapter.startDate && chapter.endDate}
|
||||||
|
<div class="flex items-center justify-start gap-x-2 md:justify-end">
|
||||||
|
<!-- {/* <Icon name="calendar-line" class="text-sm text-muted" /> */} -->
|
||||||
|
<span class="text-sm text-muted">
|
||||||
|
{new Date(chapter.startDate).toLocaleDateString('fr-FR', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
})}{' '}
|
||||||
|
-{' '}
|
||||||
|
{new Date(chapter.endDate).toLocaleDateString('fr-FR', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="h-1 w-1/4 rounded-lg bg-gray-200">
|
||||||
|
<div class="h-1 w-1/2 rounded-lg bg-gradient-to-tl from-brand to-brand-accent" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="mt-1 flex justify-start gap-x-2">
|
||||||
|
<!-- {isInEventGroup(chapter) && (
|
||||||
|
<>
|
||||||
|
<FilterDifficulty
|
||||||
|
chapters={data}
|
||||||
|
chapter={chapter}
|
||||||
|
filter={filterDifficulty}
|
||||||
|
setFilter={setFilterDifficulty}
|
||||||
|
setFilterChapter={setFilterChapter}
|
||||||
|
/>
|
||||||
|
<FilterTags
|
||||||
|
chapters={data}
|
||||||
|
chapter={chapter}
|
||||||
|
filter={filterTags}
|
||||||
|
setFilter={setFilterTags}
|
||||||
|
setFilterChapter={setFilterChapter}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)} -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="mt-4 flex flex-col space-y-4">
|
||||||
|
{#each chapter.puzzles as puzzle}
|
||||||
|
<Puzzle {puzzle} />
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
<!-- {isInEventGroup(chapter) && (
|
||||||
|
<ul class="mt-4 flex flex-col space-y-4">
|
||||||
|
{filteredData &&
|
||||||
|
filteredData
|
||||||
|
.sort((a, b) => a.scoreMax - b.scoreMax)
|
||||||
|
.map((puzzle) => (
|
||||||
|
<PuzzleProp key={puzzle.id} puzzle={puzzle} chapter={chapter} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)} -->
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { API_URL } from '$env/static/private';
|
||||||
|
import { redirect, type Actions } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, fetch, cookies, params: { chapterId } }) => {
|
||||||
|
await parent();
|
||||||
|
throw redirect(303, chapterId ? `/dashboard/chapters/${chapterId}` : `/dashboard/chapters`);
|
||||||
|
}) satisfies PageServerLoad;
|
|
@ -1,18 +1,35 @@
|
||||||
import type { PageServerLoad } from './$types';
|
|
||||||
import { API_URL } from '$env/static/private';
|
import { API_URL } from '$env/static/private';
|
||||||
import { error, redirect, type Actions } from '@sveltejs/kit';
|
import { error, redirect, type Actions } from '@sveltejs/kit';
|
||||||
import type Puzzle from '$lib/components/Puzzle.svelte';
|
import type Puzzle from '$lib/components/Puzzle.svelte';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ parent, fetch, cookies, params: { id } }) => {
|
export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzleId } }) => {
|
||||||
await parent();
|
await parent();
|
||||||
|
|
||||||
const session = cookies.get('session');
|
const session = cookies.get('session');
|
||||||
|
|
||||||
if (isNaN(parseInt(id))) {
|
if (isNaN(parseInt(puzzleId))) {
|
||||||
throw redirect(303, '/dashboard/puzzles');
|
throw redirect(303, `/dashboard/chapters/${chapterId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}/puzzle/${id}`, {
|
// check if puzzle is from the chapter
|
||||||
|
let res = await fetch(`${API_URL}/chapter/${chapterId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${session}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw redirect(303, `/dashboard/chapters`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { puzzles } = (await res.json()) as { puzzles: Puzzle[] };
|
||||||
|
|
||||||
|
if (!puzzles.some((puzzle) => puzzle.id === parseInt(puzzleId))) {
|
||||||
|
throw redirect(303, `/dashboard/chapters/${chapterId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = await fetch(`${API_URL}/puzzle/${puzzleId}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${session}`
|
Authorization: `Bearer ${session}`
|
||||||
}
|
}
|
|
@ -24,6 +24,8 @@
|
||||||
)} -->
|
)} -->
|
||||||
<ul class="flex flex-col space-y-2">
|
<ul class="flex flex-col space-y-2">
|
||||||
{#each groups as group}
|
{#each groups as group}
|
||||||
|
{@const players = group.players.sort((a, b) => b.score - a.score)}
|
||||||
|
{@const last = players[players.length - 1]}
|
||||||
<li class="flex justify-between space-x-2">
|
<li class="flex justify-between space-x-2">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<span
|
<span
|
||||||
|
@ -35,15 +37,10 @@
|
||||||
<div class="flex flex-col gap-x-2 sm:flex-row sm:items-center">
|
<div class="flex flex-col gap-x-2 sm:flex-row sm:items-center">
|
||||||
<span class="text-lg">{group.name}</span>
|
<span class="text-lg">{group.name}</span>
|
||||||
<span class="text-sm text-highlight-secondary">
|
<span class="text-sm text-highlight-secondary">
|
||||||
<!-- {group.players && group.players.length > 1
|
{#if players.length > 1}
|
||||||
? group.players
|
{#each players as player}
|
||||||
.map((player) => player.pseudo || 'Anonyme')
|
{player.pseudo || 'Anonyme'}{#if player !== last}, {/if}
|
||||||
.sort((a, b) => a.localeCompare(b))
|
{' '}
|
||||||
.join(', ')
|
|
||||||
: group.players[0].pseudo} -->
|
|
||||||
{#if group.players.length > 1}
|
|
||||||
{#each group.players as player}
|
|
||||||
{player.pseudo || 'Anonyme'} {' '}
|
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
{group.players[0].pseudo}
|
{group.players[0].pseudo}
|
||||||
|
@ -54,8 +51,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-semibold">Essai{group.players.reduce((a, b) => a + b.tries, 0) || 0 ? 's' : ''}</span>
|
<span class="text-sm font-semibold"
|
||||||
<span class="text-lg text-highlight-secondary">{group.players.reduce((a, b) => a + b.tries, 0) || 0}</span>
|
>Essai{group.players.reduce((a, b) => a + b.tries, 0) || 0 ? 's' : ''}</span
|
||||||
|
>
|
||||||
|
<span class="text-lg text-highlight-secondary"
|
||||||
|
>{group.players.reduce((a, b) => a + b.tries, 0) || 0}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-semibold">Score</span>
|
<span class="text-sm font-semibold">Score</span>
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import type { ActionData } from './$types';
|
import type { ActionData } from './$types';
|
||||||
|
|
||||||
import Button from '$lib/components/ui/Button.svelte';
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
import Input from '$lib/components/ui/Input.svelte';
|
import Input from '$lib/components/ui/Input.svelte';
|
||||||
|
|
||||||
|
import plausible from '$lib/stores/Plausible';
|
||||||
|
|
||||||
$: user = $page.data.user;
|
$: user = $page.data.user;
|
||||||
|
|
||||||
export let form: ActionData;
|
export let form: ActionData;
|
||||||
|
|
||||||
$: console.log(form);
|
$: optedOut = $plausible;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="flex flex-col gap-4" method="POST" use:enhance>
|
<form class="flex flex-col gap-4" method="POST" use:enhance>
|
||||||
|
@ -35,6 +37,23 @@
|
||||||
value={user?.description}
|
value={user?.description}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label for="optout"> Ne pas me tracer de manière anonyme </label>
|
||||||
|
<input
|
||||||
|
class="h-4 w-4"
|
||||||
|
name="optout"
|
||||||
|
type="checkbox"
|
||||||
|
value={optedOut}
|
||||||
|
on:change={() => plausible.set(!optedOut)}
|
||||||
|
checked={optedOut}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-highlight-secondary">
|
||||||
|
Nous utilisons Plausible pour analyser l'utilisation de notre site web de manière anonyme.
|
||||||
|
</p>
|
||||||
|
|
||||||
<Button variant="brand">
|
<Button variant="brand">
|
||||||
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
|
||||||
Modifier
|
Modifier
|
||||||
|
|
Loading…
Add table
Reference in a new issue