Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
2.3 KiB
TypeScript
92 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import {
|
|
fetchAuthConfig,
|
|
loginWithRedirect,
|
|
fetchMe,
|
|
getValidAccessToken,
|
|
clearTokens,
|
|
logout,
|
|
type AuthConfig,
|
|
} from "@/lib/auth";
|
|
|
|
interface AuthUser {
|
|
user_id: string;
|
|
email: string | null;
|
|
username: string | null;
|
|
roles: string[];
|
|
}
|
|
|
|
export function useAuth() {
|
|
const [config, setConfig] = useState<AuthConfig | null>(null);
|
|
const [user, setUser] = useState<AuthUser | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
try {
|
|
const cfg = await fetchAuthConfig();
|
|
setConfig(cfg);
|
|
} catch {
|
|
// Config blip must not wipe an existing session.
|
|
}
|
|
|
|
const token = await getValidAccessToken();
|
|
if (!token) {
|
|
setUser(null);
|
|
setIsAuthenticated(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const me = await fetchMe(token);
|
|
setUser(me);
|
|
setIsAuthenticated(true);
|
|
} catch {
|
|
// Access may have expired mid-flight — try one refresh path again.
|
|
const retry = await getValidAccessToken();
|
|
if (!retry || retry === token) {
|
|
clearTokens();
|
|
setUser(null);
|
|
setIsAuthenticated(false);
|
|
return;
|
|
}
|
|
try {
|
|
const me = await fetchMe(retry);
|
|
setUser(me);
|
|
setIsAuthenticated(true);
|
|
} catch {
|
|
clearTokens();
|
|
setUser(null);
|
|
setIsAuthenticated(false);
|
|
}
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
// Keep session alive while tab is open.
|
|
const id = window.setInterval(() => {
|
|
void getValidAccessToken();
|
|
}, 60_000);
|
|
return () => window.clearInterval(id);
|
|
}, [load]);
|
|
|
|
const login = async (redirectPath?: string) => {
|
|
await loginWithRedirect(redirectPath);
|
|
};
|
|
|
|
const signOut = async () => {
|
|
const cfg = config || (await fetchAuthConfig());
|
|
await logout(cfg);
|
|
};
|
|
|
|
return { user, config, loading, isAuthenticated, login, signOut, reload: load };
|
|
}
|