Files
dndbeyond_src/ddb_main/components/Layout/Layout.tsx
T

87 lines
2.6 KiB
TypeScript

import clsx from "clsx";
import { FC, HTMLAttributes } from "react";
import { createPortal } from "react-dom";
import { useMatch, useSearchParams } from "react-router-dom";
import { NavigationMenu } from "@dndbeyond/navigation-menu";
import { Footer } from "@dndbeyond/ttui/components/Footer";
import config from "~/config";
import useUser from "~/hooks/useUser";
import styles from "./styles.module.css";
const BASE_PATHNAME = config.basePathname;
const NOTIFICATIONS_CACHE_KEY = "dndbNotificationsCache";
const seedNotificationsCache = () => {
try {
const existing = localStorage.getItem(NOTIFICATIONS_CACHE_KEY);
const parsed = existing ? JSON.parse(existing) : null;
const isValid =
parsed &&
typeof parsed.expiresAtMs === "number" &&
Date.now() < parsed.expiresAtMs;
if (!isValid) {
localStorage.setItem(
NOTIFICATIONS_CACHE_KEY,
JSON.stringify({
expiresAtMs: Date.now() + 60 * 60 * 1000,
counts: {
unreadNotifications: null,
unreadMessages: null,
unreadReports: null,
unreadAppeals: null,
},
})
);
}
} catch {
// localStorage unavailable
}
};
interface LayoutProps extends HTMLAttributes<HTMLDivElement> {}
export const Layout: FC<LayoutProps> = ({ children, ...props }) => {
const [searchParams] = useSearchParams();
const isVttView = searchParams.get("view") === "vtt";
const isDev = process.env.NODE_ENV === "development";
const user = useUser();
const matchSheet = useMatch(`${BASE_PATHNAME}/:characterId/`);
// Don't show the navigation in production
if (!isDev) return <>{children}</>;
// Seed the notifications cache so NavigationMenu skips the JWT fetch in local
// dev, where the auth service is not proxied.
seedNotificationsCache();
const navigationUser =
user && user.avatarUrl
? {
displayName: user.displayName,
avatarUrl: user.avatarUrl,
subscriptionTier: user.subscriptionTier as "Master",
roles: user.roles,
}
: undefined;
return (
<div {...props}>
{!isVttView &&
createPortal(
<NavigationMenu user={navigationUser} />,
document.getElementById("header-wrapper")!
)}
<div className={clsx(["container", styles.devContainer])}>{children}</div>
{!isVttView &&
(!matchSheet ||
matchSheet.pathname === "/characters/builder" ||
matchSheet.pathname === "/characters/premade") &&
createPortal(<Footer />, document.getElementById("footer-wrapper")!)}
</div>
);
};