diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b73523c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "kickstarter", + "msrp" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 8a6cbda..90fe91c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "@fontsource/fira-mono": "^4.5.7", "@lukeed/uuid": "^2.0.0", - "cookie": "^0.5.0" + "cookie": "^0.5.0", + "zod": "^3.14.4" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91c3c0e..278c367 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,11 +23,13 @@ specifiers: svelte-preprocess: ^4.10.6 tslib: ^2.3.1 typescript: ^4.6.3 + zod: ^3.14.4 dependencies: '@fontsource/fira-mono': 4.5.7 '@lukeed/uuid': 2.0.0 cookie: 0.5.0 + zod: 3.14.4 devDependencies: '@playwright/test': 1.21.1 @@ -2823,3 +2825,7 @@ packages: dependencies: buffer-crc32: 0.2.13 dev: true + + /zod/3.14.4: + resolution: {integrity: sha512-U9BFLb2GO34Sfo9IUYp0w3wJLlmcyGoMd75qU9yf+DrdGA4kEx6e+l9KOkAlyUO0PSQzZCa3TR4qVlcmwqSDuw==} + dev: false diff --git a/src/components/game.svelte b/src/components/game.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/form.ts b/src/lib/form.ts index 70a7756..9754907 100644 --- a/src/lib/form.ts +++ b/src/lib/form.ts @@ -59,7 +59,7 @@ export function enhance( const url = new URL(form.action); url.search = url.hash = ''; - // invalidate(url.href); + invalidate(url.href); } else if (error) { error({ data, form, error: null, response }); } else { diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..c0cfd74 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,72 @@ +export type Game = { + id: string; + handle: string; + name: string; + url: string; + edit_url: string; + price: number; + price_ca: number; + price_uk: number; + price_au: number; + msrp: number; + year_published: number; + min_players: number; + max_players: number; + min_playtime: number; + max_playtime: number; + min_age: number; + description: string; + players: string; + playtime: string; +} + +export type SearchQuery = { + client_id: string; + limit?: number; + skip?: number; + ids?: string[]; + list_id?: string; + kickstarter?: boolean; + random?: boolean; + name?: string; + exact?: boolean; + fuzzy_match?: boolean; + designer?: string; + publisher?: string; + artist?: string; + mechanics?: string; + categories?: string; + order_by?: string; + ascending?: boolean; + min_players?: number; + max_players?: number; + min_playtime?: number; + max_playtime?: number; + min_age?: number; + year_published?: number; + gt_min_players?: number; + gt_max_players?: number; + gt_min_playtime?: number; + gt_max_playtime?: number; + gt_min_age?: number; + gt_year_published?: number; + gt_price?: bigint; + gt_msrp?: bigint; + gt_discount?: bigint; + gt_reddit_count?: number; + gt_reddit_week_count?: number; + gt_reddit_day_count?: number; + lt_min_players?: number; + lt_max_players?: number; + lt_min_playtime?: number; + lt_max_playtime?: number; + lt_min_age?: number; + lt_year_published?: number; + lt_price?: bigint; + lt_msrp?: bigint; + lt_discount?: bigint; + lt_reddit_count?: number; + lt_reddit_week_count?: number; + lt_reddit_day_count?: number; + fields?: string; +} \ No newline at end of file diff --git a/src/lib/zodValidation.ts b/src/lib/zodValidation.ts new file mode 100644 index 0000000..0b5301d --- /dev/null +++ b/src/lib/zodValidation.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; + +export const BoardGameSearch = z.object({ + minAge: z.number(), + maxAge: z.number(), + minPlayers: z.number(), + maxPlayers: z.number(), +}); + +export const Game = z.object({ + id: z.string(), + handle: z.string(), + name: z.string(), + url: z.string(), + edit_url: z.string(), + price: z.number(), + price_ca: z.number(), + price_uk: z.number(), + price_au: z.number(), + msrp: z.number(), + year_published: z.number(), + min_players: z.number(), + max_players: z.number(), + min_playtime: z.number(), + max_playtime: z.number(), + min_age: z.number(), + description: z.string(), + players: z.string(), + playtime: z.string(), +}); diff --git a/src/routes/api/games/index.ts b/src/routes/api/games/index.ts new file mode 100644 index 0000000..485aa4e --- /dev/null +++ b/src/routes/api/games/index.ts @@ -0,0 +1,62 @@ +import type { SearchQuery } from "$lib/types"; +import type { RequestHandler } from "@sveltejs/kit"; + +export const post: RequestHandler = async ({ request }) => { + const form = await request.formData(); + const queryParams : SearchQuery = { + order_by: 'rank', + ascending: false, + limit: 20, + client_id: import.meta.env.VITE_PUBLIC_CLIENT_ID, + } + + const minAge = form.get('minAge'); + const minPlayers = form.get('minPlayers'); + const maxPlayers = form.get('maxPlayers'); + const random = form.get('random') === 'on' || false; + + if (minPlayers) queryParams.gt_min_players = (+minPlayers === 1 ? 0 : (+minPlayers - 1)); + if (maxPlayers) queryParams.lt_max_players = +maxPlayers + 1; + if (minAge) queryParams.gt_min_age = +minAge === 1 ? 0 : +minAge - 1; + queryParams.random = random; + + const newQueryParams = {}; + for (const key in queryParams) { + newQueryParams[key] = new String(queryParams[key]); + } + const urlQueryParams = new URLSearchParams(newQueryParams); + console.log('urlQueryParams', JSON.stringify(urlQueryParams, null, 2)); + + 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', response); + if (response.status === 404) { + // user hasn't created a todo list. + // start with an empty array + return { + body: { + games: [] + } + }; + } + + if (response.status === 200) { + const gameResponse = await response.json(); + const games = gameResponse?.games; + console.log('games', games); + return { + body: { + games: gameResponse?.games, + } + }; + } + + return { + status: response.status + }; +} \ No newline at end of file diff --git a/src/routes/games/index.svelte b/src/routes/games/index.svelte index 6276ad9..b1fabd4 100644 --- a/src/routes/games/index.svelte +++ b/src/routes/games/index.svelte @@ -1,34 +1,27 @@ @@ -37,44 +30,62 @@
-
{ - loading = true; - }, - result: async ({ data, form, response }) => { - loading = false; - console.log(JSON.stringify(data)) - } - }} - > -
- - - - + +
+
+ + +
+
+ +
+ + +

