der tenant admin hat eine neue, mobil unterstützende UI, login redirect funktioniert, typescript fehler wurden bereinigt. Neue Blog Posts von ChatGPT eingebaut, übersetzt von Gemini 2.5

This commit is contained in:
Codex Agent
2025-11-05 19:27:10 +01:00
parent adb93b5f9d
commit c6ac04eb15
44 changed files with 1995 additions and 1949 deletions

View File

@@ -11,6 +11,7 @@ import {
} from "..";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { FrostedSurface } from "../../components/tenant/frosted-surface";
import { ADMIN_BILLING_PATH, ADMIN_WELCOME_SUMMARY_PATH } from "../../constants";
import { useTenantPackages } from "../hooks/useTenantPackages";
import { Package } from "../../api";
@@ -115,16 +116,16 @@ export default function WelcomePackagesPage() {
const renderPackageList = () => {
if (packagesState.status === "loading") {
return (
<div className="flex items-center gap-3 rounded-2xl bg-brand-sky-soft/60 p-6 text-sm text-brand-navy/80">
<Loader2 className="size-5 animate-spin text-brand-rose" />
<FrostedSurface className="flex items-center gap-3 border border-white/20 p-6 text-sm text-slate-600 shadow-md shadow-rose-200/20 dark:border-slate-800/70 dark:bg-slate-950/80 dark:text-slate-300">
<Loader2 className="size-5 animate-spin text-rose-400" />
{t("packages.state.loading")}
</div>
</FrostedSurface>
);
}
if (packagesState.status === "error") {
return (
<Alert variant="destructive">
<Alert variant="destructive" className="border-rose-300/60 bg-rose-50/80 text-rose-700 dark:border-rose-500/40 dark:bg-rose-500/15 dark:text-rose-200">
<AlertCircle className="size-4" />
<AlertTitle>{t("packages.state.errorTitle")}</AlertTitle>
<AlertDescription>{packagesState.message ?? t("packages.state.errorDescription")}</AlertDescription>
@@ -134,7 +135,7 @@ export default function WelcomePackagesPage() {
if (packagesState.status === "success" && packagesState.catalog.length === 0) {
return (
<Alert variant="default" className="border-brand-rose-soft bg-brand-card/60 text-brand-navy">
<Alert variant="default" className="border-white/20 bg-white/70 text-slate-700 shadow-sm dark:border-slate-800/60 dark:bg-slate-950/80 dark:text-slate-300">
<AlertTitle>{t("packages.state.emptyTitle")}</AlertTitle>
<AlertDescription>{t("packages.state.emptyDescription")}</AlertDescription>
</Alert>
@@ -170,59 +171,70 @@ export default function WelcomePackagesPage() {
}
return (
<div
<FrostedSurface
key={pkg.id}
className="flex flex-col gap-4 rounded-2xl border border-brand-rose-soft bg-brand-card p-6 shadow-brand-primary"
className="flex flex-col gap-4 border border-white/20 p-6 text-slate-900 shadow-md shadow-rose-200/20 transition-colors duration-200 dark:border-slate-800/70 dark:bg-slate-950/80 dark:text-slate-100"
>
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-brand-rose">
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-rose-400 dark:text-rose-200">
{t(isSubscription ? "packages.card.subscription" : "packages.card.creditPack")}
</p>
<h3 className="text-xl font-semibold text-brand-slate">{pkg.name}</h3>
<h3 className="text-xl font-semibold text-slate-900 dark:text-slate-100">{pkg.name}</h3>
</div>
<span className="text-lg font-medium text-brand-rose">{priceText}</span>
<span className="text-lg font-medium text-rose-400 dark:text-rose-200">{priceText}</span>
</div>
<p className="text-sm text-brand-navy/80">
<p className="text-sm text-slate-600 dark:text-slate-300">
{pkg.max_photos
? t("packages.card.descriptionWithPhotos", { count: pkg.max_photos })
: t("packages.card.description")}
</p>
<div className="flex flex-wrap items-center gap-2 text-xs uppercase tracking-wide text-brand-rose">
<div className="flex flex-wrap items-center gap-2 text-xs uppercase tracking-wide text-rose-400 dark:text-rose-200">
{badges.map((badge) => (
<span key={badge} className="rounded-full bg-brand-rose-soft px-3 py-1">
<span key={badge} className="rounded-full bg-rose-100/80 px-3 py-1 shadow-inner shadow-rose-200/60 dark:bg-rose-500/20 dark:text-rose-100">
{badge}
</span>
))}
{featureLabels.map((feature) => (
<span key={feature} className="rounded-full bg-brand-rose-soft px-3 py-1">
<span key={feature} className="rounded-full bg-rose-100/80 px-3 py-1 shadow-inner shadow-rose-200/60 dark:bg-rose-500/20 dark:text-rose-100">
{feature}
</span>
))}
{isActive && (
<span className="rounded-full bg-brand-sky-soft px-3 py-1 text-brand-navy">
{isActive ? (
<span className="rounded-full bg-sky-100/80 px-3 py-1 text-sky-600 shadow-inner shadow-sky-200/40 dark:bg-sky-500/20 dark:text-sky-200">
{t("packages.card.active")}
</span>
)}
) : null}
</div>
<Button
size="lg"
className="mt-auto rounded-full bg-brand-rose text-white shadow-lg shadow-rose-300/40 hover:bg-[var(--brand-rose-strong)]"
onClick={() => handleSelectPackage(pkg)}
>
{t("packages.card.select")}
<ArrowRight className="ml-2 size-4" />
</Button>
{purchased && (
<p className="text-xs text-brand-rose">
{t("packages.card.purchased", {
date: purchased.purchased_at
? dateFormatter.format(new Date(purchased.purchased_at))
: t("packages.card.purchasedUnknown"),
})}
</p>
)}
</div>
<div className="flex flex-col gap-3 pt-2">
<Button
size="lg"
className="rounded-full bg-gradient-to-r from-[#ff5f87] via-[#ec4899] to-[#6366f1] text-white shadow-lg shadow-rose-300/30 hover:from-[#ff4470] hover:via-[#ec4899] hover:to-[#4f46e5]"
onClick={() => handleSelectPackage(pkg)}
disabled={isActive}
>
{isActive ? t("packages.card.active") : t("packages.card.select")}
<ArrowRight className="ml-2 size-4" />
</Button>
{purchased ? (
<p className="text-xs text-slate-500 dark:text-slate-400">
{t("packages.card.purchased", {
date: purchased.purchased_at
? dateFormatter.format(new Date(purchased.purchased_at))
: t("packages.card.purchasedUnknown"),
})}
</p>
) : null}
<Button
variant="ghost"
className="text-sm text-slate-600 hover:text-rose-400 dark:text-slate-400 dark:hover:text-rose-200"
onClick={() => navigate(ADMIN_BILLING_PATH)}
>
<CreditCard className="mr-2 h-4 w-4" />
{t("packages.card.viewBilling")}
</Button>
</div>
</FrostedSurface>
);
})}
</div>