diff --git a/apps/web/src/lib/components/marketplace/DeckListGrid.svelte b/apps/web/src/lib/components/marketplace/DeckListGrid.svelte index 918c1e0..9cbf0d0 100644 --- a/apps/web/src/lib/components/marketplace/DeckListGrid.svelte +++ b/apps/web/src/lib/components/marketplace/DeckListGrid.svelte @@ -37,7 +37,7 @@ .deck-row { list-style: none; margin: 0; - padding-block: 0 1rem; + padding-block: 1.25rem 2.5rem; display: flex; flex-direction: row; gap: 1rem; diff --git a/apps/web/src/routes/decks/+page.svelte b/apps/web/src/routes/decks/+page.svelte index 5a765b5..c2e5ba7 100644 --- a/apps/web/src/routes/decks/+page.svelte +++ b/apps/web/src/routes/decks/+page.svelte @@ -5,10 +5,19 @@ import { listDecks } from '$lib/api/decks.ts'; import { listCards } from '$lib/api/cards.ts'; import { listDueReviews } from '$lib/api/reviews.ts'; + import { + getMySubscriptions, + getMarketplaceDeck, + type DeckListEntry, + } from '$lib/api/marketplace.ts'; import { devUser } from '$lib/auth/dev-stub.svelte.ts'; import InboxBanner from '$lib/components/InboxBanner.svelte'; - import DeckGrid from '$lib/components/DeckGrid.svelte'; + import DeckStack from '$lib/components/DeckStack.svelte'; + import NewDeckCard from '$lib/components/NewDeckCard.svelte'; + import DeckListGrid from '$lib/components/marketplace/DeckListGrid.svelte'; + import SkeletonGrid from '$lib/components/marketplace/SkeletonGrid.svelte'; import { t } from '$lib/i18n/index.svelte.ts'; + import { Books, Star } from '@mana/shared-icons'; interface DeckWithCounts { deck: Deck; @@ -17,8 +26,9 @@ } let decks = $state([]); - let loading = $state(true); - let error = $state(null); + let subscriptions = $state([]); + let loadingOwn = $state(true); + let loadingSubs = $state(true); let selectedId = $state(null); onMount(async () => { @@ -26,12 +36,12 @@ goto('/'); return; } - await refresh(); + void loadOwnDecks(); + void loadSubscriptions(); }); - async function refresh() { + async function loadOwnDecks() { try { - loading = true; const r = await listDecks(); const enriched = await Promise.all( r.decks.map(async (deck) => { @@ -47,54 +57,215 @@ }), ); decks = enriched; - error = null; - } catch (e) { - error = (e as Error).message; } finally { - loading = false; + loadingOwn = false; + } + } + + async function loadSubscriptions() { + try { + const { subscriptions: subs } = await getMySubscriptions(); + const entries = await Promise.all( + subs.map(async (sub) => { + try { + const { deck, latest_version, owner } = await getMarketplaceDeck(sub.deck_slug); + const entry: DeckListEntry = { + slug: deck.slug, + title: deck.title, + description: deck.description, + language: deck.language, + category: deck.category, + license: deck.license, + price_credits: deck.price_credits, + card_count: latest_version?.card_count ?? 0, + star_count: 0, + subscriber_count: 0, + is_featured: deck.is_featured, + created_at: deck.created_at, + owner: owner + ? { + slug: owner.slug, + display_name: owner.display_name, + verified_mana: owner.verified_mana, + verified_community: owner.verified_community, + } + : { + slug: '', + display_name: '', + verified_mana: false as boolean, + verified_community: false as boolean, + }, + }; + return entry; + } catch { + return null; + } + }), + ); + subscriptions = entries.filter((e): e is DeckListEntry => e !== null); + } finally { + loadingSubs = false; } } function handleSelect(deckId: string) { selectedId = deckId; - setTimeout(() => { - goto(`/study/${deckId}`); - }, 220); + setTimeout(() => goto(`/study/${deckId}`), 220); } -

{t('decks.title')}

+ + Meine Decks · Cardecky + -
- +
+
+

{t('decks.title')}

+
+ +
+
+ + +
+

+ + Eigene Decks +

+ + {#if loadingOwn} + + {:else} +
    +
  • + +
  • + {#each decks as { deck, cardCount, dueCount } (deck.id)} +
  • + { + e.preventDefault(); + handleSelect(deck.id); + }} + /> +
  • + {/each} +
+ {/if} +
+ + + {#if loadingSubs || subscriptions.length > 0} +
+

+ + Abonniert +

+ + {#if loadingSubs} + + {:else} + + {/if} +
+ {/if}
-{#if loading} -

{t('decks.loading')}

-{:else if error} -

{t('decks.error', { msg: error })}

-{:else} - -{/if} -