Compare commits

...

3 commits

Author SHA1 Message Date
glazk0
8ba01a5019
feat: Dockerfile improvement 2024-02-19 22:06:20 +01:00
glazk0
f1889e0eab
fix: utils & register / confirmation issue 2024-02-19 22:06:12 +01:00
glazk0
0eabfa812f
chore: deps update 2024-02-19 22:05:22 +01:00
49 changed files with 1664 additions and 861 deletions

View file

@ -1,35 +1,37 @@
# Base Stage
FROM node:21-slim AS base
FROM node:18-alpine AS base ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN npm i -g pnpm RUN corepack enable
FROM base AS dependencies
WORKDIR /app WORKDIR /app
COPY package.json pnpm-lock.yaml ./ COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile # Dependencies Stage for Production
FROM base AS deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
# Build Stage
FROM base AS build FROM base AS build
WORKDIR /app RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
COPY . . COPY . .
COPY --from=dependencies /app/node_modules ./node_modules RUN pnpm run build
RUN pnpm build
RUN pnpm prune --prod
# Deploy Stage
FROM base AS deploy FROM base AS deploy
WORKDIR /app COPY --from=deps /app/node_modules /app/node_modules
COPY --from=build /app/build /app/build
COPY --from=build /app/build ./build # Run as non-root user
COPY --from=build /app/node_modules ./node_modules USER node
COPY --from=build /app/package.json ./package.json
ARG PORT=3000 ARG PORT=3000
@ -37,4 +39,4 @@ ENV NODE_ENV=production PORT=$PORT
EXPOSE $PORT EXPOSE $PORT
CMD ["node", "build"] CMD ["node", "build"]

View file

@ -17,38 +17,38 @@
}, },
"dependencies": { "dependencies": {
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.1.0",
"lucide-svelte": "^0.279.0", "lucide-svelte": "^0.330.0",
"marked": "^7.0.5", "marked": "^12.0.0",
"svelte-boring-avatars": "^1.2.4", "svelte-boring-avatars": "^1.2.5",
"tailwind-merge": "^1.14.0" "tailwind-merge": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {
"@melt-ui/pp": "^0.1.4", "@melt-ui/pp": "^0.3.0",
"@melt-ui/svelte": "^0.50.1", "@melt-ui/svelte": "^0.73.0",
"@playwright/test": "^1.40.0", "@playwright/test": "^1.41.2",
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/kit": "^1.27.6", "@sveltejs/kit": "^2.5.0",
"@types/marked": "^5.0.2", "@sveltejs/vite-plugin-svelte": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^7.0.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.17",
"eslint": "^8.54.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.35.1",
"postcss": "^8.4.31", "postcss": "^8.4.35",
"prettier": "^2.8.8", "prettier": "^3.2.5",
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^3.1.2",
"prettier-plugin-tailwindcss": "^0.4.1", "prettier-plugin-tailwindcss": "^0.5.11",
"svelte": "^4.2.7", "svelte": "^4.2.10",
"svelte-check": "^3.6.1", "svelte-check": "^3.6.4",
"svelte-sequential-preprocessor": "^2.0.1", "svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-superforms": "^1.10.2", "sveltekit-superforms": "^2.1.0",
"tailwindcss": "^3.3.5", "tailwindcss": "^3.4.1",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"typescript": "^5.3.2", "typescript": "^5.3.3",
"vite": "^4.5.0", "vite": "^5.1.1",
"vitest": "^0.32.4", "vitest": "^1.2.2",
"zod": "^3.22.4" "zod": "^3.22.4"
} }
} }

2071
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -80,10 +80,13 @@
@layer base { @layer base {
* { * {
box-sizing: border-box;
scroll-behavior: smooth;
@apply border-border text-white; @apply border-border text-white;
} }
body { body {
@apply text-foreground; @apply text-foreground bg-gradient-to-b from-primary-800 to-primary-900;
} }
} }
@ -91,6 +94,7 @@
.console { .console {
@apply relative top-0.5 inline-block; @apply relative top-0.5 inline-block;
} }
input:-webkit-autofill, input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,
input:-webkit-autofill:focus, input:-webkit-autofill:focus,

4
src/app.d.ts vendored
View file

@ -7,10 +7,10 @@ declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}
interface Locals { interface Locals {
user?: User; user: User | null;
} }
interface PageData { interface PageData {
user?: User; user: User | null;
} }
// interface Platform {} // interface Platform {}
} }

View file

