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>
53 lines
1.4 KiB
TypeScript
53 lines
1.4 KiB
TypeScript
import { createAuthClient } from "better-auth/react";
|
|
import { twoFactorClient } from "better-auth/client/plugins";
|
|
import { passkeyClient } from "@better-auth/passkey/client";
|
|
|
|
const OTP_STEP_STORAGE_KEY = "hellthhub_login_step_otp";
|
|
const OTP_STEP_TTL_MS = 5 * 60 * 1000;
|
|
|
|
function setOtpStepActive() {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
window.localStorage.setItem(
|
|
OTP_STEP_STORAGE_KEY,
|
|
JSON.stringify({ expiresAt: Date.now() + OTP_STEP_TTL_MS }),
|
|
);
|
|
} catch {
|
|
// ignore storage access issues
|
|
}
|
|
}
|
|
|
|
export function resolveApiBaseUrl() {
|
|
const configured = process.env.NEXT_PUBLIC_API_URL;
|
|
if (configured) return configured;
|
|
|
|
if (process.env.NODE_ENV === "production") {
|
|
throw new Error("NEXT_PUBLIC_API_URL muss in Produktion gesetzt sein");
|
|
}
|
|
|
|
if (typeof window !== "undefined") {
|
|
const host = window.location.hostname;
|
|
if (host.includes("::1")) {
|
|
return "http://127.0.0.1:3002";
|
|
}
|
|
}
|
|
|
|
return "http://localhost:3002";
|
|
}
|
|
|
|
export const authClient = createAuthClient({
|
|
baseURL: resolveApiBaseUrl(),
|
|
plugins: [
|
|
twoFactorClient({
|
|
onTwoFactorRedirect: async () => {
|
|
setOtpStepActive();
|
|
if (typeof window !== "undefined" && window.location.pathname !== "/login/otp") {
|
|
window.location.assign("/login/otp");
|
|
}
|
|
},
|
|
}),
|
|
passkeyClient(),
|
|
],
|
|
});
|
|
|
|
export const { signIn, signOut, signUp, useSession } = authClient;
|