+ Resources are provisioned based on your account's organization. +

+
+
+
+
+ + +
+
+ + +
+
+ +
-

Games

- {#each games as game (game.id)} + {#each games as game}

{game.name}

@@ -100,6 +111,13 @@ font-weight: 600; } + button { + border-radius: 4px; + margin: 0; + padding: 0.2rem; + background-color: palegreen; + } + .games { display: grid; gap: 2rem; @@ -110,7 +128,13 @@ } .game-form { + display: flex; + place-items: center; + } + + .game-form fieldset { display: grid; - grid-template-columns: 1fr; + gap: 1rem; + grid-template-columns: repeat(3, minmax(200px, 1fr)); } \ No newline at end of file diff --git a/src/routes/games/index.ts b/src/routes/games/index.ts index 012ca98..dda0e96 100644 --- a/src/routes/games/index.ts +++ b/src/routes/games/index.ts @@ -1,84 +1,84 @@ import type { RequestHandler } from '@sveltejs/kit'; import { boardGameApi } from '../_api'; -export const get: RequestHandler = async ({ params }) => { - const queryParams = { - order_by: 'rank', - ascending: 'false', - limit: '10', - } - const response = await boardGameApi('get', `search`, queryParams); - console.log('response', response); - if (response.status === 404) { - // user hasn't created a todo list. - // start with an empty array - return { - body: { - games: [] - } - }; - } +// export const get: RequestHandler = async ({ params }) => { +// const queryParams = { +// order_by: 'rank', +// ascending: 'false', +// limit: '10', +// } +// const response = await boardGameApi('get', `search`, queryParams); +// console.log('response', response); +// if (response.status === 404) { +// // user hasn't created a todo list. +// // start with an empty array +// return { +// body: { +// games: [] +// } +// }; +// } - if (response.status === 200) { - const gameResponse = await response.json(); - const games = gameResponse?.games; - console.log('games', games); - return { - body: { - games: gameResponse?.games, - } - }; - } +// if (response.status === 200) { +// const gameResponse = await response.json(); +// const games = gameResponse?.games; +// console.log('games', games); +// return { +// body: { +// games: gameResponse?.games, +// } +// }; +// } - return { - status: response.status - }; -} +// return { +// status: response.status +// }; +// } -export const post: RequestHandler = async ({ request }) => { - const form = await request.formData(); - const minAge = form.get('minAge') || 0; - console.log('minAge', minAge); - const maxAge = form.get('maxAge') || 0; - console.log('maxAge', maxAge); - const minPlayers = form.get('minPlayers') || 1; - console.log('minPlayers', minPlayers); - const maxPlayers = form.get('maxPlayers') || 1; - console.log('maxPlayers', maxPlayers); +// export const post: RequestHandler = async ({ request }) => { +// const form = await request.formData(); +// const minAge = form.get('minAge') || 0; +// console.log('minAge', minAge); +// const maxAge = form.get('maxAge') || 0; +// console.log('maxAge', maxAge); +// const minPlayers = form.get('minPlayers') || 1; +// console.log('minPlayers', minPlayers); +// const maxPlayers = form.get('maxPlayers') || 1; +// console.log('maxPlayers', maxPlayers); - const queryParams = { - order_by: 'rank', - ascending: 'false', - limit: '2', - gt_min_players: String(+minPlayers === 1 ? 0 : +minPlayers - 1), - lt_max_players: String(+maxPlayers + 1), - gt_min_age: String(+minAge === 1 ? 0 : +minAge - 1), - lt_max_age: String(+maxAge + 1), - } - const response = await boardGameApi('get', `search`, queryParams); - console.log('response', response); - if (response.status === 404) { - // user hasn't created a todo list. - // start with an empty array - return { - body: { - games: [] - } - }; - } +// const queryParams = { +// order_by: 'rank', +// ascending: 'false', +// limit: '1', +// gt_min_players: String(+minPlayers === 1 ? 0 : +minPlayers - 1), +// lt_max_players: String(+maxPlayers + 1), +// gt_min_age: String(+minAge === 1 ? 0 : +minAge - 1), +// lt_max_age: String(+maxAge + 1), +// } +// const response = await boardGameApi('get', `search`, queryParams); +// console.log('response', response); +// if (response.status === 404) { +// // user hasn't created a todo list. +// // start with an empty array +// return { +// body: { +// games: [] +// } +// }; +// } - if (response.status === 200) { - const gameResponse = await response.json(); - const games = gameResponse?.games; - console.log('games', games); - return { - body: { - games: gameResponse?.games, - } - }; - } +// if (response.status === 200) { +// const gameResponse = await response.json(); +// const games = gameResponse?.games; +// console.log('games', games); +// return { +// body: { +// games, +// } +// }; +// } - return { - status: response.status - }; -} \ No newline at end of file +// return { +// status: response.status +// }; +// } \ No newline at end of file