@ -1,28 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr" class="scroll-smooth bg-gradient-to-b from-primary-800 to-primary-900"> <html lang="fr">
<head> <head>
<meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="%sveltekit.assets%/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="%sveltekit.assets%/assets/icons/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="%sveltekit.assets%/assets/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="%sveltekit.assets%/assets/icons/favicon-16x16.png"
/>
<meta name="viewport" content="width=device-width" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover" class="relative min-h-screen"> <body data-sveltekit-preload-data="hover" class="relative min-h-screen">

View file

@ -4,24 +4,27 @@ import { API_URL } from '$env/static/private';
import type { User } from '$lib/types'; import type { User } from '$lib/types';
export const handle = (async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
const session = event.cookies.get('session'); const session = event.cookies.get('session');
if (session) { if (!session) {
const res = await fetch(`${API_URL}/player/`, { event.locals.user = null;
headers: { return resolve(event);
Authorization: `Bearer ${session}`
}
});
if (res.ok) {
const user = (await res.json()) as User;
event.locals.user = user;
} else {
event.locals.user = undefined;
event.cookies.delete('session');
}
} }
return await resolve(event); const res = await fetch(`${API_URL}/player/`, {
}) satisfies Handle; headers: {
Authorization: `Bearer ${session}`
}
});
if (res.ok) {
const user = (await res.json()) as User;
event.locals.user = user;
} else {
event.locals.user = null;
event.cookies.delete('session', { path: '/' });
}
return resolve(event);
};

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/Utils'; import { cn } from '$lib/utils';
export let name: string; export let name: string;
export let src: string; export let src: string;

View file

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/Utils';
import type { Chapter } from '$lib/types';
import { Trophy } from 'lucide-svelte'; import { Trophy } from 'lucide-svelte';
import ChevronRight from './Icons/ChevronRight.svelte'; import type { Chapter } from '$lib/types';
import Button from './ui/Button.svelte'; import { cn } from '$lib/utils';
import ChevronRight from './Icons/ChevronRight.svelte';
export let chapter: Chapter; export let chapter: Chapter;
</script> </script>

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;
@ -16,4 +16,4 @@
><path ><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" 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 /></svg
> >

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
let className: string | undefined | null = undefined; let className: string | undefined | null = undefined;

View file

