2026-04-06 17:20:48 +02:00
|
|
|
/**
|
|
|
|
|
* DashboardShell — real sidebar + header layout wrapper.
|
|
|
|
|
* Used for pages that need actual backend connectivity
|
|
|
|
|
* (My Profile, My Portfolio, Verification) instead of the preview mock.
|
|
|
|
|
*/
|
2026-04-29 11:51:37 +02:00
|
|
|
import { For, JSX, createMemo } from "solid-js";
|
2026-04-15 20:03:47 +02:00
|
|
|
import { AiChatWidget } from "./AiChatWidget";
|
2026-04-29 11:51:37 +02:00
|
|
|
import NotificationBell from "./NotificationBell";
|
2026-04-06 17:20:48 +02:00
|
|
|
import {
|
2026-04-15 20:03:47 +02:00
|
|
|
User,
|
|
|
|
|
Briefcase,
|
|
|
|
|
LayoutDashboard,
|
|
|
|
|
FolderOpen,
|
|
|
|
|
MapPin,
|
|
|
|
|
Star,
|
|
|
|
|
CreditCard,
|
|
|
|
|
Globe,
|
|
|
|
|
ShieldCheck,
|
|
|
|
|
HelpCircle,
|
|
|
|
|
Settings,
|
|
|
|
|
RefreshCw,
|
|
|
|
|
LogOut,
|
|
|
|
|
Bell,
|
|
|
|
|
ChevronRight,
|
|
|
|
|
} from "lucide-solid";
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
const ICON_MAP: Record<string, any> = {
|
2026-04-26 23:58:43 +02:00
|
|
|
my_dashboard: LayoutDashboard,
|
|
|
|
|
my_profile: User,
|
|
|
|
|
my_portfolio: FolderOpen,
|
|
|
|
|
portfolio: FolderOpen,
|
2026-04-15 20:03:47 +02:00
|
|
|
leads: MapPin,
|
2026-04-26 23:58:43 +02:00
|
|
|
my_responses: Star,
|
|
|
|
|
responses: Star,
|
2026-04-15 20:03:47 +02:00
|
|
|
credits: CreditCard,
|
2026-04-26 23:58:43 +02:00
|
|
|
explore_nxtgauge: Globe,
|
|
|
|
|
explore: Globe,
|
2026-04-15 20:03:47 +02:00
|
|
|
verification: ShieldCheck,
|
2026-04-26 23:58:43 +02:00
|
|
|
verification_status: ShieldCheck,
|
|
|
|
|
help_center: HelpCircle,
|
|
|
|
|
help: HelpCircle,
|
|
|
|
|
support: HelpCircle,
|
2026-04-15 20:03:47 +02:00
|
|
|
settings: Settings,
|
2026-04-26 23:58:43 +02:00
|
|
|
switch_services: RefreshCw,
|
|
|
|
|
switch_service: RefreshCw,
|
|
|
|
|
logout: LogOut,
|
2026-04-15 20:03:47 +02:00
|
|
|
jobs: Briefcase,
|
2026-04-26 23:58:43 +02:00
|
|
|
job_postings: Briefcase,
|
2026-04-15 20:03:47 +02:00
|
|
|
applications: Briefcase,
|
2026-04-26 23:58:43 +02:00
|
|
|
my_applications: FolderOpen,
|
|
|
|
|
shortlisted_candidates: User,
|
|
|
|
|
my_requirements: FolderOpen,
|
|
|
|
|
requirements: FolderOpen,
|
|
|
|
|
received_responses: Bell,
|
|
|
|
|
shortlisted_responses: Star,
|
|
|
|
|
saved_jobs: Star,
|
2026-04-06 17:20:48 +02:00
|
|
|
};
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
function normalizeSidebarIconKey(value: string): string {
|
|
|
|
|
return String(value || "")
|
|
|
|
|
.trim()
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.replace(/\s+/g, "_")
|
|
|
|
|
.replace(/-/g, "_");
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 17:20:48 +02:00
|
|
|
function SidebarIcon(props: { label: string }) {
|
2026-04-26 23:58:43 +02:00
|
|
|
const key = normalizeSidebarIconKey(props.label);
|
2026-04-06 17:20:48 +02:00
|
|
|
const Icon = ICON_MAP[key] || ChevronRight;
|
|
|
|
|
return <Icon size={16} />;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 01:21:36 +02:00
|
|
|
function titleCase(value: string) {
|
2026-04-15 20:03:47 +02:00
|
|
|
return String(value || "")
|
2026-04-10 01:21:36 +02:00
|
|
|
.toLowerCase()
|
2026-04-15 20:03:47 +02:00
|
|
|
.replace(/_/g, " ")
|
2026-04-10 01:21:36 +02:00
|
|
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 17:20:48 +02:00
|
|
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
sidebarItems: string[];
|
|
|
|
|
activeSidebar: string;
|
|
|
|
|
onSidebarSelect: (item: string) => void;
|
|
|
|
|
roleKey: string;
|
|
|
|
|
userName?: string;
|
|
|
|
|
children: JSX.Element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Brand colours ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
export const ORANGE = "#FF5E13";
|
|
|
|
|
export const NAVY = "#0D0D2A";
|
2026-04-22 01:26:36 +02:00
|
|
|
const DARK_INK = "#03004E";
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export default function DashboardShell(props: Props) {
|
|
|
|
|
const roleLabel = createMemo(() => {
|
2026-04-15 20:03:47 +02:00
|
|
|
const k = String(props.roleKey || "").replace(/_/g, " ");
|
2026-04-06 17:20:48 +02:00
|
|
|
return k.charAt(0).toUpperCase() + k.slice(1).toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
2026-04-15 20:03:47 +02:00
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
"min-height": "100vh",
|
|
|
|
|
background: "#F8FAFC",
|
|
|
|
|
"font-family": "'Exo 2', sans-serif",
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-06 17:20:48 +02:00
|
|
|
{/* ── Sidebar ──────────────────────────────────────────────────────── */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<aside
|
|
|
|
|
style={{
|
|
|
|
|
width: "220px",
|
|
|
|
|
"flex-shrink": "0",
|
|
|
|
|
background: "#FFFFFF",
|
|
|
|
|
display: "flex",
|
|
|
|
|
"flex-direction": "column",
|
|
|
|
|
padding: "0",
|
|
|
|
|
"min-height": "100vh",
|
|
|
|
|
position: "sticky",
|
|
|
|
|
top: "0",
|
|
|
|
|
height: "100vh",
|
|
|
|
|
"overflow-y": "auto",
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-06 17:20:48 +02:00
|
|
|
{/* Logo */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<div style={{ padding: "20px 16px 12px", "border-bottom": "1px solid #E5E7EB" }}>
|
|
|
|
|
<img
|
|
|
|
|
src="/nxtgauge-logo.png"
|
|
|
|
|
alt="Nxtgauge"
|
|
|
|
|
style={{ height: "40px", "object-fit": "contain", "max-width": "170px" }}
|
|
|
|
|
/>
|
2026-04-06 17:20:48 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Role badge */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<div style={{ padding: "10px 16px", "border-bottom": "1px solid #E5E7EB" }}>
|
|
|
|
|
<p
|
|
|
|
|
style={{
|
|
|
|
|
margin: "0",
|
|
|
|
|
"font-size": "10px",
|
|
|
|
|
"letter-spacing": "0.08em",
|
|
|
|
|
"text-transform": "uppercase",
|
|
|
|
|
color: "#6B7280",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Active Role
|
|
|
|
|
</p>
|
|
|
|
|
<p
|
|
|
|
|
style={{
|
|
|
|
|
margin: "2px 0 0",
|
|
|
|
|
"font-size": "12px",
|
|
|
|
|
"font-weight": "700",
|
|
|
|
|
color: "#111827",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{roleLabel()}
|
|
|
|
|
</p>
|
2026-04-06 17:20:48 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Nav items */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<nav style={{ flex: "1", padding: "8px 8px" }}>
|
2026-04-06 17:20:48 +02:00
|
|
|
<For each={props.sidebarItems}>
|
|
|
|
|
{(item) => {
|
|
|
|
|
const isActive = () => item.toLowerCase() === props.activeSidebar.toLowerCase();
|
2026-04-15 20:03:47 +02:00
|
|
|
const isLogout = item.toLowerCase() === "logout";
|
2026-04-06 17:20:48 +02:00
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => props.onSidebarSelect(item)}
|
|
|
|
|
style={{
|
2026-04-15 20:03:47 +02:00
|
|
|
display: "flex",
|
|
|
|
|
"align-items": "center",
|
|
|
|
|
gap: "9px",
|
|
|
|
|
width: "100%",
|
|
|
|
|
"text-align": "left",
|
|
|
|
|
height: "34px",
|
|
|
|
|
padding: "0 10px",
|
|
|
|
|
"border-radius": "8px",
|
|
|
|
|
border: "none",
|
|
|
|
|
cursor: "pointer",
|
|
|
|
|
"font-size": "12px",
|
|
|
|
|
"font-weight": "600",
|
|
|
|
|
"margin-bottom": "4px",
|
|
|
|
|
background: isActive() ? "#FFF3EE" : "transparent",
|
|
|
|
|
color: isActive() ? ORANGE : isLogout ? "#DC2626" : "#6B7280",
|
|
|
|
|
transition: "background 0.15s, color 0.15s",
|
2026-04-06 17:20:48 +02:00
|
|
|
}}
|
|
|
|
|
>
|
2026-04-15 20:03:47 +02:00
|
|
|
<span style={{ "flex-shrink": "0", color: isActive() ? ORANGE : "#9CA3AF" }}>
|
2026-04-06 17:20:48 +02:00
|
|
|
<SidebarIcon label={item} />
|
|
|
|
|
</span>
|
2026-04-10 01:21:36 +02:00
|
|
|
{titleCase(item)}
|
2026-04-06 17:20:48 +02:00
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
</For>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
{/* User footer */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<div style={{ padding: "12px 16px", "border-top": "1px solid #E5E7EB" }}>
|
|
|
|
|
<p
|
|
|
|
|
style={{
|
|
|
|
|
margin: "0",
|
|
|
|
|
"font-size": "12px",
|
|
|
|
|
"font-weight": "600",
|
|
|
|
|
color: "#374151",
|
|
|
|
|
overflow: "hidden",
|
|
|
|
|
"text-overflow": "ellipsis",
|
|
|
|
|
"white-space": "nowrap",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{props.userName || "User"}
|
2026-04-06 17:20:48 +02:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
{/* ── Main content ─────────────────────────────────────────────────── */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<div style={{ flex: "1", display: "flex", "flex-direction": "column", "min-width": "0" }}>
|
2026-04-06 17:20:48 +02:00
|
|
|
{/* Top bar */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<header
|
|
|
|
|
style={{
|
|
|
|
|
height: "56px",
|
|
|
|
|
background: "#fff",
|
|
|
|
|
"border-bottom": "1px solid #E5E7EB",
|
|
|
|
|
display: "flex",
|
|
|
|
|
"align-items": "center",
|
|
|
|
|
"justify-content": "space-between",
|
|
|
|
|
padding: "0 24px",
|
|
|
|
|
"flex-shrink": "0",
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-22 01:26:36 +02:00
|
|
|
<p style={{ margin: "0", "font-size": "18px", "font-weight": "700", color: NAVY }}>
|
2026-04-10 01:21:36 +02:00
|
|
|
{titleCase(props.activeSidebar)}
|
2026-04-06 17:20:48 +02:00
|
|
|
</p>
|
2026-04-15 20:03:47 +02:00
|
|
|
<div style={{ display: "flex", "align-items": "center", gap: "12px" }}>
|
2026-04-29 11:51:37 +02:00
|
|
|
<NotificationBell />
|
2026-04-15 20:03:47 +02:00
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
width: "32px",
|
|
|
|
|
height: "32px",
|
|
|
|
|
"border-radius": "999px",
|
|
|
|
|
background: ORANGE,
|
|
|
|
|
color: "#fff",
|
|
|
|
|
display: "flex",
|
|
|
|
|
"align-items": "center",
|
|
|
|
|
"justify-content": "center",
|
|
|
|
|
"font-size": "13px",
|
|
|
|
|
"font-weight": "700",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{(props.userName || "U").charAt(0).toUpperCase()}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-04-06 17:20:48 +02:00
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
{/* Page content */}
|
2026-04-15 20:03:47 +02:00
|
|
|
<main style={{ flex: "1", padding: "24px", "overflow-y": "auto" }}>{props.children}</main>
|
2026-04-06 17:20:48 +02:00
|
|
|
</div>
|
2026-04-15 20:03:47 +02:00
|
|
|
<AiChatWidget />
|
2026-04-06 17:20:48 +02:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Shared UI primitives ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export const CARD = {
|
2026-04-15 20:03:47 +02:00
|
|
|
background: "#fff",
|
|
|
|
|
border: "1px solid #E5E7EB",
|
2026-04-22 01:26:36 +02:00
|
|
|
"border-radius": "16px",
|
|
|
|
|
padding: "14px",
|
2026-04-15 20:03:47 +02:00
|
|
|
"box-shadow": "0 1px 4px rgba(0,0,0,0.06)",
|
2026-04-06 17:20:48 +02:00
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
export const BTN_PRIMARY = {
|
2026-04-22 01:26:36 +02:00
|
|
|
height: "34px",
|
|
|
|
|
"border-radius": "8px",
|
2026-04-15 20:03:47 +02:00
|
|
|
border: "none",
|
2026-04-06 17:20:48 +02:00
|
|
|
background: NAVY,
|
2026-04-15 20:03:47 +02:00
|
|
|
color: "#fff",
|
2026-04-22 01:26:36 +02:00
|
|
|
padding: "0 14px",
|
|
|
|
|
"font-size": "12px",
|
2026-04-15 20:03:47 +02:00
|
|
|
"font-weight": "700",
|
|
|
|
|
cursor: "pointer",
|
2026-04-06 17:20:48 +02:00
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
export const BTN_ORANGE = {
|
2026-04-22 01:26:36 +02:00
|
|
|
height: "34px",
|
|
|
|
|
"border-radius": "8px",
|
2026-04-15 20:03:47 +02:00
|
|
|
border: "none",
|
2026-04-06 17:20:48 +02:00
|
|
|
background: ORANGE,
|
2026-04-15 20:03:47 +02:00
|
|
|
color: "#fff",
|
2026-04-22 01:26:36 +02:00
|
|
|
padding: "0 14px",
|
|
|
|
|
"font-size": "12px",
|
2026-04-15 20:03:47 +02:00
|
|
|
"font-weight": "700",
|
|
|
|
|
cursor: "pointer",
|
2026-04-06 17:20:48 +02:00
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
export const BTN_GHOST = {
|
2026-04-22 01:26:36 +02:00
|
|
|
height: "34px",
|
|
|
|
|
"border-radius": "8px",
|
2026-04-15 20:03:47 +02:00
|
|
|
border: "1px solid #E5E7EB",
|
|
|
|
|
background: "#fff",
|
|
|
|
|
color: "#374151",
|
2026-04-22 01:26:36 +02:00
|
|
|
padding: "0 14px",
|
|
|
|
|
"font-size": "12px",
|
|
|
|
|
"font-weight": "700",
|
2026-04-15 20:03:47 +02:00
|
|
|
cursor: "pointer",
|
2026-04-06 17:20:48 +02:00
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
export const INPUT = {
|
2026-04-22 01:26:36 +02:00
|
|
|
height: "36px",
|
2026-04-15 20:03:47 +02:00
|
|
|
width: "100%",
|
|
|
|
|
"border-radius": "8px",
|
|
|
|
|
border: "1px solid #E5E7EB",
|
2026-04-22 01:26:36 +02:00
|
|
|
padding: "0 10px",
|
|
|
|
|
"font-size": "12px",
|
2026-04-15 20:03:47 +02:00
|
|
|
color: "#111827",
|
|
|
|
|
background: "#fff",
|
|
|
|
|
"box-sizing": "border-box",
|
2026-04-22 01:26:36 +02:00
|
|
|
outline: "none",
|
2026-04-06 17:20:48 +02:00
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
export const LABEL = {
|
2026-04-15 20:03:47 +02:00
|
|
|
display: "block",
|
2026-04-22 01:26:36 +02:00
|
|
|
"font-size": "11px",
|
|
|
|
|
"font-weight": "700",
|
|
|
|
|
color: "#6B7280",
|
2026-04-15 20:03:47 +02:00
|
|
|
"margin-bottom": "6px",
|
2026-04-22 01:26:36 +02:00
|
|
|
"letter-spacing": "0.01em",
|
|
|
|
|
"text-transform": "none",
|
2026-04-06 17:20:48 +02:00
|
|
|
} as const;
|
2026-04-26 23:58:43 +02:00
|
|
|
|
|
|
|
|
export const PKG_CARD = {
|
|
|
|
|
WHITE: "#ffffff",
|
|
|
|
|
COIN_BG: "#FFFBF8",
|
|
|
|
|
COIN_AVATAR_BG: "linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%)",
|
|
|
|
|
BEST_VALUE_BG: `linear-gradient(135deg, ${ORANGE} 0%, #FF8A5C 100%)`,
|
|
|
|
|
BORDER_DEFAULT: "#E5E7EB",
|
|
|
|
|
TEXT_SECONDARY: "#6B7280",
|
|
|
|
|
TEXT_PRIMARY: "#111827",
|
|
|
|
|
TEXT_ACCENT: ORANGE,
|
|
|
|
|
TEXT_MUTED: "#9CA3AF",
|
|
|
|
|
TEXT_SUCCESS: "#16A34A",
|
|
|
|
|
SHADOW_DEFAULT: "0 1px 3px rgba(0, 0, 0, 0.08)",
|
|
|
|
|
SHADOW_ACCENT: "0 4px 20px rgba(255, 94, 19, 0.15)",
|
|
|
|
|
} as const;
|