mirror of
https://github.com/temetro/temetro.git
synced 2026-08-13 12:06:52 +00:00
1ecbd16404
Connects the UI-only app to the new backend over Better Auth + a small patient API client, replacing the in-memory fixture and placeholder identity. - Better Auth React client (lib/auth-client.ts) + shared access-control roles; API client (lib/api-client.ts) sending credentials cross-origin. - Designed (auth) route group: login, signup, verify-email, forgot/reset password, clinic onboarding, and accept-invite. - proxy.ts (this Next's renamed middleware) optimistic redirect + an authoritative client AppAuthGuard requiring a session and active clinic. - Real identity in nav-user (useSession + sign out); the unused team switcher repurposed as an organization (clinic) switcher; Care-team settings tab manages members and invitations. - lib/patients.ts now calls the org-scoped API (types unchanged); patients table and create/edit dialog updated for async create/update. - next.config: standalone output + Dockerfile for containerized runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
2.0 KiB
TypeScript
92 lines
2.0 KiB
TypeScript
import Image from "next/image";
|
|
import type { ReactNode } from "react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
// Centered, branded shell shared by every auth page.
|
|
export function AuthShell({
|
|
title,
|
|
subtitle,
|
|
children,
|
|
footer,
|
|
}: {
|
|
title: string;
|
|
subtitle?: ReactNode;
|
|
children: ReactNode;
|
|
footer?: ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="flex min-h-full w-full flex-col items-center justify-center px-4 py-12">
|
|
<div className="w-full max-w-sm">
|
|
<div className="mb-8 flex flex-col items-center gap-3 text-center">
|
|
<Image
|
|
alt="temetro"
|
|
className="size-10"
|
|
height={40}
|
|
priority
|
|
src="/temetro-logo.png"
|
|
width={40}
|
|
/>
|
|
<div>
|
|
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
|
{subtitle && (
|
|
<p className="mt-1.5 text-sm text-muted-foreground">{subtitle}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="rounded-3xl border border-border bg-card/40 p-6 shadow-sm">
|
|
{children}
|
|
</div>
|
|
{footer && (
|
|
<div className="mt-6 text-center text-sm text-muted-foreground">
|
|
{footer}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function Field({
|
|
label,
|
|
htmlFor,
|
|
hint,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
htmlFor: string;
|
|
hint?: ReactNode;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="text-sm font-medium text-foreground" htmlFor={htmlFor}>
|
|
{label}
|
|
</label>
|
|
{children}
|
|
{hint && <p className="text-xs text-muted-foreground">{hint}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function FormAlert({
|
|
tone = "error",
|
|
children,
|
|
}: {
|
|
tone?: "error" | "success";
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<p
|
|
className={cn(
|
|
"rounded-2xl px-3 py-2 text-sm",
|
|
tone === "error"
|
|
? "bg-destructive/10 text-destructive"
|
|
: "bg-primary/10 text-primary"
|
|
)}
|
|
>
|
|
{children}
|
|
</p>
|
|
);
|
|
}
|