Files
famapp/src/modules/lists/manifest.tsx
T
ginnoir a312d4ce39
CI / checks (push) Successful in 12m55s
CI / build (push) Successful in 15m16s
fix: tighten sharing and shadcn composition
2026-06-13 05:20:01 -05:00

113 lines
3.8 KiB
TypeScript

import type { ModuleManifest, WidgetContext } from "../_core/module";
import { z } from "zod";
import { listLists, listWidgetItems, searchItems, searchLists } from "./server/queries";
import { canShareList, loadListForShare, type ListShareData } from "./server/share-queries";
import { ListSharedView } from "./components/shared-view";
import { ListWidget } from "./components/list-widget";
const listIdsSchema = z.union([z.literal("all"), z.array(z.string().uuid())]);
const listWidgetConfigSchema = z.object({
listIds: listIdsSchema,
showCompleted: z.boolean(),
limit: z.number().int().min(1).max(50).optional(),
});
async function ListWidgetServer({ config }: { config: unknown; ctx: WidgetContext }) {
const parsed = listWidgetConfigSchema.parse(config);
const items = await listWidgetItems({
listIds: parsed.listIds,
showCompleted: parsed.showCompleted,
limit: parsed.limit ?? 10,
});
return <ListWidget initialItems={items} />;
}
const manifest: ModuleManifest = {
id: "lists",
name: "Lists",
nav: { href: "/lists", label: "Lists", icon: "list" },
entities: [
{
type: "lists.list",
label: { singular: "List", plural: "Lists" },
share: { canShare: true, defaultCapabilities: ["read", "write"] },
search: { search: searchLists },
resolveUrl: (id) => `/lists/${id}`,
canShareEntity: canShareList,
loadForShare: loadListForShare,
renderSharedView: ({ data, capabilities, token }) => (
<ListSharedView data={data as ListShareData} canWrite={capabilities.write} token={token} />
),
renderActivity: (entry) => {
const name = entry.payload?.name as string | undefined;
if (entry.action === "create") return `Created list${name ? ` "${name}"` : ""}`;
if (entry.action === "archive") return `Archived list${name ? ` "${name}"` : ""}`;
if (entry.action === "delete") return "Deleted list";
return `Updated list${name ? ` "${name}"` : ""}`;
},
},
{
type: "lists.item",
label: { singular: "List item", plural: "List items" },
share: { canShare: false },
search: { search: searchItems },
resolveUrl: (id) => `/lists/items/${id}`,
renderActivity: (entry) => {
const text = entry.payload?.text as string | undefined;
if (entry.action === "create") return `Added${text ? ` "${text}"` : " item"}`;
if (entry.action === "delete") return `Removed${text ? ` "${text}"` : " item"}`;
if (entry.action === "toggle") {
const done = entry.payload?.done as boolean | undefined;
const label = text ? ` "${text}"` : " item";
return done ? `Checked off${label}` : `Unchecked${label}`;
}
return `Updated${text ? ` "${text}"` : " item"}`;
},
},
],
dashboardWidgets: [
{
id: "lists.list",
title: "List items",
description: "Open or completed items from selected lists.",
category: "Lists",
defaultSize: { w: 4, h: 3 },
minSize: { w: 3, h: 2 },
defaultPriority: 30,
configSchema: listWidgetConfigSchema,
defaultConfig: { listIds: "all", showCompleted: false },
resolveConfigOptions: async () => ({
lists: (await listLists()).map((list) => ({
id: list.id,
type: list.type,
name: list.name,
})),
}),
render: (props) => <ListWidgetServer {...props} />,
},
],
quickAdds: [
{
id: "lists.add-shopping",
label: "Add to shopping",
icon: "shopping-cart",
url: "/lists",
},
{
id: "lists.add-task",
label: "Add to tasks",
icon: "list-checks",
url: "/lists",
},
{
id: "lists.new-list",
label: "New list",
icon: "list-plus",
url: "/lists",
},
],
};
export default manifest;