From 47419b3cac0f0a1e780430c7b34389dd062a15f4 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 8 May 2026 17:54:19 +0200 Subject: [PATCH] Phase 9c: Inbox-Banner auf /decks und /study MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InboxBanner.svelte zeigt einen klickbaren Hinweis, wenn der User ein Inbox-Deck hat und es Karten enthält. Linkt aufs Inbox-Deck, wo die Karten in andere Decks umsortiert werden können. API-Pfad bleibt schmal: kein neuer Endpunkt — die Komponente nutzt listDecks() + listCards(inbox.id) und filtert clientseitig auf name === "Inbox" (der stabile API-Konstantenname). Wenn das später Hot-Path wird, ist GET /api/v1/inbox/stats ein additiver Fix. svelte-check 356 files 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/lib/api/inbox.ts | 23 ++++++++++++++ .../web/src/lib/components/InboxBanner.svelte | 30 +++++++++++++++++++ apps/web/src/routes/decks/+page.svelte | 5 ++++ apps/web/src/routes/study/+page.svelte | 5 ++++ 4 files changed, 63 insertions(+) create mode 100644 apps/web/src/lib/api/inbox.ts create mode 100644 apps/web/src/lib/components/InboxBanner.svelte diff --git a/apps/web/src/lib/api/inbox.ts b/apps/web/src/lib/api/inbox.ts new file mode 100644 index 0000000..f075309 --- /dev/null +++ b/apps/web/src/lib/api/inbox.ts @@ -0,0 +1,23 @@ +import type { Deck } from '@cards/domain'; +import { listDecks } from './decks.ts'; +import { listCards } from './cards.ts'; + +const INBOX_NAME = 'Inbox'; + +export interface InboxStats { + deck: Deck | null; + cardCount: number; +} + +/** + * Lädt das Inbox-Deck (Name = "Inbox", auto-erzeugt vom API beim + * ersten Share-Receive) und zählt die Karten darin. Liefert ein + * neutrales Result, wenn der User noch keinen Share empfangen hat. + */ +export async function loadInboxStats(): Promise { + const decks = await listDecks(); + const inbox = decks.decks.find((d) => d.name === INBOX_NAME) ?? null; + if (!inbox) return { deck: null, cardCount: 0 }; + const cards = await listCards(inbox.id); + return { deck: inbox, cardCount: cards.total }; +} diff --git a/apps/web/src/lib/components/InboxBanner.svelte b/apps/web/src/lib/components/InboxBanner.svelte new file mode 100644 index 0000000..df5a0dc --- /dev/null +++ b/apps/web/src/lib/components/InboxBanner.svelte @@ -0,0 +1,30 @@ + + +{#if stats && stats.deck && stats.cardCount > 0} + + 📥 Inbox + · + + {stats.cardCount} eingegangene + {stats.cardCount === 1 ? 'Karte' : 'Karten'} aus anderen Apps + + — sortieren → + +{/if} diff --git a/apps/web/src/routes/decks/+page.svelte b/apps/web/src/routes/decks/+page.svelte index 35004a5..5837ce9 100644 --- a/apps/web/src/routes/decks/+page.svelte +++ b/apps/web/src/routes/decks/+page.svelte @@ -5,6 +5,7 @@ import { listDecks, deleteDeck } from '$lib/api/decks.ts'; import { devUser } from '$lib/auth/dev-stub.svelte.ts'; import { toasts } from '$lib/stores/toasts.svelte.ts'; + import InboxBanner from '$lib/components/InboxBanner.svelte'; let decks = $state([]); let loading = $state(true); @@ -54,6 +55,10 @@ > +
+ +
+ {#if loading}

Lade…

{:else if error} diff --git a/apps/web/src/routes/study/+page.svelte b/apps/web/src/routes/study/+page.svelte index eec479f..d062be2 100644 --- a/apps/web/src/routes/study/+page.svelte +++ b/apps/web/src/routes/study/+page.svelte @@ -5,6 +5,7 @@ import { listDecks } from '$lib/api/decks.ts'; import { listDueReviews } from '$lib/api/reviews.ts'; import { devUser } from '$lib/auth/dev-stub.svelte.ts'; + import InboxBanner from '$lib/components/InboxBanner.svelte'; type Item = { deck: Deck; due: number }; @@ -37,6 +38,10 @@

Lernen

+
+ +
+ {#if loading}

Lade…

{:else}