Zum Hauptinhalt springen

Zurück zum Devlog

Freitag, 13. Februar 2026

Feature

55 Commits

18 min Lesezeit

Gift Codes, Stripe Integration & Zitare Deployment

Neues Gift Code System mit Credit-Gutscheinen, Stripe Integration für Subscriptions und Credit-Käufe, Zitare Production Deployment mit multilingualer Quote-Datenbank, sowie Calendar Drag-to-Create und Matrix WhatsApp-Style Navigation

T

Till Schneider

Autor

Ein massiver Tag mit 55 Commits und mehreren Major Features:

  • Gift Codes - Credit-Gutscheine mit Code-Einlösung
  • Stripe Integration - Subscriptions und Credit-Käufe
  • Zitare Deployment - Production-Ready mit multilingualer Quote-DB
  • Calendar UX - Drag-to-Create, Resize Handles, Live Preview
  • Matrix Mobile - WhatsApp-Style Navigation für PWA
  • Avatar Upload - S3/MinIO Integration mit Onboarding
  • ManaCore Dashboard - Profile, Subscriptions, Credits UI

Gift Codes & Credit System

Neues Geschenkkarten-System für Credit-Gutscheine.

Architektur

┌─────────────────────────────────────────────────────────────────┐
│                     Gift Code System                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Admin erstellt Code          User löst Code ein                │
│  ┌─────────────────┐          ┌─────────────────┐               │
│  │ POST /gifts     │          │ POST /gifts/    │               │
│  │                 │          │   redeem        │               │
│  │ code: MANA-XXX  │          │ code: MANA-XXX  │               │
│  │ credits: 100    │          │                 │               │
│  │ maxUses: 10     │          └────────┬────────┘               │
│  └─────────────────┘                   │                        │
│                                        ▼                        │
│                              ┌─────────────────┐                │
│                              │ Credit Balance  │                │
│                              │ += 100 Credits  │                │
│                              └─────────────────┘                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

API Endpoints

// Gift Codes verwalten
POST /api/v1/gifts              // Code erstellen (Admin)
GET  /api/v1/gifts              // Alle Codes auflisten
GET  /api/v1/gifts/:code        // Code-Details abrufen
POST /api/v1/gifts/redeem       // Code einlösen (User)
DELETE /api/v1/gifts/:code      // Code löschen

// Request Body: Code einlösen
{
  "code": "MANA-WELCOME-2026"
}

// Response
{
  "success": true,
  "credits": 100,
  "message": "100 Credits wurden deinem Konto gutgeschrieben!"
}

Database Schema

// auth.schema.ts
export const giftCodes = authSchema.table('gift_codes', {
	id: text('id').primaryKey(),
	code: text('code').notNull().unique(),
	credits: integer('credits').notNull(),
	maxUses: integer('max_uses').default(1),
	currentUses: integer('current_uses').default(0),
	expiresAt: timestamp('expires_at'),
	createdBy: text('created_by').references(() => users.id),
	createdAt: timestamp('created_at').defaultNow(),
});

export const giftRedemptions = authSchema.table('gift_redemptions', {
	id: text('id').primaryKey(),
	codeId: text('code_id').references(() => giftCodes.id),
	userId: text('user_id').references(() => users.id),
	redeemedAt: timestamp('redeemed_at').defaultNow(),
});

Matrix Bot Integration

Gift Codes können auch via Matrix eingelöst werden:

User: !redeem MANA-WELCOME-2026
Bot:  ✅ Code eingelöst! Du hast 100 Credits erhalten.
      Aktuelles Guthaben: 250 Credits

Stripe Integration

Vollständige Stripe-Anbindung für Payments.

Subscription Plans

// Subscription Tiers
const SUBSCRIPTION_PLANS = {
	free: {
		name: 'Free',
		price: 0,
		credits: 50, // monatlich
		features: ['Basic Apps', '50 Credits/Monat'],
	},
	plus: {
		name: 'Plus',
		price: 4.99,
		stripePriceId: 'price_plus_monthly',
		credits: 500,
		features: ['Alle Apps', '500 Credits/Monat', 'Priority Support'],
	},
	pro: {
		name: 'Pro',
		price: 14.99,
		stripePriceId: 'price_pro_monthly',
		credits: 2000,
		features: ['Alle Apps', '2000 Credits/Monat', 'API Zugang', 'Priorität'],
	},
};

