# Description of Changes Refactor frontend auth to the shared folder and hook it up to both the portal and editor so they share the same system. Also adds various tasks to help run the portal, including `task dev:portal` to spawn the portal with the backend, and `task dev:portal:proxy` to spawn the editor, portal and backend, and a reverse proxy (at localhost:3000) to allow you to use both at once to simulate how this will actually be deployed, allowing you to check whether the seamless transition between the two actually works.
43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
/**
|
|
* Gate that renders its children only for an authenticated admin.
|
|
*
|
|
* - Still loading -> `loading`
|
|
* - Signed out -> `fallback` (login screen)
|
|
* - Signed in but not admin -> calls `onForbidden` (e.g. redirect to the
|
|
* editor) and renders `forbidden` in the meantime
|
|
*/
|
|
import { useEffect, type ReactNode } from "react";
|
|
import { useAuth } from "@shared/auth/context";
|
|
|
|
export interface RequireAdminProps {
|
|
children: ReactNode;
|
|
/** Rendered when there is no session (e.g. the login panel). */
|
|
fallback: ReactNode;
|
|
/** Invoked once when an authenticated non-admin is detected. */
|
|
onForbidden: () => void;
|
|
/** Rendered while the session is still resolving. */
|
|
loading?: ReactNode;
|
|
/** Rendered for an authenticated non-admin (while `onForbidden` runs). */
|
|
forbidden?: ReactNode;
|
|
}
|
|
|
|
export function RequireAdmin({
|
|
children,
|
|
fallback,
|
|
onForbidden,
|
|
loading = null,
|
|
forbidden = null,
|
|
}: RequireAdminProps) {
|
|
const { session, loading: isLoading, isAdmin } = useAuth();
|
|
|
|
const shouldRedirect = !isLoading && !!session && !isAdmin;
|
|
useEffect(() => {
|
|
if (shouldRedirect) onForbidden();
|
|
}, [shouldRedirect, onForbidden]);
|
|
|
|
if (isLoading) return <>{loading}</>;
|
|
if (!session) return <>{fallback}</>;
|
|
if (!isAdmin) return <>{forbidden}</>;
|
|
return <>{children}</>;
|
|
}
|