mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Updating og image generation, updating Lucia Beta, and using Shadcn Form for the search form.
This commit is contained in:
parent
019798eb0b
commit
386d4e7e3a
22 changed files with 879 additions and 573 deletions
41
package.json
41
package.json
|
|
@ -28,16 +28,16 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@melt-ui/pp": "^0.3.0",
|
"@melt-ui/pp": "^0.3.0",
|
||||||
"@melt-ui/svelte": "^0.70.0",
|
"@melt-ui/svelte": "^0.70.0",
|
||||||
"@playwright/test": "^1.41.0",
|
"@playwright/test": "^1.41.1",
|
||||||
"@resvg/resvg-js": "^2.4.1",
|
"@resvg/resvg-js": "^2.6.0",
|
||||||
"@sveltejs/adapter-auto": "^3.1.0",
|
"@sveltejs/adapter-auto": "^3.1.1",
|
||||||
"@sveltejs/adapter-vercel": "^4.0.5",
|
"@sveltejs/enhanced-img": "^0.1.8",
|
||||||
"@sveltejs/kit": "^2.3.5",
|
"@sveltejs/kit": "^2.5.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@types/cookie": "^0.5.4",
|
"@types/cookie": "^0.5.4",
|
||||||
"@types/node": "^18.19.8",
|
"@types/node": "^18.19.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||||
"@typescript-eslint/parser": "^6.19.0",
|
"@typescript-eslint/parser": "^6.19.1",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.17",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
|
@ -59,14 +59,14 @@
|
||||||
"svelte-meta-tags": "^3.1.0",
|
"svelte-meta-tags": "^3.1.0",
|
||||||
"svelte-preprocess": "^5.1.3",
|
"svelte-preprocess": "^5.1.3",
|
||||||
"svelte-sequential-preprocessor": "^2.0.1",
|
"svelte-sequential-preprocessor": "^2.0.1",
|
||||||
"sveltekit-flash-message": "^2.3.1",
|
"sveltekit-flash-message": "^2.4.1",
|
||||||
"sveltekit-superforms": "^1.13.3",
|
"sveltekit-superforms": "^1.13.4",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.6.1",
|
"tslib": "^2.6.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.11",
|
"vite": "^5.0.12",
|
||||||
"vitest": "^1.2.1",
|
"vitest": "^1.2.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
@ -78,32 +78,33 @@
|
||||||
"@fontsource/fira-mono": "^4.5.10",
|
"@fontsource/fira-mono": "^4.5.10",
|
||||||
"@iconify-icons/line-md": "^1.2.26",
|
"@iconify-icons/line-md": "^1.2.26",
|
||||||
"@iconify-icons/mdi": "^1.2.47",
|
"@iconify-icons/mdi": "^1.2.47",
|
||||||
"@lucia-auth/adapter-prisma": "4.0.0-beta.9",
|
"@lucia-auth/adapter-prisma": "4.0.0-beta.10",
|
||||||
"@lukeed/uuid": "^2.0.1",
|
"@lukeed/uuid": "^2.0.1",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@prisma/client": "^5.8.1",
|
"@prisma/client": "^5.8.1",
|
||||||
"@sentry/sveltekit": "^7.88.0",
|
"@sentry/sveltekit": "^7.88.0",
|
||||||
|
"@sveltejs/adapter-vercel": "^5.1.0",
|
||||||
"@types/feather-icons": "^4.29.4",
|
"@types/feather-icons": "^4.29.4",
|
||||||
"@vercel/og": "^0.5.13",
|
"@vercel/og": "^0.5.20",
|
||||||
"bits-ui": "^0.11.8",
|
"bits-ui": "^0.15.1",
|
||||||
"boardgamegeekclient": "^1.9.1",
|
"boardgamegeekclient": "^1.9.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"feather-icons": "^4.29.1",
|
"feather-icons": "^4.29.1",
|
||||||
"formsnap": "^0.4.2",
|
"formsnap": "^0.4.3",
|
||||||
"html-entities": "^2.4.0",
|
"html-entities": "^2.4.0",
|
||||||
"iconify-icon": "^1.0.8",
|
"iconify-icon": "^1.0.8",
|
||||||
"just-kebab-case": "^4.2.0",
|
"just-kebab-case": "^4.2.0",
|
||||||
"loader": "^2.1.1",
|
"loader": "^2.1.1",
|
||||||
"lucia": "3.0.0-beta.14",
|
"lucia": "3.0.0-beta.15",
|
||||||
"lucide-svelte": "^0.298.0",
|
"lucide-svelte": "^0.298.0",
|
||||||
"open-props": "^1.6.16",
|
"open-props": "^1.6.17",
|
||||||
"oslo": "^0.27.1",
|
"oslo": "^1.0.1",
|
||||||
"radix-svelte": "^0.9.0",
|
"radix-svelte": "^0.9.0",
|
||||||
"svelte-french-toast": "^1.2.0",
|
"svelte-french-toast": "^1.2.0",
|
||||||
"svelte-lazy-loader": "^1.0.0",
|
"svelte-lazy-loader": "^1.0.0",
|
||||||
"tailwind-merge": "^2.2.0",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwind-variants": "^0.1.19",
|
"tailwind-variants": "^0.1.19",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
"zod-to-json-schema": "^3.22.3"
|
"zod-to-json-schema": "^3.22.3"
|
||||||
|
|
|
||||||
1044
pnpm-lock.yaml
1044
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
39
src/lib/components/search/GameSearchForm.svelte
Normal file
39
src/lib/components/search/GameSearchForm.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { SuperValidated } from 'sveltekit-superforms';
|
||||||
|
import { search_schema, type SearchSchema } from '$lib/zodValidation';
|
||||||
|
import * as Form from "$lib/components/ui/form";
|
||||||
|
|
||||||
|
export let form: SuperValidated<SearchSchema>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<search>
|
||||||
|
<Form.Root id="search-form" action="/search" method="GET" data-sveltekit-reload {form} schema={search_schema} let:config>
|
||||||
|
<fieldset>
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Field {config} name="q">
|
||||||
|
<Form.Label for="label">Search</Form.Label>
|
||||||
|
<Form.Input />
|
||||||
|
<Form.Validation />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {config} name="skip">
|
||||||
|
<Form.Input type="hidden" />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {config} name="limit">
|
||||||
|
<Form.Input type="hidden" />
|
||||||
|
</Form.Field>
|
||||||
|
</Form.Item>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Form.Field {config} name="exact">
|
||||||
|
<Form.Label>Exact Search</Form.Label>
|
||||||
|
<Form.Checkbox class="mt-0" />
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<Form.Button>Submit</Form.Button>
|
||||||
|
</Form.Root>
|
||||||
|
</search>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
</style>
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
|
||||||
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
|
||||||
import type { SuperValidated } from 'sveltekit-superforms';
|
|
||||||
import type { SearchSchema } from '$lib/zodValidation';
|
|
||||||
import { Label } from '$lib/components/ui/label';
|
|
||||||
import { Input } from '$lib/components/ui/input';
|
|
||||||
import { Button } from '$lib/components/ui/button';
|
|
||||||
|
|
||||||
export let data;
|
|
||||||
console.log("text search data", data);
|
|
||||||
export let showButton: boolean = false;
|
|
||||||
export let advancedSearch: boolean = false;
|
|
||||||
|
|
||||||
const { form, errors }: SuperValidated<SearchSchema> = superForm(data.form);
|
|
||||||
|
|
||||||
const dev = process.env.NODE_ENV !== 'production';
|
|
||||||
|
|
||||||
// TODO: Keep all Pagination Values on back and forth browser
|
|
||||||
// TODO: Add cache for certain number of pages so back and forth doesn't request data again
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if dev}
|
|
||||||
<SuperDebug collapsible data={$form} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<search>
|
|
||||||
<form id="search-form" action="/search" method="GET">
|
|
||||||
<div class="search">
|
|
||||||
<fieldset class="text-search">
|
|
||||||
<Label for="label">Search</Label>
|
|
||||||
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
|
|
||||||
{#if $errors.q}
|
|
||||||
<p class="text-sm text-destructive">{$errors.q}</p>
|
|
||||||
{/if}
|
|
||||||
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
|
|
||||||
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
{#if showButton}
|
|
||||||
<Button type="submit">Submit</Button>
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
</search>
|
|
||||||
|
|
||||||
<style lang="postcss">
|
|
||||||
:global(.disclosure-button) {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -7,12 +7,10 @@ const buttonVariants = tv({
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
destructive:
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary:
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline"
|
link: "text-primary underline-offset-4 hover:underline"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPrimitive.Description
|
<FormPrimitive.Description class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
|
||||||
class={cn("text-sm text-muted-foreground", className)}
|
|
||||||
{...$$restProps}
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</FormPrimitive.Description>
|
</FormPrimitive.Description>
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,6 @@
|
||||||
const { errors, ids } = getFormField();
|
const { errors, ids } = getFormField();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Label
|
<Label for={$ids.input} class={cn($errors && "text-destructive", className)} {...$$restProps}>
|
||||||
for={$ids.input}
|
|
||||||
class={cn($errors && "text-destructive", className)}
|
|
||||||
{...$$restProps}
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</Label>
|
</Label>
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,12 @@
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
type $$Events = SelectPrimitive.TriggerEvents;
|
type $$Events = SelectPrimitive.TriggerEvents;
|
||||||
const { attrStore } = getFormField();
|
const { attrStore, value } = getFormField();
|
||||||
export let placeholder = "";
|
export let placeholder = "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select.Trigger
|
<Select.Trigger {...$$restProps} {...$attrStore} on:click on:keydown type="button">
|
||||||
{...$$restProps}
|
<slot value={$value}>
|
||||||
{...$attrStore}
|
<Select.Value {placeholder} />
|
||||||
on:click
|
</slot>
|
||||||
on:keydown
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Select.Value {placeholder} />
|
|
||||||
<slot />
|
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@
|
||||||
import { getFormField } from "formsnap";
|
import { getFormField } from "formsnap";
|
||||||
import type { HTMLTextareaAttributes } from "svelte/elements";
|
import type { HTMLTextareaAttributes } from "svelte/elements";
|
||||||
import type { TextareaGetFormField } from ".";
|
import type { TextareaGetFormField } from ".";
|
||||||
import {
|
import { Textarea, type TextareaEvents } from "$lib/components/ui/textarea";
|
||||||
Textarea,
|
|
||||||
type TextareaEvents
|
|
||||||
} from "$lib/components/ui/textarea";
|
|
||||||
|
|
||||||
type $$Props = HTMLTextareaAttributes;
|
type $$Props = HTMLTextareaAttributes;
|
||||||
type $$Events = TextareaEvents;
|
type $$Events = TextareaEvents;
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,7 @@ const SelectGroup = SelectComp.Group;
|
||||||
const SelectItem = SelectComp.Item;
|
const SelectItem = SelectComp.Item;
|
||||||
const SelectSeparator = SelectComp.Separator;
|
const SelectSeparator = SelectComp.Separator;
|
||||||
|
|
||||||
export type TextareaGetFormField = Omit<
|
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, "value"> & {
|
||||||
ReturnType<typeof getFormField>,
|
|
||||||
"value"
|
|
||||||
> & {
|
|
||||||
value: Writable<string>;
|
value: Writable<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ export type InputEvents = {
|
||||||
change: FormInputEvent<Event>;
|
change: FormInputEvent<Event>;
|
||||||
click: FormInputEvent<MouseEvent>;
|
click: FormInputEvent<MouseEvent>;
|
||||||
focus: FormInputEvent<FocusEvent>;
|
focus: FormInputEvent<FocusEvent>;
|
||||||
|
focusin: FormInputEvent<FocusEvent>;
|
||||||
|
focusout: FormInputEvent<FocusEvent>;
|
||||||
keydown: FormInputEvent<KeyboardEvent>;
|
keydown: FormInputEvent<KeyboardEvent>;
|
||||||
keypress: FormInputEvent<KeyboardEvent>;
|
keypress: FormInputEvent<KeyboardEvent>;
|
||||||
keyup: FormInputEvent<KeyboardEvent>;
|
keyup: FormInputEvent<KeyboardEvent>;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
on:change
|
on:change
|
||||||
on:click
|
on:click
|
||||||
on:focus
|
on:focus
|
||||||
|
on:focusin
|
||||||
|
on:focusout
|
||||||
on:keydown
|
on:keydown
|
||||||
on:keypress
|
on:keypress
|
||||||
on:keyup
|
on:keyup
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,6 @@
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RadioGroupPrimitive.Root
|
<RadioGroupPrimitive.Root bind:value class={cn("grid gap-2", className)} {...$$restProps}>
|
||||||
bind:value
|
|
||||||
class={cn("grid gap-2", className)}
|
|
||||||
{...$$restProps}
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</RadioGroupPrimitive.Root>
|
</RadioGroupPrimitive.Root>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,4 @@
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SelectPrimitive.Separator
|
<SelectPrimitive.Separator class={cn("-mx-1 my-1 h-px bg-muted", className)} {...$$restProps} />
|
||||||
class={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
||||||
{...$$restProps}
|
|
||||||
/>
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
class={cn(
|
class={cn(
|
||||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 line-clamp-1 truncate",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
|
|
|
||||||
45
src/lib/renderImage.ts
Normal file
45
src/lib/renderImage.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import satori from 'satori';
|
||||||
|
import { Resvg } from '@resvg/resvg-js';
|
||||||
|
import { html as toReactNode } from 'satori-html';
|
||||||
|
import { dev } from '$app/environment';
|
||||||
|
import { read } from '$app/server';
|
||||||
|
|
||||||
|
// we use a Vite plugin to turn this import into the result of fs.readFileSync during build
|
||||||
|
import firaSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf';
|
||||||
|
|
||||||
|
const fontData = read(firaSansSemiBold).arrayBuffer();
|
||||||
|
|
||||||
|
export async function componentToPng(component,
|
||||||
|
props: Record<string, string | undefined>,
|
||||||
|
height: number, width: number) {
|
||||||
|
const result = component.render(props);
|
||||||
|
const markup = toReactNode(`${result.html}<style lang="css">${result.css.code}</style>`);
|
||||||
|
|
||||||
|
const svg = await satori(markup, {
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Fira Sans',
|
||||||
|
data: await fontData,
|
||||||
|
style: 'normal'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
height: +height,
|
||||||
|
width: +width
|
||||||
|
});
|
||||||
|
|
||||||
|
const resvg = new Resvg(svg, {
|
||||||
|
fitTo: {
|
||||||
|
mode: 'width',
|
||||||
|
value: +width
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const image = resvg.render();
|
||||||
|
|
||||||
|
return new Response(image.asPng(), {
|
||||||
|
headers: {
|
||||||
|
'content-type': 'image/png',
|
||||||
|
'cache-control': dev ? 'no-cache, no-store' : 'public, immutable, no-transform, max-age=86400'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ import { superValidate } from 'sveltekit-superforms/server';
|
||||||
import { search_schema } from '$lib/zodValidation';
|
import { search_schema } from '$lib/zodValidation';
|
||||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import prisma from '$lib/prisma';
|
|
||||||
import type { Game } from '@prisma/client';
|
import type { Game } from '@prisma/client';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ fetch, url }) => {
|
export const load: PageServerLoad = async ({ fetch, url }) => {
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@ async function searchForGames(
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Error searching board games ${e}`);
|
console.log(`Error searching board games ${e}`);
|
||||||
// throw error(500, { message: 'Something went wrong' });
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
|
|
@ -103,21 +102,27 @@ async function searchForGames(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
limit: 10,
|
||||||
|
skip: 0,
|
||||||
|
order: 'name',
|
||||||
|
sort: 'asc',
|
||||||
|
q: '',
|
||||||
|
exact: false,
|
||||||
|
};
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals, fetch, url }) => {
|
export const load: PageServerLoad = async ({ locals, fetch, url }) => {
|
||||||
const defaults = {
|
|
||||||
limit: 10,
|
|
||||||
skip: 0,
|
|
||||||
order: 'asc',
|
|
||||||
sort: 'name'
|
|
||||||
};
|
|
||||||
const searchParams = Object.fromEntries(url?.searchParams);
|
const searchParams = Object.fromEntries(url?.searchParams);
|
||||||
console.log('searchParams', searchParams);
|
console.log('searchParams', searchParams);
|
||||||
searchParams.limit = searchParams.limit || `${defaults.limit}`;
|
|
||||||
searchParams.skip = searchParams.skip || `${defaults.skip}`;
|
|
||||||
searchParams.order = searchParams.order || defaults.order;
|
searchParams.order = searchParams.order || defaults.order;
|
||||||
searchParams.sort = searchParams.sort || defaults.sort;
|
searchParams.sort = searchParams.sort || defaults.sort;
|
||||||
const form = await superValidate(searchParams, search_schema);
|
searchParams.q = searchParams.q || defaults.q;
|
||||||
// const modifyListForm = await superValidate(listGameSchema);
|
const form = await superValidate({
|
||||||
|
...searchParams,
|
||||||
|
skip: Number(searchParams.skip || defaults.skip),
|
||||||
|
limit: Number(searchParams.limit || defaults.limit),
|
||||||
|
exact: searchParams.exact ? searchParams.exact === 'true' : defaults.exact
|
||||||
|
}, search_schema);
|
||||||
|
|
||||||
const queryParams: SearchQuery = {
|
const queryParams: SearchQuery = {
|
||||||
limit: form.data?.limit,
|
limit: form.data?.limit,
|
||||||
|
|
@ -125,7 +130,6 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
|
||||||
q: form.data?.q
|
q: form.data?.q
|
||||||
};
|
};
|
||||||
|
|
||||||
// fields: ('id,name,min_age,min_players,max_players,thumb_url,min_playtime,max_playtime,min_age,description');
|
|
||||||
try {
|
try {
|
||||||
if (form.data?.q === '') {
|
if (form.data?.q === '') {
|
||||||
return {
|
return {
|
||||||
|
|
@ -169,6 +173,8 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
|
||||||
const urlQueryParams = new URLSearchParams(newQueryParams);
|
const urlQueryParams = new URLSearchParams(newQueryParams);
|
||||||
const searchData = await searchForGames(locals, fetch, urlQueryParams);
|
const searchData = await searchForGames(locals, fetch, urlQueryParams);
|
||||||
|
|
||||||
|
console.log('search data', JSON.stringify(searchData, null, 2));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
// modifyListForm,
|
// modifyListForm,
|
||||||
|
|
@ -178,6 +184,8 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Error searching board games ${e}`);
|
console.log(`Error searching board games ${e}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('returning default no data')
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
searchData: {
|
searchData: {
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,26 @@
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
||||||
import { createPagination, createToolbar, melt } from '@melt-ui/svelte';
|
import { createPagination, createToolbar, melt } from '@melt-ui/svelte';
|
||||||
import { ChevronLeft, ChevronRight, LayoutList, LayoutGrid } from 'lucide-svelte';
|
import { ChevronLeft, ChevronRight, LayoutList, LayoutGrid, Check } from 'lucide-svelte';
|
||||||
import type { SearchSchema } from '$lib/zodValidation';
|
import { search_schema, type SearchSchema } from '$lib/zodValidation';
|
||||||
import Game from '$components/Game.svelte';
|
import Game from '$components/Game.svelte';
|
||||||
import { Label } from '$lib/components/ui/label';
|
import { Label } from '$lib/components/ui/label';
|
||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { Checkbox } from "$lib/components/ui/checkbox";
|
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||||
|
import * as Form from "$lib/components/ui/form";
|
||||||
|
import GameSearchForm from '$components/search/GameSearchForm.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
const { form, errors }: SuperValidated<SearchSchema> = superForm(data.form);
|
|
||||||
const { games, totalCount } = data?.searchData;
|
const { games, totalCount } = data?.searchData;
|
||||||
|
|
||||||
|
console.log('data found', data);
|
||||||
|
console.log('found games', games);
|
||||||
|
console.log('found totalCount', totalCount);
|
||||||
|
|
||||||
let submitButton: HTMLElement;
|
let submitButton: HTMLElement;
|
||||||
let pageSize = +form?.limit || 10;
|
let pageSize: number = data.form.limit || 10;
|
||||||
|
|
||||||
$: showPagination = totalCount > pageSize;
|
$: showPagination = totalCount > pageSize;
|
||||||
|
|
||||||
|
|
@ -48,33 +53,10 @@
|
||||||
|
|
||||||
<div class="game-search">
|
<div class="game-search">
|
||||||
{#if dev}
|
{#if dev}
|
||||||
<SuperDebug collapsible data={$form} />
|
<SuperDebug collapsible data={data.form} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<search>
|
<GameSearchForm form={data.form} />
|
||||||
<form id="search-form" action="/search" method="GET">
|
|
||||||
<fieldset>
|
|
||||||
<Label for="label">Search</Label>
|
|
||||||
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
|
|
||||||
{#if $errors.q}
|
|
||||||
<p class="text-sm text-destructive">{$errors.q}</p>
|
|
||||||
{/if}
|
|
||||||
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
|
|
||||||
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="flex items-center space-x-2">
|
|
||||||
<Checkbox id="exact" bind:checked={$form.exact} aria-labelledby="exact-label" />
|
|
||||||
<Label
|
|
||||||
id="exact-label"
|
|
||||||
for="exact"
|
|
||||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Exact Search
|
|
||||||
</Label>
|
|
||||||
</fieldset>
|
|
||||||
<Button type="submit">Submit</Button>
|
|
||||||
</form>
|
|
||||||
</search>
|
|
||||||
|
|
||||||
<section class="games">
|
<section class="games">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -139,15 +121,11 @@
|
||||||
button {
|
button {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: 2px;
|
|
||||||
background-color: rgb(var(--color-white) / 1);
|
background-color: rgb(var(--color-white) / 1);
|
||||||
color: rgb(var(--color-magnum-700) / 1);
|
color: rgb(var(--color-magnum-700) / 1);
|
||||||
box-shadow: 0px 1px 2px 0px rgb(var(--color-black) / 0.05);
|
box-shadow: 0px 1px 2px 0px rgb(var(--color-black) / 0.05);
|
||||||
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
padding-inline: 0.75rem;
|
|
||||||
height: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,27 @@
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import satori from 'satori';
|
|
||||||
import { Resvg } from '@resvg/resvg-js';
|
|
||||||
import { html as toReactNode } from 'satori-html';
|
|
||||||
import NotoSans from '$lib/fonts/NotoSans-Regular.ttf';
|
|
||||||
import SocialImageCard from '$components/socialImageCard.svelte';
|
import SocialImageCard from '$components/socialImageCard.svelte';
|
||||||
|
import { componentToPng } from '$lib/renderImage.js';
|
||||||
|
|
||||||
const height = 630;
|
const height = 630;
|
||||||
const width = 1200;
|
const width = 1200;
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ url }) => {
|
/** @type {import('./$types').RequestHandler} */
|
||||||
|
export async function GET({ url }) {
|
||||||
try {
|
try {
|
||||||
const ogImage = `${new URL(url.origin).href}images/bored-game.png`;
|
const faviconImageLocation = 'images/bored-game.png';
|
||||||
|
const image = `${new URL(url.origin).href}${faviconImageLocation}`;
|
||||||
const header = url.searchParams.get('header') ?? undefined;
|
const header = url.searchParams.get('header') ?? undefined;
|
||||||
const page = url.searchParams.get('page') ?? undefined;
|
const page = url.searchParams.get('page') ?? undefined;
|
||||||
const content = url.searchParams.get('content') ?? '';
|
const content = url.searchParams.get('content') ?? '';
|
||||||
const result = SocialImageCard.render({
|
|
||||||
|
return componentToPng(SocialImageCard, {
|
||||||
header,
|
header,
|
||||||
page,
|
page,
|
||||||
content,
|
content,
|
||||||
image: ogImage,
|
image,
|
||||||
width,
|
width: `${width}`,
|
||||||
height,
|
height: `${height}`,
|
||||||
url: new URL(url.origin).href
|
url: new URL(url.origin).href
|
||||||
});
|
}, height, width);
|
||||||
console.log('result', result);
|
|
||||||
const element = toReactNode(`${result.html}<style>${result.css.code}</style>`);
|
|
||||||
const svg = await satori(element, {
|
|
||||||
fonts: [
|
|
||||||
{
|
|
||||||
name: 'Noto Sans',
|
|
||||||
data: Buffer.from(NotoSans),
|
|
||||||
style: 'normal'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
height,
|
|
||||||
width
|
|
||||||
});
|
|
||||||
|
|
||||||
const resvg = new Resvg(svg, {
|
|
||||||
fitTo: {
|
|
||||||
mode: 'width',
|
|
||||||
value: width
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const image = resvg.render();
|
|
||||||
|
|
||||||
return new Response(image.asPng(), {
|
|
||||||
headers: {
|
|
||||||
'content-type': 'image/png'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
--toast-error-background: var(--tomatoOrange);
|
--toast-error-background: var(--tomatoOrange);
|
||||||
|
|
||||||
/* Media Queryies - Not yet supported in CSS */
|
/* Media Queryies - Not yet supported in CSS */
|
||||||
/*
|
/*
|
||||||
--xsmall: 340px;
|
--xsmall: 340px;
|
||||||
--small: 500px;
|
--small: 500px;
|
||||||
--large: 960px;
|
--large: 960px;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { sentrySvelteKit } from "@sentry/sveltekit";
|
import { sentrySvelteKit } from "@sentry/sveltekit";
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
// import fs from 'fs';
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
@ -19,6 +18,9 @@ export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||||
},
|
},
|
||||||
|
define: {
|
||||||
|
SUPERFORMS_LEGACY: true
|
||||||
|
},
|
||||||
css: {
|
css: {
|
||||||
devSourcemap: true,
|
devSourcemap: true,
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
|
|
@ -43,15 +45,3 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// function rawFonts(ext) {
|
|
||||||
// return {
|
|
||||||
// name: 'vite-plugin-raw-fonts',
|
|
||||||
// transform(code, id) {
|
|
||||||
// if (ext.some((e) => id.endsWith(e))) {
|
|
||||||
// const buffer = fs.readFileSync(id);
|
|
||||||
// return { code: `export default ${JSON.stringify(buffer)}`, map: null };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
Loading…
Reference in a new issue