Credit Purchases

// Einmal-Käufe
POST /api/v1/credits/purchase
{
  "package": "credits_500"  // 500 Credits für 4.99€
}

// Verfügbare Pakete
const CREDIT_PACKAGES = {
  credits_100:  { credits: 100,  price: 0.99,  stripePriceId: '...' },
  credits_500:  { credits: 500,  price: 4.99,  stripePriceId: '...' },
  credits_1000: { credits: 1000, price: 8.99,  stripePriceId: '...' },
  credits_5000: { credits: 5000, price: 39.99, stripePriceId: '...' },
};

Webhook Handler

// stripe.controller.ts
@Post('webhook')
async handleWebhook(@Req() req: Request) {
  const event = this.stripe.webhooks.constructEvent(
    req.body,
    req.headers['stripe-signature'],
    this.webhookSecret,
  );

  switch (event.type) {
    case 'checkout.session.completed':
      await this.handleCheckoutComplete(event.data.object);
      break;
    case 'customer.subscription.updated':
      await this.handleSubscriptionUpdate(event.data.object);
      break;
    case 'invoice.paid':
      await this.handleInvoicePaid(event.data.object);
      break;
  }
}

Zitare Production Deployment

Zitare ist jetzt live auf zitare.mana.how!

Docker Infrastructure

# docker-compose.macmini.yml
zitare-backend:
  build:
    context: .
    dockerfile: apps/zitare/apps/backend/Dockerfile
  ports:
    - '3007:3007'
  environment:
    DATABASE_URL: ${ZITARE_DATABASE_URL}
    MANA_CORE_AUTH_URL: http://mana-core-auth:3001
  healthcheck:
    test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3007/health']

zitare-web:
  build:
    context: .
    dockerfile: apps/zitare/apps/web/Dockerfile
  ports:
    - '5018:5018'
  depends_on:
    - zitare-backend

@zitare/content Package

Neues Package für shared Quotes:

// packages/zitare-content/src/quotes.ts
export interface Quote {
	id: string;
	text: string;
	author: string;
	source?: string;
	year?: number;
	language: 'de' | 'en';
	category: QuoteCategory;
	tags: string[];
}

export const quotes: Quote[] = [
	{
		id: 'goethe-001',
		text: 'Es ist nicht genug zu wissen, man muss auch anwenden...',
		author: 'Johann Wolfgang von Goethe',
		source: 'Wilhelm Meisters Wanderjahre',
		year: 1829,
		language: 'de',
		category: 'wisdom',
		tags: ['wissen', 'handeln', 'motivation'],
	},
	// ... 500+ Zitate
];

Multilingual Support

// Backend: Sprache aus Header
@Get('quote/daily')
getDailyQuote(@Headers('accept-language') lang: string) {
  const language = lang?.startsWith('de') ? 'de' : 'en';
  return this.quoteService.getDailyQuote(language);
}

// Frontend: i18n Integration
const { t, locale } = useTranslation();
const quote = await fetchDailyQuote($locale);

Quote Metadata

Alle Zitate mit vollständigen Metadaten:

FeldBeschreibung
authorVollständiger Name
sourceBuch/Werk/Rede
yearEntstehungsjahr
categorywisdom, motivation, love, life, …
tagsSuchbare Schlagwörter

Calendar UX Improvements

Mehrere UX-Verbesserungen für den Kalender.

Drag-to-Create

Events können jetzt durch Drag & Drop erstellt werden:

<script lang="ts">
	let dragStart = $state<{ hour: number; day: Date } | null>(null);
	let dragEnd = $state<{ hour: number; day: Date } | null>(null);

	function handleMouseDown(hour: number, day: Date) {
		dragStart = { hour, day };
	}

	function handleMouseMove(hour: number, day: Date) {
		if (dragStart) {
			dragEnd = { hour, day };
		}
	}

	function handleMouseUp() {
		if (dragStart && dragEnd) {
			createEvent({
				start: new Date(dragStart.day.setHours(dragStart.hour)),
				end: new Date(dragEnd.day.setHours(dragEnd.hour)),
			});
		}
		dragStart = null;
		dragEnd = null;
	}
</script>

Live Time Preview

Beim Ziehen wird die neue Zeit live angezeigt:

