Merge pull request #5 from BradNut/pagination

Pagination
This commit is contained in:
Bradley Shellnut 2022-10-26 17:41:12 -04:00 committed by GitHub
commit 8b090b5a4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1783 additions and 707 deletions

View file

@ -1,6 +1,9 @@
{
"useTabs": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View file

@ -1,6 +1,6 @@
{
"name": "boredgame",
"version": "0.0.1",
"version": "0.0.2",
"scripts": {
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
"build": "vite build",
@ -13,40 +13,41 @@
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@playwright/test": "^1.25.1",
"@playwright/test": "^1.27.1",
"@rgossiaux/svelte-headlessui": "1.0.2",
"@rgossiaux/svelte-heroicons": "^0.1.2",
"@sveltejs/adapter-auto": "1.0.0-next.71",
"@sveltejs/kit": "1.0.0-next.461",
"@sveltejs/adapter-auto": "1.0.0-next.72",
"@sveltejs/kit": "1.0.0-next.480",
"@types/cookie": "^0.5.1",
"@types/node": "^18.7.14",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"carbon-components-svelte": "^0.70.4",
"carbon-icons-svelte": "^11.2.0",
"eslint": "^8.23.0",
"@types/node": "^18.11.7",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"carbon-components-svelte": "^0.70.12",
"carbon-icons-svelte": "^11.4.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-svelte3": "^4.0.0",
"just-debounce-it": "^3.1.1",
"prettier": "^2.7.1",
"prettier-plugin-svelte": "^2.7.0",
"sass": "^1.54.8",
"svelte": "^3.49.0",
"svelte-check": "^2.9.0",
"prettier-plugin-svelte": "^2.8.0",
"sass": "^1.55.0",
"svelte": "^3.52.0",
"svelte-check": "^2.9.2",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.0.0",
"typescript": "^4.8.2",
"vite": "^3.1.0-beta.1"
"typescript": "^4.8.4",
"vite": "^3.2.0"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.5.9",
"@fontsource/fira-mono": "^4.5.10",
"@leveluptuts/svelte-side-menu": "^1.0.5",
"@leveluptuts/svelte-toy": "^2.0.3",
"@lukeed/uuid": "^2.0.0",
"@types/feather-icons": "^4.7.0",
"cookie": "^0.5.0",
"feather-icons": "^4.29.0",
"zod": "^3.18.0"
"zod": "^3.19.1",
"zod-to-json-schema": "^3.18.1"
}
}

View file

@ -1,10 +1,10 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 3000
}
webServer: {
command: 'npm run build && npm run preview',
port: 4173
}
};
export default config;

File diff suppressed because it is too large Load diff

15
src/app.d.ts vendored
View file

@ -1,11 +1,14 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
// interface Stuff {}
interface Locals {
userid: string;
}
// interface PageData {}
// interface PageError {}
// interface Platform {}
}

65
src/db/actions.ts Normal file
View file

@ -0,0 +1,65 @@
function isNumber(str: string): boolean {
if (typeof str !== 'string') {
return false;
}
if (str.trim() === '') {
return false;
}
return !Number.isNaN(Number(str));
}
function convertToBoolean(input: string): boolean | undefined {
try {
return JSON.parse(input.toLowerCase());
} catch (e) {
return undefined;
}
}
export async function getFormDataObject(request: Request): Promise<{ [key: string]: string }> {
const formData = await request.formData();
let data = formData.entries();
var obj = data.next();
var retrieved: any = {};
while (undefined !== obj.value) {
retrieved[obj.value[0]] = obj.value[1];
obj = data.next();
}
return retrieved;
}
export async function getFormData<T>(request: Request, schema: any): T {
const data = await getFormDataObject(request);
return transformFormDataTypes<T>(data, schema);
}
// TODO: Modify this as schema refers to a JSON schema helper
export function transformFormDataTypes<T>(data, schema): T {
for (const property in data) {
if (isNumber(schema[property])) {
data[property] = parseInt(data[property]);
} else if (typeof convertToBoolean(schema[property]) === boolean) {
data[property] = convertToBoolean(schema[property]); // data[property] === 'true';
} else if (Array.isArray(JSON.parse(schema[property]))) {
data[property] = JSON.parse(schema[property]);
}
}
return data;
}
interface Actions {
[key: string]: any // Action
}
export const Games: Actions = {
search: async function search({ request, locals }): Promise<any> {
}
// create: async function create({ request, locals }): Promise<any> {
// const data = await getFormDataObject<any>(request);
// return data;
// }
}

16
src/hooks.server..ts Normal file
View file

@ -0,0 +1,16 @@
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
let userid = event.cookies.get('userid');
if (!userid) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
userid = crypto.randomUUID();
event.cookies.set('userid', userid, { path: '/' });
}
event.locals.userid = userid;
return resolve(event);
};

View file

@ -1,23 +0,0 @@
import type { Handle } from '@sveltejs/kit';
import * as cookie from 'cookie';
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
event.locals.userid = cookies['userid'] || crypto.randomUUID();
const response = await resolve(event);
if (!cookies['userid']) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
response.headers.set(
'set-cookie',
cookie.serialize('userid', event.locals.userid, {
path: '/',
httpOnly: true
})
);
}
return response;
};

0
src/lib/apis/game.ts Normal file
View file

View file

