create-plume-react-project
Version:
CLI to create Plume React Project
123 lines (111 loc) • 3.11 kB
text/typescript
import {
Ref, useCallback, useEffect, useRef,
} from 'react';
import {
consumeRedirectUrl,
isSafeAppUrl,
setRedirectUrl,
} from './RedirectManager';
const REDIRECT_PARAM: string = 'redirect';
/**
* Append a redirect param to the given login URL.
* @param loginUrl
* @param targetUrl
* @param paramName
*/
export function appendRedirectParam(
loginUrl: string,
targetUrl: string,
paramName: string = REDIRECT_PARAM,
): string {
const u: URL = new URL(loginUrl, window.location.origin);
u.searchParams.set(paramName, targetUrl);
return u.pathname + u.search + u.hash; // always relative
}
type RequireAccessOpts = {
hasAccess: boolean,
currentUrl: string, // the router provides this
fallbackUrl: string,
navigate: (url: string) => void, // the router's navigate/push function
useQueryParam?: boolean,
paramName?: string,
shouldRedirect?: boolean, // skipping redirect on login page to avoid loop
};
/**
* A hook to require access to a page, otherwise redirect to a fallback URL (e.g. login page).
* @param hasAccess
* @param currentUrl
* @param loginUrl
* @param navigate
* @param useQueryParam
* @param paramName
*/
export function useRequireAccess(
{
hasAccess,
currentUrl,
fallbackUrl,
navigate,
useQueryParam = true,
paramName = REDIRECT_PARAM,
shouldRedirect = true,
}: Readonly<RequireAccessOpts>,
) {
useEffect(() => {
if (!hasAccess && shouldRedirect) {
setRedirectUrl(currentUrl);
const next: string = useQueryParam
? appendRedirectParam(fallbackUrl, currentUrl, paramName)
: fallbackUrl;
navigate(next);
}
}, [hasAccess, currentUrl, fallbackUrl, navigate, useQueryParam, paramName, shouldRedirect]);
return { allowed: hasAccess };
}
type ReturnAfterAccessOpts = {
hasAccess: boolean,
navigate: (url: string) => void,
defaultRedirect?: string,
paramName?: string,
};
/**
* A hook to redirect the user after login/access to the intended page.
* @param hasAccess
* @param navigate
* @param defaultRedirect
* @param paramName
*/
export function useReturnAfterAccess(
{
hasAccess,
navigate,
defaultRedirect = '/',
paramName = REDIRECT_PARAM,
}: Readonly<ReturnAfterAccessOpts>,
): void {
const redirectRef: Ref<string | null> = useRef<string | null>(null);
const returnToRedirection: () => void = useCallback(() => {
if (redirectRef.current === null) {
// 1. check session storage
let target: string | null = consumeRedirectUrl();
// 2. fallback to query param
if (!target) {
const qp: string | null = new URLSearchParams(window.location.search).get(paramName!);
if (qp) {
target = qp;
}
}
// 3. validate or fallback
if (!isSafeAppUrl(target)) {
target = defaultRedirect!;
}
redirectRef.current = target;
}
navigate(`${redirectRef.current ?? defaultRedirect}`);
}, [defaultRedirect, navigate, paramName]);
useEffect(() => {
if (hasAccess) {
returnToRedirection();
}
}, [hasAccess, returnToRedirection]);
}