): string => {
+ return Object.keys(style).reduce((str, key) => {
+ if (style[key] === undefined) return str;
+ return str + `${key}:${style[key]};`;
+ }, '');
+ };
- return {
- duration: params.duration ?? 200,
- delay: 0,
- css: (t) => {
- const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
- const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
- const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
+ return {
+ duration: params.duration ?? 200,
+ delay: 0,
+ css: (t) => {
+ const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
+ const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
+ const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
- return styleToString({
- transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
- opacity: t
- });
- },
- easing: cubicOut
- };
-};
\ No newline at end of file
+ return styleToString({
+ transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
+ opacity: t
+ });
+ },
+ easing: cubicOut
+ };
+};
+
+export const serializeNonPOJOs = (obj: unknown) => {
+ return structuredClone(obj);
+};
diff --git a/apps/web/src/routes/(auth)/+layout.server.ts b/apps/web/src/routes/(auth)/+layout.server.ts
new file mode 100644
index 0000000..359b4bc
--- /dev/null
+++ b/apps/web/src/routes/(auth)/+layout.server.ts
@@ -0,0 +1,10 @@
+import { redirect } from '@sveltejs/kit';
+import type { LayoutServerLoad } from './$types';
+
+export const load = (async ({ locals }) => {
+ if (locals.pocketBase.authStore.isValid) {
+ throw redirect(303, '/');
+ }
+
+ return {};
+}) satisfies LayoutServerLoad;
diff --git a/apps/web/src/routes/(auth)/login/+page.server.ts b/apps/web/src/routes/(auth)/login/+page.server.ts
new file mode 100644
index 0000000..20fc83f
--- /dev/null
+++ b/apps/web/src/routes/(auth)/login/+page.server.ts
@@ -0,0 +1,48 @@
+import { redirect } from '@sveltejs/kit';
+
+export const actions = {
+ default: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+ if (locals.pocketBase.authStore.isValid) {
+ return;
+ }
+
+ const formData = await request.formData();
+
+ const email = formData.get('email');
+ const password = formData.get('password');
+
+ try {
+ if (typeof email !== 'string') {
+ throw new Error('Email must be a string');
+ }
+
+ if (email.length < 5) {
+ throw new Error('Please enter a valid e-mail address');
+ }
+
+ if (typeof password !== 'string') {
+ throw new Error('Password must be a string');
+ }
+
+ if (password.length < 8) {
+ throw new Error('Password must be at least 8 characters in length');
+ }
+
+ await locals.pocketBase.collection('users').authWithPassword(email, password);
+ } catch (error) {
+ console.error(error);
+
+ if (!(error instanceof Error)) {
+ return {
+ email,
+ password,
+ error: 'Unknown error occured when signing up user'
+ };
+ }
+
+ return { error: error.message, email, password };
+ }
+
+ throw redirect(303, '/');
+ }
+};
diff --git a/apps/web/src/routes/(auth)/login/+page.svelte b/apps/web/src/routes/(auth)/login/+page.svelte
new file mode 100644
index 0000000..9bdf34d
--- /dev/null
+++ b/apps/web/src/routes/(auth)/login/+page.svelte
@@ -0,0 +1,25 @@
+
+
+Log in
+
+
+
+{#if form?.error}
+ {form.error}
+{/if}
+
+Don't have an account?
+
+Sign up
diff --git a/apps/web/src/routes/(auth)/signup/+page.server.ts b/apps/web/src/routes/(auth)/signup/+page.server.ts
new file mode 100644
index 0000000..ae464a3
--- /dev/null
+++ b/apps/web/src/routes/(auth)/signup/+page.server.ts
@@ -0,0 +1,65 @@
+import { redirect } from '@sveltejs/kit';
+
+export const actions = {
+ default: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+ if (locals.pocketBase.authStore.isValid) {
+ return;
+ }
+
+ const formData = await request.formData();
+
+ const name = formData.get('name');
+ const email = formData.get('email');
+ const password = formData.get('password');
+
+ try {
+ if (typeof name !== 'string') {
+ throw new Error('Name must be a string');
+ }
+
+ if (name.length === 0) {
+ throw new Error('Please enter a valid name');
+ }
+
+ if (typeof email !== 'string') {
+ throw new Error('Email must be a string');
+ }
+
+ if (email.length < 5) {
+ throw new Error('Please enter a valid e-mail address');
+ }
+
+ if (typeof password !== 'string') {
+ throw new Error('Password must be a string');
+ }
+
+ if (password.length < 8) {
+ throw new Error('Password must be at least 8 characters in length');
+ }
+
+ await locals.pocketBase.collection('users').create({
+ email,
+ password,
+ name,
+ passwordConfirm: password
+ });
+
+ await locals.pocketBase.collection('users').authWithPassword(email, password);
+ } catch (error) {
+ console.error(error);
+
+ if (!(error instanceof Error)) {
+ return {
+ name,
+ email,
+ password,
+ error: 'Unknown error occured when signing up user'
+ };
+ }
+
+ return { error: error.message, name, email, password };
+ }
+
+ throw redirect(303, '/');
+ }
+};
diff --git a/apps/web/src/routes/(auth)/signup/+page.svelte b/apps/web/src/routes/(auth)/signup/+page.svelte
new file mode 100644
index 0000000..e1606db
--- /dev/null
+++ b/apps/web/src/routes/(auth)/signup/+page.svelte
@@ -0,0 +1,23 @@
+
+
+Sign up
+
+
+
+{#if form?.error}
+ {form.error}
+{/if}
+
+Already have an account?
+
+Log in
diff --git a/apps/web/src/routes/(board)/+layout.server.ts b/apps/web/src/routes/(board)/+layout.server.ts
new file mode 100644
index 0000000..f144f49
--- /dev/null
+++ b/apps/web/src/routes/(board)/+layout.server.ts
@@ -0,0 +1,10 @@
+import { redirect } from '@sveltejs/kit';
+import type { LayoutServerLoad } from './$types';
+
+export const load = (async ({ locals }) => {
+ if (!locals.pocketBase.authStore.isValid) {
+ throw redirect(303, '/signup');
+ }
+
+ return {};
+}) satisfies LayoutServerLoad;
diff --git a/apps/web/src/routes/(board)/board/+page.svelte b/apps/web/src/routes/(board)/board/+page.svelte
new file mode 100644
index 0000000..305df77
--- /dev/null
+++ b/apps/web/src/routes/(board)/board/+page.svelte
@@ -0,0 +1,8 @@
+
+
+Create a new dashboard
+
+
diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte
index f67d7ab..17abf18 100644
--- a/apps/web/src/routes/+layout.svelte
+++ b/apps/web/src/routes/+layout.svelte
@@ -1 +1,5 @@
-
\ No newline at end of file
+
+
+
diff --git a/apps/web/src/routes/+page.server.ts b/apps/web/src/routes/+page.server.ts
new file mode 100644
index 0000000..96bcf6f
--- /dev/null
+++ b/apps/web/src/routes/+page.server.ts
@@ -0,0 +1,7 @@
+import type { PageServerLoad } from './$types';
+
+export const load = (async ({ locals }) => {
+ return {
+ authenticated: locals.pocketBase.authStore.isValid
+ };
+}) satisfies PageServerLoad;
diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte
index 5982b0a..51e11f3 100644
--- a/apps/web/src/routes/+page.svelte
+++ b/apps/web/src/routes/+page.svelte
@@ -1,2 +1,26 @@
-Welcome to SvelteKit
-Visit kit.svelte.dev to read the documentation
+
+
+Login with SvelteKit and Pocketbase
+
+{#if data.authenticated}
+ Create a new game
+{:else}
+
+{/if}
+
+
diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js
index 8d260d9..38fb24e 100644
--- a/apps/web/tailwind.config.js
+++ b/apps/web/tailwind.config.js
@@ -1,64 +1,64 @@
-import { fontFamily } from "tailwindcss/defaultTheme";
+import { fontFamily } from 'tailwindcss/defaultTheme';
/** @type {import('tailwindcss').Config} */
const config = {
- darkMode: ["class"],
- content: ["./src/**/*.{html,js,svelte,ts}"],
- safelist: ["dark"],
+ darkMode: ['class'],
+ content: ['./src/**/*.{html,js,svelte,ts}'],
+ safelist: ['dark'],
theme: {
container: {
center: true,
- padding: "2rem",
+ padding: '2rem',
screens: {
- "2xl": "1400px"
+ '2xl': '1400px'
}
},
extend: {
colors: {
- border: "hsl(var(--border) / )",
- input: "hsl(var(--input) / )",
- ring: "hsl(var(--ring) / )",
- background: "hsl(var(--background) / )",
- foreground: "hsl(var(--foreground) / )",
+ border: 'hsl(var(--border) / )',
+ input: 'hsl(var(--input) / )',
+ ring: 'hsl(var(--ring) / )',
+ background: 'hsl(var(--background) / )',
+ foreground: 'hsl(var(--foreground) / )',
primary: {
- DEFAULT: "hsl(var(--primary) / )",
- foreground: "hsl(var(--primary-foreground) / )"
+ DEFAULT: 'hsl(var(--primary) / )',
+ foreground: 'hsl(var(--primary-foreground) / )'
},
secondary: {
- DEFAULT: "hsl(var(--secondary) / )",
- foreground: "hsl(var(--secondary-foreground) / )"
+ DEFAULT: 'hsl(var(--secondary) / )',
+ foreground: 'hsl(var(--secondary-foreground) / )'
},
destructive: {
- DEFAULT: "hsl(var(--destructive) / )",
- foreground: "hsl(var(--destructive-foreground) / )"
+ DEFAULT: 'hsl(var(--destructive) / )',
+ foreground: 'hsl(var(--destructive-foreground) / )'
},
muted: {
- DEFAULT: "hsl(var(--muted) / )",
- foreground: "hsl(var(--muted-foreground) / )"
+ DEFAULT: 'hsl(var(--muted) / )',
+ foreground: 'hsl(var(--muted-foreground) / )'
},
accent: {
- DEFAULT: "hsl(var(--accent) / )",
- foreground: "hsl(var(--accent-foreground) / )"
+ DEFAULT: 'hsl(var(--accent) / )',
+ foreground: 'hsl(var(--accent-foreground) / )'
},
popover: {
- DEFAULT: "hsl(var(--popover) / )",
- foreground: "hsl(var(--popover-foreground) / )"
+ DEFAULT: 'hsl(var(--popover) / )',
+ foreground: 'hsl(var(--popover-foreground) / )'
},
card: {
- DEFAULT: "hsl(var(--card) / )",
- foreground: "hsl(var(--card-foreground) / )"
+ DEFAULT: 'hsl(var(--card) / )',
+ foreground: 'hsl(var(--card-foreground) / )'
}
},
borderRadius: {
- lg: "var(--radius)",
- md: "calc(var(--radius) - 2px)",
- sm: "calc(var(--radius) - 4px)"
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
},
fontFamily: {
sans: [...fontFamily.sans]
}
}
- },
+ }
};
export default config;