@ -1,47 +1,138 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import {
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions
} from '@rgossiaux/svelte-headlessui';
import {
CheckIcon,
ChevronLeftIcon,
ChevronRightIcon
} from '@rgossiaux/svelte-heroicons/outline';
import { boredState } from '$root/lib/stores/boredState';
const totalCount = $boredState.search.totalCount; // TODO: Check default value
console.log('totalCount', totalCount);
$: pageSize = $boredState.search.pageSize;
console.log('pageSize', pageSize);
$: currentPage = $boredState.search.currentPage;
console.log('currentPage', currentPage);
$: skip = $boredState.search.skip;
console.log('skip', skip);
const dispatch = createEventDispatcher();
// export let pageSize = 10;
export let pageSize: number; // Reactive, bind
export let currentPage: number; // Reactive, bind
export let totalItems: number;
export let pageSizes: ReadonlyArray<Number> = [10];
export let forwardText: string;
export let backwardText: string;
export let pageSizeInputDisabled: boolean = false;
const totalPages: number = Math.ceil(totalCount / pageSize);
const totalPages: number = Math.ceil(totalItems / pageSize);
console.log('totalPages', totalPages);
const prevPage: number = currentPage - 1;
const nextPage: number = currentPage + 1;
const hasNextPage: boolean = nextPage <= totalPages;
const hasPrevPage: boolean = prevPage >= 1;
const itemsLeft: number =
totalCount - currentPage * pageSize >= 0 ? totalCount - currentPage * pageSize : 0;
const pageArray = Array.from({ length: 10 }, (_, i) => i + 1);
// console.log('pageArray', pageArray);
totalItems - currentPage * $boredState.search.pageSize >= 0
? totalItems - currentPage * $boredState.search.pageSize
: 0;
</script>
<div class="container">
<button type="button" class="btn" disabled={!hasPrevPage}>Prev</button>
{#each pageArray as page}
<button
type="button"
class="btn"
aria-current={page === currentPage}
class:current={page === currentPage}>{page}</button
>
{/each}
<button type="button" class="btn" disabled={!hasNextPage}>Next</button>
<span>
<p>Items per-page:</p>
<div class="list-container">
<Listbox
class="list-box"
value={$boredState.search.pageSize}
on:change={(e) => {
dispatch('pageSizeEvent', e.detail);
// boredState.update((n) => ({
// ...n,
// search: { totalCount, pageSize: e.detail, skip, currentPage }
// }));
}}
let:open
>
<ListboxButton>{pageSize}</ListboxButton>
{#if open}
<div transition:fade>
<ListboxOptions static class="list-options">
{#each pageSizes as size (size)}
<ListboxOption
value={size}
disabled={pageSizeInputDisabled}
class={({ active }) => (active ? 'active' : '')}
let:selected
>
{#if selected}
<CheckIcon />
{/if}
<p>{size.toString()}</p>
</ListboxOption>
{/each}
</ListboxOptions>
</div>
{/if}
</Listbox>
</div>
</span>
<p>
Page {currentPage || 1} of {totalPages || 1}
</p>
<p>
{itemsLeft} Item{itemsLeft > 1 || itemsLeft === 0 ? 's' : ''} Left
</p>
<button
type="button"
class="btn"
disabled={!hasPrevPage}
on:click={() => dispatch('previousPageEvent', prevPage)}
><ChevronLeftIcon width="24" height="24" />
<p class="word">{backwardText || 'Prev'}</p></button
>
<button
type="button"
class="btn"
disabled={!hasNextPage}
on:click={() => dispatch('nextPageEvent', nextPage)}
><p class="word">{forwardText || 'Next'}</p>
<ChevronRightIcon width="24" height="24" /></button
>
</div>
<style lang="scss">
.container {
display: flex;
justify-content: center;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin: 3rem 0;
.btn {
display: flex;
gap: 0.5rem;
padding: 1rem;
}
}
@media (max-width: 800px) {
.word {
display: none;
}
}
.list-container :global(.list-box) {
height: 100%;
position: relative;
}
.list-container :global(.list-options) {
position: absolute;
}
.list-container :global(.active) {
display: flex;
gap: 1rem;
padding: 1rem;
}
button {

View file

@ -1,82 +1,86 @@
<script lang="ts">
import { boredState } from '$lib/stores/boredState';
import { boredState } from '$lib/stores/boredState';
let submitting = $boredState?.loading;
let minAge = 1;
let minPlayers = 1;
let maxPlayers = 1;
let exactMinPlayers = false;
let exactMaxPlayers = false;
export let form: ActionData;
console.log('form', form);
let submitting = $boredState?.loading;
let minAge = 1;
let minPlayers = 1;
let maxPlayers = 1;
let exactMinPlayers = false;
let exactMaxPlayers = false;
</script>
<!-- <form on:submit|preventDefault={handleSubmit} method="post"> -->
<fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
<div>
<label for="minAge">
Min Age
<input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} />
</label>
</div>
<div>
<label for="minPlayers">
Min Players
<input
id="minPlayers"
name="minPlayers"
bind:value={minPlayers}
type="number"
min={0}
max={50}
/>
</label>
<label for="exactMinPlayers" style="display: flex; gap: 1rem; place-items: center;">
<span>Exact?</span>
<input
id="exactMinPlayers"
type="checkbox"
name="exactMinPlayers"
bind:checked={exactMinPlayers}
/>
</label>
</div>
<div>
<label for="maxPlayers">
Max Players
<input
id="maxPlayers"
name="maxPlayers"
bind:value={maxPlayers}
type="number"
min={0}
max={50}
/>
</label>
<label for="exactMaxPlayers" style="display: flex; gap: 1rem; place-items: center;">
<span>Exact?</span>
<input
id="exactMaxPlayers"
type="checkbox"
name="exactMaxPlayers"
bind:checked={exactMaxPlayers}
/>
</label>
</div>
<div>
<label for="minAge">
Min Age
<input id="minAge" name="minAge" bind:value={minAge} type="number" min="1" max="120" />
</label>
</div>
<div>
<label for="minPlayers">
Min Players
<input
id="minPlayers"
name="minPlayers"
bind:value={minPlayers}
type="number"
min="1"
max="50"
/>
</label>
<label for="exactMinPlayers" style="display: flex; gap: 1rem; place-items: center;">
<span>Exact?</span>
<input
id="exactMinPlayers"
type="checkbox"
name="exactMinPlayers"
bind:checked={exactMinPlayers}
/>
</label>
{#if form?.error?.id === 'minPlayers'}
{form.error.message}
{/if}
</div>
<div>
<label for="maxPlayers">
Max Players
<input
id="maxPlayers"
name="maxPlayers"
bind:value={maxPlayers}
type="number"
min="1"
max="50"
/>
</label>
<label for="exactMaxPlayers" style="display: flex; gap: 1rem; place-items: center;">
<span>Exact?</span>
<input
id="exactMaxPlayers"
type="checkbox"
name="exactMaxPlayers"
bind:checked={exactMaxPlayers}
/>
</label>
</div>
</fieldset>
<!-- <button type="submit" disabled={submitting}>Submit</button> -->
<!-- </form> -->
<style lang="scss">
fieldset {
display: grid;
grid-template-columns: repeat(3, 1fr);
fieldset {
display: grid;
grid-template-columns: repeat(3, 1fr);
@media (max-width: 800px) {
grid-template-columns: 1fr;
}
}
@media (max-width: 800px) {
grid-template-columns: 1fr;
}
}
label {
display: grid;
margin: 1rem;
}
label {
display: grid;
margin: 1rem;
}
</style>

View file

@ -1,43 +1,72 @@
<script lang="ts">
import { boredState } from '$lib/stores/boredState';
import { gameStore } from '$lib/stores/gameSearchStore';
import { applyAction, enhance } from '$app/forms';
import { boredState } from '$lib/stores/boredState';
import { gameStore } from '$lib/stores/gameSearchStore';
import { ToastType } from '$root/lib/types';
import { toast } from '../../toast/toast';
async function handleSubmit(event: SubmitEvent) {
// submitting = true;
boredState.update((n) => ({ ...n, loading: true }));
const form = event.target as HTMLFormElement;
console.log('form', form);
const response = await fetch('/api/games', {
method: 'POST',
headers: { accept: 'application/json' },
body: new FormData(form)
});
const responseData = await response.json();
// submitting = false;
boredState.update((n) => ({ ...n, loading: false }));
gameStore.removeAll();
gameStore.addAll(responseData?.games);
// games = responseData?.games;
}
// async function handleSubmit(event: SubmitEvent) {
// // submitting = true;
// boredState.update((n) => ({ ...n, loading: true }));
// const form = event.target as HTMLFormElement;
// console.log('form', form);
// const response = await fetch('/api/games', {
// method: 'POST',
// headers: { accept: 'application/json' },
// body: new FormData(form)
// });
// const responseData = await response.json();
// // submitting = false;
// boredState.update((n) => ({ ...n, loading: false }));
// gameStore.removeAll();
// gameStore.addAll(responseData?.games);
// // games = responseData?.games;
// }
let submitting = $boredState?.loading;
let submitting = $boredState?.loading;
let checked = true;
</script>
<form on:submit|preventDefault={handleSubmit} method="post">
<fieldset aria-busy={submitting} disabled={submitting}>
<input type="checkbox" id="random" name="random" hidden checked />
<button class="btn" type="submit" disabled={submitting}>Random Game 🎲</button>
</fieldset>
<form
action="/search"
method="POST"
use:enhance={() => {
boredState.update((n) => ({ ...n, loading: true }));
return async ({ result }) => {
boredState.update((n) => ({ ...n, loading: false }));
console.log('result main page search', result);
// `result` is an `ActionResult` object
if (result.type === 'success') {
console.log('In success');
gameStore.removeAll();
const resultGames = result?.data?.games;
if (resultGames?.length <= 0) {
toast.send('No results!', { duration: 3000, type: ToastType.INFO, dismissible: true });
}
gameStore.addAll(resultGames);
console.log(`Frontend result: ${JSON.stringify(result)}`);
await applyAction(result);
} else {
console.log('Invalid');
await applyAction(result);
}
};
}}
>
<fieldset aria-busy={submitting} disabled={submitting}>
<input type="checkbox" id="random" name="random" hidden {checked} />
<button class="btn" type="submit" disabled={submitting}>Random Game 🎲</button>
</fieldset>
</form>
<style lang="scss">
fieldset {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
fieldset {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
button {
max-width: 450px;
padding: var(--spacing-8) var(--spacing-16);
}
button {
max-width: 450px;
padding: var(--spacing-8) var(--spacing-16);
}
</style>

View file

@ -1,88 +1,88 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
import { boredState } from '$lib/stores/boredState';
import AdvancedSearch from '$lib/components/search/advancedSearch/index.svelte';
import { fade } from 'svelte/transition';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
import { boredState } from '$lib/stores/boredState';
import AdvancedSearch from '$lib/components/search/advancedSearch/index.svelte';
export let showButton: boolean = false;
export let advancedSearch: boolean = false;
export let showButton: boolean = false;
export let advancedSearch: boolean = false;
export let form: ActionData;
let submitting = $boredState?.loading;
let name = '';
let submitting = $boredState?.loading;
let name = '';
</script>
<!-- <form on:submit|preventDefault={handleSearch} method="post"> -->
<div class="search">
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
<label for="name">
Search
<input
id="name"
name="name"
bind:value={name}
type="text"
aria-label="Search boardgame"
placeholder="Search boardgame"
/>
</label>
</fieldset>
{#if advancedSearch}
<Disclosure let:open>
<DisclosureButton class="disclosure-button">
<span>Advanced Search?</span>
<ChevronRightIcon
class="icon disclosure-icon"
style={open
? 'transform: rotate(90deg); transition: transform 0.5s ease;'
: 'transform: rotate(0deg); transition: transform 0.5s ease;'}
/>
</DisclosureButton>
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
<label for="name">
Search
<input
id="name"
name="name"
bind:value={name}
type="text"
aria-label="Search boardgame"
placeholder="Search boardgame"
/>
</label>
</fieldset>
{#if advancedSearch}
<Disclosure let:open>
<DisclosureButton class="disclosure-button">
<span>Advanced Search?</span>
<ChevronRightIcon
class="icon disclosure-icon"
style={open
? 'transform: rotate(90deg); transition: transform 0.5s ease;'
: 'transform: rotate(0deg); transition: transform 0.5s ease;'}
/>
</DisclosureButton>
{#if open}
<div transition:fade>
<!-- Using `static`, `DisclosurePanel` is always rendered,
{#if open}
<div transition:fade>
<!-- Using `static`, `DisclosurePanel` is always rendered,
and ignores the `open` state -->
<DisclosurePanel static>
<AdvancedSearch />
</DisclosurePanel>
</div>
{/if}
</Disclosure>
{/if}
<DisclosurePanel static>
<AdvancedSearch form />
</DisclosurePanel>
</div>
{/if}
</Disclosure>
{/if}
</div>
{#if showButton}
<button class="btn" type="submit" disabled={submitting}>Submit</button>
<button class="btn" type="submit" disabled={submitting}>Submit</button>
{/if}
<!-- </form> -->
<style lang="scss">
.search {
display: grid;
gap: 1rem;
}
.search {
display: grid;
gap: 1rem;
}
:global(.disclosure-button) {
display: flex;
gap: 0.25rem;
place-items: center;
}
:global(.disclosure-button) {
display: flex;
gap: 0.25rem;
place-items: center;
}
button {
padding: 1rem;
margin: 1.5rem 0;
}
button {
padding: 1rem;
margin: 1.5rem 0;
}
label {
display: grid;
grid-template-columns: auto auto;
gap: 1rem;
place-content: start;
place-items: center;
label {
display: grid;
grid-template-columns: auto auto;
gap: 1rem;
place-content: start;
place-items: center;
@media (max-width: 850px) {
display: flex;
flex-wrap: wrap;
}
}
@media (max-width: 850px) {
display: flex;
flex-wrap: wrap;
}
}
</style>

View file

@ -12,7 +12,7 @@ const state = () => {
const initial: BoredStore = {
loading: false, dialog: initialDialog, search: {
totalCount: 1,
pageSize: 25,
pageSize: 10,
skip: 0,
currentPage: 1
}

View file

@ -1,4 +1,5 @@
import { z } from 'zod';
import zodToJsonSchema from 'zod-to-json-schema';
export const BoardGameSearch = z.object({
minAge: z.number(),
@ -7,7 +8,9 @@ export const BoardGameSearch = z.object({
maxPlayers: z.number()
});
export const Game = z.object({
export const Board
export const game_schema = z.object({
id: z.string(),
handle: z.string(),
name: z.string(),
@ -28,3 +31,9 @@ export const Game = z.object({
players: z.string(),
playtime: z.string()
});
export const game_raw_schema_json = zodToJsonSchema(game_schema, {
$refStrategy: 'none',
});
export type Game = z.infer<typeof game_schema>;

View file

@ -1,159 +1,159 @@
<script lang="ts">
import { browser } from '$app/environment';
import { navigating } from '$app/stores';
import { fade } from 'svelte/transition';
import debounce from 'just-debounce-it';
import { Toy } from '@leveluptuts/svelte-toy';
import Header from '$lib/components/header/Header.svelte';
import Loading from '$lib/components/loading.svelte';
import Transition from '$lib/components/transition/index.svelte';
import Portal from '$lib/Portal.svelte';
import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore';
import { gameStore } from '$lib/stores/gameSearchStore';
import { toast } from '$lib/components/toast/toast';
import Toast from '$lib/components/toast/Toast.svelte';
import '$root/styles/styles.scss';
import { browser } from '$app/environment';
import { navigating } from '$app/stores';
import { fade } from 'svelte/transition';
import debounce from 'just-debounce-it';
import { Toy } from '@leveluptuts/svelte-toy';
import Header from '$lib/components/header/Header.svelte';
import Loading from '$lib/components/loading.svelte';
import Transition from '$lib/components/transition/index.svelte';
import Portal from '$lib/Portal.svelte';
import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore';
import { gameStore } from '$lib/stores/gameSearchStore';
import { toast } from '$lib/components/toast/toast';
import Toast from '$lib/components/toast/Toast.svelte';
import '$root/styles/styles.scss';
$: {
if ($navigating) {
debounce(() => {
boredState.update((n) => ({ ...n, loading: true }));
}, 250);
}
if (!$navigating) {
boredState.update((n) => ({ ...n, loading: false }));
}
}
$: {
if ($navigating) {
debounce(() => {
boredState.update((n) => ({ ...n, loading: true }));
}, 250);
}
if (!$navigating) {
boredState.update((n) => ({ ...n, loading: false }));
}
}
$: isOpen = $boredState?.dialog?.isOpen;
$: isOpen = $boredState?.dialog?.isOpen;
if (browser) {
let collectionEmpty = $collectionStore.length === 0 || false;
console.log('collectionEmpty', collectionEmpty);
console.log('localStorage.collection', localStorage.collection);
if (collectionEmpty && localStorage?.collection && localStorage?.collection?.length !== 0) {
const collection = JSON.parse(localStorage.collection);
console.log('collection', collection);
if (collection?.length !== 0) {
collectionStore.addAll(collection);
}
}
}
if (browser) {
let collectionEmpty = $collectionStore.length === 0 || false;
console.log('collectionEmpty', collectionEmpty);
console.log('localStorage.collection', localStorage.collection);
if (collectionEmpty && localStorage?.collection && localStorage?.collection?.length !== 0) {
const collection = JSON.parse(localStorage.collection);
console.log('collection', collection);
if (collection?.length !== 0) {
collectionStore.addAll(collection);
}
}
}
const dev = process.env.NODE_ENV !== 'production';
const dev = process.env.NODE_ENV !== 'production';
</script>
{#if dev}
<Toy register={{ boredState, collectionStore, gameStore, toast }} />
<Toy register={{ boredState, collectionStore, gameStore, toast }} />
{/if}
<Transition transition={{ type: 'fade', duration: 250 }}>
<div class="wrapper">
<Header />
<Transition transition={{ type: 'page' }}>
<main>
<slot />
</main>
</Transition>
<footer>
<p>Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a></p>
<p>
<a
target="__blank"
href="https://www.flaticon.com/free-icons/board-game"
title="board game icons">Board game icons created by Freepik - Flaticon</a
>
</p>
</footer>
</div>
{#if $boredState?.loading}
<Portal>
<Transition transition={{ type: 'fade', duration: 0 }}>
<div class="loading">
<Loading />
<h3>Loading...</h3>
</div>
</Transition>
<div class="background" />
</Portal>
{/if}
{#if isOpen}
<div class="container">
<svelte:component this={$boredState?.dialog?.content} />
</div>
{/if}
<Toast />
<div class="wrapper">
<Header />
<Transition transition={{ type: 'page' }}>
<main>
<slot />
</main>
</Transition>
<footer>
<p>Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a></p>
<p>
<a
target="__blank"
href="https://www.flaticon.com/free-icons/board-game"
title="board game icons">Board game icons created by Freepik - Flaticon</a
>
</p>
</footer>
</div>
{#if $boredState?.loading}
<Portal>
<Transition transition={{ type: 'fade', duration: 0 }}>
<div class="loading">
<Loading />
<h3>Loading...</h3>
</div>
</Transition>
<div class="background" />
</Portal>
{/if}
{#if isOpen}
<div class="container">
<svelte:component this={$boredState?.dialog?.content} />
</div>
{/if}
<Toast />
</Transition>
<style lang="scss">
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
h3 {
color: white;
}
}
h3 {
color: white;
}
}
.background {
background: black;
opacity: 0.8;
cursor: none;
inset: 0;
position: fixed;
z-index: 100;
}
.background {
background: black;
opacity: 0.8;
cursor: none;
inset: 0;
position: fixed;
z-index: 100;
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
main {
flex: 1;
display: flex;
flex-direction: column;
max-width: 850px;
margin: 0 auto;
padding: 2rem 0rem;
max-width: 80%;
main {
flex: 1;
display: flex;
flex-direction: column;
max-width: 850px;
margin: 0 auto;
padding: 2rem 0rem;
max-width: 80%;
@media (min-width: 1500px) {
max-width: 60%;
}
box-sizing: border-box;
}
@media (min-width: 1500px) {
max-width: 60%;
}
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
:global(.dialog-overlay) {
position: fixed;
inset: 0;
z-index: 100;
background-color: rgb(0 0 0);
opacity: 0.8;
}
:global(.dialog-overlay) {
position: fixed;
inset: 0;
z-index: 100;
background-color: rgb(0 0 0);
opacity: 0.8;
}
</style>

View file

@ -0,0 +1,8 @@
import type { PageServerLoad, Actions } from './$types';
export const actions: Actions = {
default: async ({ request, locals }): Promise<any> => {
// Do things in here
return {};
}
}

View file

@ -1,121 +1,182 @@
<script lang="ts">
import type { GameType, SavedGameType } from '$root/lib/types';
import { gameStore } from '$lib/stores/gameSearchStore';
import { boredState } from '$root/lib/stores/boredState';
import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte';
import Game from '$lib/components/game/index.svelte';
import TextSearch from '$lib/components/search/textSearch/index.svelte';
import RandomSearch from '$lib/components/search/random/index.svelte';
import Random from '$lib/components/random/index.svelte';
import Pagination from '$lib/components/pagination/index.svelte';
import { enhance, applyAction } from '$app/forms';
import type { ActionData, PageData } from './$types';
import { ToastType, type GameType, type SavedGameType } from '$root/lib/types';
import { toast } from '$root/lib/components/toast/toast';
import { gameStore } from '$lib/stores/gameSearchStore';
import { boredState } from '$root/lib/stores/boredState';
import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte';
import Game from '$lib/components/game/index.svelte';
import TextSearch from '$lib/components/search/textSearch/index.svelte';
import RandomSearch from '$lib/components/search/random/index.svelte';
import Random from '$lib/components/random/index.svelte';
import Pagination from '$lib/components/pagination/index.svelte';
async function handleSearch(event: SubmitEvent) {
boredState.update((n) => ({ ...n, loading: true }));
const form = event.target as HTMLFormElement;
console.log('form', form);
const response = await fetch('/api/game', {
method: 'POST',
headers: { accept: 'application/json' },
body: new FormData(form)
});
const responseData = await response.json();
boredState.update((n) => ({ ...n, loading: false }));
gameStore.removeAll();
gameStore.addAll(responseData?.games);
}
export let data: PageData;
export let form: ActionData;
console.log('form routesss', form);
console.log('Formed data:', JSON.stringify(data));
let pageSize: number;
let currentPage: number;
$: totalItems = 0;
console.log('totalItems', totalItems);
let isOpen: boolean = false;
let gameToRemove: GameType | SavedGameType;
console.log('isOpen', isOpen);
// async function handleItemsPerPageChange(event) {
// const perPage = event?.detail;
// if ($gameStore.length )
// }
async function handleNextPageEvent(event: CustomEvent) {
console.log('Next page called', event);
boredState.update((n) => ({ ...n, loading: true }));
const form = event.target as HTMLFormElement;
console.log('form', form);
const response = await fetch('/api/game', {
method: 'POST',
headers: { accept: 'application/json' },
body: new FormData(form)
});
const responseData = await response.json();
boredState.update((n) => ({ ...n, loading: false }));
gameStore.removeAll();
gameStore.addAll(responseData?.games);
const skip = $boredState?.search?.skip;
const pageSize = $boredState?.search?.pageSize;
const currentPage = $boredState?.search?.currentPage;
const totalCount = responseData?.totalCount;
boredState.update((n) => ({
...n,
search: { totalCount, skip, pageSize, currentPage }
}));
}
interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType;
}
let isOpen: boolean = false;
let gameToRemove: GameType | SavedGameType;
console.log('isOpen', isOpen);
function handleRemoveGame(event: RemoveGameEvent) {
console.log('event', event);
gameToRemove = event?.detail;
boredState.update((n) => ({
...n,
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
}));
}
interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType;
}
function handleRemoveGame(event: RemoveGameEvent) {
console.log('event', event);
gameToRemove = event?.detail;
boredState.update((n) => ({
...n,
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
}));
}
</script>
<svelte:head>
<title>Bored Game | Home</title>
<title>Bored Game | Home</title>
</svelte:head>
<h1>Search Boardgames!</h1>
<p style="margin: 1rem 0;">
Input your requirements to search for board game that match your criteria.
Input your requirements to search for board games that match your criteria.
</p>
<div class="game-search">
<form on:submit|preventDefault={handleSearch} method="post">
<TextSearch showButton advancedSearch />
</form>
<section>
<p>Or pick a random game!</p>
<div class="random-buttons">
<RandomSearch />
<Random />
</div>
</section>
<form
action="/search"
method="post"
use:enhance={() => {
boredState.update((n) => ({ ...n, loading: true }));
return async ({ result }) => {
boredState.update((n) => ({ ...n, loading: false }));
console.log('result main page search', result);
// `result` is an `ActionResult` object
if (result.type === 'success') {
console.log('In success');
gameStore.removeAll();
const resultGames = result?.data?.games;
if (resultGames?.length <= 0) {
toast.send('No results!', { duration: 3000, type: ToastType.INFO, dismissible: true });
}
gameStore.addAll(resultGames);
totalItems = result?.data?.totalCount;
console.log(`Frontend result: ${JSON.stringify(result)}`);
await applyAction(result);
} else {
console.log('Invalid');
await applyAction(result);
}
};
}}
>
<TextSearch showButton advancedSearch {form} />
</form>
<section>
<p>Or pick a random game!</p>
<div class="random-buttons">
<RandomSearch />
<Random />
</div>
</section>
</div>
{#if $gameStore?.length > 0}
<div class="games">
<h1>Games Found:</h1>
<div class="games-list">
{#each $gameStore as game (game.id)}
<Game on:removeGameEvent={handleRemoveGame} {game} />
{/each}
</div>
<Pagination />
</div>
<div class="games">
<h1>Games Found:</h1>
<div class="games-list">
{#each $gameStore as game (game.id)}
<Game on:removeGameEvent={handleRemoveGame} {game} />
{/each}
</div>
<!-- <Pagination
{pageSize}
{currentPage}
{totalItems}
forwardText="Next"
backwardText="Prev"
pageSizes={[10, 25, 50, 100]}
on:nextPageEvent={handleNextPageEvent}
on:previousPageEvent={(event) => console.log('Prev page called', event)}
on:perPageEvent={(event) => console.log('Per page called', event)}
/> -->
</div>
{/if}
<style lang="scss">
.game-search {
display: grid;
gap: 2rem;
.game-search {
display: grid;
gap: 2rem;
section {
display: grid;
gap: 1rem;
}
}
section {
display: grid;
gap: 1rem;
}
}
.games {
margin: 2rem 0rem;
.games {
margin: 2rem 0rem;
h1 {
margin-bottom: 2rem;
}
}
h1 {
margin-bottom: 2rem;
}
}
.games-list {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 2rem;
.games-list {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 2rem;
@media (max-width: 800px) {
grid-template-columns: 1fr 1fr;
}
@media (max-width: 800px) {
grid-template-columns: 1fr 1fr;
}
@media (max-width: 650px) {
grid-template-columns: 1fr;
}
}
@media (max-width: 650px) {
grid-template-columns: 1fr;
}
}
.random-buttons {
display: flex;
place-content: space-between;
place-items: center;
.random-buttons {
display: flex;
place-content: space-between;
place-items: center;
@media (max-width: 650px) {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 650px) {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
</style>

View file

@ -0,0 +1,13 @@
<svelte:head>
<title>Bored Game | About</title>
<meta name="description" content="About Bored Game" />
</svelte:head>
<div class="content">
<h1>About Bored Game</h1>
<p>
One day we were bored and wanted to play one of our board games. Our problem was that we didn't
know which one to play.
</p>
<p>Rather than just pick one I decided to make this overcomplicated version of choice.</p>
</div>

View file

@ -0,0 +1,9 @@
import { dev } from '$app/environment';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const csr = dev;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;

View file

@ -8,7 +8,7 @@
(The data on the todo app will expire periodically; no
guarantees are made. Don't use it to organize your life.)
*/
import { BOARD_GAME_ATLAS_CLIENT_ID } from '$env/static/private';
import { URLSearchParams } from 'url';
const base = 'https://api.boardgameatlas.com/api';
@ -20,7 +20,7 @@ export function boardGameApi(
data?: Record<string, unknown>
) {
// console.log('queryParams', queryParams);
queryParams.client_id = import.meta.env.VITE_PUBLIC_CLIENT_ID;
queryParams.client_id = BOARD_GAME_ATLAS_CLIENT_ID;
const urlQueryParams = new URLSearchParams(queryParams);
const url = `${base}/${resource}${urlQueryParams ? `?${urlQueryParams}` : ''}`;
return fetch(url, {

View file

@ -9,7 +9,8 @@ export const POST: RequestHandler = async ({ request }) => {
const queryParams: SearchQuery = {
order_by: 'rank',
ascending: false,
limit: 25,
limit: 10,
skip: 0,
client_id: import.meta.env.VITE_PUBLIC_CLIENT_ID,
fuzzy_match: true,
name: ''
@ -54,6 +55,7 @@ export const POST: RequestHandler = async ({ request }) => {
});
return json$1({
totalCount,
games
});
}

View file

@ -91,9 +91,9 @@ export const POST: RequestHandler = async ({ request }) => {
games.push(mapAPIGameToBoredGame(game));
});
console.log('games', games);
return json$1({
return {
games
});
};
}
return new Response(undefined, { status: response.status });

View file

@ -1,6 +1,7 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'
import { boardGameApi } from '../../api';
// import { Games } from '$lib/db/actions';
type GamePageParams = {
params: {
@ -8,8 +9,12 @@ type GamePageParams = {
}
}
// export const actions = {
// default Games.create,
// }
export const load: PageServerLoad = async ({ params }: GamePageParams) => {
console.log('params', params);
console.log('params', params);
const queryParams = {
ids: `${params?.id}`
};

View file

@ -0,0 +1,224 @@
import type { Actions, PageServerLoad, RequestEvent } from '../$types';
import { BOARD_GAME_ATLAS_CLIENT_ID } from '$env/static/private';
import { invalid } from '@sveltejs/kit';
import type { GameType, SearchQuery } from '$root/lib/types';
import { mapAPIGameToBoredGame } from '$root/lib/util/gameMapper';
export const load: PageServerLoad = (v) => {
console.log('page server load request', v)
return {
games: [],
totalCount: 0
};
};
export const actions: Actions = {
default: async ({ request, locals }: RequestEvent): Promise<any> => {
console.log("In search action specific")
// Do things in here
const form = await request.formData();
console.log('action form', form);
const queryParams: SearchQuery = {
order_by: 'rank',
ascending: false,
limit: 10,
skip: 0,
client_id: BOARD_GAME_ATLAS_CLIENT_ID,
fuzzy_match: true,
name: ''
};
const random = form.get('random') && form.get('random') === 'on';
if (random) {
queryParams.random = random;
} else {
const minAge = form.get('minAge');
const minPlayers = form.get('minPlayers');
const maxPlayers = form.get('maxPlayers');
const exactMinAge = form.get('exactMinAge') || false;
const exactMinPlayers = form.get('exactMinPlayers') || false;
const exactMaxPlayers = form.get('exactMaxPlayers') || false;
if (minAge) {
if (exactMinAge) {
queryParams.min_age = +minAge;
} else {
queryParams.gt_min_age = +minAge === 1 ? 0 : +minAge - 1;
}
}
if (minPlayers && maxPlayers) {
if (minPlayers > maxPlayers) {
return invalid(400, { minPlayers, error: { id: 'minPlayers', message: 'Min must be less than max' } });
} else if (maxPlayers < minPlayers) {
return invalid(400, { maxPlayers, error: { id: 'maxPlayers', message: 'Max must be greater than min' } });
}
if (exactMinPlayers) {
queryParams.min_players = +minPlayers;
} else {
queryParams.gt_min_players = +minPlayers === 1 ? 0 : +minPlayers - 1;
}
if (exactMaxPlayers) {
queryParams.max_players = +maxPlayers;
} else {
queryParams.lt_max_players = +maxPlayers + 1;
}
}
const name = form.has('name') ? form.get('name') : await request?.text();
console.log('name', name);
if (name) {
queryParams.name = `${name}`;
}
}
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);
try {
const url = `https://api.boardgameatlas.com/api/search${urlQueryParams ? `?${urlQueryParams}` : ''
}`;
const response = await fetch(url, {
method: 'get',
headers: {
'content-type': 'application/json'
}
});
console.log('board game response', response);
if (response.status !== 200) {
console.log('Status not 200', response.status)
invalid(response.status, {});
}
if (response.status === 200) {
const gameResponse = await response.json();
console.log('gameResponse', gameResponse);
const gameList = gameResponse?.games;
const totalCount = gameResponse?.count;
console.log('totalCount', totalCount);
const games: GameType[] = [];
gameList.forEach((game) => {
games.push(mapAPIGameToBoredGame(game));
});
console.log('returning from search', games)
return {
games,
totalCount: games.length
};
}
} catch (e) {
console.log(`Error searching board games ${e}`);
}
return {
games: [],
totalCount: 0
};
}
// const id = form.get('id');
// const ids = form.get('ids');
// const minAge = form.get('minAge');
// const minPlayers = form.get('minPlayers');
// const maxPlayers = form.get('maxPlayers');
// const exactMinAge = form.get('exactMinAge') || false;
// const exactMinPlayers = form.get('exactMinPlayers') || false;
// const exactMaxPlayers = form.get('exactMaxPlayers') || false;
// const random = form.get('random') === 'on' || false;
// 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 (id) {
// queryParams.ids = new Array(`${id}`);
// }
// if (ids) {
// // TODO: Pass in ids array from localstorage / game store
// queryParams.ids = new Array(ids);
// }
// queryParams.random = random;
// console.log('queryParams', queryParams);
// const newQueryParams: Record<string, string> = {};
// for (const key in queryParams) {
// newQueryParams[key] = `${queryParams[key as keyof typeof queryParams]}`;
// }
// const urlQueryParams = new URLSearchParams(newQueryParams);
// const url = `https://api.boardgameatlas.com/api/search${urlQueryParams ? `?${urlQueryParams}` : ''
// }`;
// const response = await fetch(url, {
// method: 'get',
// headers: {
// 'content-type': 'application/json'
// }
// });
// console.log('response status', response.status);
// console.log('board game response action', response);
// if (response.status === 404) {
// // user hasn't created a todo list.
// // start with an empty array
// return {
// success: true,
// games: [],
// totalCount: 0
// };
// }
// if (response.status === 200) {
// const gameResponse = await response.json();
// console.log('gameResponse', gameResponse);
// const gameList = gameResponse?.games;
// const games: GameType[] = [];
// gameList.forEach((game: GameType) => {
// games.push(mapAPIGameToBoredGame(game));
// });
// console.log('action games', games);
// return {
// games,
// totalCount: games.length
// };
// }
// return { success: false };
// }
// create: async function create({ request, locals }): Promise<any> {
// const data = await getFormDataObject<any>(request);
// return data;
// }
}

View file

@ -0,0 +1,103 @@
<script lang="ts">
import { applyAction, enhance } from '$app/forms';
import type { ActionData, PageData } from './$types';
import Game from '$lib/components/game/index.svelte';
import { gameStore } from '$lib/stores/gameSearchStore';
import TextSearch from '$lib/components/search/textSearch/index.svelte';
import RemoveCollectionDialog from '$root/lib/components/dialog/RemoveCollectionDialog.svelte';
import { ToastType, type GameType, type SavedGameType } from '$root/lib/types';
import { boredState } from '$root/lib/stores/boredState';
import { toast } from '$root/lib/components/toast/toast';
export let data: PageData;
export let form: ActionData;
console.log('search page form', form);
console.log('search page data stuff', data);
let gameToRemove: GameType | SavedGameType;
$: if (data?.games) {
gameStore.removeAll();
gameStore.addAll(data?.games);
}
$: if (form?.games) {
gameStore.removeAll();
gameStore.addAll(form?.games);
}
interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType;
}
function handleRemoveGame(event: RemoveGameEvent) {
console.log('event', event);
gameToRemove = event?.detail;
boredState.update((n) => ({
...n,
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
}));
}
</script>
<div class="game-search">
<form
action="?/search"
method="post"
use:enhance={() => {
boredState.update((n) => ({ ...n, loading: true }));
return async ({ result }) => {
boredState.update((n) => ({ ...n, loading: false }));
console.log(result);
// `result` is an `ActionResult` object
if (result.type === 'error') {
toast.send('Error!', { duration: 3000, type: ToastType.ERROR, dismissible: true });
await applyAction(result);
} else {
gameStore.removeAll();
gameStore.addAll(result?.data?.games);
totalItems = result?.data?.totalCount;
console.log(`Frontend result: ${JSON.stringify(result)}`);
toast.send('Sucess!', { duration: 3000, type: ToastType.INFO, dismissible: true });
await applyAction(result);
}
};
}}
>
<TextSearch showButton />
</form>
</div>
{#if $gameStore?.length > 0}
<div class="games">
<h1>Games Found:</h1>
<div class="games-list">
{#each $gameStore as game (game.id)}
<Game on:removeGameEvent={handleRemoveGame} {game} />
{/each}
</div>
</div>
{/if}
<style lang="scss">
.games {
margin: 2rem 0rem;
h1 {
margin-bottom: 2rem;
}
}
.games-list {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 2rem;
@media (max-width: 800px) {
grid-template-columns: 1fr 1fr;
}
@media (max-width: 650px) {
grid-template-columns: 1fr;
}
}
</style>

181
src/search/actions.ts Normal file
View file

@ -0,0 +1,181 @@
import { invalid, type RequestEvent } from '@sveltejs/kit';
import { BOARD_GAME_ATLAS_CLIENT_ID } from '$env/static/private';
import type { GameType, SearchQuery } from "$root/lib/types";
import { mapAPIGameToBoredGame } from "$root/lib/util/gameMapper";
interface Actions {
[key: string]: any // Action
}
export const Games: Actions = {
search: async ({ request, locals }: RequestEvent): Promise<any> => {
console.log("In search action specific")
// Do things in here
const form = await request.formData();
console.log('action form', form);
const queryParams: SearchQuery = {
order_by: 'rank',
ascending: false,
limit: 10,
skip: 0,
client_id: BOARD_GAME_ATLAS_CLIENT_ID,
fuzzy_match: true,
name: ''
};
const name = form.has('name') ? form.get('name') : await request?.text();
console.log('name', name);
if (name) {
queryParams.name = `${name}`;
}
const newQueryParams: Record<string, string> = {};
for (const key in queryParams) {
console.log('key', key);
console.log('queryParams[key]', queryParams[key]);
newQueryParams[key] = `${queryParams[key]}`;
}
const urlQueryParams = new URLSearchParams(newQueryParams);
console.log('urlQueryParams', urlQueryParams);
try {
throw new Error("test error");
// const url = `https://api.boardgameatlas.com/api/search${urlQueryParams ? `?${urlQueryParams}` : ''
// }`;
// const response = await fetch(url, {
// method: 'get',
// headers: {
// 'content-type': 'application/json'
// }
// });
// console.log('board game response', response);
// if (response.status !== 200) {
// console.log('Status not 200', response.status)
// invalid(response.status, {});
// }
// if (response.status === 200) {
// const gameResponse = await response.json();
// console.log('gameResponse', gameResponse);
// const gameList = gameResponse?.games;
// const totalCount = gameResponse?.count;
// console.log('totalCount', totalCount);
// const games: GameType[] = [];
// gameList.forEach((game) => {
// games.push(mapAPIGameToBoredGame(game));
// });
// console.log('returning from search')
// return {
// games,
// totalCount: games.length
// };
// }
// return {
// games: [],
// totalCount: 0
// };
} catch (e) {
console.log(`Error searching board games ${e}`);
invalid(400, { reason: 'Exception' })
}
}
// const id = form.get('id');
// const ids = form.get('ids');
// const minAge = form.get('minAge');
// const minPlayers = form.get('minPlayers');
// const maxPlayers = form.get('maxPlayers');
// const exactMinAge = form.get('exactMinAge') || false;
// const exactMinPlayers = form.get('exactMinPlayers') || false;
// const exactMaxPlayers = form.get('exactMaxPlayers') || false;
// const random = form.get('random') === 'on' || false;
// 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 (id) {
// queryParams.ids = new Array(`${id}`);
// }
// if (ids) {
// // TODO: Pass in ids array from localstorage / game store
// queryParams.ids = new Array(ids);
// }
// queryParams.random = random;
// console.log('queryParams', queryParams);
// const newQueryParams: Record<string, string> = {};
// for (const key in queryParams) {
// newQueryParams[key] = `${queryParams[key as keyof typeof queryParams]}`;
// }
// const urlQueryParams = new URLSearchParams(newQueryParams);
// const url = `https://api.boardgameatlas.com/api/search${urlQueryParams ? `?${urlQueryParams}` : ''
// }`;
// const response = await fetch(url, {
// method: 'get',
// headers: {
// 'content-type': 'application/json'
// }
// });
// console.log('response status', response.status);
// console.log('board game response action', response);
// if (response.status === 404) {
// // user hasn't created a todo list.
// // start with an empty array
// return {
// success: true,
// games: [],
// totalCount: 0
// };
// }
// if (response.status === 200) {
// const gameResponse = await response.json();
// console.log('gameResponse', gameResponse);
// const gameList = gameResponse?.games;
// const games: GameType[] = [];
// gameList.forEach((game: GameType) => {
// games.push(mapAPIGameToBoredGame(game));
// });
// console.log('action games', games);
// return {
// games,
// totalCount: games.length
// };
// }
// return { success: false };
// }
// create: async function create({ request, locals }): Promise<any> {
// const data = await getFormDataObject<any>(request);
// return data;
// }
}

View file

@ -9,14 +9,16 @@ const config = {
kit: {
adapter: adapter(),
alias: {
$components: 'src/components',
$root: './src'
},
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
},
vitePlugin: {
experimental: {
inspector: {
toggleKeyCombo: 'control-alt-shift',
},
},
},
};
export default config;

View file

@ -1,13 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View file

@ -1,8 +0,0 @@
import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [sveltekit()]
};
export default config;

8
vite.config.ts Normal file
View file

@ -0,0 +1,8 @@
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
plugins: [sveltekit()]
};
export default config;