mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Refactor component names, add api for random games, and use on main page.
This commit is contained in:
parent
daa9a628d1
commit
994d1d462c
23 changed files with 197 additions and 262 deletions
|
|
@ -27,7 +27,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@melt-ui/pp": "^0.1.4",
|
"@melt-ui/pp": "^0.1.4",
|
||||||
"@melt-ui/svelte": "^0.66.3",
|
"@melt-ui/svelte": "^0.66.4",
|
||||||
"@playwright/test": "^1.40.1",
|
"@playwright/test": "^1.40.1",
|
||||||
"@resvg/resvg-js": "^2.4.1",
|
"@resvg/resvg-js": "^2.4.1",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,10 @@ dependencies:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@melt-ui/pp':
|
'@melt-ui/pp':
|
||||||
specifier: ^0.1.4
|
specifier: ^0.1.4
|
||||||
version: 0.1.4(@melt-ui/svelte@0.66.3)(svelte@4.2.8)
|
version: 0.1.4(@melt-ui/svelte@0.66.4)(svelte@4.2.8)
|
||||||
'@melt-ui/svelte':
|
'@melt-ui/svelte':
|
||||||
specifier: ^0.66.3
|
specifier: ^0.66.4
|
||||||
version: 0.66.3(svelte@4.2.8)
|
version: 0.66.4(svelte@4.2.8)
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.40.1
|
specifier: ^1.40.1
|
||||||
version: 1.40.1
|
version: 1.40.1
|
||||||
|
|
@ -1035,14 +1035,14 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@melt-ui/pp@0.1.4(@melt-ui/svelte@0.66.3)(svelte@4.2.8):
|
/@melt-ui/pp@0.1.4(@melt-ui/svelte@0.66.4)(svelte@4.2.8):
|
||||||
resolution: {integrity: sha512-zR+Kl3CZJPJBHW8V7YcdQCMI/dVcnW9Ct3yGbVaIywYVStVRS7F9uEDOea3xLLT2WTGodQePzPlUn53yKFu87g==}
|
resolution: {integrity: sha512-zR+Kl3CZJPJBHW8V7YcdQCMI/dVcnW9Ct3yGbVaIywYVStVRS7F9uEDOea3xLLT2WTGodQePzPlUn53yKFu87g==}
|
||||||
engines: {pnpm: '>=8.6.3'}
|
engines: {pnpm: '>=8.6.3'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@melt-ui/svelte': '>= 0.29.0'
|
'@melt-ui/svelte': '>= 0.29.0'
|
||||||
svelte: ^3.55.0 || ^4.0.0 || ^5.0.0-next.1
|
svelte: ^3.55.0 || ^4.0.0 || ^5.0.0-next.1
|
||||||
dependencies:
|
dependencies:
|
||||||
'@melt-ui/svelte': 0.66.3(svelte@4.2.8)
|
'@melt-ui/svelte': 0.66.4(svelte@4.2.8)
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
svelte: 4.2.8
|
svelte: 4.2.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -1061,8 +1061,8 @@ packages:
|
||||||
svelte: 4.2.8
|
svelte: 4.2.8
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@melt-ui/svelte@0.66.3(svelte@4.2.8):
|
/@melt-ui/svelte@0.66.4(svelte@4.2.8):
|
||||||
resolution: {integrity: sha512-inwvI+YjvMWykK8PEYIg9sAx0sQHI29XeX9hfrdtP47mFVa61pQLqrLoADBpTSb5gO9ZzuL01agXGFfzjK1iPw==}
|
resolution: {integrity: sha512-RYzgje5/0WQiN8YYAoeuHP7Ua/Ew2oPqBVOkMqlqRquARyiD1gP1RsfXWH637i06q+T5S+UuIiVkc4b/pbcZ4g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: '>=3 <5'
|
svelte: '>=3 <5'
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import { lucia } from '$lib/server/auth';
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://742e43279df93a3c4a4a78c12eb1f879@o4506057768632320.ingest.sentry.io/4506057770401792',
|
dsn: 'https://742e43279df93a3c4a4a78c12eb1f879@o4506057768632320.ingest.sentry.io/4506057770401792',
|
||||||
tracesSampleRate: 1,
|
tracesSampleRate: 1,
|
||||||
environment: dev ? 'development' : 'production'
|
environment: dev ? 'development' : 'production',
|
||||||
|
enabled: !dev
|
||||||
});
|
});
|
||||||
|
|
||||||
export const authentication: Handle = async function ({ event, resolve }) {
|
export const authentication: Handle = async function ({ event, resolve }) {
|
||||||
|
|
|
||||||
64
src/lib/components/Game.svelte
Normal file
64
src/lib/components/Game.svelte
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { GameType, SavedGameType } from '$lib/types';
|
||||||
|
import * as Card from "$lib/components/ui/card";
|
||||||
|
import type { CollectionItem } from '@prisma/client';
|
||||||
|
|
||||||
|
export let game: GameType | CollectionItem;
|
||||||
|
export let detailed: boolean = false;
|
||||||
|
export let variant: 'default' | 'compact' = 'default';
|
||||||
|
|
||||||
|
// Naive and assumes description is only on our GameType at the moment
|
||||||
|
function isGameType(game: GameType | SavedGameType): game is GameType {
|
||||||
|
return (game as GameType).description !== undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<article class="grid grid-template-cols-2 gap-4">
|
||||||
|
<Card.Root class={variant === 'compact' ? 'game-card-compact' : ''}>
|
||||||
|
<Card.Header>
|
||||||
|
<Card.Title class="game-card-header">
|
||||||
|
<span style:--transition-name="game-name-{game.slug}">
|
||||||
|
{game.name}
|
||||||
|
{#if game?.year_published}
|
||||||
|
({game?.year_published})
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</Card.Title>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content class={variant === 'compact' ? 'pt-6' : ''}>
|
||||||
|
<a
|
||||||
|
class="thumbnail"
|
||||||
|
href={`/game/${game.id}`}
|
||||||
|
title={`View ${game.name}`}
|
||||||
|
data-sveltekit-preload-data
|
||||||
|
>
|
||||||
|
<img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
|
||||||
|
<div class="game-details">
|
||||||
|
{#if game?.players}
|
||||||
|
<p>Players: {game.players}</p>
|
||||||
|
<p>Time: {game.playtime} minutes</p>
|
||||||
|
{#if isGameType(game) && game?.min_age}
|
||||||
|
<p>Min Age: {game.min_age}</p>
|
||||||
|
{/if}
|
||||||
|
{#if detailed && isGameType(game) && game?.description}
|
||||||
|
<div class="description">{@html game.description}</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
:global(.game-card-compact) {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
.game-card-header {
|
||||||
|
span {
|
||||||
|
view-transition-name: var(--transition-name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import type { GameType, SavedGameType } from '$lib/types';
|
|
||||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '$components/ui/card';
|
|
||||||
import { Button } from '$components/ui/button';
|
|
||||||
import type { CollectionItem } from '@prisma/client';
|
|
||||||
|
|
||||||
// export let data: SuperValidated<ListGameSchema>;
|
|
||||||
export let game: GameType | CollectionItem;
|
|
||||||
export let detailed: boolean = false;
|
|
||||||
|
|
||||||
// Naive and assumes description is only on our GameType at the moment
|
|
||||||
function isGameType(game: GameType | SavedGameType): game is GameType {
|
|
||||||
return (game as GameType).description !== undefined;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<article class="grid grid-template-cols-2 gap-4" transition:fade|global>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{game.name}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<a
|
|
||||||
class="thumbnail"
|
|
||||||
href={`/game/${game.id}`}
|
|
||||||
title={`View ${game.name}`}
|
|
||||||
data-sveltekit-preload-data
|
|
||||||
>
|
|
||||||
<img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
|
|
||||||
<div class="game-details">
|
|
||||||
{#if game?.players}
|
|
||||||
<p>Players: {game.players}</p>
|
|
||||||
<p>Time: {game.playtime} minutes</p>
|
|
||||||
{#if isGameType(game) && game?.min_age}
|
|
||||||
<p>Min Age: {game.min_age}</p>
|
|
||||||
{/if}
|
|
||||||
{#if detailed && isGameType(game) && game?.description}
|
|
||||||
<div class="description">{@html game.description}</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<!-- <article class="game-container" transition:fade|global>
|
|
||||||
<h2>{game.name}</h2>
|
|
||||||
<a
|
|
||||||
class="thumbnail"
|
|
||||||
href={`/game/${game.id}`}
|
|
||||||
title={`View ${game.name}`}
|
|
||||||
data-sveltekit-preload-data
|
|
||||||
>
|
|
||||||
<!-- <Image src={game.thumb_url} alt={`Image of ${game.name}`} /> -->
|
|
||||||
<!-- <img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" /> -->
|
|
||||||
<!-- loading="lazy" decoding="async" -->
|
|
||||||
<!-- </a> -->
|
|
||||||
|
|
||||||
<!-- <div class="game-details">
|
|
||||||
{#if game?.players}
|
|
||||||
<p>Players: {game.players}</p>
|
|
||||||
<p>Time: {game.playtime} minutes</p>
|
|
||||||
{#if isGameType(game) && game?.min_age}
|
|
||||||
<p>Min Age: {game.min_age}</p>
|
|
||||||
{/if}
|
|
||||||
{#if detailed && isGameType(game) && game?.description}
|
|
||||||
<div class="description">{@html game.description}</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="game-buttons">
|
|
||||||
<Button size="md" kind={existsInCollection ? 'danger' : 'primary'} icon on:click={onCollectionClick}>
|
|
||||||
{collectionText}
|
|
||||||
{#if existsInCollection}
|
|
||||||
<iconify-icon icon={minusCircle} width="24" height="24" />
|
|
||||||
{:else}
|
|
||||||
<iconify-icon icon={plusCircle} width="24" height="24" />
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
<Button size="md" kind={existsInWishlist ? 'danger' : 'primary'} icon on:click={onWishlistClick}>
|
|
||||||
{wishlistText}
|
|
||||||
{#if existsInWishlist}
|
|
||||||
<iconify-icon icon={minusCircle} width="24" height="24" />
|
|
||||||
{:else}
|
|
||||||
<iconify-icon icon={plusCircle} width="24" height="24" />
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</div> -->
|
|
||||||
<!-- </article> -->
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.game-container {
|
|
||||||
display: grid;
|
|
||||||
/* grid-template-rows: repeat(auto-fill, 1fr); */
|
|
||||||
grid-template-rows: 0.15fr minmax(250px, 1fr) 0.2fr 0.2fr;
|
|
||||||
place-items: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
@media (max-width: 650px) {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
gap: var(--spacing-16);
|
|
||||||
padding: var(--spacing-16) var(--spacing-16);
|
|
||||||
transition: all 0.3s;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: var(--primary);
|
|
||||||
/* &:hover {
|
|
||||||
background-color: hsla(222, 9%, 65%, 1);
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* .game-info {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin: 0.2rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.game-details {
|
|
||||||
p,
|
|
||||||
a {
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-buttons {
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from "$lib/utils";
|
|
||||||
|
|
||||||
let className: string | undefined | null = undefined;
|
|
||||||
export { className as class };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class={cn(
|
|
||||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...$$restProps}
|
|
||||||
on:click
|
|
||||||
on:focusin
|
|
||||||
on:focusout
|
|
||||||
on:mouseenter
|
|
||||||
on:mouseleave
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from "$lib/utils";
|
|
||||||
|
|
||||||
let className: string | undefined | null = undefined;
|
|
||||||
export { className as class };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={cn("p-6 pt-0", className)} {...$$restProps}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from "$lib/utils";
|
|
||||||
|
|
||||||
let className: string | undefined | null = undefined;
|
|
||||||
export { className as class };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<p class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
|
|
||||||
<slot />
|
|
||||||
</p>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from "$lib/utils";
|
|
||||||
|
|
||||||
let className: string | undefined | null = undefined;
|
|
||||||
export { className as class };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={cn("flex items-center p-6 pt-0", className)} {...$$restProps}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from "$lib/utils";
|
|
||||||
|
|
||||||
let className: string | undefined | null = undefined;
|
|
||||||
export { className as class };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={cn("flex flex-col space-y-1.5 p-6", className)} {...$$restProps}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from "$lib/utils";
|
|
||||||
|
|
||||||
let className: string | undefined | null = undefined;
|
|
||||||
export { className as class };
|
|
||||||
|
|
||||||
export let tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" = "h3";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:element
|
|
||||||
this={tag}
|
|
||||||
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
|
||||||
{...$$restProps}
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</svelte:element>
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import { tick, onDestroy } from 'svelte';
|
// import { tick, onDestroy } from 'svelte';
|
||||||
import Game from '$lib/components/game/index.svelte';
|
import Game from '$components/Game.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
console.log(`Page data: ${JSON.stringify(data)}`);
|
console.log(`Page data: ${JSON.stringify(data)}`);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Game from '$lib/components/game/index.svelte';
|
import Game from '$components/Game.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
console.log('data', data);
|
console.log('data', data);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Game from '$lib/components/game/index.svelte';
|
import Game from '$components/Game.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
console.log('data', data);
|
console.log('data', data);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import 'iconify-icon';
|
import 'iconify-icon';
|
||||||
import Header from '$components/Header.svelte';
|
import Header from '$components/Header.svelte';
|
||||||
import Footer from '$lib/components/footer.svelte';
|
import Footer from '$components/Footer.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,8 @@ export const load: PageServerLoad = async ({ fetch, url }) => {
|
||||||
const form = await superValidate(formData, search_schema);
|
const form = await superValidate(formData, search_schema);
|
||||||
console.log('form', form);
|
console.log('form', form);
|
||||||
|
|
||||||
const count = 5;
|
const randomGames: Game[] = await fetch('/api/game/random?limit=6').then(res => res.json());
|
||||||
const ids: { id: string }[] = await prisma.$queryRaw`SELECT id FROM games ORDER BY RAND() LIMIT ${count}`;
|
console.log('randomGames', randomGames);
|
||||||
const randomGames: Game[] = await prisma.game.findMany({
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
in: ids.map(id => id.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { form, metaTagsChild: metaTags, randomGames };
|
return { form, metaTagsChild: metaTags, randomGames };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import TextSearch from '$lib/components/search/textSearch/header.svelte';
|
// import TextSearch from '$lib/components/search/textSearch/header.svelte';
|
||||||
import RandomSearch from '$lib/components/search/random/index.svelte';
|
import RandomSearch from '$lib/components/search/random/index.svelte';
|
||||||
import Game from '$lib/components/game/index.svelte';
|
import Game from '$components/Game.svelte';
|
||||||
import logo from '$lib/assets/bored-game.png';
|
import logo from '$lib/assets/bored-game.png';
|
||||||
|
|
||||||
// import Random from '$lib/components/random/header.svelte';
|
// import Random from '$lib/components/random/header.svelte';
|
||||||
|
|
@ -26,13 +26,13 @@
|
||||||
<!-- <TextSearch showButton advancedSearch data={data.form} /> -->
|
<!-- <TextSearch showButton advancedSearch data={data.form} /> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section>
|
<section class="random-games">
|
||||||
{#each randomGames as game (game.id)}
|
{#each randomGames as game (game.id)}
|
||||||
<Game {game} />
|
<Game variant='compact' {game} />
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="postcss">
|
||||||
.game-search {
|
.game-search {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
|
|
@ -43,6 +43,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.random-games {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.random-buttons {
|
.random-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-content: space-between;
|
place-content: space-between;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
<title>{game?.name} | Bored Game</title>
|
<title>{game?.name} | Bored Game</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h2>{game?.name}
|
<h2 style:--transition-name="game-name-{game.slug}">{game?.name}
|
||||||
{#if game?.year_published}
|
{#if game?.year_published}
|
||||||
({game?.year_published})
|
({game?.year_published})
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
import type { SuperValidated } from 'sveltekit-superforms';
|
import type { SuperValidated } from 'sveltekit-superforms';
|
||||||
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, melt } from '@melt-ui/svelte';
|
import { createPagination, createToolbar, melt } from '@melt-ui/svelte';
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
|
import { ChevronLeft, ChevronRight, LayoutList, LayoutGrid } from 'lucide-svelte';
|
||||||
import type { SearchSchema } from '$lib/zodValidation';
|
import type { SearchSchema } from '$lib/zodValidation';
|
||||||
import Game from '$lib/components/game/index.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';
|
||||||
|
|
@ -23,7 +23,15 @@
|
||||||
$: showPagination = totalCount > pageSize;
|
$: showPagination = totalCount > pageSize;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
elements: { root, pageTrigger, prevButton, nextButton },
|
elements: { root: toolbarRoot },
|
||||||
|
builders: { createToolbarGroup },
|
||||||
|
} = createToolbar();
|
||||||
|
const {
|
||||||
|
elements: { group: listStyleGroup, item: listStyleItem },
|
||||||
|
} = createToolbarGroup();
|
||||||
|
|
||||||
|
const {
|
||||||
|
elements: { root: paginationRoot, pageTrigger, prevButton, nextButton },
|
||||||
states: { pages, range }
|
states: { pages, range }
|
||||||
} = createPagination({
|
} = createPagination({
|
||||||
count: totalCount,
|
count: totalCount,
|
||||||
|
|
@ -31,6 +39,11 @@
|
||||||
defaultPage: 1,
|
defaultPage: 1,
|
||||||
siblingCount: 1
|
siblingCount: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gameListStyle: 'grid' | 'list' = 'grid';
|
||||||
|
function handleListStyle(event) {
|
||||||
|
console.log(event, typeof event);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="game-search">
|
<div class="game-search">
|
||||||
|
|
@ -64,7 +77,19 @@
|
||||||
</search>
|
</search>
|
||||||
|
|
||||||
<section class="games">
|
<section class="games">
|
||||||
<h1>Games Found:</h1>
|
<div>
|
||||||
|
<h1>Games Found:</h1>
|
||||||
|
<div use:melt={$toolbarRoot}>
|
||||||
|
<div use:melt={$listStyleGroup} class="search-toolbar">
|
||||||
|
<button class="style-item" aria-label='list' use:melt={$listStyleItem('list')} on:m-click|preventDefault={(e) => handleListStyle(e)}>
|
||||||
|
<LayoutList class="square-5" />
|
||||||
|
</button>
|
||||||
|
<button class="style-item" aria-label='grid' use:melt={$listStyleItem('grid')} on:m-click|preventDefault={(e) => handleListStyle(e)}>
|
||||||
|
<LayoutGrid class="square-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="games-list">
|
<div class="games-list">
|
||||||
{#if totalCount > 0}
|
{#if totalCount > 0}
|
||||||
{#each games as game (game.id)}
|
{#each games as game (game.id)}
|
||||||
|
|
@ -75,7 +100,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if showPagination}
|
{#if showPagination}
|
||||||
<nav use:melt={$root}>
|
<nav use:melt={$paginationRoot}>
|
||||||
<p class="text-center">Showing items {$range.start} - {$range.end}</p>
|
<p class="text-center">Showing items {$range.start} - {$range.end}</p>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button use:melt={$prevButton}><ChevronLeft /></button>
|
<button use:melt={$prevButton}><ChevronLeft /></button>
|
||||||
|
|
@ -156,6 +181,11 @@
|
||||||
.games-list {
|
.games-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
--listColumns: 4;
|
--listColumns: 4;
|
||||||
|
|
||||||
|
.list {
|
||||||
|
--listColumns: 1;
|
||||||
|
}
|
||||||
|
|
||||||
grid-template-columns: repeat(var(--listColumns), minmax(250px, 1fr));
|
grid-template-columns: repeat(var(--listColumns), minmax(250px, 1fr));
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
|
|
||||||
|
|
@ -175,4 +205,24 @@
|
||||||
--listColumns: 1;
|
--listColumns: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-toolbar {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-item {
|
||||||
|
padding: 0.1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='on'] {
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import 'iconify-icon';
|
import 'iconify-icon';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onNavigate } from "$app/navigation";
|
import { onNavigate } from "$app/navigation";
|
||||||
import Analytics from '$lib/components/analytics.svelte';
|
import Analytics from '$components/Analytics.svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
import Loading from '$lib/components/Loading.svelte';
|
||||||
import { theme } from '$state/theme';
|
import { theme } from '$state/theme';
|
||||||
import PageLoadingIndicator from '$lib/page_loading_indicator.svelte';
|
import PageLoadingIndicator from '$lib/page_loading_indicator.svelte';
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
...$page.data.metaTagsChild
|
...$page.data.metaTagsChild
|
||||||
}
|
}
|
||||||
|
|
||||||
const flash = getFlash(page, {
|
// const flash = getFlash(page, {
|
||||||
clearAfterMs: 6000
|
// clearAfterMs: 6000
|
||||||
});
|
// });
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// set the theme to the user's active theme
|
// set the theme to the user's active theme
|
||||||
|
|
@ -39,21 +39,21 @@
|
||||||
document.querySelector('html')?.setAttribute('data-theme', $theme);
|
document.querySelector('html')?.setAttribute('data-theme', $theme);
|
||||||
});
|
});
|
||||||
|
|
||||||
flash.subscribe(($flash) => {
|
// flash.subscribe(($flash) => {
|
||||||
if (!$flash) return;
|
// if (!$flash) return;
|
||||||
|
|
||||||
if ($flash.type === 'success') {
|
// if ($flash.type === 'success') {
|
||||||
toast.success($flash.message);
|
// toast.success($flash.message);
|
||||||
} else {
|
// } else {
|
||||||
toast.error($flash.message, {
|
// toast.error($flash.message, {
|
||||||
duration: 5000
|
// duration: 5000
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Clearing the flash message could sometimes
|
// // Clearing the flash message could sometimes
|
||||||
// be required here to avoid double-toasting.
|
// // be required here to avoid double-toasting.
|
||||||
flash.set(undefined);
|
// flash.set(undefined);
|
||||||
});
|
// });
|
||||||
|
|
||||||
onNavigate(async (navigation) => {
|
onNavigate(async (navigation) => {
|
||||||
if (!document.startViewTransition) return;
|
if (!document.startViewTransition) return;
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Loading />
|
<!-- <Loading /> -->
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.layout {
|
.layout {
|
||||||
|
|
|
||||||
31
src/routes/api/game/random/+server.ts
Normal file
31
src/routes/api/game/random/+server.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import prisma from '$lib/prisma.js';
|
||||||
|
import type { Game } from '@prisma/client';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const GET = async ({ url, locals }) => {
|
||||||
|
const searchParams = Object.fromEntries(url.searchParams);
|
||||||
|
const limit = parseInt(searchParams?.limit) || 1;
|
||||||
|
|
||||||
|
if (limit <= 0 || limit > 6) {
|
||||||
|
error(400, { message: 'Limit must be between 1 and 6' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalGames = await prisma.game.count();
|
||||||
|
const randomIndex = Math.floor(Math.random() * totalGames);
|
||||||
|
const ids: { id: string }[] = await prisma.$queryRaw`
|
||||||
|
SELECT id
|
||||||
|
FROM games
|
||||||
|
ORDER BY id
|
||||||
|
LIMIT ${limit}
|
||||||
|
OFFSET ${randomIndex}
|
||||||
|
`;
|
||||||
|
const randomGames: Game[] = await prisma.game.findMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: ids.map(id => id.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return json(randomGames);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue