Files

281 lines
12 KiB
JavaScript

// ============================================================
// vibn-marketplace · marketplace-shells.jsx
// ------------------------------------------------------------
// Layout shells for marketplace pages.
//
// MarketplaceTopShell — public, transparent-on-scroll top
// nav with brand left, links center, host-mode toggle and
// avatar right. Compact variant shrinks the SearchBar.
//
// DashboardShell — left sidebar with side switch (Guest /
// Host modes), workspace, nav sections.
// ============================================================
// ── MarketplaceTopShell ──────────────────────────────────────
// Props:
// brand { name, mark }
// showSearch bool — inline SearchBar in the header
// searchProps { destination, dates, guests }
// user { name, color, role }
// onHostMode fn — called when user clicks "Switch to hosting"
// compact bool — slimmer header (e.g. for non-home pages)
const MarketplaceTopShell = ({
brand = { name: "Atlas" },
showSearch, searchProps = {},
user, onHostMode = () => {},
compact, children,
footer = true,
}) => (
<div className="vibn-app" style={{
width: "100%", minHeight: "100%", display: "flex",
flexDirection: "column", background: "var(--bg)",
fontFamily: "var(--font-sans)", color: "var(--text)",
}}>
<header style={{
padding: compact ? "14px 32px" : "20px 40px",
borderBottom: "1px solid var(--border)",
background: "var(--surface)", position: "sticky", top: 0, zIndex: 5,
}}>
<div style={{
display: "grid",
gridTemplateColumns: showSearch ? "auto 1fr auto" : "auto 1fr auto",
alignItems: "center", gap: 24,
}}>
{/* Brand */}
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
{brand.mark || <AtlasMark size={compact ? 22 : 26}/>}
<span style={{
fontFamily: "var(--font-display)",
fontSize: compact ? 18 : 22,
fontWeight: "var(--weight-semibold)",
letterSpacing: "-0.01em",
color: "var(--accent)",
}}>{brand.name}</span>
</div>
{/* Center: search OR links */}
{showSearch ? (
<div style={{ maxWidth: 720, width: "100%", justifySelf: "center" }}>
<SearchBar compact={compact} {...searchProps}/>
</div>
) : (
<nav style={{
display: "flex", justifyContent: "center", gap: 28,
fontSize: "var(--text-sm)",
}}>
<span style={{ color: "var(--text)", fontWeight: 500, cursor: "pointer" }}>Stays</span>
<span style={{ color: "var(--text-2)", cursor: "pointer" }}>Experiences</span>
<span style={{ color: "var(--text-2)", cursor: "pointer" }}>Guides</span>
<span style={{ color: "var(--text-2)", cursor: "pointer" }}>Gift cards</span>
</nav>
)}
{/* Right */}
<div style={{ display: "flex", alignItems: "center", gap: 8, justifySelf: "end" }}>
<button onClick={onHostMode} style={{
background: "transparent", border: "none", padding: "8px 14px",
borderRadius: 999, fontSize: "var(--text-sm)",
fontWeight: "var(--weight-medium)", color: "var(--text)",
fontFamily: "var(--font-sans)", cursor: "pointer",
whiteSpace: "nowrap",
}}>Become a host</button>
<IconButton name="more" size="md" variant="secondary"/>
{user && (
<div style={{
display: "flex", alignItems: "center", gap: 8,
padding: "4px 4px 4px 14px", borderRadius: 999,
border: "1px solid var(--border)",
background: "var(--surface)",
boxShadow: "var(--shadow-sm)",
}}>
<Icon name="more" size={14}/>
<Avatar name={user.name} color={user.color} size={28}/>
</div>
)}
</div>
</div>
</header>
<main style={{ flex: 1, minWidth: 0 }}>{children}</main>
{footer && (
<footer style={{
padding: "28px 40px", borderTop: "1px solid var(--border)",
background: "var(--surface-2)",
display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 32,
}}>
{[
["Support", ["Help centre","Cancellation options","Safety information","Report a concern"]],
["Community",["Host an event","Atlas.org","Community forum","Refer a host"]],
["Hosting", ["Become a host","Hosting resources","Community forum","Responsible hosting"]],
["Atlas", ["Newsroom","Investors","Careers","Press centre"]],
].map(([title, items]) => (
<div key={title}>
<div style={{
fontSize: "var(--text-sm)", fontWeight: "var(--weight-semibold)",
color: "var(--text)", marginBottom: 12,
}}>{title}</div>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{items.map(it => (
<span key={it} style={{
fontSize: "var(--text-sm)", color: "var(--text-2)", cursor: "pointer",
}}>{it}</span>
))}
</div>
</div>
))}
</footer>
)}
</div>
);
// Brand mark for the Atlas marketplace — a stylized compass-ish glyph
const AtlasMark = ({ size = 26 }) => (
<svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true">
<path d="M16 2 L19 13 L30 16 L19 19 L16 30 L13 19 L2 16 L13 13 Z"
fill="var(--accent)"/>
<circle cx="16" cy="16" r="2.5" fill="var(--bg)"/>
</svg>
);
// ── MarketplaceDashboardShell ────────────────────────────────
// Side nav for the demand (guest) and supply (host) dashboards.
// role "guest" | "host"
// active id of current nav item
// onRoleSwitch fn
const MarketplaceDashboardShell = ({
brand = { name: "Atlas" },
role = "guest",
active,
onRoleSwitch = () => {},
user,
children,
}) => {
const guestNav = [
{ id: "trips", label: "My trips", icon: "briefcase" },
{ id: "saved", label: "Saved", icon: "star" },
{ id: "inbox", label: "Messages", icon: "inbox", count: 3 },
{ id: "_account", section: "Account" },
{ id: "profile", label: "Profile", icon: "people" },
{ id: "payment", label: "Payments", icon: "doc" },
{ id: "settings", label: "Settings", icon: "settings" },
];
const hostNav = [
{ id: "today", label: "Today", icon: "home" },
{ id: "calendar", label: "Calendar", icon: "check" },
{ id: "listings", label: "Listings", icon: "building", count: 3 },
{ id: "earnings", label: "Earnings", icon: "bar" },
{ id: "inbox", label: "Inbox", icon: "inbox", count: 5 },
{ id: "_growth", section: "Growth" },
{ id: "insights", label: "Insights", icon: "spark" },
{ id: "reviews", label: "Reviews", icon: "star" },
{ id: "_account", section: "Account" },
{ id: "profile", label: "Profile", icon: "people" },
{ id: "payouts", label: "Payouts", icon: "doc" },
{ id: "settings", label: "Settings", icon: "settings" },
];
const nav = role === "host" ? hostNav : guestNav;
return (
<div className="vibn-app" style={{
width: "100%", height: "100%",
display: "grid", gridTemplateColumns: "260px 1fr",
overflow: "hidden",
}}>
<aside style={{
background: "var(--surface-2)",
borderRight: "1px solid var(--border)",
display: "flex", flexDirection: "column",
}}>
{/* Brand */}
<div style={{
padding: "16px 18px", display: "flex", alignItems: "center", gap: 10,
borderBottom: "1px solid var(--border)",
}}>
<AtlasMark size={22}/>
<span style={{
fontFamily: "var(--font-display)", fontSize: 20,
fontWeight: "var(--weight-semibold)", color: "var(--accent)",
}}>{brand.name}</span>
</div>
{/* Role switch — segmented */}
<div style={{ padding: 16 }}>
<div style={{
display: "flex", padding: 3,
background: "var(--surface-alt)", borderRadius: 999,
}}>
{["guest", "host"].map(r => (
<button key={r} onClick={() => onRoleSwitch(r)} style={{
flex: 1, padding: "8px 12px", borderRadius: 999,
background: r === role ? "var(--surface)" : "transparent",
color: r === role ? "var(--text)" : "var(--text-2)",
border: "none", cursor: "pointer", fontFamily: "var(--font-sans)",
fontSize: "var(--text-sm)", fontWeight: 500,
boxShadow: r === role ? "var(--shadow-sm)" : "none",
textTransform: "capitalize",
}}>{r === "guest" ? "Travelling" : "Hosting"}</button>
))}
</div>
</div>
{/* Nav */}
<nav style={{ padding: "0 10px", flex: 1, overflowY: "auto" }}>
{nav.map(it => it.section ? (
<div key={it.id} style={{
fontSize: "var(--text-xs)", color: "var(--text-3)",
padding: "16px 10px 6px", textTransform: "uppercase",
letterSpacing: "0.06em", fontWeight: 500,
}}>{it.section}</div>
) : (
<div key={it.id} style={{
display: "flex", alignItems: "center", gap: 10,
padding: "8px 12px", borderRadius: "var(--radius-sm)",
fontSize: "var(--text-md)", marginBottom: 1, cursor: "pointer",
color: it.id === active ? "var(--text)" : "var(--text-2)",
fontWeight: it.id === active ? 500 : 400,
background: it.id === active ? "var(--surface)" : "transparent",
boxShadow: it.id === active ? "var(--shadow-sm)" : "none",
}}>
<span style={{ color: it.id === active ? "var(--accent)" : "var(--text-3)", display: "flex" }}>
<Icon name={it.icon} size={15}/>
</span>
<span style={{ flex: 1 }}>{it.label}</span>
{it.count != null && <span style={{
fontSize: "var(--text-xs)", color: it.id === active ? "var(--accent)" : "var(--text-3)",
fontWeight: 500,
}}>{it.count}</span>}
</div>
))}
</nav>
{/* Help footer */}
<div style={{
padding: 14, borderTop: "1px solid var(--border)",
display: "flex", alignItems: "center", gap: 12,
}}>
<div style={{
width: 36, height: 36, borderRadius: 10,
background: "var(--accent-soft)", color: "var(--accent)",
display: "flex", alignItems: "center", justifyContent: "center",
}}>
<Icon name="info" size={16}/>
</div>
<div style={{ flex: 1, minWidth: 0, lineHeight: 1.3 }}>
<div style={{ fontSize: "var(--text-sm)", fontWeight: 500 }}>Need help?</div>
<div style={{ fontSize: "var(--text-xs)", color: "var(--text-3)" }}>24/7 support · all channels</div>
</div>
</div>
</aside>
<main style={{ overflow: "hidden", display: "flex", flexDirection: "column", background: "var(--bg)" }}>
{children}
</main>
</div>
);
};
Object.assign(window, {
MarketplaceTopShell, MarketplaceDashboardShell, AtlasMark,
});