Update carousels, card hover, and IranBay branding.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user