87 lines
2.6 KiB
TypeScript
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>
|
|
);
|
|
};
|