Merge pull request #2 from BradNut/merge-searches

Merge searches
This commit is contained in:
Bradley Shellnut 2022-08-08 16:39:53 -07:00 committed by GitHub
commit 31ec4c981b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 238 additions and 132 deletions

View file

@ -22,7 +22,7 @@
"@types/node": "^18.6.4", "@types/node": "^18.6.4",
"@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0", "@typescript-eslint/parser": "^5.32.0",
"carbon-components-svelte": "^0.67.5", "carbon-components-svelte": "^0.67.7",
"carbon-icons-svelte": "^11.2.0", "carbon-icons-svelte": "^11.2.0",
"eslint": "^8.21.0", "eslint": "^8.21.0",
"eslint-config-prettier": "^8.1.0", "eslint-config-prettier": "^8.1.0",

View file

@ -15,7 +15,7 @@ specifiers:
'@types/node': ^18.6.4 '@types/node': ^18.6.4
'@typescript-eslint/eslint-plugin': ^5.32.0 '@typescript-eslint/eslint-plugin': ^5.32.0
'@typescript-eslint/parser': ^5.32.0 '@typescript-eslint/parser': ^5.32.0
carbon-components-svelte: ^0.67.5 carbon-components-svelte: ^0.67.7
carbon-icons-svelte: ^11.2.0 carbon-icons-svelte: ^11.2.0
cookie: ^0.5.0 cookie: ^0.5.0
eslint: ^8.21.0 eslint: ^8.21.0
@ -54,7 +54,7 @@ devDependencies:
'@types/node': 18.6.4 '@types/node': 18.6.4
'@typescript-eslint/eslint-plugin': 5.32.0_iosr3hrei2tubxveewluhu5lhy '@typescript-eslint/eslint-plugin': 5.32.0_iosr3hrei2tubxveewluhu5lhy
'@typescript-eslint/parser': 5.32.0_qugx7qdu5zevzvxaiqyxfiwquq '@typescript-eslint/parser': 5.32.0_qugx7qdu5zevzvxaiqyxfiwquq
carbon-components-svelte: 0.67.5 carbon-components-svelte: 0.67.7
carbon-icons-svelte: 11.2.0 carbon-icons-svelte: 11.2.0
eslint: 8.21.0 eslint: 8.21.0
eslint-config-prettier: 8.5.0_eslint@8.21.0 eslint-config-prettier: 8.5.0_eslint@8.21.0
@ -601,8 +601,8 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/carbon-components-svelte/0.67.5: /carbon-components-svelte/0.67.7:
resolution: {integrity: sha512-2azoyIy5xuWYOW+voMJe5p1J72bi8L+BpxkZR0FXCxv/+eK8wrDySaLRmyeYku/qNcD1/PAm4Fl4JZoakeGW4A==} resolution: {integrity: sha512-fx/O38tzw9uLiquqqB9zOI5ARA8pSBTOt1gSbgMNwLADYW0weAsr1dLvLnOmSLmXr3T0jaPgWHSkuEv4VTeo/w==}
dependencies: dependencies:
flatpickr: 4.6.9 flatpickr: 4.6.9
dev: true dev: true

