mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding superforms, image lazy loader, and image icons.
This commit is contained in:
parent
b09f71244f
commit
240bf4aa9e
12 changed files with 1096 additions and 984 deletions
46
package.json
46
package.json
|
|
@ -13,55 +13,59 @@
|
||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write --plugin-search-dir=. ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-icons/line-md": "^1.2.21",
|
"@playwright/test": "^1.33.0",
|
||||||
"@iconify-icons/mdi": "^1.2.44",
|
|
||||||
"@playwright/test": "^1.31.2",
|
|
||||||
"@rgossiaux/svelte-headlessui": "1.0.2",
|
"@rgossiaux/svelte-headlessui": "1.0.2",
|
||||||
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
||||||
"@sveltejs/adapter-auto": "^1.0.3",
|
"@sveltejs/adapter-auto": "^1.0.3",
|
||||||
"@sveltejs/adapter-vercel": "^1.0.6",
|
"@sveltejs/adapter-vercel": "^1.0.6",
|
||||||
"@sveltejs/kit": "^1.11.0",
|
"@sveltejs/kit": "^1.16.3",
|
||||||
"@types/cookie": "^0.5.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"@types/node": "^18.15.0",
|
"@types/node": "^18.16.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||||
"@typescript-eslint/parser": "^5.54.1",
|
"@typescript-eslint/parser": "^5.59.5",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.40.0",
|
||||||
"eslint-config-prettier": "^8.7.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte": "^2.28.0",
|
||||||
"just-clone": "^6.2.0",
|
"just-clone": "^6.2.0",
|
||||||
"just-debounce-it": "^3.2.0",
|
"just-debounce-it": "^3.2.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.23",
|
||||||
"postcss-color-functional-notation": "^4.2.4",
|
"postcss-color-functional-notation": "^4.2.4",
|
||||||
"postcss-custom-media": "^9.1.2",
|
"postcss-custom-media": "^9.1.3",
|
||||||
"postcss-env-function": "^4.0.6",
|
"postcss-env-function": "^4.0.6",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
"postcss-media-minmax": "^5.0.0",
|
"postcss-media-minmax": "^5.0.0",
|
||||||
"postcss-nested": "^6.0.1",
|
"postcss-nested": "^6.0.1",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-svelte": "^2.9.0",
|
"prettier-plugin-svelte": "^2.10.0",
|
||||||
"sass": "^1.59.2",
|
"sass": "^1.62.1",
|
||||||
"svelte": "^3.56.0",
|
"svelte": "^3.59.1",
|
||||||
"svelte-check": "^2.10.3",
|
"svelte-check": "^2.10.3",
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
|
"sveltekit-superforms": "^0.8.6",
|
||||||
"tslib": "^2.5.0",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"vite": "^4.1.4",
|
"vite": "^4.3.5",
|
||||||
"vitest": "^0.25.3"
|
"vitest": "^0.25.3",
|
||||||
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/fira-mono": "^4.5.10",
|
"@fontsource/fira-mono": "^4.5.10",
|
||||||
|
"@iconify-icons/line-md": "^1.2.22",
|
||||||
|
"@iconify-icons/mdi": "^1.2.45",
|
||||||
"@leveluptuts/svelte-side-menu": "^1.0.5",
|
"@leveluptuts/svelte-side-menu": "^1.0.5",
|
||||||
"@leveluptuts/svelte-toy": "^2.0.3",
|
"@leveluptuts/svelte-toy": "^2.0.3",
|
||||||
"@lukeed/uuid": "^2.0.0",
|
"@lukeed/uuid": "^2.0.1",
|
||||||
"@types/feather-icons": "^4.29.1",
|
"@types/feather-icons": "^4.29.1",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"feather-icons": "^4.29.0",
|
"feather-icons": "^4.29.0",
|
||||||
"iconify-icon": "^1.0.7",
|
"iconify-icon": "^1.0.7",
|
||||||
|
"loader": "^2.1.1",
|
||||||
|
"open-props": "^1.5.8",
|
||||||
|
"svelte-lazy": "^1.2.1",
|
||||||
"svelte-lazy-loader": "^1.0.0",
|
"svelte-lazy-loader": "^1.0.0",
|
||||||
"zod": "^3.21.4",
|
"zod-to-json-schema": "^3.21.1"
|
||||||
"zod-to-json-schema": "^3.20.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1676
pnpm-lock.yaml
1676
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionData } from './$types';
|
|
||||||
import { boredState } from '$lib/stores/boredState';
|
import { boredState } from '$lib/stores/boredState';
|
||||||
import type { PageData } from '.svelte-kit/types/src/routes/$types';
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let form;
|
||||||
|
export let errors;
|
||||||
|
export let constraints;
|
||||||
|
console.log('advanced search data', $form);
|
||||||
|
|
||||||
let submitting = $boredState?.loading;
|
let submitting = $boredState?.loading;
|
||||||
let minAge = +data?.minAge || 1;
|
let minAge = +$form?.minAge || 1;
|
||||||
let minPlayers = +data?.minPlayers || 1;
|
let minPlayers = +$form?.minPlayers || 1;
|
||||||
let maxPlayers = +data?.maxPlayers || 1;
|
let maxPlayers = +$form?.maxPlayers || 1;
|
||||||
let exactMinPlayers = Boolean(data?.exactMinPlayers) || false;
|
let exactMinPlayers = Boolean($form?.exactMinPlayers) || false;
|
||||||
let exactMaxPlayers = Boolean(data?.exactMaxPlayers) || false;
|
let exactMaxPlayers = Boolean($form?.exactMaxPlayers) || false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
|
<fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
|
||||||
|
|
@ -19,10 +20,10 @@
|
||||||
Min Age
|
Min Age
|
||||||
<input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} />
|
<input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} />
|
||||||
</label>
|
</label>
|
||||||
{#if data?.errors?.minAge}
|
{#if $errors?.minAge}
|
||||||
<div id="minPlayers-error" class="error">
|
<div id="minPlayers-error" class="error">
|
||||||
<p aria-label={`Error: ${data?.errors?.minAge}`} class="center">
|
<p aria-label={`Error: ${$errors?.minAge}`} class="center">
|
||||||
{data?.errors?.minAge}
|
{$errors?.minAge}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -49,10 +50,10 @@
|
||||||
bind:value={exactMinPlayers}
|
bind:value={exactMinPlayers}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{#if data?.errors?.minPlayers}
|
{#if $errors?.minPlayers}
|
||||||
<div id="minPlayers-error" class="error">
|
<div id="minPlayers-error" class="error">
|
||||||
<p aria-label={`Error: ${data?.errors?.minPlayers}`} class="center">
|
<p aria-label={`Error: ${$errors?.minPlayers}`} class="center">
|
||||||
{data?.errors?.minPlayers}
|
{$errors?.minPlayers}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -79,10 +80,10 @@
|
||||||
bind:value={exactMaxPlayers}
|
bind:value={exactMaxPlayers}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{#if data?.error?.id === 'maxPlayers'}
|
{#if $errors?.id === 'maxPlayers'}
|
||||||
<div id="maxPlayers-error" class="error">
|
<div id="maxPlayers-error" class="error">
|
||||||
<p aria-label={`Error: ${data.error.message}`} class="center">
|
<p aria-label={`Error: ${$errors.message}`} class="center">
|
||||||
Error: {data.error.message}
|
Error: {$errors.message}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { applyAction, enhance, type SubmitFunction } from '$app/forms';
|
import { applyAction, enhance, type SubmitFunction } from '$app/forms';
|
||||||
import type { ActionData, PageData } from './$types';
|
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
|
||||||
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
|
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
|
||||||
|
|
@ -21,11 +21,9 @@
|
||||||
detail: GameType | SavedGameType;
|
detail: GameType | SavedGameType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let data: PageData;
|
export let form;
|
||||||
// console.log('search page data', data);
|
export let errors;
|
||||||
export let form: ActionData;
|
export let constraints;
|
||||||
// console.log('search page form', form);
|
|
||||||
const errors = data?.errors;
|
|
||||||
|
|
||||||
export let showButton: boolean = false;
|
export let showButton: boolean = false;
|
||||||
export let advancedSearch: boolean = false;
|
export let advancedSearch: boolean = false;
|
||||||
|
|
@ -33,13 +31,13 @@
|
||||||
let gameToRemove: GameType | SavedGameType;
|
let gameToRemove: GameType | SavedGameType;
|
||||||
let numberOfGameSkeleton = 1;
|
let numberOfGameSkeleton = 1;
|
||||||
let submitButton: HTMLElement;
|
let submitButton: HTMLElement;
|
||||||
let pageSize = +data?.limit || 10;
|
let pageSize = +form?.limit || 10;
|
||||||
let totalItems = +data?.totalCount || 0;
|
let totalItems = +form?.searchData?.totalCount || 0;
|
||||||
let offset = +data?.skip || 0;
|
let offset = +form?.skip || 0;
|
||||||
let page = Math.floor(offset / pageSize) + 1 || 1;
|
let page = Math.floor(offset / pageSize) + 1 || 1;
|
||||||
let submitting = $boredState?.loading;
|
let submitting = $boredState?.loading;
|
||||||
let name = data?.name || '';
|
let name = form?.name || '';
|
||||||
let disclosureOpen = errors || false;
|
let disclosureOpen = $errors.length > 0 || false;
|
||||||
|
|
||||||
$: skip = (page - 1) * pageSize;
|
$: skip = (page - 1) * pageSize;
|
||||||
$: showPagination = $gameStore?.length > 1;
|
$: showPagination = $gameStore?.length > 1;
|
||||||
|
|
@ -121,8 +119,8 @@
|
||||||
await applyAction(result);
|
await applyAction(result);
|
||||||
} else if (result.type === 'success') {
|
} else if (result.type === 'success') {
|
||||||
gameStore.removeAll();
|
gameStore.removeAll();
|
||||||
gameStore.addAll(result?.data?.games);
|
gameStore.addAll(result?.data?.searchData?.games);
|
||||||
totalItems = result?.data?.totalCount;
|
totalItems = result?.data?.searchData?.totalCount;
|
||||||
// toast.send('Success!', { duration: 3000, type: ToastType.INFO, dismissible: true });
|
// toast.send('Success!', { duration: 3000, type: ToastType.INFO, dismissible: true });
|
||||||
await applyAction(result);
|
await applyAction(result);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -131,28 +129,36 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dev = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
// TODO: Keep all Pagination Values on back and forth browser
|
// TODO: Keep all Pagination Values on back and forth browser
|
||||||
// TODO: Add cache for certain number of pages so back and forth doesn't request data again
|
// TODO: Add cache for certain number of pages so back and forth doesn't request data again
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form id="search-form" action="/search" method="get" on:submit={() => {
|
{#if dev}
|
||||||
|
<SuperDebug data={$form} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<form id="search-form" action="/search" method="GET" on:submit={() => {
|
||||||
skip = 0;
|
skip = 0;
|
||||||
}}>
|
}}>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
|
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
|
||||||
<label for="q">
|
<label for="q">Search</label>
|
||||||
Search
|
<input
|
||||||
<input
|
id="q"
|
||||||
id="q"
|
name="q"
|
||||||
name="q"
|
bind:value={$form.q}
|
||||||
bind:value={name}
|
data-invalid={$errors?.q}
|
||||||
type="text"
|
{...$constraints.q}
|
||||||
aria-label="Search boardgame"
|
type="text"
|
||||||
placeholder="Search boardgame"
|
aria-label="Search board games"
|
||||||
/>
|
placeholder="Search board games"
|
||||||
</label>
|
/>
|
||||||
<input id="skip" type="hidden" name="skip" bind:value={skip} />
|
{#if $errors?.q}<span class="invalid">{$errors?.q}</span>{/if}
|
||||||
<input id="limit" type="hidden" name="limit" bind:value={pageSize} />
|
|
||||||
|
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
|
||||||
|
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{#if advancedSearch}
|
{#if advancedSearch}
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
|
|
@ -174,7 +180,9 @@
|
||||||
<!-- Using `static`, `DisclosurePanel` is always rendered,
|
<!-- Using `static`, `DisclosurePanel` is always rendered,
|
||||||
and ignores the `open` state -->
|
and ignores the `open` state -->
|
||||||
<DisclosurePanel static>
|
<DisclosurePanel static>
|
||||||
<AdvancedSearch {data} />
|
{#if disclosureOpen}
|
||||||
|
<AdvancedSearch {form} {errors} {constraints} />
|
||||||
|
{/if}
|
||||||
</DisclosurePanel>
|
</DisclosurePanel>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
5
src/lib/config.ts
Normal file
5
src/lib/config.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { dev } from '$app/environment';
|
||||||
|
|
||||||
|
export const title = 'Bored Game';
|
||||||
|
export const description = 'Bored? Find a game! Bored Game!';
|
||||||
|
export const url = dev ? 'http://localhost:5173' : 'https://boredgame.vercel.app';
|
||||||
|
|
@ -31,13 +31,15 @@ function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema
|
||||||
|
|
||||||
export const search_schema = z
|
export const search_schema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().optional(),
|
q: z.string().trim().optional().default(''),
|
||||||
minAge: IntegerString(z.number().min(1).max(120).optional()),
|
minAge: IntegerString(z.number().min(1).max(120).optional()),
|
||||||
minPlayers: IntegerString(z.number().min(1).max(50).optional()),
|
minPlayers: IntegerString(z.number().min(1).max(50).optional()),
|
||||||
maxPlayers: IntegerString(z.number().min(1).max(50).optional()),
|
maxPlayers: IntegerString(z.number().min(1).max(50).optional()),
|
||||||
exactMinAge: z.preprocess((a) => Boolean(a), z.boolean().optional()),
|
exactMinAge: z.preprocess((a) => Boolean(a), z.boolean().optional()),
|
||||||
exactMinPlayers: z.preprocess((a) => Boolean(a), z.boolean().optional()),
|
exactMinPlayers: z.preprocess((a) => Boolean(a), z.boolean().optional()),
|
||||||
exactMaxPlayers: z.preprocess((a) => Boolean(a), z.boolean().optional())
|
exactMaxPlayers: z.preprocess((a) => Boolean(a), z.boolean().optional()),
|
||||||
|
limit: z.number().min(10).max(100).default(10),
|
||||||
|
skip: z.number().min(0).default(0)
|
||||||
})
|
})
|
||||||
.superRefine(
|
.superRefine(
|
||||||
({ minPlayers, maxPlayers, minAge, exactMinAge, exactMinPlayers, exactMaxPlayers }, ctx) => {
|
({ minPlayers, maxPlayers, minAge, exactMinAge, exactMinPlayers, exactMaxPlayers }, ctx) => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import type { PageServerLoad, Actions } from './$types';
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
|
import { search_schema } from '$lib/zodValidation';
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const load = async ({ fetch, url }) => {
|
||||||
default: async ({ request, locals }): Promise<any> => {
|
const formData = Object.fromEntries(url?.searchParams);
|
||||||
// Do things in here
|
formData.name = formData?.q;
|
||||||
return {};
|
const form = await superValidate(formData, search_schema);
|
||||||
}
|
return { form };
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default: async ({ request, locals }): Promise<any> => {
|
||||||
|
// Do things in here
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionData, PageData } from './$types';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
import TextSearch from '$lib/components/search/textSearch/index.svelte';
|
import TextSearch from '$lib/components/search/textSearch/index.svelte';
|
||||||
import RandomSearch from '$lib/components/search/random/index.svelte';
|
import RandomSearch from '$lib/components/search/random/index.svelte';
|
||||||
import Random from '$lib/components/random/index.svelte';
|
import Random from '$lib/components/random/index.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data;
|
||||||
// console.log('data', data);
|
const { form, errors, constraints } = superForm(data?.form);
|
||||||
export let form: ActionData;
|
|
||||||
// console.log('form', form);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -26,7 +24,7 @@
|
||||||
<Random />
|
<Random />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<TextSearch showButton advancedSearch {data} {form} />
|
<TextSearch showButton advancedSearch {form} {errors} {constraints} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
|
||||||
import { boardGameApi } from '../../api';
|
import { boardGameApi } from '../../api';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, setHeaders }) => {
|
export const load = async ({ params, setHeaders }) => {
|
||||||
const queryParams = {
|
const queryParams = {
|
||||||
ids: `${params?.id}`,
|
ids: `${params?.id}`,
|
||||||
fields:
|
fields:
|
||||||
|
|
|
||||||
|
|
@ -1,93 +1,12 @@
|
||||||
import type { Actions, PageServerLoad, RequestEvent } from '../$types';
|
import type { Actions, RequestEvent } from '../$types';
|
||||||
import { BOARD_GAME_ATLAS_CLIENT_ID } from '$env/static/private';
|
import { BOARD_GAME_ATLAS_CLIENT_ID } from '$env/static/private';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import type { GameType, SearchQuery } from '$root/lib/types';
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
import { mapAPIGameToBoredGame } from '$root/lib/util/gameMapper';
|
import type { GameType, SearchQuery } from '$lib/types';
|
||||||
import { search_schema } from '$root/lib/zodValidation';
|
import { mapAPIGameToBoredGame } from '$lib/util/gameMapper';
|
||||||
import { ZodError } from 'zod';
|
import { search_schema } from '$lib/zodValidation';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ fetch, url }) => {
|
|
||||||
const formData = Object.fromEntries(url?.searchParams);
|
|
||||||
formData.name = formData?.q;
|
|
||||||
const limit = parseInt(formData?.limit) || 10;
|
|
||||||
const skip = parseInt(formData?.skip) || 0;
|
|
||||||
|
|
||||||
const queryParams: SearchQuery = {
|
|
||||||
order_by: 'rank',
|
|
||||||
ascending: false,
|
|
||||||
limit,
|
|
||||||
skip,
|
|
||||||
client_id: BOARD_GAME_ATLAS_CLIENT_ID,
|
|
||||||
fuzzy_match: true,
|
|
||||||
name: '',
|
|
||||||
fields:
|
|
||||||
'id,name,min_age,min_players,max_players,thumb_url,min_playtime,max_playtime,min_age,description'
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('Parsing Search Schema');
|
|
||||||
const { name, minAge, minPlayers, maxPlayers, exactMinAge, exactMinPlayers, exactMaxPlayers } =
|
|
||||||
search_schema.parse(formData);
|
|
||||||
|
|
||||||
if (minAge) {
|
|
||||||
if (exactMinAge) {
|
|
||||||
queryParams.min_age = minAge;
|
|
||||||
} else {
|
|
||||||
queryParams.gt_min_age = minAge === 1 ? 0 : minAge - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minPlayers) {
|
|
||||||
if (exactMinPlayers) {
|
|
||||||
queryParams.min_players = minPlayers;
|
|
||||||
} else {
|
|
||||||
queryParams.gt_min_players = minPlayers === 1 ? 0 : minPlayers - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maxPlayers) {
|
|
||||||
if (exactMaxPlayers) {
|
|
||||||
queryParams.max_players = maxPlayers;
|
|
||||||
} else {
|
|
||||||
queryParams.lt_max_players = maxPlayers + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
queryParams.name = name;
|
|
||||||
}
|
|
||||||
} catch (parsingError: unknown) {
|
|
||||||
let errors;
|
|
||||||
if (parsingError instanceof ZodError) {
|
|
||||||
const { fieldErrors } = parsingError.flatten();
|
|
||||||
console.log(`Errors with user input ${fieldErrors}}`);
|
|
||||||
errors = fieldErrors;
|
|
||||||
//throw error(400, { message: 'There was an error searching for games!' }); // fail(400, { data: formData, errors });
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
errors,
|
|
||||||
name: formData.name,
|
|
||||||
minAge: formData.minAge,
|
|
||||||
minPlayers: formData.minPlayers,
|
|
||||||
maxPlayers: formData.maxPlayers,
|
|
||||||
exactMinPlayers: formData.exactMinPlayers,
|
|
||||||
exactMaxPlayers: formData.exactMaxPlayers,
|
|
||||||
games: [],
|
|
||||||
totalCount: 0,
|
|
||||||
limit,
|
|
||||||
skip
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const newQueryParams: Record<string, string> = {};
|
|
||||||
for (const key in queryParams) {
|
|
||||||
// console.log('key', key);
|
|
||||||
// console.log('queryParams[key]', queryParams[key as keyof SearchQuery]);
|
|
||||||
newQueryParams[key] = `${queryParams[key as keyof SearchQuery]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlQueryParams = new URLSearchParams(newQueryParams);
|
|
||||||
console.log('urlQueryParams', urlQueryParams);
|
|
||||||
|
|
||||||
|
async function searchForGames(urlQueryParams) {
|
||||||
try {
|
try {
|
||||||
const url = `https://api.boardgameatlas.com/api/search${
|
const url = `https://api.boardgameatlas.com/api/search${
|
||||||
urlQueryParams ? `?${urlQueryParams}` : ''
|
urlQueryParams ? `?${urlQueryParams}` : ''
|
||||||
|
|
@ -98,20 +17,19 @@ export const load: PageServerLoad = async ({ fetch, url }) => {
|
||||||
'content-type': 'application/json'
|
'content-type': 'application/json'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// console.log('board game response', response);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.log('Status not 200', response.status);
|
console.log('Status not 200', response.status);
|
||||||
throw error(response.status);
|
throw error(response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 200) {
|
const games: GameType[] = [];
|
||||||
|
let totalCount = 0;
|
||||||
|
if (response.ok) {
|
||||||
const gameResponse = await response.json();
|
const gameResponse = await response.json();
|
||||||
// console.log('gameResponse', gameResponse);
|
|
||||||
const gameList = gameResponse?.games;
|
const gameList = gameResponse?.games;
|
||||||
const totalCount = gameResponse?.count;
|
totalCount = gameResponse?.count;
|
||||||
console.log('totalCount', totalCount);
|
console.log('totalCount', totalCount);
|
||||||
const games: GameType[] = [];
|
|
||||||
gameList.forEach((game) => {
|
gameList.forEach((game) => {
|
||||||
if (game?.min_players && game?.max_players) {
|
if (game?.min_players && game?.max_players) {
|
||||||
game.players = `${game.min_players}-${game.max_players}`;
|
game.players = `${game.min_players}-${game.max_players}`;
|
||||||
|
|
@ -119,30 +37,75 @@ export const load: PageServerLoad = async ({ fetch, url }) => {
|
||||||
}
|
}
|
||||||
games.push(mapAPIGameToBoredGame(game));
|
games.push(mapAPIGameToBoredGame(game));
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log('returning from search', games)
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: formData.name,
|
|
||||||
minAge: formData.minAge,
|
|
||||||
minPlayers: formData.minPlayers,
|
|
||||||
maxPlayers: formData.maxPlayers,
|
|
||||||
exactMinPlayers: formData.exactMinPlayers,
|
|
||||||
exactMaxPlayers: formData.exactMaxPlayers,
|
|
||||||
games,
|
|
||||||
totalCount,
|
|
||||||
limit,
|
|
||||||
skip
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
totalCount,
|
||||||
|
games
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Error searching board games ${e}`);
|
console.log(`Error searching board games ${e}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
games: [],
|
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
limit,
|
games: []
|
||||||
skip
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load = async ({ fetch, url }) => {
|
||||||
|
const defaults = {
|
||||||
|
limit: 10,
|
||||||
|
skip: 0
|
||||||
|
};
|
||||||
|
const searchParams = Object.fromEntries(url?.searchParams);
|
||||||
|
searchParams.limit = searchParams.limit || `${defaults.limit}`;
|
||||||
|
searchParams.skip = searchParams.skip || `${defaults.skip}`;
|
||||||
|
const form = await superValidate(searchParams, search_schema);
|
||||||
|
|
||||||
|
const queryParams: SearchQuery = {
|
||||||
|
order_by: 'rank',
|
||||||
|
ascending: false,
|
||||||
|
limit: form.data?.limit,
|
||||||
|
skip: form.data?.skip,
|
||||||
|
client_id: BOARD_GAME_ATLAS_CLIENT_ID,
|
||||||
|
fuzzy_match: true,
|
||||||
|
name: form.data?.q,
|
||||||
|
fields:
|
||||||
|
'id,name,min_age,min_players,max_players,thumb_url,min_playtime,max_playtime,min_age,description'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (form.data?.minAge) {
|
||||||
|
if (form.data?.exactMinAge) {
|
||||||
|
queryParams.min_age = form.data?.minAge;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
queryParams.lt_max_players = form.data?.maxPlayers + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newQueryParams: Record<string, string> = {};
|
||||||
|
for (const key in queryParams) {
|
||||||
|
newQueryParams[key] = `${queryParams[key as keyof SearchQuery]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlQueryParams = new URLSearchParams(newQueryParams);
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
searchData: await searchForGames(urlQueryParams)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionData, PageData } from './$types';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
import { gameStore } from '$lib/stores/gameSearchStore';
|
import { gameStore } from '$lib/stores/gameSearchStore';
|
||||||
import TextSearch from '$lib/components/search/textSearch/index.svelte';
|
import TextSearch from '$lib/components/search/textSearch/index.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data;
|
||||||
export let form: ActionData;
|
const { form, errors, constraints } = superForm(data?.form);
|
||||||
|
|
||||||
$: if (data?.games) {
|
$: if (data?.searchData?.games) {
|
||||||
gameStore.removeAll();
|
gameStore.removeAll();
|
||||||
gameStore.addAll(data?.games);
|
gameStore.addAll(data?.searchData?.games);
|
||||||
}
|
|
||||||
|
|
||||||
$: if (form?.games) {
|
|
||||||
gameStore.removeAll();
|
|
||||||
gameStore.addAll(form?.games);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="game-search">
|
<div class="game-search">
|
||||||
<TextSearch showButton advancedSearch {data} {form} />
|
<TextSearch showButton advancedSearch {form} {errors} {constraints} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import type { UserConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
const config: UserConfig = {
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()]
|
||||||
test: {
|
});
|
||||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue