/** * Feature Store -- Users activate feature instances in their own mandates. * Uses the Own Instance Pattern -- each activation creates a dedicated FeatureInstance * in the selected mandate. Explicit mandate selection required. */ import React from 'react'; import { FaCogs, FaComments, FaHeadset, FaProjectDiagram } from 'react-icons/fa'; import { useLanguage } from '../providers/language/LanguageContext'; import { useStore } from '../hooks/useStore'; import type { StoreFeature, UserMandate } from '../api/storeApi'; import { formatBinaryDataSizeFromMebibytes } from '../utils/formatDataSize'; import styles from './Store.module.css'; const FEATURE_ICONS: Record = { automation: , graphicalEditor: , teamsbot: , workspace: , commcoach: , }; /** Fallback when GET /store/features omits description (German i18n keys). */ const STORE_FEATURE_DESCRIPTION_FALLBACK: Record = { automation: 'Erstelle und verwalte Automatisierungen, um wiederkehrende Aufgaben effizient zu erledigen.', graphicalEditor: 'n8n-style Flow-Automatisierung mit grafischem Editor, RAG und Tools.', teamsbot: 'Integriere einen AI-Bot in deine Microsoft Teams Meetings und Channels.', workspace: 'Nutze den gemeinsamen AI Workspace: Chats, Tools und Kontext pro Instanz.', commcoach: 'CommCoach: Kommunikation trainieren mit KI-gestütztem Coaching und Feedback.', }; function _storeCardDescription(feature: StoreFeature, t: (key: string, fallback?: string) => string): string { const raw = (feature.description && feature.description.trim()) || STORE_FEATURE_DESCRIPTION_FALLBACK[feature.featureCode]; return raw ? t(raw) : ''; } interface FeatureCardProps { feature: StoreFeature; mandates: UserMandate[]; actionLoading: string | null; onActivate: (code: string, mandateId?: string) => void; onDeactivate: (code: string, mandateId: string, instanceId: string) => void; } const FeatureCard: React.FC = ({ feature, mandates, actionLoading, onActivate, onDeactivate, }) => { const { t } = useLanguage(); const isProcessing = actionLoading === feature.featureCode; const icon = FEATURE_ICONS[feature.featureCode]; const activeInstances = feature.instances.filter(inst => inst.isActive); const hasActive = activeInstances.length > 0; return (
{icon && {icon}}

{t(feature.label)}

{_storeCardDescription(feature, t)}

{activeInstances.length > 0 && (
{activeInstances.map((inst) => (
{inst.mandateName || inst.label}
))}
)} {activeInstances.length === 0 && (
{t('Verfügbar')}
)}
{feature.canActivate && mandates.map((m) => ( ))}
); }; const StorePage: React.FC = () => { const { t } = useLanguage(); const { features, mandates, subscriptionInfo, loading, actionLoading, error, activate, deactivate } = useStore(); return (

{t('Feature Store')}

{t('Aktiviere Features für dein Konto. Deine Daten sind isoliert und nur für dich sichtbar.')}

{subscriptionInfo && subscriptionInfo.plan && (
{t('Plan:')} {subscriptionInfo.plan} {subscriptionInfo.maxFeatureInstances != null ? <>{t('Module')}: {subscriptionInfo.currentFeatureInstances}/{subscriptionInfo.maxFeatureInstances} : <>{subscriptionInfo.currentFeatureInstances} {t('Module aktiv')} {subscriptionInfo.includedModules != null && subscriptionInfo.includedModules > 0 && ( <> ({subscriptionInfo.includedModules} {t('inklusive')}) )} } {subscriptionInfo.maxDataVolumeMB != null && ( {t('Speicher')}:{' '} {formatBinaryDataSizeFromMebibytes(subscriptionInfo.maxDataVolumeMB)} )} {subscriptionInfo.budgetAiPerUserCHF != null && subscriptionInfo.budgetAiPerUserCHF > 0 && ( {t('AI-Budget')}: {subscriptionInfo.budgetAiPerUserCHF} CHF / User )} {subscriptionInfo.status === 'TRIALING' && subscriptionInfo.trialEndsAt && ( {t('Testphase endet am')}: {new Date(subscriptionInfo.trialEndsAt).toLocaleDateString()} )}
)} {error &&
{error}
} {loading ? (
{t('Lade Features…')}
) : features.length === 0 ? (
{t('Keine Features im Store verfügbar.')}
) : (
{features.map((feature) => ( ))}
)}
); }; export default StorePage;