@open-mercato/ui 0.6.1-develop.3115.bc23703ef8 → 0.6.1-develop.3144.b39fb4dfa6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/AGENTS.md +81 -0
  2. package/package.json +3 -3
package/AGENTS.md CHANGED
@@ -231,6 +231,85 @@ const leadTagMap: TagMap<'customer' | 'hot' | 'inactive' | 'renewal'> = {
231
231
  - Keep `extensionTableId` stable and deterministic.
232
232
  - Render injected row actions and bulk actions through `RowActions`/bulk handlers so they follow the same guard and i18n behavior as built-ins.
233
233
 
234
+ ### Using DataTable in Portal Pages
235
+
236
+ `DataTable` is exported from the package root (`@open-mercato/ui`) and is the recommended list view component for **customer portal pages**, not just backoffice surfaces. Build portal list views with `DataTable` directly rather than hand-rolling `<table>` markup or wrapping a custom component, so portal lists stay consistent with the rest of the product (sorting, pagination, empty states, accessibility, row-click semantics).
237
+
238
+ Backoffice-only features are **all opt-in via props** — they do not activate unless explicitly passed. Portal pages should leave them unset:
239
+
240
+ | Prop | Treat as | Why portal omits it |
241
+ |------|----------|---------------------|
242
+ | `exporter` | backoffice-only | Portal users should not export full datasets to CSV/JSON/XML/Markdown. |
243
+ | `perspective` | backoffice-only | Saved-view perspectives belong in admin tools. |
244
+ | `advancedFilter` | backoffice-only | The advanced filter builder is a power-user tool. |
245
+ | `columnChooser` | backoffice-only | Column chooser/reorder is a backoffice affordance. |
246
+ | `injectionSpotId` / `replacementHandle` / `entityId` | backoffice-only | Widget injection and component replacement target backoffice extension points; portals use `usePortalInjectedMenuItems` and portal-specific spots instead (see Portal Extension below). |
247
+ | `bulkActions` | backoffice-only by default | Avoid bulk admin operations from the portal; if a portal flow legitimately needs row selection, scope it tightly and gate via `requireCustomerFeatures`. |
248
+
249
+ Portal-safe props (always available):
250
+
251
+ - `columns` — column definitions with `header`/`accessorKey` plus optional `meta.truncate`/`meta.maxWidth`
252
+ - `data` — the rows to render (load via `apiCall` from `@open-mercato/ui/backend/utils/apiCall`)
253
+ - `isLoading`, `error`, `emptyState` — loading/error/empty UX
254
+ - `pagination`, `sorting`, `onSortingChange`, `sortable`
255
+ - `onRowClick`, `rowClickActionIds`, `disableRowClick` — row navigation
256
+ - `rowActions` — per-row inline actions via `RowActions`
257
+ - `searchValue`, `onSearchChange`, `searchPlaceholder` — basic search bar
258
+ - `filters`, `filterValues`, `onFiltersApply`, `onFiltersClear` — basic FilterBar filters
259
+ - `title`, `actions`, `toolbar` — header chrome
260
+
261
+ Minimal portal usage example (drop into any `frontend/[orgSlug]/portal/.../page.tsx`):
262
+
263
+ ```tsx
264
+ 'use client'
265
+ import * as React from 'react'
266
+ import { DataTable } from '@open-mercato/ui'
267
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
268
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
269
+
270
+ type PortalOrderRow = {
271
+ id: string
272
+ number: string
273
+ status: string
274
+ totalLabel: string
275
+ }
276
+
277
+ export default function PortalOrdersList({ orgSlug }: { orgSlug: string }) {
278
+ const t = useT()
279
+ const [rows, setRows] = React.useState<PortalOrderRow[]>([])
280
+ const [isLoading, setIsLoading] = React.useState(true)
281
+
282
+ React.useEffect(() => {
283
+ let cancelled = false
284
+ setIsLoading(true)
285
+ apiCall<{ items: PortalOrderRow[] }>(`/api/portal/orders?orgSlug=${encodeURIComponent(orgSlug)}`)
286
+ .then((res) => { if (!cancelled) setRows(res.data?.items ?? []) })
287
+ .finally(() => { if (!cancelled) setIsLoading(false) })
288
+ return () => { cancelled = true }
289
+ }, [orgSlug])
290
+
291
+ return (
292
+ <DataTable<PortalOrderRow>
293
+ title={t('orders.title', { fallback: 'Your orders' })}
294
+ columns={[
295
+ { header: t('orders.number'), accessorKey: 'number' },
296
+ { header: t('orders.status'), accessorKey: 'status' },
297
+ { header: t('orders.total'), accessorKey: 'totalLabel' },
298
+ ]}
299
+ data={rows}
300
+ isLoading={isLoading}
301
+ onRowClick={(row) => { window.location.href = `/${orgSlug}/portal/orders/${row.id}` }}
302
+ emptyState={t('orders.empty', { fallback: 'No orders yet.' })}
303
+ />
304
+ )
305
+ }
306
+ ```
307
+
308
+ Notes:
309
+ - The example deliberately omits `exporter`, `perspective`, `advancedFilter`, `columnChooser`, `injectionSpotId`, and `replacementHandle` — those are backoffice-only.
310
+ - Wrap mutation calls with `useGuardedMutation(...).runMutation(...)` per the **UI & HTTP** rules in the root `AGENTS.md` if the row actions perform writes.
311
+ - Gate the page with `requireCustomerAuth` and `requireCustomerFeatures` in the sibling `page.meta.ts` (see Portal Page Metadata below).
312
+
234
313
  ## CrudForm Field Injection (UMES Phase G)
235
314
 
236
315
  - `CrudForm` automatically resolves injected field widgets from `crud-form:<entityId>:fields`; always pass a stable `entityId`.
@@ -289,6 +368,8 @@ const leadTagMap: TagMap<'customer' | 'hot' | 'inactive' | 'renewal'> = {
289
368
 
290
369
  The portal extensibility system lets app modules build customer-facing pages that integrate with the shared portal shell, navigation, auth, and event bridge.
291
370
 
371
+ > **Portal list views**: use `DataTable` from `@open-mercato/ui` for any portal page that displays a list of records (orders, addresses, invoices, etc.). Do not hand-roll `<table>` markup. See **DataTable Guidelines → Using DataTable in Portal Pages** above for the portal-safe prop subset and a minimal example.
372
+
292
373
  ### Portal Hooks (`packages/ui/src/portal/hooks/`)
293
374
 
294
375
  | Hook | Import | Purpose |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.1-develop.3115.bc23703ef8",
3
+ "version": "0.6.1-develop.3144.b39fb4dfa6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -140,12 +140,12 @@
140
140
  "remark-gfm": "^4.0.1"
141
141
  },
142
142
  "peerDependencies": {
143
- "@open-mercato/shared": "0.6.1-develop.3115.bc23703ef8",
143
+ "@open-mercato/shared": "0.6.1-develop.3144.b39fb4dfa6",
144
144
  "react": ">=18.0.0",
145
145
  "react-dom": ">=18.0.0"
146
146
  },
147
147
  "devDependencies": {
148
- "@open-mercato/shared": "0.6.1-develop.3115.bc23703ef8",
148
+ "@open-mercato/shared": "0.6.1-develop.3144.b39fb4dfa6",
149
149
  "@testing-library/dom": "^10.4.1",
150
150
  "@testing-library/jest-dom": "^6.9.1",
151
151
  "@testing-library/react": "^16.3.1",