feat: unify tenant admin ui and add photo moderation

This commit is contained in:
Codex Agent
2025-11-07 13:50:55 +01:00
parent 9cc9950b0c
commit 253239455b
14 changed files with 995 additions and 583 deletions

View File

@@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { AdminLayout } from '../components/AdminLayout';
@@ -13,10 +12,11 @@ import { getTenantPackagesOverview, getTenantPaddleTransactions, PaddleTransacti
import { isAuthError } from '../auth/tokens';
import {
TenantHeroCard,
FrostedCard,
FrostedSurface,
tenantHeroPrimaryButtonClass,
tenantHeroSecondaryButtonClass,
SectionCard,
SectionHeader,
} from '../components/tenant';
type PackageWarning = { id: string; tone: 'warning' | 'danger'; message: string };
@@ -196,25 +196,20 @@ export default function BillingPage() {
<BillingSkeleton />
) : (
<>
<FrostedCard className="mt-6 border border-white/20">
<CardHeader className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<CardTitle className="flex items-center gap-2 text-xl text-slate-900">
<Sparkles className="h-5 w-5 text-pink-500" />
{t('billing.sections.overview.title')}
</CardTitle>
<CardDescription className="text-sm text-slate-600">
{t('billing.sections.overview.description')}
</CardDescription>
</div>
<Badge className={activePackage ? 'bg-pink-500/10 text-pink-700 dark:bg-pink-500/20 dark:text-pink-200' : 'bg-slate-200 text-slate-700 dark:bg-slate-800 dark:text-slate-200'}>
{activePackage ? activePackage.package_name : t('billing.sections.overview.emptyBadge')}
</Badge>
</CardHeader>
<CardContent>
{activePackage ? (
<div className="space-y-4">
{activeWarnings.length > 0 && (
<SectionCard className="mt-6 space-y-5">
<SectionHeader
eyebrow={t('billing.sections.overview.badge', 'Aktuelles Paket')}
title={t('billing.sections.overview.title')}
description={t('billing.sections.overview.description')}
endSlot={(
<Badge className={activePackage ? 'bg-pink-500/10 text-pink-700 dark:bg-pink-500/20 dark:text-pink-200' : 'bg-slate-200 text-slate-700 dark:bg-slate-800 dark:text-slate-200'}>
{activePackage ? activePackage.package_name : t('billing.sections.overview.emptyBadge')}
</Badge>
)}
/>
{activePackage ? (
<div className="space-y-4">
{activeWarnings.length > 0 && (
<div className="space-y-2">
{activeWarnings.map((warning) => (
<Alert
@@ -263,20 +258,15 @@ export default function BillingPage() {
) : (
<EmptyState message={t('billing.sections.overview.empty')} />
)}
</CardContent>
</FrostedCard>
</SectionCard>
<FrostedCard className="border border-white/20">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-xl text-slate-900">
<Sparkles className="h-5 w-5 text-amber-500" />
{t('billing.sections.packages.title')}
</CardTitle>
<CardDescription className="text-sm text-slate-600">
{t('billing.sections.packages.description')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<SectionCard className="space-y-4">
<SectionHeader
eyebrow={t('billing.sections.packages.badge', 'Pakete')}
title={t('billing.sections.packages.title')}
description={t('billing.sections.packages.description')}
/>
<div className="space-y-3">
{packages.length === 0 ? (
<EmptyState message={t('billing.sections.packages.empty')} />
) : (
@@ -295,20 +285,16 @@ export default function BillingPage() {
);
})
)}
</CardContent>
</FrostedCard>
</div>
</SectionCard>
<FrostedCard className="border border-white/20">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-xl text-slate-900">
<Sparkles className="h-5 w-5 text-sky-500" />
{t('billing.sections.transactions.title')}
</CardTitle>
<CardDescription className="text-sm text-slate-600">
{t('billing.sections.transactions.description')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<SectionCard className="space-y-4">
<SectionHeader
eyebrow={t('billing.sections.transactions.badge', 'Transaktionen')}
title={t('billing.sections.transactions.title')}
description={t('billing.sections.transactions.description')}
/>
<div className="space-y-3">
{transactions.length === 0 ? (
<EmptyState message={t('billing.sections.transactions.empty')} />
) : (
@@ -341,8 +327,8 @@ export default function BillingPage() {
)}
</Button>
)}
</CardContent>
</FrostedCard>
</div>
</SectionCard>
</>
)}