hellth-hub/apps/admin/app/_components/auth-guard.tsx
Sebastian Mayer 2436dd9cac Initial commit: Hellth Hub monorepo
Mobile-first Web-App fuer persoenliches Health-, Ernaehrungs-,
Aktivitaets- und Einkaufs-Tracking im Home-Lab-Betrieb.

Stack: pnpm Monorepo - Next.js 16 + React 19 + Tailwind (apps/admin) -
Hono + tRPC + Better Auth mit 2FA und Passkeys (packages/api) -
PostgreSQL + Drizzle ORM (packages/db) - Docker fuer Postgres und pgAdmin.

Module:
- Auth mit Login, 2FA (TOTP + Backup-Codes), Passkeys und Admin-Recovery
- Tagesansicht mit Essen, Trinken, Gewicht, Aktivitaeten und Coach-Hinweisen
- Lebensmittelkatalog mit Naehrwerten, Portionen, OCR/Barcode/Open Food Facts
- Rezepte mit Live-Naehrwertberechnung und Community-Vorschlaegen
- Essensplanung und Wochenbudget mit Sportbonus
- Einkaufslisten mit Maerkten und Laufweg-Sortierung
- Hellfireclub mit Aktivitaetspunkten und gemeinsamer Aktivitaetsplanung
- Wearables-Vorbereitung (Fitbit / Google Fit / Health Connect)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 22:15:16 +02:00

109 lines
3 KiB
TypeScript

"use client";
import { useSession } from "@/lib/auth-client";
import { hasCompletedHealthProfile } from "@/lib/hellth-data";
import { trpc } from "@/lib/trpc";
import { usePathname, useRouter } from "next/navigation";
import { useEffect } from "react";
const PLATFORM_ALLOWED_PREFIXES = [
"/",
"/aktivitaeten",
"/benutzer",
"/einstellungen",
"/erinnerungen",
"/hellfireclub",
"/einkaufsliste",
"/lebensmittel",
"/maerkte",
"/onboarding",
"/planung",
"/rezepte",
"/ziele",
"/wochenbudget",
"/sicherheit/2fa-setup",
];
export function AuthGuard({ children }: { children: React.ReactNode }) {
const { data: session, isPending } = useSession();
const router = useRouter();
const pathname = usePathname();
const platformAdminEmails = (
process.env.NEXT_PUBLIC_PLATFORM_ADMIN_EMAILS ?? "admin@onl1.eu"
)
.split(",")
.map((email) => email.trim().toLowerCase())
.filter(Boolean);
const userRole = ((session?.user as { role?: string } | undefined)?.role ?? "").toLowerCase();
const userEmail = (session?.user?.email ?? "").toLowerCase();
const isPlatformAdmin =
userRole === "admin" ||
userRole === "platform_admin" ||
platformAdminEmails.includes(userEmail);
const securityQuery = trpc.security.status.useQuery(undefined, {
enabled: !!session && !isPending,
retry: false,
});
const requiresTwoFactorSetup = securityQuery.data?.requiresTwoFactorSetup ?? false;
useEffect(() => {
if (!isPending && !session) {
router.push("/login");
return;
}
if (!isPending && session) {
if (securityQuery.isLoading) return;
if (requiresTwoFactorSetup && !pathname.startsWith("/sicherheit/2fa-setup")) {
router.push("/sicherheit/2fa-setup");
return;
}
if (isPlatformAdmin) {
const isAllowed = PLATFORM_ALLOWED_PREFIXES.some((prefix) =>
prefix === "/" ? pathname === "/" : pathname.startsWith(prefix),
);
if (!isAllowed) {
router.push("/");
return;
}
} else {
const userKey = session.user.id ?? session.user.email ?? null;
const onboardingComplete = hasCompletedHealthProfile(userKey);
if (!onboardingComplete && !pathname.startsWith("/onboarding")) {
router.push("/onboarding");
return;
}
if (pathname.startsWith("/benutzer") || pathname.startsWith("/einstellungen")) {
router.push("/");
}
}
}
}, [
session,
isPending,
isPlatformAdmin,
pathname,
router,
requiresTwoFactorSetup,
securityQuery.isLoading,
]);
if (isPending || (!!session && securityQuery.isLoading)) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#0F766E] border-t-transparent rounded-full animate-spin" />
<p className="text-sm text-gray-400">Laden...</p>
</div>
</div>
);
}
if (!session) return null;
return <>{children}</>;
}