diff --git a/MANACORE-TODOS.md b/MANACORE-TODOS.md
index f06cb9c3a..42d6d0bbe 100644
--- a/MANACORE-TODOS.md
+++ b/MANACORE-TODOS.md
@@ -86,10 +86,14 @@
- `services/mana-core-auth/src/stripe/` - Stripe-Module
- `services/mana-core-auth/src/credits/credits.service.ts` - Purchase-Methoden
+**Frontend (Implementiert 2026-02-13):**
+
+- [x] Credits-Seite: Stripe Checkout Integration
+- [x] Loading-States und Toast-Benachrichtigungen
+
**Noch offen:**
- [ ] Rechnungs-PDF generieren
-- [ ] Frontend: Stripe Elements einbinden
---
@@ -177,12 +181,12 @@ Archivierte Apps (memoro, storyteller) wurden bereits entfernt.
- `services/mana-core-auth/src/auth/dto/change-password.dto.ts` - Passwort-Ändern DTO
- `services/mana-core-auth/src/auth/dto/delete-account.dto.ts` - Konto-Löschen DTO
-**Noch offen (Frontend):**
+**Frontend (Implementiert 2026-02-13):**
-- [ ] Profil-Edit Modal/Seite erstellen
-- [ ] Passwort-Ändern Dialog
-- [ ] Konto-Löschung mit Bestätigung
-- [ ] Avatar-Upload mit S3/MinIO Integration
+- [x] Profil-Edit Modal erstellt (`EditProfileModal.svelte`)
+- [x] Passwort-Ändern Dialog erstellt (`ChangePasswordModal.svelte`)
+- [x] Konto-Löschung mit Bestätigung (`DeleteAccountModal.svelte`)
+- [ ] Avatar-Upload mit S3/MinIO Integration (noch offen)
---
@@ -246,10 +250,13 @@ POST /api/v1/subscriptions/reactivate # Reaktivieren
GET /api/v1/subscriptions/invoices # Rechnungen
```
-**Noch offen (Frontend):**
+**Frontend (Implementiert 2026-02-13):**
-- [ ] Plan-Übersicht Seite im Frontend
-- [ ] Plan-Vergleichs-UI
+- [x] Plan-Übersicht Seite im Frontend (`/subscription`)
+- [x] Plan-Vergleichs-UI mit monatlich/jährlich Toggle
+- [x] Stripe Checkout Integration für Subscriptions
+- [x] Billing Portal Integration
+- [x] Rechnungsübersicht
- [ ] Stripe Price IDs in DB eintragen (nach Stripe-Setup)
---
@@ -399,4 +406,4 @@ Diese Tasks können schnell erledigt werden:
---
-_Zuletzt aktualisiert: 2026-02-13 (Profile-Features Backend)_
+_Zuletzt aktualisiert: 2026-02-13 (Profile + Subscription + Credits Frontend)_
diff --git a/apps/manacore/apps/web/src/lib/api/credits.ts b/apps/manacore/apps/web/src/lib/api/credits.ts
index e24a11ab1..f69549952 100644
--- a/apps/manacore/apps/web/src/lib/api/credits.ts
+++ b/apps/manacore/apps/web/src/lib/api/credits.ts
@@ -119,4 +119,22 @@ export const creditsService = {
body: JSON.stringify({ amount, appId, description }),
});
},
+
+ /**
+ * Initiate a credit purchase via Stripe Checkout
+ */
+ async initiatePurchase(packageId: string): Promise<{
+ purchaseId: string;
+ checkoutUrl: string;
+ amount: number;
+ credits: number;
+ }> {
+ const successUrl = `${window.location.origin}/credits?success=true`;
+ const cancelUrl = `${window.location.origin}/credits?canceled=true`;
+
+ return fetchWithAuth('/api/v1/credits/purchase', {
+ method: 'POST',
+ body: JSON.stringify({ packageId, successUrl, cancelUrl }),
+ });
+ },
};
diff --git a/apps/manacore/apps/web/src/lib/api/profile.ts b/apps/manacore/apps/web/src/lib/api/profile.ts
new file mode 100644
index 000000000..a23aa516d
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/api/profile.ts
@@ -0,0 +1,99 @@
+/**
+ * Profile Service for ManaCore Web App
+ * Handles profile updates, password changes, and account deletion
+ */
+
+import { authStore } from '$lib/stores/auth.svelte';
+
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// Types
+export interface UserProfile {
+ id: string;
+ name: string;
+ email: string;
+ emailVerified: boolean;
+ image?: string;
+ role: string;
+ createdAt: string;
+}
+
+export interface UpdateProfileRequest {
+ name?: string;
+ image?: string;
+}
+
+export interface ChangePasswordRequest {
+ currentPassword: string;
+ newPassword: string;
+}
+
+export interface DeleteAccountRequest {
+ password: string;
+ reason?: string;
+}
+
+// Helper function for authenticated requests
+async function fetchWithAuth(endpoint: string, options: RequestInit = {}): Promise {
+ const token = await authStore.getAccessToken();
+
+ const response = await fetch(`${MANA_AUTH_URL}${endpoint}`, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
+ ...options.headers,
+ },
+ });
+
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ message: 'Request failed' }));
+ throw new Error(error.message || `HTTP ${response.status}`);
+ }
+
+ return response.json();
+}
+
+// Profile Service
+export const profileService = {
+ /**
+ * Get current user profile
+ */
+ async getProfile(): Promise {
+ return fetchWithAuth('/api/v1/auth/profile');
+ },
+
+ /**
+ * Update user profile
+ */
+ async updateProfile(
+ data: UpdateProfileRequest
+ ): Promise<{ success: boolean; user: UserProfile }> {
+ return fetchWithAuth('/api/v1/auth/profile', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ },
+
+ /**
+ * Change password
+ */
+ async changePassword(
+ data: ChangePasswordRequest
+ ): Promise<{ success: boolean; message: string }> {
+ return fetchWithAuth('/api/v1/auth/change-password', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ },
+
+ /**
+ * Delete account (soft-delete)
+ */
+ async deleteAccount(data: DeleteAccountRequest): Promise<{ success: boolean; message: string }> {
+ return fetchWithAuth('/api/v1/auth/account', {
+ method: 'DELETE',
+ body: JSON.stringify(data),
+ });
+ },
+};
diff --git a/apps/manacore/apps/web/src/lib/api/subscriptions.ts b/apps/manacore/apps/web/src/lib/api/subscriptions.ts
new file mode 100644
index 000000000..848ea11ff
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/api/subscriptions.ts
@@ -0,0 +1,157 @@
+/**
+ * Subscriptions Service for ManaCore Web App
+ * Handles subscription plans, checkout, and billing portal
+ */
+
+import { authStore } from '$lib/stores/auth.svelte';
+
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// Types
+export interface SubscriptionPlan {
+ id: string;
+ name: string;
+ description?: string;
+ monthlyCredits: number;
+ priceMonthlyEuroCents: number;
+ priceYearlyEuroCents: number;
+ features: string[];
+ isDefault: boolean;
+ active: boolean;
+ sortOrder: number;
+ stripePriceIdMonthly?: string;
+ stripePriceIdYearly?: string;
+}
+
+export interface Subscription {
+ id: string;
+ userId: string;
+ planId: string;
+ status: 'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete';
+ billingInterval: 'month' | 'year';
+ currentPeriodStart: string;
+ currentPeriodEnd: string;
+ cancelAtPeriodEnd: boolean;
+ stripeSubscriptionId?: string;
+}
+
+export interface Invoice {
+ id: string;
+ number?: string;
+ status: string;
+ amountDueEuroCents: number;
+ amountPaidEuroCents: number;
+ currency: string;
+ hostedInvoiceUrl?: string;
+ invoicePdfUrl?: string;
+ periodStart?: string;
+ periodEnd?: string;
+ paidAt?: string;
+ createdAt: string;
+}
+
+export interface CurrentSubscription {
+ plan: SubscriptionPlan | null;
+ subscription: Subscription | null;
+ isFreePlan: boolean;
+}
+
+// Helper function for authenticated requests
+async function fetchWithAuth(endpoint: string, options: RequestInit = {}): Promise {
+ const token = await authStore.getAccessToken();
+
+ const response = await fetch(`${MANA_AUTH_URL}${endpoint}`, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
+ ...options.headers,
+ },
+ });
+
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ message: 'Request failed' }));
+ throw new Error(error.message || `HTTP ${response.status}`);
+ }
+
+ return response.json();
+}
+
+// Subscriptions Service
+export const subscriptionsService = {
+ /**
+ * Get all available plans (public)
+ */
+ async getPlans(): Promise {
+ const response = await fetch(`${MANA_AUTH_URL}/api/v1/subscriptions/plans`);
+ if (!response.ok) {
+ throw new Error('Failed to fetch plans');
+ }
+ return response.json();
+ },
+
+ /**
+ * Get current subscription
+ */
+ async getCurrentSubscription(): Promise {
+ return fetchWithAuth('/api/v1/subscriptions/current');
+ },
+
+ /**
+ * Create checkout session for subscription
+ */
+ async createCheckout(
+ planId: string,
+ billingInterval: 'month' | 'year'
+ ): Promise<{ sessionId: string; url: string }> {
+ const successUrl = `${window.location.origin}/subscription?success=true`;
+ const cancelUrl = `${window.location.origin}/subscription?canceled=true`;
+
+ return fetchWithAuth('/api/v1/subscriptions/checkout', {
+ method: 'POST',
+ body: JSON.stringify({
+ planId,
+ billingInterval,
+ successUrl,
+ cancelUrl,
+ }),
+ });
+ },
+
+ /**
+ * Open billing portal for self-service
+ */
+ async openPortal(): Promise<{ url: string }> {
+ const returnUrl = `${window.location.origin}/subscription`;
+
+ return fetchWithAuth('/api/v1/subscriptions/portal', {
+ method: 'POST',
+ body: JSON.stringify({ returnUrl }),
+ });
+ },
+
+ /**
+ * Cancel subscription (at period end)
+ */
+ async cancelSubscription(): Promise<{ success: boolean; cancelAtPeriodEnd: boolean }> {
+ return fetchWithAuth('/api/v1/subscriptions/cancel', {
+ method: 'POST',
+ });
+ },
+
+ /**
+ * Reactivate canceled subscription
+ */
+ async reactivateSubscription(): Promise<{ success: boolean }> {
+ return fetchWithAuth('/api/v1/subscriptions/reactivate', {
+ method: 'POST',
+ });
+ },
+
+ /**
+ * Get invoice history
+ */
+ async getInvoices(limit = 20): Promise {
+ return fetchWithAuth(`/api/v1/subscriptions/invoices?limit=${limit}`);
+ },
+};
diff --git a/apps/manacore/apps/web/src/lib/components/profile/ChangePasswordModal.svelte b/apps/manacore/apps/web/src/lib/components/profile/ChangePasswordModal.svelte
new file mode 100644
index 000000000..000a131df
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/components/profile/ChangePasswordModal.svelte
@@ -0,0 +1,227 @@
+
+
+{#if show}
+
+
+{/if}
diff --git a/apps/manacore/apps/web/src/lib/components/profile/DeleteAccountModal.svelte b/apps/manacore/apps/web/src/lib/components/profile/DeleteAccountModal.svelte
new file mode 100644
index 000000000..e6053a76b
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/components/profile/DeleteAccountModal.svelte
@@ -0,0 +1,260 @@
+
+
+{#if show}
+
+
+
+
+
+
+ {#if step === 'confirm'}
+
+
+ Diese Aktion ist unwiderruflich!
+
+
+ Dein Konto und alle deine Daten werden dauerhaft gelöscht. Dies umfasst:
+
+
+
+
+ -
+
+ Alle Projektdaten (Chats, Todos, Termine, etc.)
+
+ -
+
+ Alle verbleibenden Credits
+
+ -
+
+ Dein aktives Abonnement (wird gekündigt)
+
+ -
+
+ Dein Nutzerkonto
+
+
+
+
+
+
+
+
+ {#if error}
+
+ {/if}
+
+
+
+
+
+ {:else}
+
+ Bitte gib dein Passwort ein, um die Löschung zu bestätigen.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if error}
+
+ {/if}
+
+
+
+
+
+
+ {/if}
+
+
+
+{/if}
diff --git a/apps/manacore/apps/web/src/lib/components/profile/EditProfileModal.svelte b/apps/manacore/apps/web/src/lib/components/profile/EditProfileModal.svelte
new file mode 100644
index 000000000..56408363b
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/components/profile/EditProfileModal.svelte
@@ -0,0 +1,150 @@
+
+
+{#if show}
+
+
+{/if}
diff --git a/apps/manacore/apps/web/src/lib/components/profile/index.ts b/apps/manacore/apps/web/src/lib/components/profile/index.ts
new file mode 100644
index 000000000..c416171aa
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/components/profile/index.ts
@@ -0,0 +1,3 @@
+export { default as EditProfileModal } from './EditProfileModal.svelte';
+export { default as ChangePasswordModal } from './ChangePasswordModal.svelte';
+export { default as DeleteAccountModal } from './DeleteAccountModal.svelte';
diff --git a/apps/manacore/apps/web/src/lib/i18n/locales/de.json b/apps/manacore/apps/web/src/lib/i18n/locales/de.json
index 2502be6f5..cfb01c168 100644
--- a/apps/manacore/apps/web/src/lib/i18n/locales/de.json
+++ b/apps/manacore/apps/web/src/lib/i18n/locales/de.json
@@ -114,6 +114,42 @@
"total_spent": "Verbraucht",
"manage": "Credits verwalten"
},
+ "profile": {
+ "title": "Profil",
+ "edit": "Profil bearbeiten",
+ "change_password": "Passwort ändern",
+ "delete_account": "Konto löschen",
+ "logout": "Abmelden",
+ "name": "Name",
+ "email": "E-Mail",
+ "save": "Speichern",
+ "cancel": "Abbrechen",
+ "current_password": "Aktuelles Passwort",
+ "new_password": "Neues Passwort",
+ "confirm_password": "Passwort bestätigen",
+ "password_changed": "Passwort erfolgreich geändert",
+ "profile_updated": "Profil erfolgreich aktualisiert"
+ },
+ "subscription": {
+ "title": "Abonnement",
+ "current_plan": "Aktueller Plan",
+ "free_plan": "Free Plan",
+ "upgrade": "Upgrade",
+ "cancel": "Kündigen",
+ "reactivate": "Reaktivieren",
+ "billing_portal": "Zahlungsmethode verwalten",
+ "monthly": "Monatlich",
+ "yearly": "Jährlich",
+ "save_percent": "Spare {percent}%",
+ "per_month": "/ Monat",
+ "per_year": "/ Jahr",
+ "mana_per_month": "Mana / Monat",
+ "features": "Features",
+ "select_plan": "Auswählen",
+ "current": "Aktuell",
+ "invoices": "Rechnungen",
+ "no_invoices": "Noch keine Rechnungen vorhanden"
+ },
"app_slider": {
"title": "Teil des Mana Ökosystems",
"memoro_desc": "KI-gestützte Sprachnotizen",
diff --git a/apps/manacore/apps/web/src/lib/i18n/locales/en.json b/apps/manacore/apps/web/src/lib/i18n/locales/en.json
index 4919f51ad..e0f416502 100644
--- a/apps/manacore/apps/web/src/lib/i18n/locales/en.json
+++ b/apps/manacore/apps/web/src/lib/i18n/locales/en.json
@@ -114,6 +114,42 @@
"total_spent": "Spent",
"manage": "Manage credits"
},
+ "profile": {
+ "title": "Profile",
+ "edit": "Edit profile",
+ "change_password": "Change password",
+ "delete_account": "Delete account",
+ "logout": "Log out",
+ "name": "Name",
+ "email": "Email",
+ "save": "Save",
+ "cancel": "Cancel",
+ "current_password": "Current password",
+ "new_password": "New password",
+ "confirm_password": "Confirm password",
+ "password_changed": "Password changed successfully",
+ "profile_updated": "Profile updated successfully"
+ },
+ "subscription": {
+ "title": "Subscription",
+ "current_plan": "Current plan",
+ "free_plan": "Free Plan",
+ "upgrade": "Upgrade",
+ "cancel": "Cancel",
+ "reactivate": "Reactivate",
+ "billing_portal": "Manage payment method",
+ "monthly": "Monthly",
+ "yearly": "Yearly",
+ "save_percent": "Save {percent}%",
+ "per_month": "/ month",
+ "per_year": "/ year",
+ "mana_per_month": "Mana / month",
+ "features": "Features",
+ "select_plan": "Select",
+ "current": "Current",
+ "invoices": "Invoices",
+ "no_invoices": "No invoices yet"
+ },
"app_slider": {
"title": "Part of the Mana Ecosystem",
"memoro_desc": "AI-powered voice notes",
diff --git a/apps/manacore/apps/web/src/routes/(app)/credits/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/credits/+page.svelte
index b55c889ea..b2a6740fb 100644
--- a/apps/manacore/apps/web/src/routes/(app)/credits/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/(app)/credits/+page.svelte
@@ -1,5 +1,6 @@
@@ -230,7 +270,8 @@
{#each packages.slice(0, 3) as pkg}
{/each}
@@ -309,9 +357,18 @@
@@ -327,3 +384,30 @@
{/if}
{/if}
+
+
+{#if toastMessage}
+
+ {toastMessage}
+
+{/if}
+
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/profile/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/profile/+page.svelte
index e4aeb28d4..0c16d74fa 100644
--- a/apps/manacore/apps/web/src/routes/(app)/profile/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/(app)/profile/+page.svelte
@@ -1,43 +1,152 @@
-
+
+
+{:else}
+
+{/if}
+
+
+ (showEditModal = false)}
+ onSuccess={handleProfileUpdate}
/>
+
+ (showPasswordModal = false)}
+ onSuccess={handlePasswordChange}
+/>
+
+ (showDeleteModal = false)}
+ onSuccess={handleAccountDeleted}
+/>
+
+
+{#if toastMessage}
+
+ {toastMessage}
+
+{/if}
+
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/subscription/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/subscription/+page.svelte
new file mode 100644
index 000000000..7d06f9cd6
--- /dev/null
+++ b/apps/manacore/apps/web/src/routes/(app)/subscription/+page.svelte
@@ -0,0 +1,505 @@
+
+
+
+
+
+ {#if loading}
+
+ {:else if error}
+
+
+
{error}
+
+
+
+ {:else}
+
+ {#if currentSubscription?.subscription}
+ {@const sub = currentSubscription.subscription}
+ {@const plan = currentSubscription.plan}
+ {@const status = getStatusBadge(sub.status)}
+
+
+
+
+
{plan?.name || 'Aktueller Plan'}
+
+ {status.text}
+
+
+
+ {plan?.monthlyCredits.toLocaleString('de-DE')} Mana / Monat
+
+
+
+
+
+
+
+
+
+
Abrechnungszeitraum
+
{sub.billingInterval === 'year' ? 'Jährlich' : 'Monatlich'}
+
+
+
Aktuelle Periode
+
+ {formatDate(sub.currentPeriodStart)} - {formatDate(sub.currentPeriodEnd)}
+
+
+
+ {#if sub.cancelAtPeriodEnd}
+
Endet am
+
{formatDate(sub.currentPeriodEnd)}
+
+ {:else}
+
Verlängert am
+
{formatDate(sub.currentPeriodEnd)}
+
+ {/if}
+
+
+
+ {:else}
+
+
+
+
+
+
Free Plan
+
+ Aktuell
+
+
+
+ 150 Mana / Monat
+
+
+
+ Upgrade auf einen bezahlten Plan für mehr Mana und Features.
+
+
+
+ {/if}
+
+
+
+
+
+
+
+
+ {#if activeTab === 'plans'}
+
+
+
+
+
+
+
+
+
+
+ {#each plans as plan}
+ {@const isCurrentPlan = currentSubscription?.plan?.id === plan.id}
+ {@const price = billingInterval === 'year' ? plan.priceYearlyEuroCents : plan.priceMonthlyEuroCents}
+ {@const savings = getSavingsPercent(plan.priceMonthlyEuroCents, plan.priceYearlyEuroCents)}
+
+
+ {#if isCurrentPlan}
+
+ Dein Plan
+
+ {/if}
+
+
{plan.name}
+ {#if plan.description}
+
{plan.description}
+ {/if}
+
+
+
+ {plan.isDefault ? 'Kostenlos' : formatPrice(price, billingInterval)}
+
+ {#if !plan.isDefault}
+
+ / {billingInterval === 'year' ? 'Jahr' : 'Monat'}
+
+ {#if billingInterval === 'year' && savings > 0}
+
+ {formatMonthlyEquivalent(plan.priceYearlyEuroCents)} / Monat
+
+ {/if}
+ {/if}
+
+
+
+ {plan.monthlyCredits.toLocaleString('de-DE')} Mana / Monat
+
+
+ {#if plan.features && plan.features.length > 0}
+
+ {#each plan.features as feature}
+ -
+
+ {feature}
+
+ {/each}
+
+ {/if}
+
+
+
+
+ {/each}
+
+ {:else if activeTab === 'invoices'}
+
+ Rechnungsverlauf
+ {#if invoices.length === 0}
+ Noch keine Rechnungen vorhanden.
+ {:else}
+
+
+
+
+ | Nummer |
+ Datum |
+ Zeitraum |
+ Betrag |
+ Status |
+ |
+
+
+
+ {#each invoices as invoice}
+
+ | {invoice.number || '-'} |
+ {formatDate(invoice.createdAt)} |
+
+ {#if invoice.periodStart && invoice.periodEnd}
+ {formatDate(invoice.periodStart)} - {formatDate(invoice.periodEnd)}
+ {:else}
+ -
+ {/if}
+ |
+
+ {formatPrice(invoice.amountPaidEuroCents, 'month')}
+ |
+
+ {#if invoice.status === 'paid'}
+
+ Bezahlt
+
+ {:else}
+
+ {invoice.status}
+
+ {/if}
+ |
+
+ {#if invoice.invoicePdfUrl}
+
+ PDF
+
+ {/if}
+ |
+
+ {/each}
+
+
+
+ {/if}
+
+ {/if}
+ {/if}
+
+
+
+{#if toastMessage}
+
+ {toastMessage}
+
+{/if}
+
+