@ -0,0 +1,49 @@
<script lang="ts">
import { page } from '$app/stores';
import { siteConfig } from '$lib/config/site';
</script>
<svelte:head>
{#key $page.url.pathname}
<title>{siteConfig.title}</title>
<meta name="title" content={siteConfig.title} />
<meta name="description" content={siteConfig.description} />
<meta name="keywords" content={siteConfig.keywords.join(',')} />
<meta name="author" content={siteConfig.author} />
<meta name="theme-color" content={siteConfig.themeColor} />
<meta name="robots" content="index,follow" />
<meta itemprop="name" content={siteConfig.title} />
<meta itemprop="description" content={siteConfig.description} />
<meta itemprop="image" content={siteConfig.imageUrl} />
<meta property="og:site_name" content={siteConfig.name} />
<meta property="og:title" content={siteConfig.title} />
<meta property="og:description" content={siteConfig.description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={siteConfig.url + $page.url.pathname} />
<meta property="og:image" content={siteConfig.imageUrl} />
<meta property="og:image:alt" content={siteConfig.title} />
<meta property="og:locale" content={siteConfig.locale} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={siteConfig.url} />
<meta name="twitter:title" content={siteConfig.title} />
<meta name="twitter:description" content={siteConfig.description} />
<meta name="twitter:image" content={siteConfig.imageUrl} />
<meta name="twitter:image:alt" content={siteConfig.title} />
<meta name="twitter:creator" content={siteConfig.twitter.creator} />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
{/key}
</svelte:head>

View file

@ -1,6 +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 type { Puzzle } from '$lib/types'; import type { Puzzle } from '$lib/types';
import ChevronRight from './Icons/ChevronRight.svelte'; import ChevronRight from './Icons/ChevronRight.svelte';

View file

@ -1,7 +1,7 @@
<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';
import Code from '$lib/components/Icons/Code.svelte'; import Code from '$lib/components/Icons/Code.svelte';

View file

@ -2,9 +2,7 @@
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, cva } from 'class-variance-authority'; import { type VariantProps, cva } from 'class-variance-authority';
import { cn } from '$lib'; import { cn } from '$lib/utils';
// TODO: Remove this
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { HTMLInputAttributes } from 'svelte/elements'; import type { HTMLInputAttributes } from 'svelte/elements';
import { cn } from '$lib'; import { cn } from '$lib/utils';
type FormInputEvent<T extends Event = Event> = T & { type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement; currentTarget: EventTarget & HTMLInputElement;

16
src/lib/config/site.ts Normal file
View file

@ -0,0 +1,16 @@
export const siteConfig = {
name: 'Peer-at Code',
url: 'https://app.peerat.dev',
title: 'Peer-at Code',
description: 'Apprendre la programmation et la cybersécurité en s\'amusant.',
imageUrl: 'https://my-site.com/images/og-image.jpg',
keywords: ['peerat', 'code', 'cybersecurite', 'programmation', 'apprendre en s\'amusant'],
author: 'peerat',
locale: 'fr',
twitter: {
creator: '@peerat'
},
themeColor: '#110F15'
};
export type SiteConfig = typeof siteConfig;

View file

@ -1 +0,0 @@
export * from './Utils';

View file

@ -1 +1 @@
export * from './Database'; export * from './database';

View file

@ -1,6 +1,6 @@
import { clsx, type ClassValue } from 'clsx'; import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }

View file

@ -1,43 +1,14 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
import { page } from '$app/stores'; import Metadata from '$lib/components/Metadata.svelte';
import Toaster from '$lib/components/Toaster.svelte'; import Toaster from '$lib/components/Toaster.svelte';
$: origin = $page.url.origin;
$: domain = $page.url.hostname;
</script> </script>
<Metadata />
<svelte:head> <svelte:head>
<title>Peer-at Code</title> <script defer data-domain="app.peerat.dev" src="https://plosibl.peerat.dev/js/script.js"></script>
<meta name="title" content="Peer-at Code" />
<meta name="description" content="Apprendre la programmation et la cybersécurité en s'amusant." />
<meta name="theme-color" content="#110F15" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="French" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={origin} />
<meta property="og:title" content="Peer-at Code" />
<meta
property="og:description"
content="Apprendre la programmation et la cybersécurité en s'amusant."
/>
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={origin} />
<meta property="twitter:title" content="Peer-at Code" />
<meta
property="twitter:description"
content="Apprendre la programmation et la cybersécurité en s'amusant."
/>
<script defer data-domain={domain} src="https://plosibl.peerat.dev/js/script.js"></script>
</svelte:head> </svelte:head>
<Toaster /> <Toaster />

View file

@ -2,6 +2,6 @@ import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => { export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard'); if (user) redirect(303, '/dashboard');
throw redirect(303, '/sign-in'); redirect(303, '/sign-in');
}) satisfies PageServerLoad; }) satisfies PageServerLoad;

View file

@ -1,2 +0,0 @@
<script lang="ts">
</script>

View file

