registry.tsx3.8 KBView on GitHub 'use client';
import { Activity, Bot, CalendarDays, DollarSign } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import type { ComponentType } from 'react';
import type { HomeOverview } from '@/modules/home/hooks/use-home-overview';
import { HomeMeetingsWidget } from './HomeMeetingsWidget';
import { HomeAgentsWidget } from './HomeAgentsWidget';
import { PipelineWidget, StatisticsWidget } from './metric-widgets';
export interface HomeWidgetRenderProps {
overview: HomeOverview | undefined;
isLoading: boolean;
}
export interface HomeWidgetDef {
id: string;
title: string;
icon: LucideIcon;
/** One line, shown in the "Add widget" picker. */
description: string;
Render: ComponentType<HomeWidgetRenderProps>;
/** True when this tile projects `statistics.getOverview` — see use-home-overview. */
needsOverview?: boolean;
/**
* Not offered: absent from the "Edit widgets" picker, so nobody can put it on a rail that
* does not already name it. It still RESOLVES — a rail that already stores the id keeps
* rendering it, which is what lets the tile stay in use while it is not yet shipped.
*
* TEMPORARY. The agents surface is unfinished, and this tile is its only entry point
* (`/agents/:agentId` is a deep link nothing links to), so leaving it out of the picker and
* out of `DEFAULT_HOME_WIDGETS` is what keeps it out of everyone else's way. Shipping it is
* this flag going away.
*/
unlisted?: boolean;
}
/**
* Every widget the rail can hold, keyed by the id stored in `userSettings.homeWidgets`.
*
* The registry is CLIENT-SIDE and the settings store only ids, so restyling, renaming or
* re-icon-ing a widget never needs a settings migration — and an id from a build that no
* longer has that widget is skipped on read (see `resolveHomeWidgets`) rather than throwing
* and taking the whole rail down with it.
*/
export const HOME_WIDGETS: readonly HomeWidgetDef[] = [
{
id: 'meetings',
title: 'Meetings',
icon: CalendarDays,
description: "Today's calendar, each meeting opening its conversation.",
Render: () => <HomeMeetingsWidget />,
},
{
id: 'agents',
title: 'Agents',
icon: Bot,
description: 'A shortlist of agents you pin yourself.',
Render: () => <HomeAgentsWidget />,
unlisted: true,
},
{
id: 'pipeline',
icon: DollarSign,
title: 'Pipeline',
description: 'Open deals and ACV by stage, scoped to one pipeline.',
Render: PipelineWidget,
needsOverview: true,
},
{
id: 'statistics',
icon: Activity,
title: 'Statistics',
description: 'Response time, follow-up time, emails sent and total actions.',
Render: StatisticsWidget,
needsOverview: true,
},
];
/** The widgets the "Edit widgets" picker offers — everything except the `unlisted` ones. */
export const LISTED_HOME_WIDGETS: readonly HomeWidgetDef[] = HOME_WIDGETS.filter(
(w) => !w.unlisted,
);
/** The rail a user who has never configured one gets. */
export const DEFAULT_HOME_WIDGETS: readonly string[] = ['meetings', 'pipeline', 'statistics'];
// Every widget, `unlisted` included: not being offered is not the same as not resolving, and
// a rail that already names an unlisted id must keep rendering it.
const BY_ID = new Map(HOME_WIDGETS.map((w) => [w.id, w]));
/**
* Stored ids → widget definitions, in the stored order, silently dropping ids this build does
* not know. A rail that threw (or blanked) because of one retired id would be unrecoverable
* from the UI, since the only way to remove an id is the rail itself.
*
* `unlisted` widgets resolve here like any other — the picker is where they are withheld.
*/
export function resolveHomeWidgets(ids: readonly string[]): HomeWidgetDef[] {
return ids.map((id) => BY_ID.get(id)).filter((w): w is HomeWidgetDef => !!w);
}