View file

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { ToastType, type GameType } from '$lib/types'; import { MinusCircleIcon, PlusCircleIcon } from '@rgossiaux/svelte-heroicons/outline';
import { ToastType, type GameType, type SavedGameType } from '$lib/types';
import { collectionStore } from '$lib/stores/collectionStore'; import { collectionStore } from '$lib/stores/collectionStore';
import { toast } from '$lib/components/toast/toast'; import { toast } from '$lib/components/toast/toast';
import { addToCollection, removeFromCollection } from '$lib/util/manipulateCollection'; import { addToCollection, removeFromCollection } from '$lib/util/manipulateCollection';
export let game: GameType; export let game: GameType;
export let detailed: boolean = false; export let detailed: boolean = false;
$: existsInCollection = $collectionStore.find((item: GameType) => item.id === game.id); $: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
</script> </script>
<article class="game-container" transition:fade> <article class="game-container" transition:fade>
@ -29,12 +30,12 @@
{/if} {/if}
</div> </div>
{#if existsInCollection} {#if existsInCollection}
<button class="btn" type="button" on:click={() => removeFromCollection(game)} <button aria-label="Remove from collection" class="btn" type="button" on:click={() => removeFromCollection(game)}
>Remove from collection -</button >Remove <MinusCircleIcon class="icon" /></button
> >
{:else} {:else}
<button class="btn" type="button" on:click={() => addToCollection(game)} <button aria-label="Add to collection" class="btn" type="button" on:click={() => addToCollection(game)}
>Add to collection +</button >Add to collection <PlusCircleIcon class="icon" /></button
> >
{/if} {/if}
</article> </article>
@ -49,12 +50,20 @@
} }
button { button {
display: flex;
justify-content: space-between;
gap: 1rem;
width: 100%; width: 100%;
border-radius: 10px; border-radius: 10px;
padding: 1rem; padding: 1rem;
background-color: var(--color-btn-primary-active); background-color: var(--color-btn-primary-active);
} }
/* :global(.icon) {
width: 24px;
height: 24px;
} */
.game-container { .game-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View file

@ -3,15 +3,22 @@
import { gameStore } from '$lib/stores/gameSearchStore'; import { gameStore } from '$lib/stores/gameSearchStore';
import { collectionStore } from '$lib/stores/collectionStore'; import { collectionStore } from '$lib/stores/collectionStore';
import { toast } from '$lib/components/toast/toast'; import { toast } from '$lib/components/toast/toast';
import { ToastType } from '$lib/types'; import { ToastType, type SavedGameType } from '$lib/types';
function getRandomCollectionGame() { async function getRandomCollectionGame() {
if ($collectionStore.length > 0) { if ($collectionStore.length > 0) {
boredState.set({ loading: true }); boredState.set({ loading: true });
let randomNumber: number = Math.round(Math.random() * $collectionStore.length - 1); let randomNumber: number = Math.round(Math.random() * $collectionStore.length - 1);
if ($collectionStore.at(randomNumber)) { if ($collectionStore.at(randomNumber)) {
gameStore.removeAll(); gameStore.removeAll();
gameStore.add($collectionStore.at(randomNumber)!); const randomGame: SavedGameType = $collectionStore.at(randomNumber)!;
const response = await fetch(`/api/game/${randomGame?.id}`, {
method: 'GET',
headers: { accept: 'application/json' },
});
const responseData = await response.json();
console.log('responseData', responseData);
gameStore.add(responseData?.game);
boredState.set({ loading: false }); boredState.set({ loading: false });
} else { } else {
toast.send('Error!', { duration: 3000, type: ToastType.ERROR, dismissible: true }); toast.send('Error!', { duration: 3000, type: ToastType.ERROR, dismissible: true });
@ -27,6 +34,6 @@
} }
</script> </script>
<button class="btn" type="button" on:click={getRandomCollectionGame} <button class="btn" type="button" on:click={() => getRandomCollectionGame()}
>Random from collection 🎲</button >Random from collection 🎲</button
> >

View file

@ -2,23 +2,23 @@
import { boredState } from '$lib/stores/boredState'; import { boredState } from '$lib/stores/boredState';
import { gameStore } from '$lib/stores/gameSearchStore'; import { gameStore } from '$lib/stores/gameSearchStore';
async function handleSubmit(event: SubmitEvent) { // async function handleSubmit(event: SubmitEvent) {
// submitting = true; // // submitting = true;
boredState.set({ loading: true }); // boredState.set({ loading: true });
const form = event.target as HTMLFormElement; // const form = event.target as HTMLFormElement;
console.log('form', form); // console.log('form', form);
const response = await fetch('/api/games', { // const response = await fetch('/api/games', {
method: 'POST', // method: 'POST',
headers: { accept: 'application/json' }, // headers: { accept: 'application/json' },
body: new FormData(form) // body: new FormData(form)
}); // });
const responseData = await response.json(); // const responseData = await response.json();
// submitting = false; // // submitting = false;
boredState.set({ loading: false }); // boredState.set({ loading: false });
gameStore.removeAll(); // gameStore.removeAll();
gameStore.addAll(responseData?.games); // gameStore.addAll(responseData?.games);
// games = responseData?.games; // // games = responseData?.games;
} // }
let submitting = $boredState?.loading; let submitting = $boredState?.loading;
let minAge = 1; let minAge = 1;
@ -28,62 +28,62 @@
let exactMaxPlayers = false; let exactMaxPlayers = false;
</script> </script>
<form on:submit|preventDefault={handleSubmit} method="post"> <!-- <form on:submit|preventDefault={handleSubmit} method="post"> -->
<fieldset aria-busy={submitting} disabled={submitting}> <fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
<div> <div>
<label for="minAge"> <label for="minAge">
Min Age Min Age
<input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} /> <input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} />
</label> </label>
</div> </div>
<div> <div>
<label for="minPlayers"> <label for="minPlayers">
Min Players Min Players
<input <input
id="minPlayers" id="minPlayers"
name="minPlayers" name="minPlayers"
bind:value={minPlayers} bind:value={minPlayers}
type="number" type="number"
min={0} min={0}
max={50} max={50}
/> />
</label> </label>
<label for="exactMinPlayers" style="display: flex; gap: 1rem; place-items: center;"> <label for="exactMinPlayers" style="display: flex; gap: 1rem; place-items: center;">
<span>Exact?</span> <span>Exact?</span>
<input <input
id="exactMinPlayers" id="exactMinPlayers"
type="checkbox" type="checkbox"
name="exactMinPlayers" name="exactMinPlayers"
bind:checked={exactMinPlayers} bind:checked={exactMinPlayers}
/> />
</label> </label>
</div> </div>
<div> <div>
<label for="maxPlayers"> <label for="maxPlayers">
Max Players Max Players
<input <input
id="maxPlayers" id="maxPlayers"
name="maxPlayers" name="maxPlayers"
bind:value={maxPlayers} bind:value={maxPlayers}
type="number" type="number"
min={0} min={0}
max={50} max={50}
/> />
</label> </label>
<label for="exactMaxPlayers" style="display: flex; gap: 1rem; place-items: center;"> <label for="exactMaxPlayers" style="display: flex; gap: 1rem; place-items: center;">
<span>Exact?</span> <span>Exact?</span>
<input <input
id="exactMaxPlayers" id="exactMaxPlayers"
type="checkbox" type="checkbox"
name="exactMaxPlayers" name="exactMaxPlayers"
bind:checked={exactMaxPlayers} bind:checked={exactMaxPlayers}
/> />
</label> </label>
</div> </div>
</fieldset> </fieldset>
<button type="submit" disabled={submitting}>Submit</button> <!-- <button type="submit" disabled={submitting}>Submit</button> -->
</form>
<!-- </form> -->
<style lang="scss"> <style lang="scss">
button { button {
border-radius: 10px; border-radius: 10px;

View file

@ -1,21 +1,14 @@
<script lang="ts"> <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 { boredState } from '$lib/stores/boredState';
import { gameStore } from '$lib/stores/gameSearchStore'; import { gameStore } from '$lib/stores/gameSearchStore';
import AdvancedSearch from '$lib/components/search/advancedSearch/index.svelte';
async function handleSearch(event: SubmitEvent) {
boredState.set({ 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.set({ loading: false });
gameStore.removeAll();
gameStore.addAll(responseData?.games);
}
export let showButton: boolean = false; export let showButton: boolean = false;
export let advancedSearch: boolean = false; export let advancedSearch: boolean = false;
@ -25,8 +18,9 @@
let name = ''; let name = '';
</script> </script>
<form on:submit|preventDefault={handleSearch} method="post"> <!-- <form on:submit|preventDefault={handleSearch} method="post"> -->
<fieldset aria-busy={submitting} disabled={submitting}> <div class="search">
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
<label for="name"> <label for="name">
Search Search
<input <input
@ -37,35 +31,63 @@
aria-label="Search boardgame" aria-label="Search boardgame"
placeholder="Search boardgame" placeholder="Search boardgame"
/> />
{#if showButton}
<button class="btn" type="submit" disabled={submitting}>Search</button>
{/if}
</label> </label>
</fieldset> </fieldset>
</form> {#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,
and ignores the `open` state -->
<DisclosurePanel static>
<AdvancedSearch />
</DisclosurePanel>
</div>
{/if}
</Disclosure>
{/if}
</div>
{#if showButton}
<button class="btn" type="submit" disabled={submitting}>Submit</button>
{/if}
<!-- </form> -->
<style lang="scss"> <style lang="scss">
form { .search {
display: grid; display: grid;
place-items: start; gap: 1rem;
} }
:global(.disclosure-button) {
display: flex;
gap: 0.25rem;
place-items: center;
}
h1 { h1 {
width: 100%; width: 100%;
} }
button { button {
padding: 1rem; padding: 1rem;
margin: 1.5rem 0;
} }
label { label {
display: grid; display: grid;
grid-template-columns: auto auto auto; grid-template-columns: auto auto;
gap: 1rem; gap: 1rem;
place-content: start;
place-items: center; place-items: center;
margin: 1rem;
@media (max-width: 850px) { @media (max-width: 850px) {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
} }
</style> </style>

View file

@ -1,23 +1,23 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import type { GameType } from '$lib/types'; import type { SavedGameType } from '$lib/types';
// Custom store // Custom store
const state = () => { const state = () => {
const { subscribe, set, update } = writable<GameType[]>([]); const { subscribe, set, update } = writable<SavedGameType[]>([]);
function addAll(games: GameType[]) { function addAll(games: SavedGameType[]) {
for (const game of games) { for (const game of games) {
add(game); add(game);
} }
} }
function add(game: GameType) { function add(game: SavedGameType) {
update((store) => [...store, game]); update((store) => [...store, game]);
} }
function remove(id: string) { function remove(id: string) {
update((store) => { update((store) => {
const newStore = store.filter((item: GameType) => item.id !== id); const newStore = store.filter((item: SavedGameType) => item.id !== id);
return [...newStore]; return [...newStore];
}); });
} }

View file

@ -3,7 +3,7 @@ import type { GameType } from '$lib/types';
// Custom store // Custom store
const newGameStore = () => { const newGameStore = () => {
const { subscribe, update } = writable<GameType[]>([]); const { subscribe, set, update } = writable<GameType[]>([]);
function add(game: GameType) { function add(game: GameType) {
update((store) => [...store, game]); update((store) => [...store, game]);
@ -26,7 +26,7 @@ const newGameStore = () => {
}); });
} }
return { subscribe, add, addAll, remove, removeAll }; return { subscribe, set, update, add, addAll, remove, removeAll };
}; };
export const gameStore = newGameStore(); export const gameStore = newGameStore();

View file

@ -18,6 +18,11 @@ export type ToastData = {
message: string; message: string;
}; };
export type SavedGameType = {
id: string;
name: string;
}
export type GameType = { export type GameType = {
id: string; id: string;
handle: string; handle: string;

View file

@ -1,9 +1,16 @@
import { collectionStore } from '$lib/stores/collectionStore'; import { collectionStore } from '$lib/stores/collectionStore';
import { toast } from '$lib/components/toast/toast'; import { toast } from '$lib/components/toast/toast';
import { ToastType, type GameType } from '$lib/types'; import { ToastType, type GameType, type SavedGameType } from '$lib/types';
function convertToSavedGame(game: GameType): SavedGameType {
return {
id: game.id,
name: game.name,
};
}
export function addToCollection(game: GameType) { export function addToCollection(game: GameType) {
collectionStore.add(game); collectionStore.add(convertToSavedGame(game));
toast.send("Added to collection", { duration: 3000, type: ToastType.INFO }); toast.send("Added to collection", { duration: 3000, type: ToastType.INFO });
} }

View file

@ -7,6 +7,7 @@
import Portal from '$lib/Portal.svelte'; import Portal from '$lib/Portal.svelte';
import { boredState } from '$lib/stores/boredState'; import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore'; 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';
// import 'carbon-components-svelte/css/all.css'; // import 'carbon-components-svelte/css/all.css';
import '$root/styles/styles.scss'; import '$root/styles/styles.scss';
@ -18,11 +19,10 @@
console.log('collectionEmpty', collectionEmpty); console.log('collectionEmpty', collectionEmpty);
console.log('localStorage.collection', localStorage.collection); console.log('localStorage.collection', localStorage.collection);
if ( if (
browser &&
collectionEmpty && collectionEmpty &&
localStorage && localStorage &&
localStorage.collection && localStorage.collection &&
localStorage.collection !== 0 localStorage.collection.length !== 0
) { ) {
const collection = JSON.parse(localStorage.collection); const collection = JSON.parse(localStorage.collection);
console.log('collection', collection); console.log('collection', collection);
@ -36,7 +36,7 @@
</script> </script>
{#if dev} {#if dev}
<Toy register={{ boredState, collectionStore, toast }} /> <Toy register={{ boredState, collectionStore, gameStore, toast }} />
{/if} {/if}
<Transition transition={{ type: 'fade', duration: 250 }}> <Transition transition={{ type: 'fade', duration: 250 }}>
<div class="wrapper"> <div class="wrapper">

View file

@ -0,0 +1,33 @@
import { boardGameApi } from '$root/routes/_api';
import type { RequestHandler } from '@sveltejs/kit';
export const GET: RequestHandler = async ({ params }) => {
const queryParams = {
ids: `${params?.id}`
};
console.log('queryParams', queryParams);
const response = await boardGameApi('get', `search`, queryParams);
if (response.status === 404) {
return {
body: {
games: []
}
};
}
if (response.status === 200) {
const gameResponse = await response.json();
// console.log('gameResponse', gameResponse);
// const games = gameResponse?.games;
console.log('game', gameResponse?.games[0]);
return {
body: {
game: gameResponse?.games[0]
}
};
}
return {
status: response.status
};
};

View file

@ -54,7 +54,7 @@ export const POST: RequestHandler = async ({ request }) => {
}); });
return { return {
body: { body: {
games: gameResponse?.games games
} }
}; };
} }

View file

@ -2,10 +2,10 @@
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Icon from '$lib/components/Icon.svelte'; import Icon from '$lib/components/Icon.svelte';
import { collectionStore } from '$lib/stores/collectionStore'; import { collectionStore } from '$lib/stores/collectionStore';
import type { GameType } from '$lib/types'; import type { GameType, SavedGameType } from '$lib/types';
import { addToCollection, removeFromCollection } from '$lib/util/manipulateCollection'; import { addToCollection, removeFromCollection } from '$lib/util/manipulateCollection';
$: existsInCollection = $collectionStore.find((item: GameType) => item.id === game.id); $: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
export let game: GameType; export let game: GameType;
let seeMore: boolean = false; let seeMore: boolean = false;
console.log(game?.description?.indexOf('</p>')); console.log(game?.description?.indexOf('</p>'));

View file

@ -6,6 +6,22 @@
import RandomSearch from '$lib/components/search/random/index.svelte'; import RandomSearch from '$lib/components/search/random/index.svelte';
import Random from '$lib/components/random/index.svelte'; import Random from '$lib/components/random/index.svelte';
import { gameStore } from '$lib/stores/gameSearchStore'; import { gameStore } from '$lib/stores/gameSearchStore';
import { boredState } from '$root/lib/stores/boredState';
async function handleSearch(event: SubmitEvent) {
boredState.set({ 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.set({ loading: false });
gameStore.removeAll();
gameStore.addAll(responseData?.games);
}
</script> </script>
<svelte:head> <svelte:head>
@ -15,8 +31,10 @@
<h1>Search Boardgames!</h1> <h1>Search Boardgames!</h1>
<p>Input your requirements to search for board game that match your criteria</p> <p>Input your requirements to search for board game that match your criteria</p>
<div class="game-search"> <div class="game-search">
<TextSearch showButton /> <form on:submit|preventDefault={handleSearch} method="post">
<AdvancedSearch /> <TextSearch showButton advancedSearch />
</form>
<!-- <AdvancedSearch /> -->
<div class="random-buttons"> <div class="random-buttons">
<RandomSearch /> <RandomSearch />
<Random /> <Random />
@ -81,7 +99,7 @@
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
@media (max-width: 550px) { @media (max-width: 650px) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }

View file

@ -272,3 +272,8 @@ ol {
background-color: var(--color-placeholder); background-color: var(--color-placeholder);
border-radius: var(--radius-base); border-radius: var(--radius-base);
} }
.icon {
width: 24px;
height: 24px;
}