This commit is contained in:
@@ -6,16 +6,16 @@ import { auth, type Org, type Role, type SessionUser } from "@/lib/api";
|
||||
export type { Org, Role, SessionUser };
|
||||
|
||||
interface AuthContextType {
|
||||
user: SessionUser | null;
|
||||
org: Org | null;
|
||||
/** True for owner and admin — the roles the /api/settings and /api/org routes require. */
|
||||
isAdmin: boolean;
|
||||
user: SessionUser | null;
|
||||
org: Org | null;
|
||||
/** True for owner and admin the roles the /api/settings and /api/org routes require. */
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({ user: null, org: null, isAdmin: false });
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,86 +23,73 @@ export function useAuth() {
|
||||
* /setup live outside the group, so no pathname guard is needed here.
|
||||
*/
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<SessionUser | null>(null);
|
||||
const [org, setOrg] = useState<Org | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [user, setUser] = useState<SessionUser | null>(null);
|
||||
const [org, setOrg] = useState<Org | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const status = await auth.bootstrapStatus();
|
||||
if (status.needs_setup) {
|
||||
window.location.href = "/setup";
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
try {
|
||||
const status = await auth.bootstrapStatus();
|
||||
if (status.needs_setup) {
|
||||
window.location.href = "/setup";
|
||||
return;
|
||||
}
|
||||
|
||||
const me = await auth.me();
|
||||
if (cancelled) return;
|
||||
setUser(me.user);
|
||||
setOrg(me.org);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
if (cancelled) return;
|
||||
const status = (err as { status?: number }).status;
|
||||
if (status === 401) {
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
setError((err as Error).message || "Unable to load your session.");
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
const me = await auth.me();
|
||||
if (cancelled) return;
|
||||
setUser(me.user);
|
||||
setOrg(me.org);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
if (cancelled) return;
|
||||
const status = (err as { status?: number }).status;
|
||||
if (status === 401) {
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
setError((err as Error).message || "Unable to load your session.");
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-background">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (error || !user) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-background p-4">
|
||||
<div className="w-full max-w-md rounded-xl border border-border bg-surface p-6 text-center">
|
||||
<h1 className="text-base font-semibold text-text-primary">Can't load your session</h1>
|
||||
<p className="mt-2 text-sm text-text-secondary">
|
||||
{error ?? "Unable to load your session."}
|
||||
</p>
|
||||
<div className="mt-5 flex justify-center gap-2">
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="rounded-lg bg-accent px-3 py-2 text-sm font-medium text-white"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<a
|
||||
href="/login"
|
||||
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-secondary"
|
||||
>
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-background">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isAdmin = user.role === "owner" || user.role === "admin";
|
||||
if (error || !user) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-background p-4">
|
||||
<div className="w-full max-w-md rounded-xl border border-border bg-surface p-6 text-center">
|
||||
<h1 className="text-base font-semibold text-text-primary">Can't load your session</h1>
|
||||
<p className="mt-2 text-sm text-text-secondary">{error ?? "Unable to load your session."}</p>
|
||||
<div className="mt-5 flex justify-center gap-2">
|
||||
<button onClick={() => window.location.reload()} className="rounded-lg bg-accent px-3 py-2 text-sm font-medium text-white">
|
||||
Retry
|
||||
</button>
|
||||
<a href="/login" className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-secondary">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, org, isAdmin }}>{children}</AuthContext.Provider>
|
||||
);
|
||||
const isAdmin = user.role === "owner" || user.role === "admin";
|
||||
|
||||
return <AuthContext.Provider value={{ user, org, isAdmin }}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user