mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 15:57:42 +02:00
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>
This commit is contained in:
parent
96e0aceb93
commit
22cb7d2c5f
67 changed files with 894 additions and 22131 deletions
28
packages/shared-subscription-ui/src/data/appCosts.json
Normal file
28
packages/shared-subscription-ui/src/data/appCosts.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"costs": [
|
||||
{
|
||||
"action": "Pro Minute Aufnahme",
|
||||
"actionKey": "subscription.cost_per_minute_recording",
|
||||
"cost": 2,
|
||||
"icon": "mic-outline"
|
||||
},
|
||||
{
|
||||
"action": "Frage an Memo stellen",
|
||||
"actionKey": "subscription.cost_ask_memo_question",
|
||||
"cost": 5,
|
||||
"icon": "chatbubble-outline"
|
||||
},
|
||||
{
|
||||
"action": "Neue Memory erstellen",
|
||||
"actionKey": "subscription.cost_create_memory",
|
||||
"cost": 5,
|
||||
"icon": "add-circle-outline"
|
||||
},
|
||||
{
|
||||
"action": "Memos kombinieren (pro Memo)",
|
||||
"actionKey": "subscription.cost_combine_memos",
|
||||
"cost": 5,
|
||||
"icon": "copy-outline"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"usage": {
|
||||
"total": 0,
|
||||
"lastWeek": 0,
|
||||
"lastMonth": 0,
|
||||
"currentMana": 150,
|
||||
"maxMana": 150,
|
||||
"history": []
|
||||
}
|
||||
}
|
||||
167
packages/shared-subscription-ui/src/data/subscriptionData.json
Normal file
167
packages/shared-subscription-ui/src/data/subscriptionData.json
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
{
|
||||
"subscriptions": [
|
||||
{
|
||||
"id": "free",
|
||||
"name": "Mana Stream Free",
|
||||
"nameEn": "Mana Stream Free",
|
||||
"nameIt": "Mana Stream Free",
|
||||
"price": 0,
|
||||
"priceUnit": "",
|
||||
"monthlyMana": 150,
|
||||
"canGiftMana": false,
|
||||
"popular": false,
|
||||
"billingCycle": "monthly",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Small_v1",
|
||||
"name": "Kleiner Mana Stream",
|
||||
"nameEn": "Small Mana Stream",
|
||||
"nameIt": "Piccolo Mana Stream",
|
||||
"price": 5.99,
|
||||
"priceUnit": "/ Monat",
|
||||
"monthlyMana": 600,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "monthly",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Small_Yearly_v1",
|
||||
"name": "Kleiner Mana Stream",
|
||||
"nameEn": "Small Mana Stream",
|
||||
"nameIt": "Piccolo Mana Stream",
|
||||
"price": 47.99,
|
||||
"priceUnit": "/ Jahr",
|
||||
"priceBreakdown": "(entspricht 3,99€ / Monat, 33% Rabatt)",
|
||||
"monthlyMana": 600,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "yearly",
|
||||
"monthlyEquivalent": 3.99,
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Medium_v1",
|
||||
"name": "Mittlerer Mana Stream",
|
||||
"nameEn": "Medium Mana Stream",
|
||||
"nameIt": "Medio Mana Stream",
|
||||
"price": 14.99,
|
||||
"priceUnit": "/ Monat",
|
||||
"monthlyMana": 1500,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "monthly",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Medium_Yearly_v1",
|
||||
"name": "Mittlerer Mana Stream",
|
||||
"nameEn": "Medium Mana Stream",
|
||||
"nameIt": "Medio Mana Stream",
|
||||
"price": 119.99,
|
||||
"priceUnit": "/ Jahr",
|
||||
"priceBreakdown": "(entspricht 9,99€ / Monat, 33% Rabatt)",
|
||||
"monthlyMana": 1500,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "yearly",
|
||||
"monthlyEquivalent": 9.99,
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Large_v1",
|
||||
"name": "Großer Mana Stream",
|
||||
"nameEn": "Large Mana Stream",
|
||||
"nameIt": "Grande Mana Stream",
|
||||
"price": 29.99,
|
||||
"priceUnit": "/ Monat",
|
||||
"monthlyMana": 3000,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "monthly",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Large_Yearly_v1",
|
||||
"name": "Großer Mana Stream",
|
||||
"nameEn": "Large Mana Stream",
|
||||
"nameIt": "Grande Mana Stream",
|
||||
"price": 239.99,
|
||||
"priceUnit": "/ Jahr",
|
||||
"priceBreakdown": "(entspricht 19,99€ / Monat, 33% Rabatt)",
|
||||
"monthlyMana": 3000,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "yearly",
|
||||
"monthlyEquivalent": 19.99,
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Giant_v1",
|
||||
"name": "Riesiger Mana Stream",
|
||||
"nameEn": "Giant Mana Stream",
|
||||
"nameIt": "Gigante Mana Stream",
|
||||
"price": 49.99,
|
||||
"priceUnit": "/ Monat",
|
||||
"monthlyMana": 5000,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "monthly",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"id": "Mana_Stream_Giant_Yearly_v1",
|
||||
"name": "Riesiger Mana Stream",
|
||||
"nameEn": "Giant Mana Stream",
|
||||
"nameIt": "Gigante Mana Stream",
|
||||
"price": 399.99,
|
||||
"priceUnit": "/ Jahr",
|
||||
"priceBreakdown": "(entspricht 33,33€ / Monat, 33% Rabatt)",
|
||||
"monthlyMana": 5000,
|
||||
"canGiftMana": true,
|
||||
"popular": false,
|
||||
"billingCycle": "yearly",
|
||||
"monthlyEquivalent": 33.33,
|
||||
"available": true
|
||||
}
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"id": "Mana_Potion_Small_v1",
|
||||
"name": "Kleiner Mana Trank",
|
||||
"nameEn": "Small Mana Potion",
|
||||
"nameIt": "Piccola Pozione di Mana",
|
||||
"manaAmount": 350,
|
||||
"price": 4.99,
|
||||
"popular": false
|
||||
},
|
||||
{
|
||||
"id": "Mana_Potion_Medium_v1",
|
||||
"name": "Mittlerer Mana Trank",
|
||||
"nameEn": "Medium Mana Potion",
|
||||
"nameIt": "Media Pozione di Mana",
|
||||
"manaAmount": 700,
|
||||
"price": 9.99,
|
||||
"popular": false
|
||||
},
|
||||
{
|
||||
"id": "Mana_Potion_Large_v1",
|
||||
"name": "Großer Mana Trank",
|
||||
"nameEn": "Large Mana Potion",
|
||||
"nameIt": "Grande Pozione di Mana",
|
||||
"manaAmount": 1400,
|
||||
"price": 19.99,
|
||||
"popular": false
|
||||
},
|
||||
{
|
||||
"id": "Mana_Potion_Giant_v2",
|
||||
"name": "Riesiger Mana Trank",
|
||||
"nameEn": "Giant Mana Potion",
|
||||
"nameIt": "Pozione di Mana Gigante",
|
||||
"manaAmount": 2800,
|
||||
"price": 39.99,
|
||||
"popular": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -5,6 +5,9 @@
|
|||
* subscription plans, mana packages, and usage information.
|
||||
*/
|
||||
|
||||
// Pages
|
||||
export { default as SubscriptionPage } from './pages/SubscriptionPage.svelte';
|
||||
|
||||
// Components
|
||||
export { default as SubscriptionCard } from './SubscriptionCard.svelte';
|
||||
export { default as PackageCard } from './PackageCard.svelte';
|
||||
|
|
@ -14,6 +17,11 @@ export { default as CostCard } from './CostCard.svelte';
|
|||
export { default as SubscriptionButton } from './SubscriptionButton.svelte';
|
||||
export { default as ManaIcon } from './ManaIcon.svelte';
|
||||
|
||||
// Default data exports
|
||||
export { default as defaultSubscriptionData } from './data/subscriptionData.json';
|
||||
export { default as defaultAppCosts } from './data/appCosts.json';
|
||||
export { default as defaultUsageData } from './data/defaultUsageData.json';
|
||||
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
SubscriptionPlan,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
<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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue