From 27ac5fc23eb891564fee5b140e5287fb0839eaba Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 16 Apr 2026 12:26:27 +0200 Subject: [PATCH] feat(credits): merge Credits + Mana subscription into one workbench app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The former /credits (balance, transactions, Stripe checkout, cost breakdown) and /mana (subscription plans placeholder) were separate pages covering the same billing domain. Merge into a single workbench app "Credits & Abo" with 5 tabs: 1. Übersicht — balance cards + recent transactions + quick-buy 2. Abonnements — SubscriptionPage from @mana/subscriptions 3. Transaktionen — full transaction history table 4. Kaufen — Stripe-integrated package cards 5. Kosten — per-operation cost breakdown with category filter Stripe redirect handling: Stripe returns to /?app=credits&success=true. The deep-link handler opens the app and strips ?app; the ListView reads ?success / ?canceled from window.location.search on mount, shows the appropriate toast, and cleans the URL via history.replaceState. - Delete /credits/+page.svelte and lib/modules/mana/ (placeholder) - Register workbench app id='credits' (Crown icon, amber) - Replace mana app registration (no longer needed) - Update all links: command menu, PillNavigation (manaHref + creditsHref both → /?app=credits), CreditsSection, CreditsWidget, TransactionsWidget, CompleteStep, sync-status, sync billing page, gift redeem page Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/app-registry/apps.ts | 6 +- .../dashboard/widgets/CreditsWidget.svelte | 2 +- .../widgets/TransactionsWidget.svelte | 2 +- .../layout/use-sync-status-items.svelte.ts | 2 +- .../onboarding/steps/CompleteStep.svelte | 2 +- .../settings/sections/CreditsSection.svelte | 6 +- .../src/lib/modules/credits/ListView.svelte | 1007 +++++++++++++++++ .../web/src/lib/modules/mana/ListView.svelte | 50 - .../apps/web/src/routes/(app)/+layout.svelte | 16 +- .../web/src/routes/(app)/credits/+page.svelte | 582 ---------- .../(app)/gifts/redeem/[code]/+page.svelte | 2 +- .../routes/(app)/settings/sync/+page.svelte | 4 +- 12 files changed, 1033 insertions(+), 648 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/credits/ListView.svelte delete mode 100644 apps/mana/apps/web/src/lib/modules/mana/ListView.svelte delete mode 100644 apps/mana/apps/web/src/routes/(app)/credits/+page.svelte diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index c310d934d..abb981a5b 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -1089,12 +1089,12 @@ registerApp({ }); registerApp({ - id: 'mana', - name: 'Mana', + id: 'credits', + name: 'Credits & Abo', color: '#F59E0B', icon: Crown, views: { - list: { load: () => import('$lib/modules/mana/ListView.svelte') }, + list: { load: () => import('$lib/modules/credits/ListView.svelte') }, }, }); diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte index c26558c04..e7ede9ceb 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte +++ b/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte @@ -56,7 +56,7 @@ {formatCredits(data.balance)} {$_('dashboard.widgets.credits.manage')} diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte index 29e3a4f6e..8f6b9418f 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte +++ b/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte @@ -58,7 +58,7 @@ 📊 {$_('dashboard.widgets.transactions.title')} - + {$_('common.view_all')} → diff --git a/apps/mana/apps/web/src/lib/components/layout/use-sync-status-items.svelte.ts b/apps/mana/apps/web/src/lib/components/layout/use-sync-status-items.svelte.ts index bb51fb176..9726b4281 100644 --- a/apps/mana/apps/web/src/lib/components/layout/use-sync-status-items.svelte.ts +++ b/apps/mana/apps/web/src/lib/components/layout/use-sync-status-items.svelte.ts @@ -39,7 +39,7 @@ export function useSyncStatusItems() { id: 'sync-paused', label: 'Sync pausiert — Credits aufladen', icon: 'bell', - onClick: () => goto('/credits?tab=packages'), + onClick: () => goto('/?app=credits&tab=packages'), }); } else { result.push({ diff --git a/apps/mana/apps/web/src/lib/components/onboarding/steps/CompleteStep.svelte b/apps/mana/apps/web/src/lib/components/onboarding/steps/CompleteStep.svelte index 6cb38a10b..a9e2b2870 100644 --- a/apps/mana/apps/web/src/lib/components/onboarding/steps/CompleteStep.svelte +++ b/apps/mana/apps/web/src/lib/components/onboarding/steps/CompleteStep.svelte @@ -67,7 +67,7 @@
diff --git a/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte b/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte index 2ae591b5b..8b8eec397 100644 --- a/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte +++ b/apps/mana/apps/web/src/lib/components/settings/sections/CreditsSection.svelte @@ -31,7 +31,7 @@ tone="yellow" > {#snippet action()} - Alle Details + Alle Details {/snippet} @@ -60,13 +60,13 @@
Credits kaufen Transaktionen diff --git a/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte b/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte new file mode 100644 index 000000000..fc075be83 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/credits/ListView.svelte @@ -0,0 +1,1007 @@ + + + +
+ {#if loading} +
+
+
+ {:else if error} + +
+

{error}

+ +
+
+ {:else} + +
+
+

Verfügbar

+

{formatCredits(balance?.balance ?? 0)}

+
+
+

Erhalten

+

{formatCredits(balance?.totalEarned ?? 0)}

+
+
+

Verbraucht

+

{formatCredits(balance?.totalSpent ?? 0)}

+
+
+ + +
+ {#each [{ key: 'overview', label: 'Übersicht' }, { key: 'subscriptions', label: 'Abonnements' }, { key: 'transactions', label: 'Transaktionen' }, { key: 'packages', label: 'Kaufen' }, { key: 'costs', label: 'Kosten' }] as tab} + + {/each} +
+ + + {#if activeTab === 'overview'} +
+ +

Letzte Transaktionen

+ {#if transactions.length === 0} +

Noch keine Transaktionen

+ {:else} +
+ {#each transactions.slice(0, 5) as tx} +
+
+ {getTransactionIcon(tx.type)} +
+

{tx.description || tx.type}

+

{formatDate(tx.createdAt)}

+
+
+ + {tx.amount > 0 ? '+' : ''}{formatCredits(tx.amount)} + +
+ {/each} +
+ + {/if} +
+ + +

Credits kaufen

+ {#if packages.length === 0} +

Keine Pakete verfügbar

+ {:else} +
+ {#each packages.slice(0, 3) as pkg} + + {/each} +
+ + {/if} +
+
+ + + {:else if activeTab === 'subscriptions'} + + + + {:else if activeTab === 'transactions'} + +

Transaktionsverlauf

+ {#if transactions.length === 0} +

Noch keine Transaktionen vorhanden.

+ {:else} +
+ + + + + + + + + + + + + {#each transactions as tx} + + + + + + + + + {/each} + +
TypBeschreibungAppBetragKontostandDatum
{getTransactionIcon(tx.type)}{tx.description || '-'}{tx.appId || '-'} + {tx.amount > 0 ? '+' : ''}{formatCredits(tx.amount)} + {formatCredits(tx.balanceAfter)}{formatDate(tx.createdAt)}
+
+ {/if} +
+ + + {:else if activeTab === 'packages'} +
+ {#each packages as pkg} + +
+

{pkg.name}

+ {#if pkg.description} +

{pkg.description}

+ {/if} +

{formatCredits(pkg.credits)}

+

Credits

+

{formatPrice(pkg.priceEuroCents)}

+ +
+
+ {/each} +
+ {#if packages.length === 0} + +

Aktuell sind keine Credit-Pakete verfügbar.

+
+ {/if} + + + {:else if activeTab === 'costs'} +
+ {#each [{ key: 'all', label: 'Alle' }, { key: 'ai', label: 'KI-Features' }, { key: 'premium', label: 'Premium' }] as filter} + + {/each} +
+ +
+

+ Lesen, Bearbeiten, Löschen und Organisieren von Einträgen ist immer kostenlos. Credits werden nur für die unten aufgeführten Aktionen verbraucht. +

+
+ + {@const groups = operationsByApp()} +
+ {#each Object.entries(groups) as [app, operations]} + +

{getAppLabel(app)}

+
+ {#each operations as op} +
+
+

{op.name}

+

{op.description}

+
+
+ {getCategoryLabel(op.category)} + 0 && op.cost < 1} + > + {op.cost === 0 ? 'Kostenlos' : op.formattedCost} + +
+
+ {/each} +
+
+ {/each} + + {#if Object.keys(groups).length === 0} + +

Keine Operationen in dieser Kategorie.

+
+ {/if} +
+ {/if} + {/if} +
+ +{#if toastMessage} +
+ {toastMessage} +
+{/if} + + diff --git a/apps/mana/apps/web/src/lib/modules/mana/ListView.svelte b/apps/mana/apps/web/src/lib/modules/mana/ListView.svelte deleted file mode 100644 index 8f640a06f..000000000 --- a/apps/mana/apps/web/src/lib/modules/mana/ListView.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - -{#if toastMessage} -
- {toastMessage} -
-{/if} - -
- -
- - diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte index 2a7be9da2..7960b03ee 100644 --- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte @@ -740,7 +740,12 @@ category: 'Navigation', onExecute: () => goto('/?app=spiral'), }, - { id: 'credits', label: 'Credits', category: 'Navigation', onExecute: () => goto('/credits') }, + { + id: 'credits', + label: 'Credits', + category: 'Navigation', + onExecute: () => goto('/?app=credits'), + }, { id: 'apps', label: 'Alle Apps', category: 'Navigation', onExecute: () => goto('/apps') }, { id: 'settings', @@ -805,7 +810,10 @@ > Cloud Sync pausiert — Credits reichen nicht aus.
- + Credits aufladen @@ -974,10 +982,10 @@ currentSyncLabel={syncStatus.label} {appItems} {userEmail} - manaHref="/?app=mana" + manaHref="/?app=credits" profileHref="/?app=profile" spiralHref="/?app=spiral" - creditsHref="/credits" + creditsHref="/?app=credits" themesHref="/?app=themes" helpHref="/?app=help" allAppsHref="/apps" diff --git a/apps/mana/apps/web/src/routes/(app)/credits/+page.svelte b/apps/mana/apps/web/src/routes/(app)/credits/+page.svelte deleted file mode 100644 index 7e03f5d54..000000000 --- a/apps/mana/apps/web/src/routes/(app)/credits/+page.svelte +++ /dev/null @@ -1,582 +0,0 @@ - - -
- - - {#if loading} -
-
-
- {:else if error} - -
-

{error}

- -
-
- {:else} - -
- -
-

Verfügbare Credits

-

- {formatCredits(balance?.balance ?? 0)} -

-
-
- -
-

Gesamt erhalten

-

- {formatCredits(balance?.totalEarned ?? 0)} -

-
-
- -
-

Gesamt verbraucht

-

- {formatCredits(balance?.totalSpent ?? 0)} -

-
-
-
- - -
- - - - -
- - - {#if activeTab === 'overview'} -
- - -

Letzte Transaktionen

- {#if transactions.length === 0} -

Noch keine Transaktionen

- {:else} -
- {#each transactions.slice(0, 5) as tx} -
-
- {getTransactionIcon(tx.type)} -
-

{tx.description || tx.type}

-

{formatDate(tx.createdAt)}

-
-
- - {tx.amount > 0 ? '+' : ''}{formatCredits(tx.amount)} - -
- {/each} -
- - {/if} -
- - - -

Credits kaufen

- {#if packages.length === 0} -

Keine Pakete verfügbar

- {:else} -
- {#each packages.slice(0, 3) as pkg} - - {/each} -
- - {/if} -
-
- {:else if activeTab === 'transactions'} - -

Transaktionsverlauf

- {#if transactions.length === 0} -

Noch keine Transaktionen vorhanden.

- {:else} -
- - - - - - - - - - - - - {#each transactions as tx} - - - - - - - - - {/each} - -
TypBeschreibungAppBetragKontostandDatum
- {getTransactionIcon(tx.type)} - {tx.description || '-'}{tx.appId || '-'} - {tx.amount > 0 ? '+' : ''}{formatCredits(tx.amount)} - - {formatCredits(tx.balanceAfter)} - {formatDate(tx.createdAt)}
-
- {/if} -
- {:else if activeTab === 'packages'} -
- {#each packages as pkg} - -
-

{pkg.name}

- {#if pkg.description} -

{pkg.description}

- {/if} -

- {formatCredits(pkg.credits)} -

-

Credits

-

- {formatPrice(pkg.priceEuroCents)} -

- -
-
- {/each} -
- {#if packages.length === 0} - -

- Aktuell sind keine Credit-Pakete verfügbar. -

-
- {/if} - {:else if activeTab === 'costs'} - -
- {#each [{ key: 'all', label: 'Alle' }, { key: 'ai', label: 'KI-Features' }, { key: 'premium', label: 'Premium' }] as filter} - - {/each} -
- - -
-

- Lesen, Bearbeiten, Löschen und Organisieren von Einträgen ist immer kostenlos. Credits werden nur für die unten aufgeführten Aktionen verbraucht. -

-
- - - {@const groups = operationsByApp()} -
- {#each Object.entries(groups) as [app, operations]} - -

{getAppLabel(app)}

-
- {#each operations as op} -
-
-

{op.name}

-

{op.description}

-
-
- {getCategoryLabel(op.category)} - - {op.cost === 0 ? 'Kostenlos' : op.formattedCost} - -
-
- {/each} -
-
- {/each} - - {#if Object.keys(groups).length === 0} - -

- Keine Operationen in dieser Kategorie. -

-
- {/if} -
- {/if} - {/if} -
- - -{#if toastMessage} -
- {toastMessage} -
-{/if} - - diff --git a/apps/mana/apps/web/src/routes/(app)/gifts/redeem/[code]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/gifts/redeem/[code]/+page.svelte index ef40d3441..8b96da549 100644 --- a/apps/mana/apps/web/src/routes/(app)/gifts/redeem/[code]/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/gifts/redeem/[code]/+page.svelte @@ -134,7 +134,7 @@