// Shared workspace structure. Uses the same primitives and brand tokens as app.jsx.
function Icon({ name, size = 20 }) {
const paths = {
home: (
<>
>
),
folder: (
),
server: (
<>
>
),
search: (
<>
>
),
audit: (
<>
>
),
timeline: (
<>
>
),
truck: (
<>
>
),
settings: (
<>
>
),
menu: ,
close: ,
plus: ,
arrow: ,
logout: (
<>
>
),
link: (
<>
>
),
};
return (
);
}
function Brand() {
return (
w.
Watson
by{" "}
StoreConnect
);
}
const workspaceLinks = [
{ key: "dashboard", label: "Overview", icon: "home", group: "Workspace" },
{ key: "list", label: "Investigations", icon: "folder" },
{ key: "environments", label: "Environments", icon: "server" },
{
key: "lookup",
label: "Record lookup",
icon: "search",
group: "Investigation tools",
},
{ key: "audit", label: "Payment audit", icon: "audit" },
{ key: "timeline", label: "Timeline", icon: "timeline" },
{ key: "shipments", label: "Shipments", icon: "truck" },
{
key: "integrations",
label: "Setup & connections",
icon: "settings",
group: "Account",
},
];
function Nav({ page, user, onNav, onLogout, children }) {
const [menuOpen, setMenuOpen] = React.useState(false);
const dialog = React.useRef(null);
const menuButton = React.useRef(null);
const active = ["show", "new"].includes(page)
? "list"
: ["env-show", "env-new"].includes(page)
? "environments"
: page;
const pageName =
workspaceLinks.find((l) => l.key === active)?.label || "Overview";
const navigate = (key) => {
closeMenu(false);
onNav(key);
};
const closeMenu = (restore = true) => {
dialog.current?.close();
setMenuOpen(false);
if (restore) menuButton.current?.focus();
};
React.useEffect(() => {
const mq = window.matchMedia("(min-width: 1100px)");
const resize = () => {
if (mq.matches) closeMenu(false);
};
mq.addEventListener("change", resize);
return () => mq.removeEventListener("change", resize);
}, []);
React.useEffect(() => {
if (!menuOpen) return;
const previous = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = previous;
};
}, [menuOpen]);
const links = (mobile = false) => (
<>
{
e.preventDefault();
navigate("dashboard");
}}
aria-label="Watson overview"
>
{mobile && (
)}
{user?.name?.slice(0, 1) || "W"}
{user?.name}
{user?.salesforce_session ? "Salesforce session" : "Watson account"}
>
);
return (
{
e.preventDefault();
document.getElementById("main-content")?.focus();
}}
>
Skip to main content
{children}
);
}
function PageHeading({ eyebrow, title, description, children }) {
return (
{eyebrow &&
{eyebrow}
}
{title}
{description &&
{description}
}
{children &&
{children}
}
);
}
function InvestigationRow({ inv, onNav }) {
const record = primarySalesforceRecord(inv);
return (
{timeAgo(inv.updated_at)}
);
}
function EmptyState({ title, children }) {
return (
);
}
function Dashboard({ user, investigations, environments, onNav }) {
const open = investigations.filter((i) => i.status === "open").length;
const resolved = investigations.filter((i) => i.status === "resolved").length;
return (
<>
onNav("new")}>
New investigation
Connect the dots
From first question
to a clear resolution.
Cases, bugs, tickets and tools. One place to find the answer.
onNav("list")}
/>
onNav("list")}
/>
onNav("list")}
/>
onNav("environments")}
/>
Recent investigations
Your team's latest work
onNav("list")}>
View all
{investigations.length ? (
{investigations.slice(0, 6).map((inv) => (
))}
) : (
Create an investigation to keep the subject, evidence and findings
together.
)}
Start with a tool
Get straight to the evidence
{[
{
key: "lookup",
icon: "search",
title: "Record lookup",
text: "Find an order, payment or record",
},
{
key: "audit",
icon: "audit",
title: "Payment audit",
text: "Trace transactions and mismatches",
},
{
key: "timeline",
icon: "timeline",
title: "Build a timeline",
text: "Follow the sequence of events",
},
{
key: "shipments",
icon: "truck",
title: "Check shipments",
text: "Review fulfillment and sync",
},
].map((t) => (
{
e.preventDefault();
onNav(t.key);
}}
>
{t.title}
{t.text}
))}
Keep the subject connected
Search Salesforce from an investigation and link the right Case,
Bug or Ticket using your own access.
onNav("new")}>
Start an investigation
>
);
}
function InvestigationList({ investigations, onNav }) {
const [filter, setFilter] = React.useState("all");
const [query, setQuery] = React.useState("");
const filtered = investigations.filter(
(i) =>
(filter === "all" || i.status === filter) &&
[
i.title,
primarySalesforceRecord(i)?.number,
salesforceRecordLabel(primarySalesforceRecord(i)?.type),
i.environment,
i.user?.name,
]
.join(" ")
.toLowerCase()
.includes(query.toLowerCase()),
);
return (
<>
onNav("new")}>
New investigation
{filtered.length} investigation{filtered.length === 1 ? "" : "s"}
{filtered.length ? (
{filtered.map((inv) => (
))}
) : (
Try another search or status, or create a new investigation.
)}
>
);
}
function EnvironmentList({ environments, onNav }) {
const [query, setQuery] = React.useState("");
const visible = environments.filter((env) =>
[env.name, env.rails_app, env.db_app]
.join(" ")
.toLowerCase()
.includes(query.toLowerCase()),
);
return (
<>
onNav("env-new")}>
Add environment
{visible.length} environment{visible.length === 1 ? "" : "s"}
{visible.map((env) => (
- Application
- {env.rails_app || env.db_app || "Not set"}
- Database
- {env.db_app || "Not set"}
onNav("env-show", env.id)}>
View environment
))}
{!visible.length && (
Try another search or add an environment to get started.
)}
>
);
}