Files

208 lines
6.4 KiB
TypeScript

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import AccountMenu from "@/components/AccountMenu";
import { useAuth } from "@/components/AuthProvider";
import { businessNavItems } from "@/data/business-nav";
import { en } from "@/lib/i18n/en";
import {
getAdminDashboardOrigin,
getCustomerDashboardOrigin,
} from "@/lib/host";
import { customerDashboardHref } from "@/lib/session-handoff";
import { getDisplayName } from "@/lib/userName";
import { getBusinessProfile } from "@/services/meshkee-businesses";
import styles from "./DashboardShell.module.css";
const websiteUrl =
process.env.NEXT_PUBLIC_WEBSITE_URL?.replace(/\/$/, "") ||
"http://localhost:3000";
export default function BusinessShell({
children,
manageMeshkeeId,
}: {
children: React.ReactNode;
/** When set, nav stays on admin host under /manage/[id]/… */
manageMeshkeeId?: string;
}) {
const pathname = usePathname();
const { user, logout, isSuperAdmin, activeBusinessId } = useAuth();
const [menuOpen, setMenuOpen] = useState(false);
const [businessName, setBusinessName] = useState("");
const [businessTitle, setBusinessTitle] = useState("");
const [logoUrl, setLogoUrl] = useState<string | null>(null);
const displayName = getDisplayName(user);
const linkedBusiness =
user?.businesses.find((item) => item.isOwner) ?? user?.businesses[0];
const meshkeeId = manageMeshkeeId || activeBusinessId || null;
const manageBase = manageMeshkeeId
? `/manage/${encodeURIComponent(manageMeshkeeId)}`
: null;
const publicPath = manageBase
? pathname.replace(manageBase, "") || "/profile"
: pathname.startsWith("/business")
? pathname.replace(/^\/business/, "") || "/"
: pathname;
useEffect(() => {
if (!meshkeeId) {
setBusinessName(linkedBusiness?.name?.trim() || "");
setBusinessTitle("");
setLogoUrl(null);
return;
}
let cancelled = false;
void (async () => {
try {
const profile = await getBusinessProfile(meshkeeId);
if (cancelled) return;
const name =
profile.profile.nameEn?.trim() ||
profile.profile.nameFa?.trim() ||
linkedBusiness?.name?.trim() ||
"";
setBusinessName(name);
setBusinessTitle(profile.profile.title?.trim() || "");
setLogoUrl(profile.profile.logoUrl?.trim() || null);
} catch {
if (cancelled) return;
setBusinessName(linkedBusiness?.name?.trim() || "");
setBusinessTitle("");
setLogoUrl(null);
}
})();
return () => {
cancelled = true;
};
}, [meshkeeId, linkedBusiness?.name]);
function navHref(itemHref: string) {
if (!manageBase) return itemHref;
return `${manageBase}${itemHref === "/" ? "/profile" : itemHref}`;
}
const brandName = businessName || linkedBusiness?.name || "Business";
return (
<div className={styles.shell}>
<aside className={`${styles.sidebar} ${menuOpen ? styles.sidebarOpen : ""}`}>
<div className={styles.brand}>
{logoUrl ? (
<img
className={styles.brandLogo}
src={logoUrl}
alt=""
width={48}
height={48}
/>
) : null}
<div className={styles.brandText}>
<strong className={styles.brandBusiness} dir="auto" title={brandName}>
{brandName}
</strong>
{businessTitle ? (
<span
className={styles.brandTitle}
dir="auto"
title={businessTitle}
>
{businessTitle}
</span>
) : null}
</div>
</div>
<nav className={styles.nav} aria-label="Business">
{businessNavItems.map((item) => {
const href = navHref(item.href);
const active =
publicPath === item.href ||
publicPath.startsWith(`${item.href}/`);
return (
<Link
key={item.label}
href={href}
className={`${styles.navItem} ${active ? styles.navItemActive : ""}`}
onClick={() => setMenuOpen(false)}
>
<img src={item.icon} alt="" width={18} height={18} />
<span>{item.label}</span>
</Link>
);
})}
</nav>
<div className={styles.sidebarFoot}>
<p className={styles.userLabel}>{displayName}</p>
{isSuperAdmin || manageMeshkeeId ? (
<Link href="/businesses" className={styles.siteLink}>
Admin dashboard
</Link>
) : (
<a href={customerDashboardHref("/")} className={styles.siteLink}>
{en.openCustomerDashboard}
</a>
)}
<a href={websiteUrl} className={styles.siteLink}>
{en.viewWebsite}
</a>
<button
type="button"
className={`outline-btn ${styles.logoutBtn}`}
onClick={() => {
logout();
window.location.href =
isSuperAdmin || manageMeshkeeId
? `${getAdminDashboardOrigin()}/login`
: `${getCustomerDashboardOrigin()}/login`;
}}
>
{en.logOut}
</button>
</div>
</aside>
{menuOpen ? (
<button
type="button"
className={styles.backdrop}
aria-label="Close menu"
onClick={() => setMenuOpen(false)}
/>
) : null}
<div className={styles.main}>
<header className={styles.topbar}>
<button
type="button"
className={styles.menuBtn}
aria-label="Open menu"
onClick={() => setMenuOpen(true)}
>
<img src="/images/icons/menu.svg" alt="" width={22} height={22} />
</button>
<div className={styles.topbarCopy}>
<p className={styles.eyebrow}>
{manageMeshkeeId ? "Admin · managing business" : en.workspaceEyebrow}
</p>
<p className={styles.topbarHint}>{en.workspaceHint}</p>
</div>
<div className={styles.topbarActions}>
<AccountMenu />
</div>
</header>
<div className={styles.content}>{children}</div>
</div>
</div>
);
}