mirror of
https://github.com/temetro/temetro.git
synced 2026-08-29 11:56:54 +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>
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import { Suspense, useEffect, useState } from "react";
|
|
|
|
import { AuthShell, FormAlert } from "@/components/auth/auth-ui";
|
|
import { Button } from "@/components/ui/button";
|
|
import { authClient } from "@/lib/auth-client";
|
|
|
|
function VerifyEmailInner() {
|
|
const router = useRouter();
|
|
const params = useSearchParams();
|
|
const email = params.get("email") ?? "";
|
|
const { data: session } = authClient.useSession();
|
|
const [notice, setNotice] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [sending, setSending] = useState(false);
|
|
|
|
// After the emailed link verifies the address, Better Auth auto-signs the
|
|
// user in and redirects here — at which point a session exists.
|
|
useEffect(() => {
|
|
if (session?.user) router.replace("/");
|
|
}, [session, router]);
|
|
|
|
const resend = async () => {
|
|
if (!email || sending) return;
|
|
setSending(true);
|
|
setError(null);
|
|
setNotice(null);
|
|
const { error: err } = await authClient.sendVerificationEmail({
|
|
email,
|
|
callbackURL: `${window.location.origin}/verify-email`,
|
|
});
|
|
setSending(false);
|
|
if (err) {
|
|
setError(err.message ?? "Could not resend the verification email.");
|
|
return;
|
|
}
|
|
setNotice("Verification email sent. Check your inbox.");
|
|
};
|
|
|
|
return (
|
|
<AuthShell
|
|
footer={
|
|
<Link className="text-foreground hover:underline" href="/login">
|
|
Back to sign in
|
|
</Link>
|
|
}
|
|
subtitle={
|
|
email ? (
|
|
<>
|
|
We sent a verification link to{" "}
|
|
<span className="text-foreground">{email}</span>. Open it to activate
|
|
your account.
|
|
</>
|
|
) : (
|
|
"Open the verification link we emailed you to activate your account."
|
|
)
|
|
}
|
|
title="Check your inbox"
|
|
>
|
|
<div className="flex flex-col gap-4">
|
|
{notice && <FormAlert tone="success">{notice}</FormAlert>}
|
|
{error && <FormAlert>{error}</FormAlert>}
|
|
<Button
|
|
className="w-full"
|
|
onClick={() => router.push("/")}
|
|
type="button"
|
|
>
|
|
I've verified — continue
|
|
</Button>
|
|
{email && (
|
|
<Button
|
|
className="w-full"
|
|
disabled={sending}
|
|
onClick={resend}
|
|
type="button"
|
|
variant="outline"
|
|
>
|
|
{sending ? "Sending…" : "Resend email"}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</AuthShell>
|
|
);
|
|
}
|
|
|
|
export default function VerifyEmailPage() {
|
|
return (
|
|
<Suspense>
|
|
<VerifyEmailInner />
|
|
</Suspense>
|
|
);
|
|
}
|