63 lines
1.4 KiB
TypeScript
63 lines
1.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Center, Spinner } from "./chakra-compat";
|
|
import { Navigate } from "react-router";
|
|
|
|
type AuthGuardProps = {
|
|
children: React.ReactNode;
|
|
fetchCurrentUser: () => Promise<unknown>;
|
|
redirectTo?: string;
|
|
loadingFallback?: React.ReactNode;
|
|
authenticatedWrapper?: (children: React.ReactNode) => React.ReactNode;
|
|
};
|
|
|
|
export function AuthGuard({
|
|
children,
|
|
fetchCurrentUser,
|
|
redirectTo = "/login",
|
|
loadingFallback,
|
|
authenticatedWrapper
|
|
}: AuthGuardProps) {
|
|
const [state, setState] = useState<{ loading: boolean; authenticated: boolean }>({
|
|
loading: true,
|
|
authenticated: false
|
|
});
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
fetchCurrentUser()
|
|
.then(() => {
|
|
if (!cancelled) {
|
|
setState({ loading: false, authenticated: true });
|
|
}
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) {
|
|
setState({ loading: false, authenticated: false });
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [fetchCurrentUser]);
|
|
|
|
if (state.loading) {
|
|
return (
|
|
<>
|
|
{loadingFallback ?? (
|
|
<Center h="var(--app-height)">
|
|
<Spinner size="xl" />
|
|
</Center>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (!state.authenticated) {
|
|
return <Navigate to={redirectTo} replace />;
|
|
}
|
|
|
|
return <>{authenticatedWrapper ? authenticatedWrapper(children) : children}</>;
|
|
}
|