mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Cleanup code, fix IntelliJ removing star imports, move sessions to Redis.
This commit is contained in:
parent
88339093e1
commit
c9b6269ce9
124 changed files with 1343 additions and 1342 deletions
|
|
@ -40,14 +40,17 @@
|
|||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"arctic": "^1.9.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"bits-ui": "^0.21.16",
|
||||
"drizzle-kit": "^0.27.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "2.36.0-next.13",
|
||||
"formsnap": "^1.0.1",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"lucia": "3.2.0",
|
||||
"lucide-svelte": "^0.408.0",
|
||||
"mode-watcher": "^0.4.1",
|
||||
"nodemailer": "^6.9.16",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^16.1.0",
|
||||
|
|
@ -61,6 +64,7 @@
|
|||
"svelte-meta-tags": "^3.1.4",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"sveltekit-flash-message": "^2.4.4",
|
||||
"sveltekit-superforms": "^2.20.0",
|
||||
"tailwindcss": "^3.4.14",
|
||||
|
|
@ -97,7 +101,6 @@
|
|||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/adapter-vercel": "^5.4.7",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"bits-ui": "^0.21.16",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"bullmq": "^5.25.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
|
@ -108,7 +111,6 @@
|
|||
"drizzle-orm": "^0.36.1",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"feather-icons": "^4.29.2",
|
||||
"formsnap": "^1.0.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"hono": "^4.6.9",
|
||||
"hono-pino": "^0.3.0",
|
||||
|
|
@ -120,7 +122,6 @@
|
|||
"just-capitalize": "^3.2.0",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"loader": "^2.1.1",
|
||||
"mode-watcher": "^0.4.1",
|
||||
"open-props": "^1.7.7",
|
||||
"oslo": "^1.2.1",
|
||||
"pg": "^8.13.1",
|
||||
|
|
@ -133,7 +134,6 @@
|
|||
"reflect-metadata": "^0.2.2",
|
||||
"stoker": "^1.3.0",
|
||||
"svelte-lazy-loader": "^1.0.0",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
|
|
|||
|
|
@ -77,9 +77,6 @@ importers:
|
|||
'@types/feather-icons':
|
||||
specifier: ^4.29.4
|
||||
version: 4.29.4
|
||||
bits-ui:
|
||||
specifier: ^0.21.16
|
||||
version: 0.21.16(svelte@5.0.0-next.175)
|
||||
boardgamegeekclient:
|
||||
specifier: ^1.9.1
|
||||
version: 1.9.1
|
||||
|
|
@ -110,9 +107,6 @@ importers:
|
|||
feather-icons:
|
||||
specifier: ^4.29.2
|
||||
version: 4.29.2
|
||||
formsnap:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.20.0(@sveltejs/kit@2.8.0(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.10(@types/node@20.17.6)))(svelte@5.0.0-next.175)(vite@5.4.10(@types/node@20.17.6)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)(typescript@5.6.3))
|
||||
handlebars:
|
||||
specifier: ^4.7.8
|
||||
version: 4.7.8
|
||||
|
|
@ -146,9 +140,6 @@ importers:
|
|||
loader:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
mode-watcher:
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1(svelte@5.0.0-next.175)
|
||||
open-props:
|
||||
specifier: ^1.7.7
|
||||
version: 1.7.7
|
||||
|
|
@ -185,9 +176,6 @@ importers:
|
|||
svelte-lazy-loader:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
svelte-sonner:
|
||||
specifier: ^0.3.28
|
||||
version: 0.3.28(svelte@5.0.0-next.175)
|
||||
tailwind-merge:
|
||||
specifier: ^2.5.4
|
||||
version: 2.5.4
|
||||
|
|
@ -255,6 +243,9 @@ importers:
|
|||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
version: 10.4.20(postcss@8.4.47)
|
||||
bits-ui:
|
||||
specifier: ^0.21.16
|
||||
version: 0.21.16(svelte@5.0.0-next.175)
|
||||
drizzle-kit:
|
||||
specifier: ^0.27.2
|
||||
version: 0.27.2
|
||||
|
|
@ -267,6 +258,9 @@ importers:
|
|||
eslint-plugin-svelte:
|
||||
specifier: 2.36.0-next.13
|
||||
version: 2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))
|
||||
formsnap:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.20.0(@sveltejs/kit@2.8.0(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.10(@types/node@20.17.6)))(svelte@5.0.0-next.175)(vite@5.4.10(@types/node@20.17.6)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)(typescript@5.6.3))
|
||||
just-clone:
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
|
|
@ -279,6 +273,9 @@ importers:
|
|||
lucide-svelte:
|
||||
specifier: ^0.408.0
|
||||
version: 0.408.0(svelte@5.0.0-next.175)
|
||||
mode-watcher:
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1(svelte@5.0.0-next.175)
|
||||
nodemailer:
|
||||
specifier: ^6.9.16
|
||||
version: 6.9.16
|
||||
|
|
@ -318,6 +315,9 @@ importers:
|
|||
svelte-sequential-preprocessor:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
svelte-sonner:
|
||||
specifier: ^0.3.28
|
||||
version: 0.3.28(svelte@5.0.0-next.175)
|
||||
sveltekit-flash-message:
|
||||
specifier: ^2.4.4
|
||||
version: 2.4.4(@sveltejs/kit@2.8.0(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.10(@types/node@20.17.6)))(svelte@5.0.0-next.175)(vite@5.4.10(@types/node@20.17.6)))(svelte@5.0.0-next.175)
|
||||
|
|
@ -3834,8 +3834,8 @@ packages:
|
|||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
object-inspect@1.13.2:
|
||||
resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
|
||||
object-inspect@1.13.3:
|
||||
resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
obuf@1.1.2:
|
||||
|
|
@ -8339,7 +8339,7 @@ snapshots:
|
|||
|
||||
object-hash@3.0.0: {}
|
||||
|
||||
object-inspect@1.13.2: {}
|
||||
object-inspect@1.13.3: {}
|
||||
|
||||
obuf@1.1.2: {}
|
||||
|
||||
|
|
@ -9122,7 +9122,7 @@ snapshots:
|
|||
call-bind: 1.0.7
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.2.4
|
||||
object-inspect: 1.13.2
|
||||
object-inspect: 1.13.3
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
|
|
|
|||
10
src/app.d.ts
vendored
10
src/app.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import type { User } from 'lucia';
|
||||
import type { ApiClient } from '$lib/server/api';
|
||||
import type { parseApiResponse } from '$lib/utils/api';
|
||||
import type { User } from 'lucia';
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
|
|
@ -24,9 +24,9 @@ declare global {
|
|||
}
|
||||
namespace Superforms {
|
||||
type Message = {
|
||||
type: 'error' | 'success' | 'info',
|
||||
text: string
|
||||
}
|
||||
type: 'error' | 'success' | 'info';
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
interface Error {
|
||||
code?: string;
|
||||
|
|
@ -37,7 +37,7 @@ declare global {
|
|||
interface Document {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
startViewTransition: (callback: never) => void; // Add your custom property/method here
|
||||
startViewTransition: (callback: never) => void; // Add your custom property/method here
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
22
src/app.html
22
src/app.html
|
|
@ -1,16 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="Bored? Find a game! Bored Game!" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon-bored-game.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<head>
|
||||
<meta name="robots" content="noindex, nofollow"/>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="description" content="Bored? Find a game! Bored Game!"/>
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon-bored-game.svg"/>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="svelte">%sveltekit.body%</div>
|
||||
</body>
|
||||
<body>
|
||||
<div id="svelte">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'reflect-metadata'
|
||||
import { StatusCodes } from '$lib/constants/status-codes'
|
||||
import type { ApiRoutes } from '$lib/server/api'
|
||||
import { parseApiResponse } from '$lib/utils/api'
|
||||
import { type Handle, redirect } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
import { hc } from 'hono/client'
|
||||
import 'reflect-metadata';
|
||||
import { StatusCodes } from '$lib/constants/status-codes';
|
||||
import type { ApiRoutes } from '$lib/server/api';
|
||||
import { parseApiResponse } from '$lib/utils/api';
|
||||
import { type Handle, redirect } from '@sveltejs/kit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { hc } from 'hono/client';
|
||||
|
||||
const apiClient: Handle = async ({ event, resolve }) => {
|
||||
/* ------------------------------ Register api ------------------------------ */
|
||||
|
|
@ -14,29 +14,30 @@ const apiClient: Handle = async ({ event, resolve }) => {
|
|||
'x-forwarded-for': event.url.host.includes('sveltekit-prerender') ? '127.0.0.1' : event.getClientAddress(),
|
||||
host: event.request.headers.get('host') || '',
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
/* ----------------------------- Auth functions ----------------------------- */
|
||||
async function getAuthedUser() {
|
||||
const { data } = await api.user.$get().then(parseApiResponse)
|
||||
return data?.user
|
||||
const { data } = await api.user.$get().then(parseApiResponse);
|
||||
return data?.user;
|
||||
}
|
||||
|
||||
async function getAuthedUserOrThrow() {
|
||||
const { data } = await api.user.$get().then(parseApiResponse)
|
||||
if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/')
|
||||
return data?.user
|
||||
const { data } = await api.user.$get().then(parseApiResponse);
|
||||
if (!data || !data.user) {
|
||||
throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/');
|
||||
}
|
||||
return data?.user;
|
||||
}
|
||||
|
||||
/* ------------------------------ Set contexts ------------------------------ */
|
||||
event.locals.api = api
|
||||
event.locals.parseApiResponse = parseApiResponse
|
||||
event.locals.getAuthedUser = getAuthedUser
|
||||
event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow
|
||||
event.locals.api = api;
|
||||
event.locals.parseApiResponse = parseApiResponse;
|
||||
event.locals.getAuthedUser = getAuthedUser;
|
||||
event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow;
|
||||
|
||||
/* ----------------------------- Return response ---------------------------- */
|
||||
const response = await resolve(event)
|
||||
return response
|
||||
}
|
||||
return await resolve(event);
|
||||
};
|
||||
|
||||
export const handle: Handle = sequence(apiClient)
|
||||
export const handle: Handle = sequence(apiClient);
|
||||
|
|
|
|||
|
|
@ -1,71 +1,71 @@
|
|||
<script>
|
||||
/**
|
||||
* @event {null} save
|
||||
* @event {{ prevValue: any; value: any; }} update
|
||||
*/
|
||||
/**
|
||||
* @event {null} save
|
||||
* @event {{ prevValue: any; value: any; }} update
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specify the local storage key
|
||||
*/
|
||||
export let key = 'local-storage-key';
|
||||
/**
|
||||
* Specify the local storage key
|
||||
*/
|
||||
export let key = 'local-storage-key';
|
||||
|
||||
/**
|
||||
* Provide a value to persist
|
||||
* @type {any}
|
||||
*/
|
||||
export let value = '';
|
||||
/**
|
||||
* Provide a value to persist
|
||||
* @type {any}
|
||||
*/
|
||||
export let value = '';
|
||||
|
||||
/**
|
||||
* Remove the persisted key value from the browser's local storage
|
||||
* @type {() => void}
|
||||
*/
|
||||
export function clearItem() {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
/**
|
||||
* Remove the persisted key value from the browser's local storage
|
||||
* @type {() => void}
|
||||
*/
|
||||
export function clearItem() {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all key values from the browser's local storage
|
||||
* @type {() => void}
|
||||
*/
|
||||
export function clearAll() {
|
||||
localStorage.clear();
|
||||
}
|
||||
/**
|
||||
* Clear all key values from the browser's local storage
|
||||
* @type {() => void}
|
||||
*/
|
||||
export function clearAll() {
|
||||
localStorage.clear();
|
||||
}
|
||||
|
||||
import { onMount, afterUpdate, createEventDispatcher } from 'svelte';
|
||||
import { afterUpdate, createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let prevValue = value;
|
||||
let prevValue = value;
|
||||
|
||||
function setItem() {
|
||||
if (typeof value === 'object') {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} else {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
function setItem() {
|
||||
if (typeof value === 'object') {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} else {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const item = localStorage.getItem(key);
|
||||
onMount(() => {
|
||||
const item = localStorage.getItem(key);
|
||||
|
||||
if (item != null) {
|
||||
try {
|
||||
value = JSON.parse(item);
|
||||
} catch (e) {
|
||||
value = item;
|
||||
}
|
||||
} else {
|
||||
setItem(value);
|
||||
dispatch('save');
|
||||
}
|
||||
});
|
||||
if (item != null) {
|
||||
try {
|
||||
value = JSON.parse(item);
|
||||
} catch (e) {
|
||||
value = item;
|
||||
}
|
||||
} else {
|
||||
setItem(value);
|
||||
dispatch('save');
|
||||
}
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (prevValue !== value) {
|
||||
setItem(value);
|
||||
dispatch('update', { prevValue, value });
|
||||
}
|
||||
afterUpdate(() => {
|
||||
if (prevValue !== value) {
|
||||
setItem(value);
|
||||
dispatch('update', { prevValue, value });
|
||||
}
|
||||
|
||||
prevValue = value;
|
||||
});
|
||||
prevValue = value;
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
import type { CollectionItems } from '$db/schema';
|
||||
import type { CollectionItems } from '$db/schema';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
|
||||
export let game: GameType | CollectionItems;
|
||||
export let variant: 'default' | 'compact' = 'default';
|
||||
export let game: GameType | CollectionItems;
|
||||
export let variant: 'default' | 'compact' = 'default';
|
||||
|
||||
// Naive and assumes description is only on our GameType at the moment
|
||||
function isGameType(game: GameType | SavedGameType): game is GameType {
|
||||
return (game as GameType).description !== undefined;
|
||||
}
|
||||
// Naive and assumes description is only on our GameType at the moment
|
||||
function isGameType(game: GameType | SavedGameType): game is GameType {
|
||||
return (game as GameType).description !== undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<article class="grid grid-template-cols-2 gap-4">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<script lang="ts">
|
||||
import Logo from '$components/logo.svelte';
|
||||
import { ListChecks, ListTodo, LogOut, Settings } from 'lucide-svelte';
|
||||
import Logo from '$components/logo.svelte';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { ListChecks, ListTodo, LogOut, Settings } from 'lucide-svelte';
|
||||
|
||||
let { user = null } = $props();
|
||||
let { user = null } = $props();
|
||||
|
||||
let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)');
|
||||
let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)');
|
||||
</script>
|
||||
|
||||
<header>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils/ui';
|
||||
import { type PinInputProps } from 'bits-ui';
|
||||
import { cn } from '$lib/utils/ui';
|
||||
import { PinInput } from 'bits-ui';
|
||||
import { type PinInputProps } from 'bits-ui';
|
||||
|
||||
interface Props extends Omit<PinInputProps, 'value'> {
|
||||
value: string;
|
||||
inputCount?: number;
|
||||
}
|
||||
interface Props extends Omit<PinInputProps, 'value'> {
|
||||
value: string;
|
||||
inputCount?: number;
|
||||
}
|
||||
|
||||
let { value = $bindable(), inputCount = 6, ...rest }: Props = $props();
|
||||
let pin = $state<string[] | undefined>(value?.split('') ?? []);
|
||||
let inputs = $derived(Array(inputCount).fill(null));
|
||||
let { value = $bindable(), inputCount = 6, ...rest }: Props = $props();
|
||||
let pin = $state<string[] | undefined>(value?.split('') ?? []);
|
||||
let inputs = $derived(Array(inputCount).fill(null));
|
||||
|
||||
$effect(() => {
|
||||
value = pin?.join('') ?? '';
|
||||
});
|
||||
$effect(() => {
|
||||
value = pin?.join('') ?? '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<PinInput.Root
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { search_schema, type SearchSchema } from '$lib/zodValidation';
|
||||
import Input from '$components/ui/input/input.svelte';
|
||||
import Checkbox from '$components/ui/checkbox/checkbox.svelte';
|
||||
import Checkbox from '$components/ui/checkbox/checkbox.svelte';
|
||||
import * as Form from '$components/ui/form';
|
||||
import Input from '$components/ui/input/input.svelte';
|
||||
import { type SearchSchema, search_schema } from '$lib/zodValidation';
|
||||
import { type Infer, type SuperValidated, superForm } from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
|
||||
export let data: SuperValidated<Infer<SearchSchema>>;
|
||||
export let data: SuperValidated<Infer<SearchSchema>>;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(search_schema),
|
||||
});
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(search_schema),
|
||||
});
|
||||
|
||||
const { form: formData } = form;
|
||||
const { form: formData } = form;
|
||||
</script>
|
||||
|
||||
<search>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HeadingLevel } from '.';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { HeadingLevel } from "./index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
level?: HeadingLevel;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { alertVariants, type Variant } from '.';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { type Variant, alertVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement> & {
|
||||
variant?: Variant;
|
||||
|
|
@ -12,10 +12,6 @@
|
|||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(alertVariants({ variant }), className)}
|
||||
{...$$restProps}
|
||||
role="alert"
|
||||
>
|
||||
<div class={cn(alertVariants({ variant }), className)} {...$$restProps} role="alert">
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import {tv, type VariantProps} from "tailwind-variants";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
import Root from "./alert.svelte";
|
||||
import Description from "./alert-description.svelte";
|
||||
import Title from "./alert-title.svelte";
|
||||
|
||||
export const alertVariants = tv({
|
||||
base: "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",
|
||||
base: "[&>svg]:text-foreground relative w-full rounded-lg border p-4 [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4",
|
||||
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive"
|
||||
}
|
||||
"border-destructive/50 text-destructive text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
variant: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type Variant = VariantProps<typeof alertVariants>["variant"];
|
||||
|
|
@ -29,5 +29,5 @@ export {
|
|||
//
|
||||
Root as Alert,
|
||||
Description as AlertDescription,
|
||||
Title as AlertTitle
|
||||
Title as AlertTitle,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { buttonVariants, type Events, type Props } from './index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Button as ButtonPrimitive } from "bits-ui";
|
||||
import { type Events, type Props, buttonVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {tv, type VariantProps} from "tailwind-variants";
|
||||
import type {Button as ButtonPrimitive} from "bits-ui";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import type { Button as ButtonPrimitive } from "bits-ui";
|
||||
import Root from "./button.svelte";
|
||||
|
||||
const buttonVariants = tv({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils/ui.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils/ui.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils/ui.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils/ui.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HeadingLevel } from './index.js';
|
||||
import { cn } from '$lib/utils/ui.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { HeadingLevel } from "./index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
tag?: HeadingLevel;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils/ui.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:checked
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn, flyAndScale } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
{transitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
|
||||
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<DropdownMenuPrimitive.Item
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.LabelProps & {
|
||||
inset?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import Circle from 'lucide-svelte/icons/circle';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Circle from "lucide-svelte/icons/circle";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{value}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SeparatorProps;
|
||||
|
||||
|
|
@ -9,6 +9,6 @@
|
|||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
class={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
class={cn("bg-muted -mx-1 my-1 h-px", className)}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn, flyAndScale } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.SubContentEvents;
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
|
||||
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import ChevronRight from 'lucide-svelte/icons/chevron-right';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
|
||||
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {DropdownMenu as DropdownMenuPrimitive} from "bits-ui";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Item from "./dropdown-menu-item.svelte";
|
||||
import Label from "./dropdown-menu-label.svelte";
|
||||
import Content from "./dropdown-menu-content.svelte";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
import * as Button from "$lib/components/ui/button/index.js";
|
||||
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts" context="module">
|
||||
import type { FormPathLeaves, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPathLeaves<T>;
|
||||
</script>
|
||||
|
|
@ -8,7 +7,7 @@
|
|||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||
errorClasses?: string | undefined | null;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
|
@ -8,7 +7,7 @@
|
|||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { getFormControl } from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import type { Label as LabelPrimitive } from "bits-ui";
|
||||
import { getFormControl } from "formsnap";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = FormPrimitive.LegendProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
type $$Events = LabelPrimitive.Events;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLUListElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import Ellipsis from 'lucide-svelte/icons/ellipsis';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import Ellipsis from "lucide-svelte/icons/ellipsis";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLLIElement>;
|
||||
let className: $$Props["class"] = undefined;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { buttonVariants, type Props } from '$lib/components/ui/button/index.js';
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
import { type Props, buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
|
||||
type $$Props = PaginationPrimitive.PageProps &
|
||||
Props & {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from 'bits-ui';
|
||||
import ChevronRight from 'lucide-svelte/icons/chevron-right';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = PaginationPrimitive.NextButtonProps;
|
||||
type $$Events = PaginationPrimitive.NextButtonEvents;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from 'bits-ui';
|
||||
import ChevronLeft from 'lucide-svelte/icons/chevron-left';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
import ChevronLeft from "lucide-svelte/icons/chevron-left";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = PaginationPrimitive.PrevButtonProps;
|
||||
type $$Events = PaginationPrimitive.PrevButtonEvents;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from 'bits-ui';
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = PaginationPrimitive.Props;
|
||||
type $$Events = PaginationPrimitive.Events;
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
export let perPage: $$Props["perPage"] = 10;
|
||||
export let page: $$Props["page"] = 1;
|
||||
export let siblingCount: $$Props["siblingCount"] = 1;
|
||||
|
||||
export { className as class };
|
||||
|
||||
$: currentPage = page;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {Select as SelectPrimitive} from "bits-ui";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
import Label from "./select-label.svelte";
|
||||
import Item from "./select-item.svelte";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { cn, flyAndScale } from '$lib/utils.js';
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { scale } from "svelte/transition";
|
||||
import { cn, flyAndScale } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = SelectPrimitive.ContentProps;
|
||||
type $$Events = SelectPrimitive.ContentEvents;
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
{outTransitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md outline-none",
|
||||
"bg-popover text-popover-foreground relative z-50 min-w-[8rem] overflow-hidden rounded-md border shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = SelectPrimitive.ItemProps;
|
||||
type $$Events = SelectPrimitive.ItemEvents;
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
{disabled}
|
||||
{label}
|
||||
class={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = SelectPrimitive.LabelProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = SelectPrimitive.SeparatorProps;
|
||||
|
||||
|
|
@ -8,4 +8,4 @@
|
|||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Separator class={cn("-mx-1 my-1 h-px bg-muted", className)} {...$$restProps} />
|
||||
<SelectPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import ChevronDown from 'lucide-svelte/icons/chevron-down';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||
import { cn } from "$lib/utils/ui.js";
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps;
|
||||
type $$Events = SelectPrimitive.TriggerEvents;
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
"border-input bg-background ring-offset-background focus-visible:ring-ring aria-[invalid]:border-destructive data-[placeholder]:[&>span]:text-muted-foreground flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
||||
import { mode } from 'mode-watcher';
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
|
||||
import { mode } from "mode-watcher";
|
||||
|
||||
type $$Props = SonnerProps;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
<script lang="ts">
|
||||
import { onNavigate } from '$app/navigation'
|
||||
import { navigating } from '$app/stores'
|
||||
import { onNavigate } from '$app/navigation';
|
||||
import { navigating } from '$app/stores';
|
||||
|
||||
let visible = false
|
||||
let progress = 0
|
||||
let load_durations: number[] = []
|
||||
$: average_load = load_durations.reduce((a, b) => a + b, 0) / load_durations.length
|
||||
const increment = 1
|
||||
let visible = false;
|
||||
let progress = 0;
|
||||
let load_durations: number[] = [];
|
||||
$: average_load = load_durations.reduce((a, b) => a + b, 0) / load_durations.length;
|
||||
const increment = 1;
|
||||
onNavigate((navigation) => {
|
||||
const typical_load_time = average_load || 200 //ms
|
||||
const frequency = typical_load_time / 100
|
||||
let start = performance.now()
|
||||
const typical_load_time = average_load || 200; //ms
|
||||
const frequency = typical_load_time / 100;
|
||||
let start = performance.now();
|
||||
// Start the progress bar
|
||||
visible = true
|
||||
progress = 0
|
||||
visible = true;
|
||||
progress = 0;
|
||||
const interval = setInterval(() => {
|
||||
// Increment the progress bar
|
||||
progress += increment
|
||||
}, frequency)
|
||||
progress += increment;
|
||||
}, frequency);
|
||||
// Resolve the promise when the page is done loading
|
||||
$navigating?.complete.then(() => {
|
||||
progress = 100 // Fill out the progress bar
|
||||
clearInterval(interval)
|
||||
progress = 100; // Fill out the progress bar
|
||||
clearInterval(interval);
|
||||
// after 100 ms hide the progress bar
|
||||
setTimeout(() => {
|
||||
visible = false
|
||||
}, 500)
|
||||
visible = false;
|
||||
}, 500);
|
||||
// Log how long that one took
|
||||
const end = performance.now()
|
||||
const duration = end - start
|
||||
load_durations = [...load_durations, duration]
|
||||
})
|
||||
})
|
||||
const end = performance.now();
|
||||
const duration = end - start;
|
||||
load_durations = [...load_durations, duration];
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="progress" class:visible style:--progress={progress}>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,39 @@
|
|||
import {
|
||||
cookieExpiresAt,
|
||||
cookieExpiresMilliseconds,
|
||||
halfCookieExpiresMilliseconds
|
||||
} from '$lib/server/api/common/utils/cookies';
|
||||
import {SessionsRepository} from '$lib/server/api/repositories/sessions.repository';
|
||||
import {sha256} from '@oslojs/crypto/sha2';
|
||||
import {encodeBase32LowerCaseNoPadding, encodeHexLowerCase} from '@oslojs/encoding';
|
||||
import {inject, injectable} from 'tsyringe';
|
||||
import type {Sessions, Users} from '../databases/postgres/tables';
|
||||
import { cookieExpiresAt, cookieExpiresMilliseconds, halfCookieExpiresMilliseconds } from '$lib/server/api/common/utils/cookies';
|
||||
import { UsersRepository } from '$lib/server/api/repositories/users.repository';
|
||||
import { RedisService } from '$lib/server/api/services/redis.service';
|
||||
import { sha256 } from '@oslojs/crypto/sha2';
|
||||
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import type { Users } from '../databases/postgres/tables';
|
||||
|
||||
export type SessionValidationResult = { session: Sessions; user: Users } | { session: null; user: null };
|
||||
export type RedisSession = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
expires_at: number;
|
||||
ip_country: string;
|
||||
ip_address: string;
|
||||
two_factor_auth_enabled: boolean;
|
||||
is_two_factor_authenticated: boolean;
|
||||
};
|
||||
|
||||
export type Session = {
|
||||
id: string;
|
||||
userId: string;
|
||||
expiresAt: Date;
|
||||
ipCountry: string;
|
||||
ipAddress: string;
|
||||
twoFactorAuthEnabled: boolean;
|
||||
isTwoFactorAuthenticated: boolean;
|
||||
};
|
||||
|
||||
export type SessionValidationResult = { session: Session; user: Users } | { session: null; user: null } | { session: Session; user: undefined };
|
||||
|
||||
@injectable()
|
||||
export class SessionsService {
|
||||
constructor(@inject(SessionsRepository) private readonly sessionsRepository: SessionsRepository) {}
|
||||
constructor(
|
||||
@inject(RedisService) private readonly redisService: RedisService,
|
||||
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
|
||||
) {}
|
||||
|
||||
generateSessionToken() {
|
||||
const bytes = new Uint8Array(20);
|
||||
|
|
@ -30,7 +50,7 @@ export class SessionsService {
|
|||
isTwoFactorAuthenticated: boolean,
|
||||
) {
|
||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const session: Sessions = {
|
||||
const session = {
|
||||
id: sessionId,
|
||||
userId,
|
||||
expiresAt: cookieExpiresAt,
|
||||
|
|
@ -39,23 +59,49 @@ export class SessionsService {
|
|||
twoFactorAuthEnabled,
|
||||
isTwoFactorAuthenticated,
|
||||
};
|
||||
await this.sessionsRepository.create(session);
|
||||
await this.redisService.client.set(
|
||||
`session:${sessionId}`,
|
||||
JSON.stringify({
|
||||
id: session.id,
|
||||
user_id: session.userId,
|
||||
expires_at: session.expiresAt,
|
||||
ip_country: session.ipCountry,
|
||||
ip_address: session.ipAddress,
|
||||
two_factor_auth_enabled: session.twoFactorAuthEnabled,
|
||||
is_two_factor_authenticated: session.isTwoFactorAuthenticated,
|
||||
}),
|
||||
'EXAT',
|
||||
Math.floor(session.expiresAt.getTime() / 1000),
|
||||
);
|
||||
return session;
|
||||
}
|
||||
|
||||
async validateSessionToken(token: string): Promise<SessionValidationResult> {
|
||||
// TODO: Why was this needed in the docs? https://lucia-next.pages.dev/sessions/basic-api/drizzle-orm
|
||||
// const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
const sessions = await this.sessionsRepository.findBySessionId(token);
|
||||
if (sessions.length < 1) {
|
||||
const item = await this.redisService.client.get(`session:${token}`);
|
||||
if (item === null) {
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
const { user, session } = sessions[0];
|
||||
const result: RedisSession = JSON.parse(item);
|
||||
const session: Session = {
|
||||
id: result.id,
|
||||
userId: result.user_id,
|
||||
expiresAt: new Date(result.expires_at * 1000),
|
||||
ipCountry: result.ip_country,
|
||||
ipAddress: result.ip_address,
|
||||
twoFactorAuthEnabled: result.two_factor_auth_enabled,
|
||||
isTwoFactorAuthenticated: result.is_two_factor_authenticated,
|
||||
};
|
||||
let user: Users | undefined = undefined;
|
||||
if (session.userId) {
|
||||
user = await this.usersRepository.findOneById(session.userId);
|
||||
}
|
||||
if (Date.now() >= session.expiresAt.getTime()) {
|
||||
await this.sessionsRepository.deleteBySessionId(token);
|
||||
await this.redisService.client.del(`session:${token}`);
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
|
|
@ -64,13 +110,26 @@ export class SessionsService {
|
|||
|
||||
if (Date.now() >= session.expiresAt.getTime() - cookieExpiresMilliseconds) {
|
||||
session.expiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
|
||||
await this.sessionsRepository.updateSessionExpiresAt(token, session.expiresAt);
|
||||
await this.redisService.client.set(
|
||||
`session:${token}`,
|
||||
JSON.stringify({
|
||||
id: session.id,
|
||||
user_id: session.userId,
|
||||
expires_at: Math.floor(session.expiresAt.getTime() / 1000),
|
||||
ip_country: session.ipCountry,
|
||||
ip_address: session.ipAddress,
|
||||
two_factor_auth_enabled: session.twoFactorAuthEnabled,
|
||||
is_two_factor_authenticated: session.isTwoFactorAuthenticated,
|
||||
}),
|
||||
'EXAT',
|
||||
Math.floor(session.expiresAt.getTime() / 1000),
|
||||
);
|
||||
}
|
||||
|
||||
return { session, user };
|
||||
}
|
||||
|
||||
async invalidateSession(sessionId: string) {
|
||||
await this.sessionsRepository.deleteBySessionId(sessionId);
|
||||
await this.redisService.client.del(`session:${sessionId}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { z, ZodNumber, ZodOptional } from 'zod';
|
||||
import { ZodNumber, ZodOptional, z } from 'zod';
|
||||
// import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
export const BoardGameSearch = z.object({
|
||||
|
|
@ -25,15 +25,7 @@ export type ListGameSchema = typeof list_game_request_schema;
|
|||
|
||||
// https://github.com/colinhacks/zod/discussions/330
|
||||
export function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema: schema) {
|
||||
return z.preprocess(
|
||||
(value) =>
|
||||
typeof value === 'string'
|
||||
? Number.parseInt(value, 10)
|
||||
: typeof value === 'number'
|
||||
? value
|
||||
: undefined,
|
||||
schema,
|
||||
);
|
||||
return z.preprocess((value) => (typeof value === 'string' ? Number.parseInt(value, 10) : typeof value === 'number' ? value : undefined), schema);
|
||||
}
|
||||
|
||||
const Search = z.object({
|
||||
|
|
@ -73,47 +65,45 @@ export const search_schema = z
|
|||
limit: z.number().min(10).max(100).default(10),
|
||||
skip: z.number().min(0).default(0),
|
||||
})
|
||||
.superRefine(
|
||||
({ minPlayers, maxPlayers, minAge, exactMinAge, exactMinPlayers, exactMaxPlayers }, ctx) => {
|
||||
console.log({ minPlayers, maxPlayers });
|
||||
if (minPlayers && maxPlayers && minPlayers > maxPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
.superRefine(({ minPlayers, maxPlayers, minAge, exactMinAge, exactMinPlayers, exactMaxPlayers }, ctx) => {
|
||||
console.log({ minPlayers, maxPlayers });
|
||||
if (minPlayers && maxPlayers && minPlayers > maxPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players must be smaller than Max Players',
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
|
||||
if (exactMinAge && !minAge) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Age required when searching for exact min age',
|
||||
path: ['minAge'],
|
||||
});
|
||||
}
|
||||
if (exactMinAge && !minAge) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Age required when searching for exact min age',
|
||||
path: ['minAge'],
|
||||
});
|
||||
}
|
||||
|
||||
if (exactMinPlayers && !minPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players required when searching for exact min players',
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
}
|
||||
if (exactMinPlayers && !minPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Min Players required when searching for exact min players',
|
||||
path: ['minPlayers'],
|
||||
});
|
||||
}
|
||||
|
||||
if (exactMaxPlayers && !maxPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Max Players required when searching for exact max players',
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
if (exactMaxPlayers && !maxPlayers) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Max Players required when searching for exact max players',
|
||||
path: ['maxPlayers'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type SearchSchema = typeof search_schema;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
|
||||
import { db } from '$lib/server/api/packages/drizzle';
|
||||
import { errorMessage } from '$lib/utils/superforms';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { loadFlash, redirect } from 'sveltekit-flash-message/server';
|
||||
import { user_roles } from '../../../../lib/server/api/databases/postgres/tables';
|
||||
|
|
|
|||
|
|
@ -1,24 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { getFlash } from 'sveltekit-flash-message';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { theme } from '$state/theme';
|
||||
import { toastMessage } from '$lib/utils/superforms.js';
|
||||
import { page } from '$app/stores';
|
||||
import { theme } from '$state/theme';
|
||||
import { getFlash } from 'sveltekit-flash-message';
|
||||
|
||||
const { data } = $props();
|
||||
const { user } = data;
|
||||
const { data } = $props();
|
||||
const { user } = data;
|
||||
|
||||
const flash = getFlash(page, {
|
||||
clearOnNavigate: true,
|
||||
clearAfterMs: 3000,
|
||||
clearArray: true
|
||||
});
|
||||
const flash = getFlash(page, {
|
||||
clearOnNavigate: true,
|
||||
clearAfterMs: 3000,
|
||||
clearArray: true,
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// set the theme to the user's active theme
|
||||
$theme = user?.theme || 'system';
|
||||
document.querySelector('html')?.setAttribute('data-theme', $theme);
|
||||
});
|
||||
$effect(() => {
|
||||
// set the theme to the user's active theme
|
||||
$theme = user?.theme || 'system';
|
||||
document.querySelector('html')?.setAttribute('data-theme', $theme);
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>Do the admin stuff</h1>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import type { PageServerLoad } from './$types'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
// const users = await db.query.users.findMany({
|
||||
|
|
@ -17,5 +17,5 @@ export const load: PageServerLoad = async (event) => {
|
|||
|
||||
return {
|
||||
// users,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import DataTable from './user-table.svelte';
|
||||
const { data } = $props();
|
||||
import DataTable from './user-table.svelte';
|
||||
|
||||
const { data } = $props();
|
||||
</script>
|
||||
|
||||
<h1>Users</h1>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms'
|
||||
import { Button } from '$lib/components/ui/button'
|
||||
import capitalize from 'just-capitalize'
|
||||
import { enhance } from '$app/forms';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import capitalize from 'just-capitalize';
|
||||
// import AddRolesForm from './add-rolesTable-form.svelte';
|
||||
|
||||
const { data } = $props()
|
||||
const { data } = $props();
|
||||
|
||||
const { user, availableRoles } = data
|
||||
const { user_roles }: { user_roles: { role: { name: string; cuid: string } }[] } = user
|
||||
const { user, availableRoles } = data;
|
||||
const { user_roles }: { user_roles: { role: { name: string; cuid: string } }[] } = user;
|
||||
</script>
|
||||
|
||||
<h1>User Details</h1>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js'
|
||||
import * as Form from '$lib/components/ui/form'
|
||||
import { Input } from '$lib/components/ui/input'
|
||||
import { type AddRoleSchema, addRoleSchema } from '$lib/validations/account'
|
||||
import { type Infer, type SuperValidated, superForm } from 'sveltekit-superforms'
|
||||
import { zodClient } from 'sveltekit-superforms/adapters'
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { type AddRoleSchema, addRoleSchema } from '$lib/validations/account';
|
||||
import { type Infer, type SuperValidated, superForm } from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
|
||||
export let availableRoles: { name: string; cuid: string }[] = []
|
||||
const data: SuperValidated<Infer<AddRoleSchema>> = availableRoles
|
||||
export let availableRoles: { name: string; cuid: string }[] = [];
|
||||
const data: SuperValidated<Infer<AddRoleSchema>> = availableRoles;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(addRoleSchema),
|
||||
|
|
@ -18,16 +16,16 @@ const form = superForm(data, {
|
|||
// toast.error("Please fix the errors in the form.");
|
||||
// }
|
||||
// }
|
||||
})
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form
|
||||
const { form: formData, enhance } = form;
|
||||
|
||||
function addRole(id: string) {
|
||||
$formData.roles = [...$formData.roles, id]
|
||||
$formData.roles = [...$formData.roles, id];
|
||||
}
|
||||
|
||||
function removeRole(id: string) {
|
||||
$formData.roles = $formData.roles.filter((i) => i !== id)
|
||||
$formData.roles = $formData.roles.filter((i) => i !== id);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts">
|
||||
import Ellipsis from 'lucide-svelte/icons/ellipsis';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { User } from 'lucide-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { User } from 'lucide-svelte';
|
||||
import Ellipsis from 'lucide-svelte/icons/ellipsis';
|
||||
|
||||
export let cuid: string;
|
||||
export let cuid: string;
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let checked: Writable<boolean>;
|
||||
export let checked: Writable<boolean>;
|
||||
</script>
|
||||
|
||||
<Checkbox bind:checked={$checked} />
|
||||
|
|
@ -1,135 +1,114 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
createTable,
|
||||
Render,
|
||||
Subscribe,
|
||||
createRender,
|
||||
} from "svelte-headless-table";
|
||||
import {
|
||||
addPagination,
|
||||
addSortBy,
|
||||
addTableFilter,
|
||||
addHiddenColumns,
|
||||
addSelectedRows,
|
||||
} from "svelte-headless-table/plugins";
|
||||
import { readable } from "svelte/store";
|
||||
import ArrowUpDown from "lucide-svelte/icons/arrow-up-down";
|
||||
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||
import * as Table from "$lib/components/ui/table";
|
||||
import DataTableActions from "./user-table-actions.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import DataTableCheckbox from "./user-table-checkbox.svelte";
|
||||
import type { Users } from '$db/schema';
|
||||
import type { Users } from '$db/schema';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import ArrowUpDown from 'lucide-svelte/icons/arrow-up-down';
|
||||
import ChevronDown from 'lucide-svelte/icons/chevron-down';
|
||||
import { Render, Subscribe, createRender, createTable } from 'svelte-headless-table';
|
||||
import { addHiddenColumns, addPagination, addSelectedRows, addSortBy, addTableFilter } from 'svelte-headless-table/plugins';
|
||||
import { readable } from 'svelte/store';
|
||||
import DataTableActions from './user-table-actions.svelte';
|
||||
import DataTableCheckbox from './user-table-checkbox.svelte';
|
||||
|
||||
export let users: Users[] = [];
|
||||
export let users: Users[] = [];
|
||||
|
||||
const table = createTable(readable(users), {
|
||||
page: addPagination(),
|
||||
sort: addSortBy({ disableMultiSort: true }),
|
||||
filter: addTableFilter({
|
||||
fn: ({ filterValue, value }) => value.includes(filterValue),
|
||||
}),
|
||||
hide: addHiddenColumns(),
|
||||
select: addSelectedRows(),
|
||||
});
|
||||
const table = createTable(readable(users), {
|
||||
page: addPagination(),
|
||||
sort: addSortBy({ disableMultiSort: true }),
|
||||
filter: addTableFilter({
|
||||
fn: ({ filterValue, value }) => value.includes(filterValue),
|
||||
}),
|
||||
hide: addHiddenColumns(),
|
||||
select: addSelectedRows(),
|
||||
});
|
||||
|
||||
const columns = table.createColumns([
|
||||
table.column({
|
||||
accessor: "cuid",
|
||||
header: (_, { pluginStates }) => {
|
||||
const { allPageRowsSelected } = pluginStates.select;
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: allPageRowsSelected,
|
||||
});
|
||||
},
|
||||
cell: ({ row }, { pluginStates }) => {
|
||||
const { getRowState } = pluginStates.select;
|
||||
const { isSelected } = getRowState(row);
|
||||
const columns = table.createColumns([
|
||||
table.column({
|
||||
accessor: 'cuid',
|
||||
header: (_, { pluginStates }) => {
|
||||
const { allPageRowsSelected } = pluginStates.select;
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: allPageRowsSelected,
|
||||
});
|
||||
},
|
||||
cell: ({ row }, { pluginStates }) => {
|
||||
const { getRowState } = pluginStates.select;
|
||||
const { isSelected } = getRowState(row);
|
||||
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: isSelected,
|
||||
});
|
||||
},
|
||||
plugins: {
|
||||
filter: {
|
||||
exclude: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: "username",
|
||||
header: "Username",
|
||||
}),
|
||||
table.column({
|
||||
accessor: "email",
|
||||
header: "Email",
|
||||
cell: ({ value }) => {
|
||||
return value ?? "N/A";
|
||||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "first_name",
|
||||
header: "First Name",
|
||||
cell: ({ value }) => {
|
||||
return value && value.length > 0 ? value : "N/A";
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: isSelected,
|
||||
});
|
||||
},
|
||||
plugins: {
|
||||
filter: {
|
||||
exclude: true,
|
||||
},
|
||||
plugins: {
|
||||
filter: {
|
||||
exclude: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: "last_name",
|
||||
header: "Last Name",
|
||||
cell: ({ value }) => {
|
||||
return value && value.length > 0 ? value : "N/A";
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'username',
|
||||
header: 'Username',
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'email',
|
||||
header: 'Email',
|
||||
cell: ({ value }) => {
|
||||
return value ?? 'N/A';
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'first_name',
|
||||
header: 'First Name',
|
||||
cell: ({ value }) => {
|
||||
return value && value.length > 0 ? value : 'N/A';
|
||||
},
|
||||
plugins: {
|
||||
filter: {
|
||||
exclude: true,
|
||||
},
|
||||
plugins: {
|
||||
filter: {
|
||||
exclude: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: 'last_name',
|
||||
header: 'Last Name',
|
||||
cell: ({ value }) => {
|
||||
return value && value.length > 0 ? value : 'N/A';
|
||||
},
|
||||
plugins: {
|
||||
filter: {
|
||||
exclude: true,
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: ({ cuid }) => cuid,
|
||||
header: "",
|
||||
cell: ({ value }) => {
|
||||
return createRender(DataTableActions, { cuid: value });
|
||||
},
|
||||
plugins: {
|
||||
sort: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
},
|
||||
}),
|
||||
table.column({
|
||||
accessor: ({ cuid }) => cuid,
|
||||
header: '',
|
||||
cell: ({ value }) => {
|
||||
return createRender(DataTableActions, { cuid: value });
|
||||
},
|
||||
plugins: {
|
||||
sort: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const {
|
||||
headerRows,
|
||||
pageRows,
|
||||
tableAttrs,
|
||||
tableBodyAttrs,
|
||||
pluginStates,
|
||||
flatColumns,
|
||||
rows,
|
||||
} = table.createViewModel(columns);
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates, flatColumns, rows } = table.createViewModel(columns);
|
||||
|
||||
const { pageIndex, hasNextPage, hasPreviousPage } = pluginStates.page;
|
||||
const { filterValue } = pluginStates.filter;
|
||||
const { hiddenColumnIds } = pluginStates.hide;
|
||||
const { selectedDataIds } = pluginStates.select;
|
||||
const { pageIndex, hasNextPage, hasPreviousPage } = pluginStates.page;
|
||||
const { filterValue } = pluginStates.filter;
|
||||
const { hiddenColumnIds } = pluginStates.hide;
|
||||
const { selectedDataIds } = pluginStates.select;
|
||||
|
||||
const ids = flatColumns.map((col) => col.id);
|
||||
let hideForId = Object.fromEntries(ids.map((id) => [id, true]));
|
||||
const ids = flatColumns.map((col) => col.id);
|
||||
let hideForId = Object.fromEntries(ids.map((id) => [id, true]));
|
||||
|
||||
$: $hiddenColumnIds = Object.entries(hideForId)
|
||||
.filter(([, hide]) => !hide)
|
||||
.map(([id]) => id);
|
||||
$: $hiddenColumnIds = Object.entries(hideForId)
|
||||
.filter(([, hide]) => !hide)
|
||||
.map(([id]) => id);
|
||||
|
||||
const columnsToHide: string[] = [];
|
||||
const columnsToHide: string[] = [];
|
||||
</script>
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script lang="ts">
|
||||
import * as Card from '$components/ui/card'
|
||||
|
||||
const { data } = $props()
|
||||
let collections = data?.collections || []
|
||||
import * as Card from '$components/ui/card';
|
||||
const { data } = $props();
|
||||
let collections = data?.collections || [];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<div class="error">
|
||||
{#if $page.status === 404}
|
||||
<h1>The page you requested doesn't exist! 🤷♂️</h1>
|
||||
<h1>The page you requested doesn't exist! 🤷</h1>
|
||||
<h3 class="mt-6"><a href="/">Go Home</a></h3>
|
||||
{:else}
|
||||
<h1 class="h1">Unexpected Error</h1>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
// import { tick, onDestroy } from 'svelte';
|
||||
import Game from '$components/Game.svelte'
|
||||
import type { UICollection } from '$lib/types'
|
||||
import Game from '$components/Game.svelte';
|
||||
import type { UICollection } from '$lib/types';
|
||||
|
||||
const { data } = $props()
|
||||
const { items = [] } = data
|
||||
console.log(`Page data: ${JSON.stringify(data)}`)
|
||||
let collection: UICollection = data?.collection ?? {}
|
||||
console.log('items', items)
|
||||
const { data } = $props();
|
||||
const { items = [] } = data;
|
||||
console.log(`Page data: ${JSON.stringify(data)}`);
|
||||
let collection: UICollection = data?.collection ?? {};
|
||||
console.log('items', items);
|
||||
|
||||
// async function handleNextPageEvent(event: CustomEvent) {
|
||||
// if (+event?.detail?.page === page + 1) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import { userNotAuthenticated } from '$lib/server/auth-utils'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
|
||||
export async function load(event) {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import GameSearchForm from "$components/search/GameSearchForm.svelte";
|
||||
import GameSearchForm from '$components/search/GameSearchForm.svelte';
|
||||
|
||||
export let data;
|
||||
export let data;
|
||||
</script>
|
||||
<h1>Add a game to your collection</h1>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import { userNotAuthenticated } from '$lib/server/auth-utils'
|
||||
import { BggForm } from '$lib/zodValidation'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
import { zod } from 'sveltekit-superforms/adapters'
|
||||
import { superValidate } from 'sveltekit-superforms/server'
|
||||
import type { PageServerLoad } from '../$types'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { BggForm } from '$lib/zodValidation';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import type { PageServerLoad } from '../$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const form = await superValidate({}, zod(BggForm))
|
||||
const form = await superValidate({}, zod(BggForm));
|
||||
|
||||
return { form }
|
||||
}
|
||||
return { form };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import Input from "$components/ui/input/input.svelte";
|
||||
import Label from "$components/ui/label/label.svelte";
|
||||
import Input from '$components/ui/input/input.svelte';
|
||||
import Label from '$components/ui/label/label.svelte';
|
||||
|
||||
export let data;
|
||||
export let data;
|
||||
</script>
|
||||
<h1>Add a game to your collection</h1>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import Transition from '$components/transition.svelte'
|
||||
import Transition from '$components/transition.svelte';
|
||||
|
||||
export let data
|
||||
const wishlistsTable = data.wishlists || []
|
||||
export let data;
|
||||
const wishlistsTable = data.wishlists || [];
|
||||
</script>
|
||||
|
||||
<aside class="wishlists">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { db } from '$lib/server/api/packages/drizzle';
|
||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
||||
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
|
||||
import { type Actions, fail } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import Game from '$components/Game.svelte'
|
||||
import Game from '$components/Game.svelte';
|
||||
|
||||
export let data
|
||||
console.log('data', data)
|
||||
const wishlist = data.wishlist
|
||||
const gamesItems = wishlist?.items
|
||||
export let data;
|
||||
console.log('data', data);
|
||||
const wishlist = data.wishlist;
|
||||
const gamesItems = wishlist?.items;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import type { Route } from '$lib/types'
|
||||
import { page } from '$app/stores';
|
||||
import type { Route } from '$lib/types';
|
||||
|
||||
const routes: Route[] = [
|
||||
{ href: '/settings/profile', label: 'Profile' },
|
||||
{ href: '/settings/security', label: 'Security' },
|
||||
]
|
||||
];
|
||||
|
||||
let { children } = $props()
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="security-nav">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// +page.server.ts
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
import type { PageServerLoad } from './$types'
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
// Redirect to a different page
|
||||
throw redirect(307, '/settings/profile')
|
||||
}
|
||||
throw redirect(307, '/settings/profile');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import type { PageServerLoad } from './$types'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
return {
|
||||
hasSetupTwoFactor: authedUser.mfa_enabled,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const actions = {}
|
||||
export const actions = {};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { Button } from '$components/ui/button/index'
|
||||
import { KeyRound } from 'lucide-svelte'
|
||||
import { Button } from '$components/ui/button/index';
|
||||
import { KeyRound } from 'lucide-svelte';
|
||||
|
||||
const { data } = $props()
|
||||
const { data } = $props();
|
||||
|
||||
const hasSetupTwoFactor = data.hasSetupTwoFactor
|
||||
const hasSetupTwoFactor = data.hasSetupTwoFactor;
|
||||
</script>
|
||||
|
||||
<div class="mt-6">
|
||||
|
|
|
|||
|
|
@ -1,81 +1,81 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import { type Actions, fail } from '@sveltejs/kit'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import { zod } from 'sveltekit-superforms/adapters'
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server'
|
||||
import type { PageServerLoad } from './$types'
|
||||
import { changeUserPasswordSchema } from './schemas'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { type Actions, fail } from '@sveltejs/kit';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { changeUserPasswordSchema } from './schemas';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const form = await superValidate(event, zod(changeUserPasswordSchema))
|
||||
const form = await superValidate(event, zod(changeUserPasswordSchema));
|
||||
|
||||
form.data = {
|
||||
current_password: '',
|
||||
password: '',
|
||||
confirm_password: '',
|
||||
}
|
||||
};
|
||||
return {
|
||||
form,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const form = await superValidate(event, zod(changeUserPasswordSchema))
|
||||
const form = await superValidate(event, zod(changeUserPasswordSchema));
|
||||
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const { error: verifyPasswordError } = await locals.api.me.verify.password
|
||||
.$post({
|
||||
json: { password: form.data.current_password },
|
||||
})
|
||||
.then(locals.parseApiResponse)
|
||||
.then(locals.parseApiResponse);
|
||||
|
||||
console.log('verifyPasswordError', verifyPasswordError)
|
||||
console.log('verifyPasswordError', verifyPasswordError);
|
||||
|
||||
if (verifyPasswordError) {
|
||||
console.error(verifyPasswordError)
|
||||
return setError(form, 'current_password', 'Your password is incorrect')
|
||||
console.error(verifyPasswordError);
|
||||
return setError(form, 'current_password', 'Your password is incorrect');
|
||||
}
|
||||
if (authedUser?.username) {
|
||||
try {
|
||||
if (form.data.password !== form.data.confirm_password) {
|
||||
return setError(form, 'Password and confirm password do not match')
|
||||
return setError(form, 'Password and confirm password do not match');
|
||||
}
|
||||
await locals.api.me.update.password.$put({
|
||||
json: { password: form.data.password, confirm_password: form.data.confirm_password },
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
form.data.password = ''
|
||||
form.data.confirm_password = ''
|
||||
form.data.current_password = ''
|
||||
return setError(form, 'current_password', 'Your password is incorrect.')
|
||||
console.error(e);
|
||||
form.data.password = '';
|
||||
form.data.confirm_password = '';
|
||||
form.data.current_password = '';
|
||||
return setError(form, 'current_password', 'Your password is incorrect.');
|
||||
}
|
||||
const message = {
|
||||
type: 'success',
|
||||
message: 'Password Updated. Please sign in.',
|
||||
} as const
|
||||
redirect(302, '/login', message, event)
|
||||
} as const;
|
||||
redirect(302, '/login', message, event);
|
||||
}
|
||||
return setError(form, 'Error occurred. Please try again or contact support if you need further help.')
|
||||
return setError(form, 'Error occurred. Please try again or contact support if you need further help.');
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
<script lang="ts">
|
||||
import * as Alert from '$components/ui/alert'
|
||||
import * as Form from '$components/ui/form'
|
||||
import { Input } from '$components/ui/input'
|
||||
import { Toggle } from '$components/ui/toggle'
|
||||
import { AlertTriangle, EyeIcon, EyeOff } from 'lucide-svelte'
|
||||
import { zodClient } from 'sveltekit-superforms/adapters'
|
||||
import { superForm } from 'sveltekit-superforms/client'
|
||||
import { changeUserPasswordSchema } from './schemas'
|
||||
import * as Alert from '$components/ui/alert';
|
||||
import * as Form from '$components/ui/form';
|
||||
import { Input } from '$components/ui/input';
|
||||
import { Toggle } from '$components/ui/toggle';
|
||||
import { AlertTriangle, EyeIcon, EyeOff } from 'lucide-svelte';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import { changeUserPasswordSchema } from './schemas';
|
||||
|
||||
const { data } = $props()
|
||||
const { data } = $props();
|
||||
|
||||
const form = superForm(data.form, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(changeUserPasswordSchema),
|
||||
delayMs: 500,
|
||||
multipleSubmits: 'prevent',
|
||||
})
|
||||
});
|
||||
|
||||
let hiddenCurrentPassword = $state(true)
|
||||
let hiddenPassword = $state(true)
|
||||
let hiddenConfirmPassword = $state(true)
|
||||
let currentPasswordInput = $derived(hiddenCurrentPassword ? 'password' : 'text')
|
||||
let passwordInput = $derived(hiddenPassword ? 'password' : 'text')
|
||||
let confirmPasswordInput = $derived(hiddenConfirmPassword ? 'password' : 'text')
|
||||
let hiddenCurrentPassword = $state(true);
|
||||
let hiddenPassword = $state(true);
|
||||
let hiddenConfirmPassword = $state(true);
|
||||
let currentPasswordInput = $derived(hiddenCurrentPassword ? 'password' : 'text');
|
||||
let passwordInput = $derived(hiddenPassword ? 'password' : 'text');
|
||||
let confirmPasswordInput = $derived(hiddenConfirmPassword ? 'password' : 'text');
|
||||
|
||||
// $inspect(hiddenCurrentPassword, hiddenPassword, hiddenConfirmPassword)
|
||||
|
||||
const { form: formData, enhance } = form
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<form method="POST" use:enhance>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { z } from 'zod'
|
||||
import { z } from 'zod';
|
||||
|
||||
export const changeUserPasswordSchema = z
|
||||
.object({
|
||||
|
|
@ -7,15 +7,15 @@ export const changeUserPasswordSchema = z
|
|||
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim(),
|
||||
})
|
||||
.superRefine(({ confirm_password, password }, ctx) => {
|
||||
refinePasswords(confirm_password, password, ctx)
|
||||
})
|
||||
refinePasswords(confirm_password, password, ctx);
|
||||
});
|
||||
|
||||
export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema
|
||||
export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema;
|
||||
|
||||
const refinePasswords = async (confirm_password: string, password: string, ctx: z.RefinementCtx) => {
|
||||
await comparePasswords(confirm_password, password, ctx)
|
||||
await checkPasswordStrength(password, ctx)
|
||||
}
|
||||
await comparePasswords(confirm_password, password, ctx);
|
||||
await checkPasswordStrength(password, ctx);
|
||||
};
|
||||
|
||||
const comparePasswords = async (confirm_password: string, password: string, ctx: z.RefinementCtx) => {
|
||||
if (confirm_password !== password) {
|
||||
|
|
@ -23,52 +23,52 @@ const comparePasswords = async (confirm_password: string, password: string, ctx:
|
|||
code: 'custom',
|
||||
message: 'Password and Confirm Password must match',
|
||||
path: ['confirm_password'],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkPasswordStrength = async (password: string, ctx: z.RefinementCtx) => {
|
||||
const minimumLength = password.length < 8
|
||||
const maximumLength = password.length > 128
|
||||
const containsUppercase = (ch: string) => /[A-Z]/.test(ch)
|
||||
const containsLowercase = (ch: string) => /[a-z]/.test(ch)
|
||||
const containsSpecialChar = (ch: string) => /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/.test(ch)
|
||||
let countOfUpperCase = 0
|
||||
let countOfLowerCase = 0
|
||||
let countOfNumbers = 0
|
||||
let countOfSpecialChar = 0
|
||||
const minimumLength = password.length < 8;
|
||||
const maximumLength = password.length > 128;
|
||||
const containsUppercase = (ch: string) => /[A-Z]/.test(ch);
|
||||
const containsLowercase = (ch: string) => /[a-z]/.test(ch);
|
||||
const containsSpecialChar = (ch: string) => /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/.test(ch);
|
||||
let countOfUpperCase = 0;
|
||||
let countOfLowerCase = 0;
|
||||
let countOfNumbers = 0;
|
||||
let countOfSpecialChar = 0;
|
||||
for (let i = 0; i < password.length; i++) {
|
||||
const char = password.charAt(i)
|
||||
const char = password.charAt(i);
|
||||
if (!Number.isNaN(+char)) {
|
||||
countOfNumbers++
|
||||
countOfNumbers++;
|
||||
} else if (containsUppercase(char)) {
|
||||
countOfUpperCase++
|
||||
countOfUpperCase++;
|
||||
} else if (containsLowercase(char)) {
|
||||
countOfLowerCase++
|
||||
countOfLowerCase++;
|
||||
} else if (containsSpecialChar(char)) {
|
||||
countOfSpecialChar++
|
||||
countOfSpecialChar++;
|
||||
}
|
||||
}
|
||||
|
||||
let errorMessage = 'Your password:'
|
||||
let errorMessage = 'Your password:';
|
||||
|
||||
if (countOfLowerCase < 1) {
|
||||
errorMessage = ' Must have at least one lowercase letter. '
|
||||
errorMessage = ' Must have at least one lowercase letter. ';
|
||||
}
|
||||
if (countOfNumbers < 1) {
|
||||
errorMessage += ' Must have at least one number. '
|
||||
errorMessage += ' Must have at least one number. ';
|
||||
}
|
||||
if (countOfUpperCase < 1) {
|
||||
errorMessage += ' Must have at least one uppercase letter. '
|
||||
errorMessage += ' Must have at least one uppercase letter. ';
|
||||
}
|
||||
if (countOfSpecialChar < 1) {
|
||||
errorMessage += ' Must have at least one special character.'
|
||||
errorMessage += ' Must have at least one special character.';
|
||||
}
|
||||
if (minimumLength) {
|
||||
errorMessage += ' Be at least 8 characters long.'
|
||||
errorMessage += ' Be at least 8 characters long.';
|
||||
}
|
||||
if (maximumLength) {
|
||||
errorMessage += ' Be less than 128 characters long.'
|
||||
errorMessage += ' Be less than 128 characters long.';
|
||||
}
|
||||
|
||||
if (errorMessage.length > 'Your password:'.length) {
|
||||
|
|
@ -76,6 +76,6 @@ const checkPasswordStrength = async (password: string, ctx: z.RefinementCtx) =>
|
|||
code: 'custom',
|
||||
message: errorMessage,
|
||||
path: ['password'],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import type { Actions } from '@sveltejs/kit'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import type { PageServerLoad } from '../../$types'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import type { Actions } from '@sveltejs/kit';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import type { PageServerLoad } from '../../$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const { data: totpData, error: totpDataError } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse)
|
||||
const { data: totpData, error: totpDataError } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse);
|
||||
|
||||
const totpEnabled = !!totpData
|
||||
const totpEnabled = !!totpData;
|
||||
|
||||
return {
|
||||
totpEnabled,
|
||||
hardwareTokenEnabled: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {}
|
||||
export const actions: Actions = {};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { Badge } from '$components/ui/badge'
|
||||
import { Button } from '$components/ui/button'
|
||||
import * as Card from '$lib/components/ui/card'
|
||||
import { Badge } from '$components/ui/badge';
|
||||
import { Button } from '$components/ui/button';
|
||||
import * as Card from '$components/ui/card';
|
||||
|
||||
const { data } = $props()
|
||||
const { data } = $props();
|
||||
|
||||
const totpEnabled = data.totpEnabled
|
||||
const hardwareTokenEnabled = data.hardwareTokenEnabled
|
||||
const totpEnabled = data.totpEnabled;
|
||||
const hardwareTokenEnabled = data.hardwareTokenEnabled;
|
||||
</script>
|
||||
|
||||
<h1>Two-factor authentication</h1>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import type { PageServerLoad } from '../../../$types'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import type { PageServerLoad } from '../../../$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
if (authedUser.mfa_enabled) {
|
||||
const { data: recoveryCodesData, error: recoveryCodesError } = await locals.api.mfa.totp.recoveryCodes.$get().then(locals.parseApiResponse)
|
||||
console.log('recoveryCodesData', recoveryCodesData)
|
||||
console.log('recoveryCodesError', recoveryCodesError)
|
||||
const { data: recoveryCodesData, error: recoveryCodesError } = await locals.api.mfa.totp.recoveryCodes.$get().then(locals.parseApiResponse);
|
||||
console.log('recoveryCodesData', recoveryCodesData);
|
||||
console.log('recoveryCodesError', recoveryCodesError);
|
||||
if (recoveryCodesError || !recoveryCodesData || !recoveryCodesData.recoveryCodes) {
|
||||
return {
|
||||
recoveryCodes: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
recoveryCodes: recoveryCodesData.recoveryCodes,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
redirect(302, '/profile', { message: 'Two-Factor Authentication is not enabled', type: 'error' }, event)
|
||||
}
|
||||
redirect(302, '/profile', { message: 'Two-Factor Authentication is not enabled', type: 'error' }, event);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts"></script>
|
||||
|
||||
<h1>Security Keys</h1>
|
||||
|
||||
<p>TODO</p>
|
||||
|
|
@ -1,35 +1,35 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages'
|
||||
import env from '$lib/server/api/common/env'
|
||||
import { decodeHex, encodeBase32 } from '@oslojs/encoding'
|
||||
import { createTOTPKeyURI } from '@oslojs/otp'
|
||||
import { type Actions, fail } from '@sveltejs/kit'
|
||||
import kebabCase from 'just-kebab-case'
|
||||
import QRCode from 'qrcode'
|
||||
import { redirect } from 'sveltekit-flash-message/server'
|
||||
import { zod } from 'sveltekit-superforms/adapters'
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server'
|
||||
import type { PageServerLoad } from '../../$types'
|
||||
import { addTwoFactorSchema, removeTwoFactorSchema } from './schemas'
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import env from '$lib/server/api/common/env';
|
||||
import { decodeHex, encodeBase32 } from '@oslojs/encoding';
|
||||
import { createTOTPKeyURI } from '@oslojs/otp';
|
||||
import { type Actions, fail } from '@sveltejs/kit';
|
||||
import kebabCase from 'just-kebab-case';
|
||||
import QRCode from 'qrcode';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import type { PageServerLoad } from '../../$types';
|
||||
import { addTwoFactorSchema, removeTwoFactorSchema } from './schemas';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema))
|
||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema))
|
||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
||||
// const addAuthNFactorForm = await superValidate(event, zod(addAuthNFactorSchema));
|
||||
|
||||
const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse)
|
||||
const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse);
|
||||
if (error || !data) {
|
||||
return fail(500, {
|
||||
addTwoFactorForm,
|
||||
})
|
||||
});
|
||||
}
|
||||
const { totpCredential } = data
|
||||
const { totpCredential } = data;
|
||||
if (totpCredential && authedUser.mfa_enabled) {
|
||||
return {
|
||||
addTwoFactorForm,
|
||||
|
|
@ -38,41 +38,41 @@ export const load: PageServerLoad = async (event) => {
|
|||
recoveryCodes: [],
|
||||
totpUri: '',
|
||||
qrCode: '',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (totpCredential && !authedUser.mfa_enabled) {
|
||||
await locals.api.mfa.totp.$delete().then(locals.parseApiResponse)
|
||||
await locals.api.mfa.totp.$delete().then(locals.parseApiResponse);
|
||||
}
|
||||
|
||||
const issuer = kebabCase(env.PUBLIC_SITE_NAME)
|
||||
const accountName = authedUser.email || authedUser.username
|
||||
const { data: createdTotpData, error: createdTotpError } = await locals.api.mfa.totp.$post().then(locals.parseApiResponse)
|
||||
const issuer = kebabCase(env.PUBLIC_SITE_NAME);
|
||||
const accountName = authedUser.email || authedUser.username;
|
||||
const { data: createdTotpData, error: createdTotpError } = await locals.api.mfa.totp.$post().then(locals.parseApiResponse);
|
||||
|
||||
if (createdTotpError || !createdTotpData) {
|
||||
return fail(500, {
|
||||
addTwoFactorForm,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const { totpCredential: createdTotpCredentials } = createdTotpData
|
||||
const { totpCredential: createdTotpCredentials } = createdTotpData;
|
||||
// pass the website's name and the user identifier (e.g. email, username)
|
||||
if (!createdTotpCredentials?.secret_data) {
|
||||
return fail(500, {
|
||||
addTwoFactorForm,
|
||||
})
|
||||
});
|
||||
}
|
||||
const decodedHexSecret = decodeHex(createdTotpCredentials.secret_data)
|
||||
const secret = encodeBase32(decodedHexSecret)
|
||||
const intervalInSeconds = 30
|
||||
const digits = 6
|
||||
const decodedHexSecret = decodeHex(createdTotpCredentials.secret_data);
|
||||
const secret = encodeBase32(decodedHexSecret);
|
||||
const intervalInSeconds = 30;
|
||||
const digits = 6;
|
||||
|
||||
const totpUri = createTOTPKeyURI(issuer, accountName, decodedHexSecret, intervalInSeconds, digits)
|
||||
const totpUri = createTOTPKeyURI(issuer, accountName, decodedHexSecret, intervalInSeconds, digits);
|
||||
|
||||
addTwoFactorForm.data = {
|
||||
password: '',
|
||||
two_factor_code: '',
|
||||
}
|
||||
};
|
||||
return {
|
||||
addTwoFactorForm,
|
||||
removeTwoFactorForm,
|
||||
|
|
@ -81,84 +81,84 @@ export const load: PageServerLoad = async (event) => {
|
|||
totpUri,
|
||||
qrCode: await QRCode.toDataURL(totpUri),
|
||||
secret,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
enableTotp: async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema))
|
||||
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
|
||||
|
||||
if (!addTwoFactorForm.valid) {
|
||||
return fail(400, {
|
||||
addTwoFactorForm,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const { error: verifyPasswordError } = await locals.api.me.verify.password
|
||||
.$post({
|
||||
json: { password: addTwoFactorForm.data.password },
|
||||
})
|
||||
.then(locals.parseApiResponse)
|
||||
.then(locals.parseApiResponse);
|
||||
|
||||
if (verifyPasswordError) {
|
||||
console.log(verifyPasswordError)
|
||||
return setError(addTwoFactorForm, 'password', 'Your password is incorrect')
|
||||
console.log(verifyPasswordError);
|
||||
return setError(addTwoFactorForm, 'password', 'Your password is incorrect');
|
||||
}
|
||||
|
||||
if (addTwoFactorForm.data.two_factor_code === '') {
|
||||
return setError(addTwoFactorForm, 'two_factor_code', 'Please enter a code')
|
||||
return setError(addTwoFactorForm, 'two_factor_code', 'Please enter a code');
|
||||
}
|
||||
|
||||
const twoFactorCode = addTwoFactorForm.data.two_factor_code
|
||||
const twoFactorCode = addTwoFactorForm.data.two_factor_code;
|
||||
const { error: verifyTotpError } = await locals.api.mfa.totp.verify
|
||||
.$post({
|
||||
json: { code: twoFactorCode },
|
||||
})
|
||||
.then(locals.parseApiResponse)
|
||||
.then(locals.parseApiResponse);
|
||||
if (verifyTotpError) {
|
||||
return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code')
|
||||
return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code');
|
||||
}
|
||||
|
||||
redirect(302, '/settings/security/mfa/recovery-codes')
|
||||
redirect(302, '/settings/security/mfa/recovery-codes');
|
||||
},
|
||||
disableTotp: async (event) => {
|
||||
const { locals } = event
|
||||
const { locals } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
if (!authedUser) {
|
||||
throw redirect(302, '/login', notSignedInMessage, event)
|
||||
throw redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema))
|
||||
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
|
||||
|
||||
if (!removeTwoFactorForm.valid) {
|
||||
return fail(400, {
|
||||
removeTwoFactorForm,
|
||||
})
|
||||
});
|
||||
}
|
||||
const { error: verifyPasswordError } = await locals.api.me.verify.password
|
||||
.$post({
|
||||
json: { password: removeTwoFactorForm.data.password },
|
||||
})
|
||||
.then(locals.parseApiResponse)
|
||||
.then(locals.parseApiResponse);
|
||||
|
||||
if (verifyPasswordError) {
|
||||
console.log(verifyPasswordError)
|
||||
return setError(removeTwoFactorForm, 'password', 'Your password is incorrect')
|
||||
console.log(verifyPasswordError);
|
||||
return setError(removeTwoFactorForm, 'password', 'Your password is incorrect');
|
||||
}
|
||||
|
||||
const { error: deleteTotpError } = await locals.api.mfa.totp.$delete().then(locals.parseApiResponse)
|
||||
const { error: deleteTotpError } = await locals.api.mfa.totp.$delete().then(locals.parseApiResponse);
|
||||
if (deleteTotpError) {
|
||||
return fail(500, {
|
||||
removeTwoFactorForm,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
redirect(
|
||||
|
|
@ -169,6 +169,6 @@ export const actions: Actions = {
|
|||
message: 'Two-Factor Authentication has been disabled.',
|
||||
},
|
||||
event,
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,36 +1,34 @@
|
|||
<script lang="ts">
|
||||
import CopyCodeBlock from '$components/CopyCodeBlock.svelte'
|
||||
import PinInput from '$components/pin-input.svelte'
|
||||
import * as Alert from '$components/ui/alert'
|
||||
import * as Form from '$components/ui/form'
|
||||
import { Input } from '$components/ui/input'
|
||||
import { AlertTriangle } from 'lucide-svelte'
|
||||
import { zodClient } from 'sveltekit-superforms/adapters'
|
||||
import { superForm } from 'sveltekit-superforms/client'
|
||||
import { addTwoFactorSchema, removeTwoFactorSchema } from './schemas'
|
||||
import CopyCodeBlock from '$components/CopyCodeBlock.svelte';
|
||||
import PinInput from '$components/pin-input.svelte';
|
||||
import * as Form from '$components/ui/form';
|
||||
import { Input } from '$components/ui/input';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import { addTwoFactorSchema, removeTwoFactorSchema } from './schemas';
|
||||
|
||||
const { data } = $props()
|
||||
const { data } = $props();
|
||||
|
||||
const { qrCode, secret, twoFactorEnabled, recoveryCodes } = data
|
||||
const { qrCode, secret, twoFactorEnabled, recoveryCodes } = data;
|
||||
|
||||
const addTwoFactorForm = superForm(data.addTwoFactorForm, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(addTwoFactorSchema),
|
||||
delayMs: 500,
|
||||
multipleSubmits: 'prevent',
|
||||
})
|
||||
});
|
||||
|
||||
const removeTwoFactorForm = superForm(data.removeTwoFactorForm, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(removeTwoFactorSchema),
|
||||
delayMs: 500,
|
||||
multipleSubmits: 'prevent',
|
||||
})
|
||||
});
|
||||
|
||||
console.log('Two Factor: ', twoFactorEnabled, recoveryCodes)
|
||||
console.log('Two Factor: ', twoFactorEnabled, recoveryCodes);
|
||||
|
||||
const { form: addTwoFactorFormData, enhance: addTwoFactorEnhance } = addTwoFactorForm
|
||||
const { form: removeTwoFactorFormData, enhance: removeTwoFactorEnhance } = removeTwoFactorForm
|
||||
const { form: addTwoFactorFormData, enhance: addTwoFactorEnhance } = addTwoFactorForm;
|
||||
const { form: removeTwoFactorFormData, enhance: removeTwoFactorEnhance } = removeTwoFactorForm;
|
||||
</script>
|
||||
|
||||
<section class="two-factor">
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { z } from 'zod'
|
||||
import { z } from 'zod';
|
||||
|
||||
export const addTwoFactorSchema = z.object({
|
||||
password: z.string({ required_error: 'Current Password is required' }),
|
||||
two_factor_code: z.string({ required_error: 'Two Factor Code is required' }).trim(),
|
||||
})
|
||||
});
|
||||
|
||||
export type AddTwoFactorSchema = typeof addTwoFactorSchema
|
||||
export type AddTwoFactorSchema = typeof addTwoFactorSchema;
|
||||
|
||||
export const removeTwoFactorSchema = addTwoFactorSchema.pick({
|
||||
password: true,
|
||||
})
|
||||
});
|
||||
|
||||
export type RemoveTwoFactorSchema = typeof removeTwoFactorSchema
|
||||
export type RemoveTwoFactorSchema = typeof removeTwoFactorSchema;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script lang="ts">
|
||||
import * as Card from '$components/ui/card'
|
||||
|
||||
const { data } = $props()
|
||||
const { wishlists = [] } = data
|
||||
import * as Card from '$components/ui/card';
|
||||
const { data } = $props();
|
||||
const { wishlists = [] } = data;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { notSignedInMessage } from '$lib/flashMessages.js';
|
||||
import { db } from '$lib/server/api/packages/drizzle';
|
||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
||||
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
|
||||
import { type Actions, error, fail } from '@sveltejs/kit';
|
||||
import { type Actions, error } from '@sveltejs/kit';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import Game from '$components/Game.svelte'
|
||||
import Game from '$components/Game.svelte';
|
||||
|
||||
const { data } = $props()
|
||||
const { items = [] } = data
|
||||
const { data } = $props();
|
||||
const { items = [] } = data;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { loadFlash } from 'sveltekit-flash-message/server'
|
||||
import type { LayoutServerLoad } from '../$types'
|
||||
import { loadFlash } from 'sveltekit-flash-message/server';
|
||||
import type { LayoutServerLoad } from '../$types';
|
||||
|
||||
export const load: LayoutServerLoad = loadFlash(async (event) => {
|
||||
const { url, locals } = event
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const { url, locals } = event;
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
|
||||
return {
|
||||
url: url.pathname,
|
||||
authedUser,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import 'iconify-icon'
|
||||
import Footer from '$components/Footer.svelte'
|
||||
import Header from '$components/Header.svelte'
|
||||
import 'iconify-icon';
|
||||
import Footer from '$components/Footer.svelte';
|
||||
import Header from '$components/Header.svelte';
|
||||
|
||||
const { data, children } = $props()
|
||||
const { data, children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen w-full flex-col">
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { fail } from '@sveltejs/kit'
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags'
|
||||
import type { PageServerLoad } from './$types'
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { locals, url } = event
|
||||
const { locals, url } = event;
|
||||
|
||||
const authedUser = await locals.getAuthedUser()
|
||||
const authedUser = await locals.getAuthedUser();
|
||||
|
||||
const image = {
|
||||
url: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
};
|
||||
const metaTags: MetaTagsProps = Object.freeze({
|
||||
title: 'Home',
|
||||
description: 'Home page',
|
||||
|
|
@ -33,18 +33,18 @@ export const load: PageServerLoad = async (event) => {
|
|||
image: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`,
|
||||
imageAlt: 'Home | Bored Game',
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
if (authedUser) {
|
||||
const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse)
|
||||
const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse)
|
||||
const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse);
|
||||
const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse);
|
||||
|
||||
if (wishlistsError || collectionsError) {
|
||||
return fail(500, 'Failed to fetch wishlistsTable or collections')
|
||||
return fail(500, 'Failed to fetch wishlistsTable or collections');
|
||||
}
|
||||
|
||||
console.log('Wishlists', wishlistsData.wishlists)
|
||||
console.log('Collections', collectionsData.collections)
|
||||
console.log('Wishlists', wishlistsData.wishlists);
|
||||
console.log('Collections', collectionsData.collections);
|
||||
return {
|
||||
metaTagsChild: metaTags,
|
||||
user: {
|
||||
|
|
@ -54,10 +54,10 @@ export const load: PageServerLoad = async (event) => {
|
|||
},
|
||||
wishlists: wishlistsData.wishlists,
|
||||
collections: collectionsData.collections,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Not Authed')
|
||||
console.log('Not Authed');
|
||||
|
||||
return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] }
|
||||
}
|
||||
return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<script lang="ts">
|
||||
import AddToList from '$components/AddToList.svelte'
|
||||
import Badge from '$components/ui/badge/badge.svelte'
|
||||
import { Button } from '$components/ui/button'
|
||||
import { Dices, ExternalLinkIcon, MinusIcon, PlusIcon } from 'lucide-svelte'
|
||||
import { Image } from 'svelte-lazy-loader'
|
||||
import type { PageData } from './$types'
|
||||
import AddToList from '$components/AddToList.svelte';
|
||||
import Badge from '$components/ui/badge/badge.svelte';
|
||||
import { Button } from '$components/ui/button';
|
||||
import { Dices, ExternalLinkIcon, MinusIcon, PlusIcon } from 'lucide-svelte';
|
||||
import { Image } from 'svelte-lazy-loader';
|
||||
|
||||
const { data } = $props()
|
||||
const { game, user, in_collection, in_wishlist } = data
|
||||
const { data } = $props();
|
||||
const { game, user, in_collection, in_wishlist } = data;
|
||||
|
||||
let seeMore: boolean = $state(false)
|
||||
let seeMore: boolean = $state(false);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
|
|||
|
|
@ -1,65 +1,65 @@
|
|||
import type { GameType, SearchQuery } from '$lib/types'
|
||||
import { createOrUpdateGameMinimal } from '$lib/utils/db/gameUtils'
|
||||
import { mapAPIGameToBoredGame } from '$lib/utils/gameMapper.js'
|
||||
import { search_schema } from '$lib/zodValidation'
|
||||
import { error } from '@sveltejs/kit'
|
||||
import type { BggThingDto } from 'boardgamegeekclient/dist/esm/dto/index.js'
|
||||
import kebabCase from 'just-kebab-case'
|
||||
import { zod } from 'sveltekit-superforms/adapters'
|
||||
import { superValidate } from 'sveltekit-superforms/server'
|
||||
import type { GameType, SearchQuery } from '$lib/types';
|
||||
import { createOrUpdateGameMinimal } from '$lib/utils/db/gameUtils';
|
||||
import { mapAPIGameToBoredGame } from '$lib/utils/gameMapper.js';
|
||||
import { search_schema } from '$lib/zodValidation';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { BggThingDto } from 'boardgamegeekclient/dist/esm/dto/index.js';
|
||||
import kebabCase from 'just-kebab-case';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
|
||||
async function searchForGames(locals: App.Locals, eventFetch: typeof fetch, urlQueryParams: URLSearchParams) {
|
||||
try {
|
||||
console.log('urlQueryParams search games', urlQueryParams)
|
||||
console.log('urlQueryParams search games', urlQueryParams);
|
||||
|
||||
const headers = new Headers()
|
||||
headers.set('Content-Type', 'application/json')
|
||||
const headers = new Headers();
|
||||
headers.set('Content-Type', 'application/json');
|
||||
const requestInit: RequestInit = {
|
||||
method: 'GET',
|
||||
headers,
|
||||
}
|
||||
const url = `/api/games/search${urlQueryParams ? `?${urlQueryParams}` : ''}`
|
||||
console.log('Calling internal api', url)
|
||||
const response = await eventFetch(url, requestInit)
|
||||
console.log('response from internal api', response)
|
||||
};
|
||||
const url = `/api/games/search${urlQueryParams ? `?${urlQueryParams}` : ''}`;
|
||||
console.log('Calling internal api', url);
|
||||
const response = await eventFetch(url, requestInit);
|
||||
console.log('response from internal api', response);
|
||||
|
||||
if (response.status !== 404 && !response.ok) {
|
||||
console.log('Status from internal api not 200', response.status)
|
||||
error(response.status)
|
||||
console.log('Status from internal api not 200', response.status);
|
||||
error(response.status);
|
||||
}
|
||||
|
||||
const games = await response.json()
|
||||
console.log('games from DB', games)
|
||||
const games = await response.json();
|
||||
console.log('games from DB', games);
|
||||
|
||||
const gameNameSearch = urlQueryParams.get('q') ?? ''
|
||||
let totalCount = games?.length || 0
|
||||
const gameNameSearch = urlQueryParams.get('q') ?? '';
|
||||
let totalCount = games?.length || 0;
|
||||
|
||||
if (totalCount === 0 || !games.find((game: GameType) => game.slug === kebabCase(gameNameSearch))) {
|
||||
console.log('No games found in DB for', gameNameSearch)
|
||||
const searchQueryParams = urlQueryParams ? `?${urlQueryParams}` : ''
|
||||
const externalResponse = await eventFetch(`/api/external/search${searchQueryParams}`, requestInit)
|
||||
console.log('No games found in DB for', gameNameSearch);
|
||||
const searchQueryParams = urlQueryParams ? `?${urlQueryParams}` : '';
|
||||
const externalResponse = await eventFetch(`/api/external/search${searchQueryParams}`, requestInit);
|
||||
|
||||
console.log('Back from external search', externalResponse)
|
||||
console.log('Back from external search', externalResponse);
|
||||
|
||||
if (!externalResponse.ok) {
|
||||
console.log('Status not 200', externalResponse.status)
|
||||
error(externalResponse.status)
|
||||
console.log('Status not 200', externalResponse.status);
|
||||
error(externalResponse.status);
|
||||
}
|
||||
|
||||
if (externalResponse.ok) {
|
||||
const gameResponse = await externalResponse.json()
|
||||
console.log('response from external api', gameResponse)
|
||||
const gameList: BggThingDto[] = gameResponse?.games
|
||||
totalCount = gameResponse?.totalCount
|
||||
console.log('totalCount', totalCount)
|
||||
const gameResponse = await externalResponse.json();
|
||||
console.log('response from external api', gameResponse);
|
||||
const gameList: BggThingDto[] = gameResponse?.games;
|
||||
totalCount = gameResponse?.totalCount;
|
||||
console.log('totalCount', totalCount);
|
||||
for (const game of gameList) {
|
||||
console.log(`Retrieving simplified external game details for id: ${game.id} with name ${game.name}`)
|
||||
const externalGameResponse = await eventFetch(`/api/external/game/${game.id}?simplified=true`)
|
||||
console.log(`Retrieving simplified external game details for id: ${game.id} with name ${game.name}`);
|
||||
const externalGameResponse = await eventFetch(`/api/external/game/${game.id}?simplified=true`);
|
||||
if (externalGameResponse.ok) {
|
||||
const externalGame = await externalGameResponse.json()
|
||||
console.log('externalGame', externalGame)
|
||||
const boredGame = mapAPIGameToBoredGame(externalGame)
|
||||
games.push(createOrUpdateGameMinimal(locals, boredGame, externalGame.id))
|
||||
const externalGame = await externalGameResponse.json();
|
||||
console.log('externalGame', externalGame);
|
||||
const boredGame = mapAPIGameToBoredGame(externalGame);
|
||||
games.push(createOrUpdateGameMinimal(locals, boredGame, externalGame.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,14 +68,14 @@ async function searchForGames(locals: App.Locals, eventFetch: typeof fetch, urlQ
|
|||
return {
|
||||
totalCount,
|
||||
games,
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(`Error searching board games ${e}`)
|
||||
console.log(`Error searching board games ${e}`);
|
||||
}
|
||||
return {
|
||||
totalCount: 0,
|
||||
games: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
|
|
@ -85,14 +85,14 @@ const defaults = {
|
|||
sort: 'asc',
|
||||
q: '',
|
||||
exact: false,
|
||||
}
|
||||
};
|
||||
|
||||
export const load = async ({ locals, fetch, url }) => {
|
||||
const searchParams = Object.fromEntries(url?.searchParams)
|
||||
console.log('searchParams', searchParams)
|
||||
searchParams.order = searchParams.order || defaults.order
|
||||
searchParams.sort = searchParams.sort || defaults.sort
|
||||
searchParams.q = searchParams.q || defaults.q
|
||||
const searchParams = Object.fromEntries(url?.searchParams);
|
||||
console.log('searchParams', searchParams);
|
||||
searchParams.order = searchParams.order || defaults.order;
|
||||
searchParams.sort = searchParams.sort || defaults.sort;
|
||||
searchParams.q = searchParams.q || defaults.q;
|
||||
const form = await superValidate(
|
||||
{
|
||||
...searchParams,
|
||||
|
|
@ -101,14 +101,14 @@ export const load = async ({ locals, fetch, url }) => {
|
|||
exact: searchParams.exact ? searchParams.exact === 'true' : defaults.exact,
|
||||
},
|
||||
zod(search_schema),
|
||||
)
|
||||
);
|
||||
|
||||
const queryParams: SearchQuery = {
|
||||
limit: form.data?.limit,
|
||||
skip: form.data?.skip,
|
||||
q: form.data?.q,
|
||||
exact: form.data?.exact,
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (form.data?.q === '') {
|
||||
|
|
@ -119,53 +119,53 @@ export const load = async ({ locals, fetch, url }) => {
|
|||
games: [],
|
||||
wishlists: [],
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (form.data?.minAge) {
|
||||
if (form.data?.exactMinAge) {
|
||||
queryParams.min_age = form.data?.minAge
|
||||
queryParams.min_age = form.data?.minAge;
|
||||
} else {
|
||||
queryParams.gt_min_age = form.data?.minAge === 1 ? 0 : form.data?.minAge - 1
|
||||
queryParams.gt_min_age = form.data?.minAge === 1 ? 0 : form.data?.minAge - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (form.data?.minPlayers) {
|
||||
if (form.data?.exactMinPlayers) {
|
||||
queryParams.min_players = form.data?.minPlayers
|
||||
queryParams.min_players = form.data?.minPlayers;
|
||||
} else {
|
||||
queryParams.gt_min_players = form.data?.minPlayers === 1 ? 0 : form.data?.minPlayers - 1
|
||||
queryParams.gt_min_players = form.data?.minPlayers === 1 ? 0 : form.data?.minPlayers - 1;
|
||||
}
|
||||
}
|
||||
if (form.data?.maxPlayers) {
|
||||
if (form.data?.exactMaxPlayers) {
|
||||
queryParams.max_players = form.data?.maxPlayers
|
||||
queryParams.max_players = form.data?.maxPlayers;
|
||||
} else {
|
||||
queryParams.lt_max_players = form.data?.maxPlayers + 1
|
||||
queryParams.lt_max_players = form.data?.maxPlayers + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const newQueryParams: Record<string, string> = {}
|
||||
const newQueryParams: Record<string, string> = {};
|
||||
for (const key in queryParams) {
|
||||
newQueryParams[key] = `${queryParams[key as keyof SearchQuery]}`
|
||||
newQueryParams[key] = `${queryParams[key as keyof SearchQuery]}`;
|
||||
}
|
||||
|
||||
const urlQueryParams = new URLSearchParams(newQueryParams)
|
||||
const searchData = await searchForGames(locals, fetch, urlQueryParams)
|
||||
const urlQueryParams = new URLSearchParams(newQueryParams);
|
||||
const searchData = await searchForGames(locals, fetch, urlQueryParams);
|
||||
|
||||
console.log('search data', JSON.stringify(searchData, null, 2))
|
||||
console.log('search data', JSON.stringify(searchData, null, 2));
|
||||
|
||||
return {
|
||||
form,
|
||||
// modifyListForm,
|
||||
searchData,
|
||||
wishlists: [],
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(`Error searching board games ${e}`)
|
||||
console.log(`Error searching board games ${e}`);
|
||||
}
|
||||
|
||||
console.log('returning default no data')
|
||||
console.log('returning default no data');
|
||||
return {
|
||||
form,
|
||||
searchData: {
|
||||
|
|
@ -173,29 +173,29 @@ export const load = async ({ locals, fetch, url }) => {
|
|||
games: [],
|
||||
},
|
||||
wishlists: [],
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
random: async ({ request, locals, fetch }) => {
|
||||
const form = await superValidate(request, zod(search_schema))
|
||||
const form = await superValidate(request, zod(search_schema));
|
||||
const queryParams: SearchQuery = {
|
||||
order_by: 'rank',
|
||||
ascending: false,
|
||||
random: true,
|
||||
fields: 'id,name,min_age,min_players,max_players,thumb_url,min_playtime,max_playtime,min_age,description',
|
||||
}
|
||||
};
|
||||
|
||||
const newQueryParams: Record<string, string> = {}
|
||||
const newQueryParams: Record<string, string> = {};
|
||||
for (const key in queryParams) {
|
||||
newQueryParams[key] = `${queryParams[key as keyof SearchQuery]}`
|
||||
newQueryParams[key] = `${queryParams[key as keyof SearchQuery]}`;
|
||||
}
|
||||
|
||||
const urlQueryParams = new URLSearchParams(newQueryParams)
|
||||
const urlQueryParams = new URLSearchParams(newQueryParams);
|
||||
|
||||
return {
|
||||
form,
|
||||
searchData: await searchForGames(locals, fetch, urlQueryParams),
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue