mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
commit
8b090b5a4c
32 changed files with 1783 additions and 707 deletions
11
.prettierrc
11
.prettierrc
|
|
@ -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" } }]
|
||||
}
|
||||
|
|
|
|||
37
package.json
37
package.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
658
pnpm-lock.yaml
658
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
15
src/app.d.ts
vendored
15
src/app.d.ts
vendored
|
|
@ -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
65
src/db/actions.ts
Normal 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
16
src/hooks.server..ts
Normal 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);
|
||||
};
|
||||
23
src/hooks.ts
23
src/hooks.ts
|
|
@ -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
0
src/lib/apis/game.ts
Normal file
0
src/lib/components/GameSearchContainer.svelte
Normal file
0
src/lib/components/GameSearchContainer.svelte
Normal 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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const state = () => {
|
|||
const initial: BoredStore = {
|
||||
loading: false, dialog: initialDialog, search: {
|
||||
totalCount: 1,
|
||||
pageSize: 25,
|
||||
pageSize: 10,
|
||||
skip: 0,
|
||||
currentPage: 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
8
src/routes/+page.server.ts
Normal file
8
src/routes/+page.server.ts
Normal 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 {};
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
13
src/routes/about/+page.svelte
Normal file
13
src/routes/about/+page.svelte
Normal 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>
|
||||
9
src/routes/about/+page.ts
Normal file
9
src/routes/about/+page.ts
Normal 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;
|
||||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
};
|
||||
|
|
|
|||
224
src/routes/search/+page.server.ts
Normal file
224
src/routes/search/+page.server.ts
Normal 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;
|
||||
// }
|
||||
}
|
||||
103
src/routes/search/+page.svelte
Normal file
103
src/routes/search/+page.svelte
Normal 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
181
src/search/actions.ts
Normal 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;
|
||||
// }
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
8
vite.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import type { UserConfig } from 'vite';
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
|
||||
export default config;
|
||||
Loading…
Reference in a new issue