import { useQuery } from "@tanstack/react-query"; import { isEmpty } from "lodash"; import { JSX } from "react"; import { Provider, useSelector } from "react-redux"; import { Navigate, Outlet, Route, Routes as Router, useMatch, useParams, } from "react-router-dom"; import { rulesEngineSelectors } from "@dndbeyond/character-rules-engine/es"; import { GameLogContextProvider } from "@dndbeyond/game-log-components"; import { PddContextProvider } from "@dndbeyond/pocket-dimension-dice/components/PddContextProvider"; import { Context as DiceWorkerContext } from "@dndbeyond/pocket-dimension-dice/context"; import configureSheetStore from "~/tools/js/CharacterSheet/store/configureStore"; import config, { authEndpoint } from "../config"; import { useAuth } from "../contexts/Authentication"; import { getCharacters } from "../helpers/characterServiceApi"; import { getStatus } from "../state/selectors/characterUtils"; import { MyCharacters } from "../subApps/listing"; import { getUrlWithParams } from "../tools/js/CharacterBuilder/utils/navigationUtils"; import { CharacterStatusEnum } from "../types"; import MaxCharactersMessage from "./max-characters-message"; import SheetBuilderApp from "./sheet-builder-app"; const BASE_PATHNAME = config.basePathname; const store = configureSheetStore(); /** * * RequireAuth checks if you are logged in and redirects to the login page if you are not. * To use just wrap your element prop in a Route component in it. * For example a the Character Sheet has a AnonymousRoute view so it is not wrapped in a RequireAuth. */ function RequireAuth({ children }: { children: JSX.Element }) { let user = useAuth(); const isCharactersListing = useMatch({ path: BASE_PATHNAME, end: true }); // loading state if (user === undefined) { return null; } // not authenticated if (user === null) { // Should only ever happen in local dev when not using app-shell locally // if you remove this it will reveal a memory leak :) if (process.env.NODE_ENV === "development" && isCharactersListing) { return (
{"YOU ARE NOT LOGGED IN. THIS VIEW IS HANDLED BY APP SHELL"}
); } window.location.href = getUrlWithParams(); } // authenticated return children; } /** * * GetCharacters uses the renderProp pattern to handle requiring getting all characters. when a user is logged in. * This ensure that locked users are not able to use this part of the app until the resolved thier locked characters. * This was setup this way to avoid having to make a new api call to get the characters when the user is Anonymous. * Therefore, keeping our api calls to a minimum and minimizing errors from failed api calls. * Note: this could be simpler but complexity was added to make typescript happy. */ function GetCallCharacters({ children, }: { children: (QueryResponseData) => void; }): JSX.Element { const isCharactersListing = useMatch({ path: BASE_PATHNAME, end: true }); const { isLoading, data, refetch, error } = useQuery({ queryKey: ["repoData"], queryFn: () => getCharacters(), refetchOnWindowFocus: false, refetchInterval: 24 * 60 * 60 * 1000, }); if (isLoading) { return <>{null}; } if (!error && data?.data?.canUnlockCharacters && !isCharactersListing) { return ( <> ); } return <>{children({ isLoading, data, refetch, error })}; } function CharacterSheetLayout() { return ( ); } function CharacterSheetProviders() { const user = useAuth(); const isUserEmpty = isEmpty(user); const campaignId = useSelector(rulesEngineSelectors.getCampaign)?.id; const { characterId } = useParams(); return ( {}} nodeEnv={process.env.NODE_ENV} env={config.environment} user={isUserEmpty ? undefined : user} gameId={campaignId ? String(campaignId) : undefined} > ); } export const Routes = () => { const user = useAuth(); return ( {/* Routes that need the Redux store + GameLog/Pdd providers */} }> {({ data }) => { const numberOfCharacters = data?.data?.characters?.filter( (character) => getStatus(character) === CharacterStatusEnum.Active ).length ?? 0; const maxCharacterSlotsAllowed = data?.data?.characterSlotLimit ?? Infinity; const hasMaxCharacters = numberOfCharacters >= maxCharacterSlotsAllowed; if (hasMaxCharacters) { return ( ); } return ; }} } /> {() => } } /> {/* Character routes */} {() => } } /> {() => } ) : ( ) } /> {() => } ) : ( ) } /> {/* Routes that don't need the store */} } /> {({ isLoading, data, refetch, error }) => ( )} } /> } /> ); };