{#if dragStart && dragEnd}
	<div class="preview-overlay">
		{formatTime(dragStart.hour)} - {formatTime(dragEnd.hour)}
	</div>
{/if}

Resize Handles

Größere Resize-Handles für bessere Touch-Bedienbarkeit:

.event-resize-handle {
	height: 12px; /* Vorher: 6px */
	cursor: ns-resize;
	touch-action: none;
}

/* Mobile: Noch größer */
@media (max-width: 768px) {
	.event-resize-handle {
		height: 20px;
	}
}

Mobile UX

  • Tasks standardmäßig ausgeblendet
  • Auto-Scroll zur aktuellen Stunde
  • Mikrofon-Button in der Input Bar integriert
  • Padding für Bottom-UI Sichtbarkeit

Matrix WhatsApp-Style Navigation

Neue Mobile-Navigation für die Matrix PWA.

┌─────────────────────────────────────────┐
│  ← Rooms          Matrix        Settings│  ← Header
├─────────────────────────────────────────┤
│                                         │
│  ┌─────────────────────────────────┐    │
│  │ General                      >  │    │
│  ├─────────────────────────────────┤    │
│  │ Development                  >  │    │
│  ├─────────────────────────────────┤    │
│  │ @user:mana.how               >  │    │
│  └─────────────────────────────────┘    │
│                                         │
│                                         │  ← Room List
│                                         │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │           + New Chat            │    │
│  └─────────────────────────────────┘    │
└─────────────────────────────────────────┘

        ↓ Tap on Room ↓

┌─────────────────────────────────────────┐
│  ←  General                    ⋮        │  ← Room Header
├─────────────────────────────────────────┤
│                                         │
│  Alice: Hey, wie geht's?                │
│                                         │
│                        Bob: Gut, danke! │
│                                         │
│  Alice: Hast du das Feature gesehen?    │
│                                         │
├─────────────────────────────────────────┤
│  [Message input...]              Send   │
└─────────────────────────────────────────┘

iOS PWA Swipe-Back

<script lang="ts">
	let touchStartX = 0;

	function handleTouchStart(e: TouchEvent) {
		touchStartX = e.touches[0].clientX;
	}

	function handleTouchEnd(e: TouchEvent) {
		const touchEndX = e.changedTouches[0].clientX;
		const diff = touchEndX - touchStartX;

		// Swipe from left edge
		if (touchStartX < 30 && diff > 100) {
			goBack();
		}
	}
</script>

<div ontouchstart={handleTouchStart} ontouchend={handleTouchEnd}>
	<!-- Room Content -->
</div>

Mobile Web App Meta Tags

<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

Avatar Upload & Onboarding

Neues Avatar-System mit S3/MinIO.

Upload Flow

// POST /api/v1/me/avatar
@Post('avatar')
@UseInterceptors(FileInterceptor('avatar'))
async uploadAvatar(
  @UploadedFile() file: Express.Multer.File,
  @CurrentUser() user: CurrentUserData,
) {
  // Resize und optimieren
  const optimized = await sharp(file.buffer)
    .resize(256, 256)
    .webp({ quality: 80 })
    .toBuffer();

  // Upload zu S3/MinIO
  const key = `avatars/${user.userId}.webp`;
  await this.storageService.upload(key, optimized, {
    contentType: 'image/webp',
    public: true,
  });

  // URL speichern
  await this.userService.updateAvatar(user.userId, key);

  return { avatarUrl: this.storageService.getPublicUrl(key) };
}

Onboarding Wizard

Neuer Wizard für neue Nutzer:

┌─────────────────────────────────────────┐
│         Welcome to ManaCore!             │
├─────────────────────────────────────────┤
│                                         │
│         ┌───────────────┐               │
│         │     📷        │  ← Avatar     │
│         │  Upload Photo │               │
│         └───────────────┘               │
│                                         │
│  Display Name: [________________]       │
│                                         │
│  Language:     [Deutsch        ▼]       │
│                                         │
│  Theme:        ○ Light  ● Dark  ○ Auto  │
│                                         │
│         [ Continue → ]                  │
│                                         │
└─────────────────────────────────────────┘

ManaCore Dashboard

Neues Dashboard auf mana.how.

Profile Section

<div class="profile-card">
	<Avatar src={user.avatarUrl} size="lg" />
	<h2>{user.name}</h2>
	<p>{user.email}</p>

	<div class="stats">
		<Stat label="Credits" value={credits} />
		<Stat label="Subscription" value={subscription.name} />
		<Stat label="Member since" value={formatDate(user.createdAt)} />
	</div>
</div>

Subscription Management

<SubscriptionCard
	current={subscription}
	plans={SUBSCRIPTION_PLANS}
	onUpgrade={handleUpgrade}
	onCancel={handleCancel}
/>

Credits Frontend

<CreditsDashboard>
	<CreditBalance balance={credits} />
	<CreditHistory {transactions} />
	<CreditPurchase packages={CREDIT_PACKAGES} />
</CreditsDashboard>

Storage Usage Widget

<StorageUsageWidget
	used={storageUsed}
	total={storageTotal}
	breakdown={[
		{ label: 'Photos', size: photosSize, color: 'blue' },
		{ label: 'Documents', size: docsSize, color: 'green' },
		{ label: 'Other', size: otherSize, color: 'gray' },
	]}
/>

API Keys Management

<ApiKeysManager
	keys={apiKeys}
	onCreate={createKey}
	onRevoke={revokeKey}
	services={['stt', 'tts', 'llm']}
/>

Settings Stores

Neue Settings-Stores für Zitare und Todo.

Pattern

// stores/settings.store.ts
import { persisted } from 'svelte-persisted-store';

export interface AppSettings {
	theme: 'light' | 'dark' | 'auto';
	language: string;
	notifications: boolean;
	// App-spezifische Settings
}

export const settings = persisted<AppSettings>('app-settings', {
	theme: 'auto',
	language: 'de',
	notifications: true,
});

Zitare Settings

interface ZitareSettings extends AppSettings {
	dailyNotificationTime: string; // "09:00"
	favoriteCategories: string[];
	showAuthorInfo: boolean;
}

Todo Settings

interface TodoSettings extends AppSettings {
	defaultPriority: Priority;
	showCompletedTasks: boolean;
	sortOrder: 'priority' | 'dueDate' | 'created';
}

Devlog Activity Grid

Neue Activity-Seite für Devlogs.

Features

  • GitHub-Style Contribution Grid
  • Commit-Statistiken pro Tag
  • Hover-Details mit Commit-Count
  • Link zu Devlog-Einträgen

Implementation

<ActivityGrid
	data={devlogStats}
	colorScale={['#eee', '#9be9a8', '#40c463', '#30a14e', '#216e39']}
	cellSize={12}
	gap={3}
/>

Weitere Änderungen

Shared UI Refactoring

LoginPage und InputBar Komponenten verbessert:

// Bessere Props-Struktur
interface LoginPageProps {
	locale: string;
	onLogin: (credentials: LoginCredentials) => Promise<void>;
	onRegister?: () => void;
	onForgotPassword?: () => void;
	showRememberMe?: boolean;
	allowSocialLogin?: boolean;
}

Matrix Bot Enhancements

  • Stats Bot: Detailliertere Statistiken
  • Todo Bot: Cleaner Response Messages
  • Offline Mode entfernt - Login erforderlich

Docker Local Builds

Weitere Services auf lokale Builds umgestellt:

ServiceGrund
matrix-todo-botNative Dependencies
matrix-calendar-botNative Dependencies
presi-backendSchnellere Iteration
mana-webShared Packages

Zusammenfassung

BereichCommitsHighlights
Gift Codes4Code-System, Matrix Integration
Stripe3Subscriptions, Credit Purchases
Zitare10Docker, Quotes, i18n
Calendar6Drag-Create, Resize, Mobile UX
Matrix8WhatsApp-Nav, PWA, Swipe
ManaCore8Profile, Credits, Storage
Avatar/Onboarding3S3 Upload, Wizard
Settings2Zitare, Todo Stores
Auth/Docker8Healthchecks, Builds
Devlog3Activity Grid

Nächste Schritte

  1. Stripe Webhooks - Production Webhook Secret konfigurieren
  2. Gift Code UI - Admin-Panel für Code-Erstellung
  3. Subscription Emails - Bestätigungen und Erinnerungen
  4. Matrix E2EE - Verschlüsselung für DMs aktivieren
  5. Zitare Push - Daily Quote Notifications

Tags

#gift-codes #stripe #subscriptions #credits #zitare #calendar #matrix #avatar #onboarding #docker #pwa