Files
lynkeduppro-crm/src/components/dashboard/dashboard.tsx
T
maaz519 46aa7a767c feat(mail): dedicated Mail reader in the CRM
A Mail surface in the Communication group, distinct from Messenger (chat) and the
work-item Inbox: read app-to-app + email messages (subject + rendered body), reply,
and compose (in-app to a person, or external to an email).

- mail-api.ts: crm.mail.list/history/reply + compose (crm.mail.internal/send), reusing
  the messenger directory for the people picker. Live via the AppShell SDK; mock in demo mode.
- mail.tsx: thread list + reader + reply + New-mail composer. HTML bodies render inside a
  SANDBOXED iframe (no scripts) — safe against untrusted email HTML.
- Wired into the sidebar (Communication) + dashboard switch.

The existing work-item Inbox stays as the notifier (IIOS's projector already flags new
mail there); this is where you read it. tsc + next build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:38:16 +05:30

82 lines
3.5 KiB
TypeScript

"use client";
// ============================================================
// Dashboard shell — premium dark theme. Keeps the sidebar +
// header and swaps the inner content. Profile / Support / Rules
// render real views; other workspace modules show a placeholder.
// ============================================================
import { useEffect, useState } from "react";
import { Sidebar, NAV_ITEMS } from "./sidebar";
import { Topbar } from "./topbar";
import { ToastProvider, PageHead, Btn, Icon } from "./ui";
import { Profile } from "./profile";
import { Support } from "./support";
import { Rules } from "./rules";
import { AiAssistant } from "./ai-assistant";
import { TeamManagement } from "./team-management";
import { Messenger } from "./messenger";
import { Mail } from "./mail";
import { Inbox } from "./inbox";
import "../../app/dashboard/dashboard.css";
export function Dashboard() {
const [theme, setTheme] = useState<"dark" | "light">("dark");
const [active, setActive] = useState("dashboard");
useEffect(() => {
// Sync the persisted theme from localStorage (an external system) on mount.
// eslint-disable-next-line react-hooks/set-state-in-effect
try { const t = localStorage.getItem("lup_dash_theme"); if (t === "light" || t === "dark") setTheme(t); } catch {}
}, []);
function toggle() {
setTheme((t) => { const n = t === "dark" ? "light" : "dark"; try { localStorage.setItem("lup_dash_theme", n); } catch {} return n; });
}
const item = NAV_ITEMS.find((i) => i.key === active);
const title = item?.label ?? "Dashboard";
const subtitle = item?.subtitle ?? "Workspace module";
return (
<div className="dash-root" data-theme={theme}>
<Sidebar active={active} onSelect={setActive} />
<div className="dash-main">
<Topbar theme={theme} onToggle={toggle} title={title} subtitle={subtitle} />
<div className="dash-content">
<ToastProvider>
{active === "profile" ? <Profile />
: active === "support" ? <Support />
: active === "rules" ? <Rules />
: active === "ai" ? <AiAssistant />
: active === "messenger" ? <Messenger />
: active === "mail" ? <Mail />
: active === "inbox" ? <Inbox />
: active === "team" ? <TeamManagement />
: <ComingSoon title={title} icon={item?.icon ?? "dashboard"} onGo={setActive} />}
</ToastProvider>
</div>
</div>
</div>
);
}
// Placeholder for workspace modules that don't have a view yet — keeps the
// full navigation coherent and points users to the live sections.
function ComingSoon({ title, icon, onGo }: { title: string; icon: string; onGo: (k: string) => void }) {
return (
<div className="view">
<PageHead eyebrow="Workspace" title={title} subtitle="This module is part of the LynkedUp Pro workspace." icon={icon} />
<div className="card coming-soon">
<span className="coming-soon-ic"><Icon name={icon} size={34} /></span>
<h3>{title} is coming soon</h3>
<p>This area is reserved for the {title} module. In the meantime, the account experience is fully built out.</p>
<div className="coming-soon-actions">
<Btn icon="user" onClick={() => onGo("profile")}>Open Profile</Btn>
<Btn variant="outline" icon="chat" onClick={() => onGo("support")}>Support Center</Btn>
<Btn variant="ghost" icon="check-circle" onClick={() => onGo("rules")}>Rules</Btn>
</div>
</div>
</div>
);
}