managarten/packages/shared-subscription-ui/src/pages/SubscriptionPage.svelte
Till-JS 22cb7d2c5f feat: unify UI components, AppSlider, and login screens across apps
SUMMARY:
Consolidate shared UI components into @manacore/shared-ui and add
AppSlider to all login screens for a consistent Mana ecosystem experience.

CHANGES:

1. UI Components Migration:
   - Added Card.svelte to @manacore/shared-ui with variants (elevated, outlined, ghost)
   - Migrated Manacore (7 files) and Manadeck (7 files) to use shared-ui
   - Removed local ui/ directories from both apps (8 components total)

2. AppSlider Unification:
   - Created shared AppSlider in @manacore/shared-ui with configurable props
   - Props: apps[], title, isDark, statusLabels, comingSoonLabel, openAppLabel, onAppClick
   - Supports both i18n and static text configurations
   - Updated Memoro AppSlider to use shared component with svelte-i18n
   - Updated Manacore AppSlider to use shared component
   - Created new AppSlider for ManaDeck and Märchenzauber

3. Login Page Enhancements:
   - Extended LoginPage in @manacore/shared-auth-ui with new snippets:
     - appSlider: Renders AppSlider at bottom (initial mode only)
     - headerControls: Renders controls (theme toggle, etc.) top-right
   - Updated all app login pages to include AppSlider:
     - ManaCore: indigo theme (#6366f1)
     - ManaDeck: violet theme (#8b5cf6)
     - Märchenzauber: pink theme (#FF6B9D)

4. Subscription Page Consolidation:
   - Created SubscriptionPage component in @manacore/shared-subscription-ui
   - Moved subscription data (plans, packages, costs) to shared package
   - Reduced subscription page code from ~100 to ~18 lines per app

FILES CHANGED:
- packages/shared-ui: Added Card, AppSlider, updated exports
- packages/shared-auth-ui: Extended LoginPage with snippets
- packages/shared-subscription-ui: Added SubscriptionPage, data files
- manacore/web: Migrated 7 files to shared-ui, updated login
- manadeck/web: Migrated 7 files to shared-ui, added AppSlider, updated login
- maerchenzauber/web: Added AppSlider, updated login
- memoro/web: Updated AppSlider to use shared component

DELETED (moved to shared packages):
- manacore/web/src/lib/components/ui/* (3 files)
- manadeck/web/src/lib/components/ui/* (5 files)
- memoro/web/src/lib/data/*.json (3 files)
- Various pnpm-lock.yaml and pnpm-workspace.yaml files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 21:53:44 +01:00

138 lines
4.3 KiB
Svelte

<script lang="ts">
import type { SubscriptionPlan, ManaPackage, UsageData, CostItem, BillingCycle } from '@manacore/shared-subscription-types';
import BillingToggle from '../BillingToggle.svelte';
import SubscriptionCard from '../SubscriptionCard.svelte';
import PackageCard from '../PackageCard.svelte';
import UsageCard from '../UsageCard.svelte';
import CostCard from '../CostCard.svelte';
// Import default data
import defaultSubscriptionData from '../data/subscriptionData.json';
import defaultAppCosts from '../data/appCosts.json';
import defaultUsageData from '../data/defaultUsageData.json';
interface Props {
/** App name for the page title */
appName: string;
/** Handler when user selects a subscription plan */
onSubscribe: (planId: string) => void;
/** Handler when user selects a mana package */
onBuyPackage: (packageId: string) => void;
/** Current plan ID (e.g., 'free', 'Mana_Stream_Small_v1') */
currentPlanId?: string;
/** Current user's usage data (optional, uses defaults if not provided) */
usageData?: UsageData;
/** Custom subscription plans (optional, uses defaults if not provided) */
subscriptions?: SubscriptionPlan[];
/** Custom mana packages (optional, uses defaults if not provided) */
packages?: ManaPackage[];
/** Custom cost items (optional, uses defaults if not provided) */
costs?: CostItem[];
/** Page title */
pageTitle?: string;
/** Subscriptions section title */
subscriptionsTitle?: string;
/** One-time purchases section title */
packagesTitle?: string;
/** Yearly discount label */
yearlyDiscount?: string;
}
let {
appName,
onSubscribe,
onBuyPackage,
currentPlanId = 'free',
usageData = defaultUsageData.usage as UsageData,
subscriptions = defaultSubscriptionData.subscriptions as SubscriptionPlan[],
packages = defaultSubscriptionData.packages as ManaPackage[],
costs = defaultAppCosts.costs as CostItem[],
pageTitle = 'Mana kaufen',
subscriptionsTitle = 'Abonnements',
packagesTitle = 'Einmalkäufe',
yearlyDiscount = '33%'
}: Props = $props();
// State
let billingCycle = $state<BillingCycle>('monthly');
// Get current plan name for display
const currentPlanName = $derived(() => {
const plan = subscriptions.find(p => p.id === currentPlanId);
return plan?.name || 'Free';
});
// Get all subscription plans for current billing cycle
function getSubscriptionPlans() {
return subscriptions.filter(
(plan) => plan.id !== 'free' && plan.billingCycle === billingCycle
);
}
// Check if a plan is the current plan
function isCurrentPlan(planId: string) {
if (currentPlanId === 'free' && planId === 'free') return true;
return planId === currentPlanId;
}
</script>
<svelte:head>
<title>Mana - {appName}</title>
</svelte:head>
<div class="flex h-full flex-col">
<!-- Content Area -->
<div class="flex-1 overflow-y-auto">
<div class="mx-auto max-w-5xl pb-12">
<h1 class="mb-8 text-3xl font-bold text-theme">{pageTitle}</h1>
<!-- Active Section (Usage & Costs) -->
<section class="mb-8">
<div class="mb-4">
<UsageCard {usageData} currentPlan={currentPlanName()} />
</div>
<div class="mb-4">
<CostCard {costs} />
</div>
</section>
<!-- Billing Toggle -->
<BillingToggle {billingCycle} onChange={(cycle: BillingCycle) => (billingCycle = cycle)} {yearlyDiscount} />
<!-- Subscriptions Section -->
<section class="mb-12 pt-2">
<h2 class="mb-6 text-2xl font-bold text-theme">{subscriptionsTitle}</h2>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
<!-- Free Tier -->
<SubscriptionCard
plan={subscriptions.find((plan) => plan.id === 'free')!}
onSelect={onSubscribe}
isCurrentPlan={isCurrentPlan('free')}
/>
<!-- All Paid Subscriptions -->
{#each getSubscriptionPlans() as plan}
<SubscriptionCard
{plan}
onSelect={onSubscribe}
isCurrentPlan={isCurrentPlan(plan.id)}
/>
{/each}
</div>
</section>
<!-- One-time Purchases Section -->
<section class="mb-12">
<h2 class="mb-6 text-2xl font-bold text-theme">{packagesTitle}</h2>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{#each packages as pkg}
<PackageCard package={pkg} onSelect={onBuyPackage} />
{/each}
</div>
</section>
</div>
</div>
</div>