@ -2,5 +2,5 @@ import { redirect, type ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = async ({ locals: { user }, parent }) => { export const load: ServerLoad = async ({ locals: { user }, parent }) => {
await parent(); await parent();
if (!user) throw redirect(303, '/sign-in'); if (!user) redirect(303, '/sign-in');
}; };

View file

@ -17,13 +17,13 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId } }) =
}); });
if (!res.ok) { if (!res.ok) {
throw redirect(302, '/dashboard/chapters'); redirect(302, '/dashboard/chapters');
} }
const chapter = (await res.json()) as Chapter; const chapter = (await res.json()) as Chapter;
if (!chapter || !chapter.show) { if (!chapter || !chapter.show) {
throw redirect(302, '/dashboard/chapters'); redirect(302, '/dashboard/chapters');
} }
return { return {

View file

@ -4,5 +4,5 @@ import type { PageServerLoad } from './$types';
export const load = (async ({ parent, params: { chapterId } }) => { export const load = (async ({ parent, params: { chapterId } }) => {
await parent(); await parent();
throw redirect(303, chapterId ? `/dashboard/chapters/${chapterId}` : `/dashboard/chapters`); redirect(303, chapterId ? `/dashboard/chapters/${chapterId}` : `/dashboard/chapters`);
}) satisfies PageServerLoad; }) satisfies PageServerLoad;

View file

@ -11,7 +11,7 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzl
const session = cookies.get('session'); const session = cookies.get('session');
if (isNaN(parseInt(puzzleId))) { if (isNaN(parseInt(puzzleId))) {
throw redirect(303, `/dashboard/chapters/${chapterId}`); redirect(303, `/dashboard/chapters/${chapterId}`);
} }
let res = await fetch(`${API_URL}/chapter/${chapterId}`, { let res = await fetch(`${API_URL}/chapter/${chapterId}`, {
@ -21,20 +21,20 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzl
}); });
if (!res.ok) { if (!res.ok) {
throw redirect(303, `/dashboard/chapters`); redirect(303, `/dashboard/chapters`);
} }
const chapter = (await res.json()) as Chapter; const chapter = (await res.json()) as Chapter;
if (!chapter || !chapter.show) { if (!chapter || !chapter.show) {
throw redirect(303, `/dashboard/chapters`); redirect(303, `/dashboard/chapters`);
} }
if ( if (
!chapter.puzzles.some((puzzle) => puzzle.id === parseInt(puzzleId)) || !chapter.puzzles.some((puzzle) => puzzle.id === parseInt(puzzleId)) ||
!chapter.puzzles.find((puzzle) => puzzle.id === parseInt(puzzleId))?.show !chapter.puzzles.find((puzzle) => puzzle.id === parseInt(puzzleId))?.show
) { ) {
throw redirect(303, `/dashboard/chapters/${chapterId}`); redirect(303, `/dashboard/chapters/${chapterId}`);
} }
res = await fetch(`${API_URL}/puzzle/${puzzleId}`, { res = await fetch(`${API_URL}/puzzle/${puzzleId}`, {
@ -44,13 +44,13 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzl
}); });
if (!res.ok) { if (!res.ok) {
throw error(404, 'Puzzle not found'); error(404, 'Puzzle not found');
} }
const puzzle = await res.json(); const puzzle = await res.json();
if (!puzzle) { if (!puzzle) {
throw error(404, 'Puzzle not found'); error(404, 'Puzzle not found');
} }
return { return {
@ -62,6 +62,6 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId, puzzl
export const actions = { export const actions = {
default: async ({ params }) => { default: async ({ params }) => {
throw redirect(303, `/dashboard/chapters/${params.chapterId}/puzzle/${params.puzzleId}`); redirect(303, `/dashboard/chapters/${params.chapterId}/puzzle/${params.puzzleId}`);
} }
} satisfies Actions; } satisfies Actions;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;

View file

@ -10,7 +10,7 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId } }) =
await parent(); await parent();
if (!chapterId) { if (!chapterId) {
throw redirect(303, '/dashboard'); redirect(303, '/dashboard');
} }
const session = cookies.get('session'); const session = cookies.get('session');
@ -22,13 +22,13 @@ export const load = (async ({ parent, fetch, cookies, params: { chapterId } }) =
}); });
if (!res.ok) { if (!res.ok) {
throw redirect(303, '/dashboard'); redirect(303, '/dashboard');
} }
const leaderboard = (await res.json()) as LeaderboardEvent; const leaderboard = (await res.json()) as LeaderboardEvent;
if (!leaderboard || !leaderboard.groups.length) { if (!leaderboard || !leaderboard.groups.length) {
throw redirect(303, '/dashboard'); redirect(303, '/dashboard');
} }
return { return {

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib'; import { cn } from '$lib/utils';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;

View file

@ -7,7 +7,7 @@
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'; import plausible from '$lib/stores/plausible';
import { addToast } from '$lib/components/Toaster.svelte'; import { addToast } from '$lib/components/Toaster.svelte';
export let data: PageData; export let data: PageData;

View file

@ -6,6 +6,7 @@ import type { PageServerLoad } from './$types';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import { z } from 'zod'; import { z } from 'zod';
import { zod } from 'sveltekit-superforms/adapters';
const forgotSchema = z.object({ const forgotSchema = z.object({
email: z email: z
@ -43,9 +44,9 @@ const confirmationSchema = z.object({
}); });
export const load = (async ({ locals: { user } }) => { export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard'); if (user) redirect(303, '/dashboard');
const form = await superValidate(forgotSchema); const form = await superValidate(zod(forgotSchema));
return { return {
form form
@ -54,7 +55,7 @@ export const load = (async ({ locals: { user } }) => {
export const actions = { export const actions = {
forgot: async ({ request, cookies }) => { forgot: async ({ request, cookies }) => {
const form = await superValidate(request, forgotSchema); const form = await superValidate(request, zod(forgotSchema));
if (!form.valid) { if (!form.valid) {
return fail(400, { form }); return fail(400, { form });
@ -77,8 +78,6 @@ export const actions = {
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
console.log(res);
if (res.ok) { if (res.ok) {
const token = res.headers.get('Authorization')?.split('Bearer ')[1]; const token = res.headers.get('Authorization')?.split('Bearer ')[1];
@ -87,21 +86,21 @@ export const actions = {
path: '/' path: '/'
}); });
throw redirect(303, '/dashboard'); redirect(303, '/dashboard');
} }
return { return {
form form
}; };
} }
form.errors.passwd = ['Code invalide ou expiré']; form.errors.code = ['Code invalide ou expiré'];
return fail(400, { return fail(400, {
form form
}); });
}, },
confirmation: async ({ request, cookies }) => { confirmation: async ({ request, cookies }) => {
const form = await superValidate(request, confirmationSchema); const form = await superValidate(request, zod(confirmationSchema));
if (!form.valid) { if (!form.valid) {
return fail(400, { form }); return fail(400, { form });
@ -125,7 +124,7 @@ export const actions = {
path: '/' path: '/'
}); });
throw redirect(303, '/dashboard'); redirect(303, '/dashboard');
} }
form.errors.code = [`Une erreur s'est produite (${res.status} ${res.statusText})`]; form.errors.code = [`Une erreur s'est produite (${res.status} ${res.statusText})`];

View file

@ -30,7 +30,6 @@
}); });
break; break;
} }
submitting = false; submitting = false;
} }
}); });
@ -58,7 +57,9 @@
type="email" type="email"
placeholder="philipzcwbarlow@peerat.dev" placeholder="philipzcwbarlow@peerat.dev"
autocomplete="off" autocomplete="off"
autocorrect="off"
required required
aria-invalid={$errors.email ? 'true' : undefined}
/> />
{#if $errors.email}<span class="text-sm text-red-500">{$errors.email}</span>{/if} {#if $errors.email}<span class="text-sm text-red-500">{$errors.email}</span>{/if}
@ -69,12 +70,26 @@
duration: 300 duration: 300
}} }}
> >
<label for="passwd"> Mot de passe </label> <label for="passwd"> Nouveau Mot de passe </label>
<Input name="passwd" placeholder="************" type="password" /> <Input
bind:value={$form.passwd}
name="passwd"
placeholder="************"
type="password"
required
aria-invalid={$errors.passwd ? 'true' : undefined}
/>
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if} {#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
<label for="code"> Code </label> <label for="code"> Code </label>
<Input name="code" placeholder="1234" type="text" /> <Input
bind:value={$form.code}
name="code"
placeholder="1234"
type="text"
required
aria-invalid={$errors.code ? 'true' : undefined}
/>
{#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if} {#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if}
</div> </div>
{/if} {/if}
@ -92,9 +107,9 @@
</li> </li>
{#if confirmation} {#if confirmation}
<li> <li>
<button formaction="?/register" class="text-highlight-secondary hover:text-brand" <button formaction="?/register" class="text-highlight-secondary hover:text-brand">
>Pas reçu ?</button Renvoyer le code
> </button>
</li> </li>
{/if} {/if}
</ul> </ul>

View file

@ -9,5 +9,5 @@ export const load: ServerLoad = async ({ cookies, locals }) => {
locals.user = undefined; locals.user = undefined;
throw redirect(303, '/'); redirect(303, '/');
}; };

View file

@ -1,30 +1,32 @@
import { API_URL } from '$env/static/private'; import { API_URL } from '$env/static/private';
import { redirect, type Actions, fail } from '@sveltejs/kit'; import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import { z } from 'zod'; import { z } from 'zod';
const schema = z.object({
pseudo: z.string().trim(),
passwd: z.string()
});
export const load = (async ({ locals: { user } }) => { export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard'); if (user) redirect(303, '/dashboard');
const form = await superValidate(schema); const form = await superValidate(zod(schema));
return { return {
form form
}; };
}) satisfies PageServerLoad; }) satisfies PageServerLoad;
const schema = z.object({
pseudo: z.string().trim(),
passwd: z.string()
});
export const actions = { export const actions = {
default: async ({ request, cookies }) => { default: async ({ request, cookies }) => {
const form = await superValidate(request, schema); const form = await superValidate(request, zod(schema));
if (!form.valid) { if (!form.valid) {
return fail(400, { form }); return fail(400, { form });
@ -40,7 +42,12 @@ export const actions = {
if (res.ok) { if (res.ok) {
const token = res.headers.get('Authorization')?.split(' ')[1]; const token = res.headers.get('Authorization')?.split(' ')[1];
if (!token) throw new Error('No token found'); if (!token) {
form.errors.passwd = ["Une erreur est survenue, veuillez réessayer plus tard"];
return fail(500, { form });
}
cookies.set('session', token, { cookies.set('session', token, {
path: '/' path: '/'

View file

@ -6,6 +6,7 @@ import type { PageServerLoad } from './$types';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import { z } from 'zod'; import { z } from 'zod';
import { zod } from 'sveltekit-superforms/adapters';
const registerSchema = z.object({ const registerSchema = z.object({
email: z email: z
@ -50,9 +51,9 @@ const confirmationSchema = z.object({
}); });
export const load = (async ({ locals: { user } }) => { export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard'); if (user) redirect(303, '/dashboard');
const form = await superValidate(registerSchema); const form = await superValidate(zod(registerSchema));
return { return {
form form
@ -61,7 +62,7 @@ export const load = (async ({ locals: { user } }) => {
export const actions = { export const actions = {
register: async ({ request }) => { register: async ({ request }) => {
const form = await superValidate(request, registerSchema); const form = await superValidate(request, zod(registerSchema));
if (!form.valid) { if (!form.valid) {
return fail(400, { form }); return fail(400, { form });
@ -99,7 +100,7 @@ export const actions = {
}); });
}, },
confirmation: async ({ request, cookies }) => { confirmation: async ({ request, cookies }) => {
const form = await superValidate(request, confirmationSchema); const form = await superValidate(request, zod(confirmationSchema));
if (!form.valid) { if (!form.valid) {
return fail(400, { form }); return fail(400, { form });

View file

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { Loader2 } from 'lucide-svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { superForm } from 'sveltekit-superforms/client'; import { superForm } from 'sveltekit-superforms/client';
import { Loader2 } from 'lucide-svelte';
import type { PageData, Snapshot } from './$types'; import type { PageData, Snapshot } from './$types';
import { addToast } from '$lib/components/Toaster.svelte';
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 { addToast } from '$lib/components/Toaster.svelte';
export let data: PageData; export let data: PageData;
@ -63,6 +63,7 @@
type="email" type="email"
placeholder="philipzcwbarlow@peerat.dev" placeholder="philipzcwbarlow@peerat.dev"
autocomplete="off" autocomplete="off"
autocorrect="off"
required required
aria-invalid={$errors.email ? 'true' : undefined} aria-invalid={$errors.email ? 'true' : undefined}
/> />
@ -75,6 +76,7 @@
type="text" type="text"
placeholder="Philip" placeholder="Philip"
autocomplete="off" autocomplete="off"
autocorrect="off"
required required
aria-invalid={$errors.firstname ? 'true' : undefined} aria-invalid={$errors.firstname ? 'true' : undefined}
/> />
@ -87,6 +89,7 @@
type="text" type="text"
placeholder="Barlow" placeholder="Barlow"
autocomplete="off" autocomplete="off"
autocorrect="off"
required required
aria-invalid={$errors.lastname ? 'true' : undefined} aria-invalid={$errors.lastname ? 'true' : undefined}
/> />
@ -99,6 +102,7 @@
type="text" type="text"
placeholder="Cypher Wolf" placeholder="Cypher Wolf"
autocomplete="off" autocomplete="off"
autocorrect="off"
required required
aria-invalid={$errors.pseudo ? 'true' : undefined} aria-invalid={$errors.pseudo ? 'true' : undefined}
/> />
@ -116,6 +120,7 @@
name="passwd" name="passwd"
placeholder="************" placeholder="************"
type="password" type="password"
required
aria-invalid={$errors.passwd ? 'true' : undefined} aria-invalid={$errors.passwd ? 'true' : undefined}
/> />
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if} {#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
@ -125,6 +130,7 @@
name="code" name="code"
placeholder="1234" placeholder="1234"
type="text" type="text"
required
aria-invalid={$errors.code ? 'true' : undefined} aria-invalid={$errors.code ? 'true' : undefined}
/> />
{#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if} {#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if}

View file

@ -1,7 +1,8 @@
import { preprocessMeltUI } from '@melt-ui/pp'; import { preprocessMeltUI } from '@melt-ui/pp';
import sequence from 'svelte-sequential-preprocessor';
import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import sequence from 'svelte-sequential-preprocessor';
/** @type {import('@sveltejs/kit').Config}*/ /** @type {import('@sveltejs/kit').Config}*/
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors

View file

@ -5,5 +5,8 @@ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit()],
test: { test: {
include: ['src/**/*.{test,spec}.{js,ts}'] include: ['src/**/*.{test,spec}.{js,ts}']
},
define: {
SUPERFORMS_LEGACY: true
} }
}); });