@nocobase/app-template-default 1.0.0-beta.83 → 1.0.0-beta.85

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 (104) hide show
  1. package/.agents/rules/build-and-deploy.md +1 -1
  2. package/.agents/rules/shell.md +4 -0
  3. package/AGENTS.md +8 -2
  4. package/README.MD +10 -2
  5. package/client/components/back-button.tsx +3 -5
  6. package/client/components/data-table/data-table.tsx +86 -32
  7. package/client/components/default-access-editor.tsx +13 -2
  8. package/client/components/feedback-form.tsx +474 -0
  9. package/client/components/lifecycle-diagram.tsx +10 -10
  10. package/client/components/list-feedback.tsx +174 -0
  11. package/client/components/permission-editor.tsx +419 -226
  12. package/client/extensions/nocobase-auth-forms/password-login-form.tsx +17 -6
  13. package/client/extensions/nocobase-auth-forms/password-registration-form.tsx +24 -7
  14. package/client/extensions/nocobase-auth-forms/password-reset-form.tsx +19 -7
  15. package/client/extensions/nocobase-auth-forms/password-reset-request-form.tsx +15 -7
  16. package/client/extensions/nocobase-device-approval/device-approval.tsx +7 -2
  17. package/client/extensions/nocobase-device-approval/locales/en-US.ts +16 -0
  18. package/client/extensions/nocobase-device-approval/locales/zh-CN.ts +7 -0
  19. package/client/extensions/nocobase-file-component-ui/components/file-preview-dialog.tsx +20 -17
  20. package/client/extensions/nocobase-workflow-management/README.md +3 -1
  21. package/client/extensions/nocobase-workflow-management/definition-list.tsx +19 -7
  22. package/client/extensions/nocobase-workflow-management/execution-list.tsx +61 -48
  23. package/client/hooks/use-list-data.ts +76 -0
  24. package/client/hooks/use-list-location.ts +23 -0
  25. package/client/hooks/use-list-tabs.ts +42 -0
  26. package/client/hooks/use-table-url-state.ts +143 -30
  27. package/client/locales/system/en-US.ts +24 -1
  28. package/client/locales/system/settings-en-US.ts +8 -10
  29. package/client/locales/system/settings-zh-CN.ts +8 -8
  30. package/client/locales/system/zh-CN.ts +22 -1
  31. package/client/pages/settings/default-access-form.tsx +44 -26
  32. package/client/pages/settings/default-access.tsx +100 -93
  33. package/client/pages/settings/hooks.tsx +4 -59
  34. package/client/pages/settings/page-permissions.ts +17 -0
  35. package/client/pages/settings/permissions-model.ts +54 -59
  36. package/client/pages/settings/role-form.tsx +98 -56
  37. package/client/pages/settings/roles.tsx +69 -95
  38. package/client/pages/settings/shared.tsx +43 -33
  39. package/client/pages/settings/user-form.tsx +51 -30
  40. package/client/pages/settings/users.tsx +105 -91
  41. package/client/routing/session-preparation.tsx +10 -3
  42. package/client/runtime.ts +0 -4
  43. package/config.example.yml +24 -0
  44. package/database/main/migrations/202611020002_create_mutation_receipts.ts +19 -0
  45. package/package.json +24 -21
  46. package/server/app.ts +0 -2
  47. package/server/config/auth.ts +2 -0
  48. package/server/config/concurrency.ts +3 -0
  49. package/server/config/index.ts +3 -0
  50. package/server/config/jobs.ts +1 -13
  51. package/server/config/notification.ts +1 -1
  52. package/server/config/queue.ts +1 -7
  53. package/server/plugins.ts +2 -0
  54. package/server/providers/index.ts +4 -1
  55. package/server/providers/submission.ts +61 -0
  56. package/tests/client/auth-i18n.test.tsx +9 -0
  57. package/tests/client/back-button.test.tsx +3 -3
  58. package/tests/client/data-table.test.tsx +9 -4
  59. package/tests/client/default-access.test.tsx +99 -5
  60. package/tests/client/form-feedback.test.tsx +589 -0
  61. package/tests/client/list-feedback.test.tsx +301 -0
  62. package/tests/client/list-recovery.test.tsx +373 -0
  63. package/tests/client/list-tabs.test.tsx +121 -0
  64. package/tests/client/manual-list-retry.test.tsx +190 -0
  65. package/tests/client/page-permission-navigation.test.ts +8 -0
  66. package/tests/client/permission-editor.test.tsx +191 -21
  67. package/tests/client/permission-filter-binding.test.tsx +1 -1
  68. package/tests/client/permission-workspace.test.ts +13 -0
  69. package/tests/client/role-return.test.tsx +2 -1
  70. package/tests/client/session-preparation.test.tsx +24 -1
  71. package/tests/client/settings-continuity.test.tsx +85 -5
  72. package/tests/client/settings-feedback.test.tsx +32 -15
  73. package/tests/client/settings-permissions.test.ts +35 -0
  74. package/tests/client/settings.test.tsx +2 -1
  75. package/tests/client/table-url-state.test.tsx +117 -3
  76. package/tests/client/user-drawer.test.tsx +2 -1
  77. package/tests/client/user-permissions.test.tsx +30 -5
  78. package/tests/database/mutation-receipts.test.ts +45 -0
  79. package/tests/fixtures/form-feedback/README.md +33 -0
  80. package/tests/fixtures/form-feedback/equipment-detail.tsx +11 -0
  81. package/tests/fixtures/form-feedback/equipment-layout.tsx +15 -0
  82. package/tests/fixtures/form-feedback/equipment-migration.ts +27 -0
  83. package/tests/fixtures/form-feedback/equipment-server.ts +197 -0
  84. package/tests/fixtures/form-feedback/equipment-validation.tsx +94 -0
  85. package/tests/fixtures/form-feedback/equipment.tsx +369 -0
  86. package/tests/fixtures/form-feedback/verify-inline.mjs +174 -0
  87. package/tests/fixtures/form-feedback/verify.mjs +373 -0
  88. package/tests/fixtures/permission-alignment.html +16 -0
  89. package/tests/fixtures/permission-alignment.tsx +124 -0
  90. package/tests/fixtures/submission/README.md +35 -0
  91. package/tests/fixtures/submission/browser.test.ts +478 -0
  92. package/tests/fixtures/submission/migration.ts +61 -0
  93. package/tests/fixtures/submission/page.tsx +138 -0
  94. package/tests/fixtures/submission/playwright.config.ts +11 -0
  95. package/tests/fixtures/submission/routes.ts +208 -0
  96. package/tests/helpers/settings-client.tsx +31 -0
  97. package/tests/playwright/permission-alignment.test.ts +109 -0
  98. package/tests/server/auth-email-config.test.ts +79 -0
  99. package/tests/server/concurrency.test.ts +61 -0
  100. package/tests/server/config.test.ts +34 -14
  101. package/tests/server/member-role.test.ts +7 -1
  102. package/tests/server/submission-settings.test.ts +241 -0
  103. package/client/route-overrides.ts +0 -9
  104. package/client/source-extensions.ts +0 -19
@@ -16,7 +16,7 @@ For a Docker source build, pass `--build-arg CDN_BASE_URL=https://cdn.example.co
16
16
 
17
17
  ## Packing the build for a deployment
18
18
 
19
- `pnpm build --tar` writes `storage/exports/dist.tar.gz` after the build. The archive holds `dist/` as a directory next to `config.example.yml`, so extracting it produces exactly those two paths rather than scattering `server/` and `node_modules/` into whatever directory you unpacked in.
19
+ `pnpm build --tar` writes `storage/exports/dist.tar.gz` after the build. The archive holds `dist/` as a directory next to `config.example.yml`, with the `Dockerfile` and `Dockerfile.dockerignore`, so extracting it produces exactly those paths rather than scattering `server/` and `node_modules/` into whatever directory you unpacked in, and a server can build the image from it alone with `docker build --build-arg DIST=prebuilt .`.
20
20
 
21
21
  `config.example.yml` travels with it because a deployment has to write a `config.yml` before it can start — `pnpm nocobase config init`, run inside `dist/`, does that from the example, in place, and `pnpm nocobase config check` there verifies it, database connection included, before the first start — and the example is the only statement of what may go in it. Directories of executable shims are left out: a `.bin` entry points at a path on the machine that installed it, and a dangling one makes `pnpm install` in the extracted tree report a corrupt store rather than repair it.
22
22
 
@@ -33,3 +33,7 @@ Use `Loading` for page or panel waits and `Spinner` (the shadcn-compatible adapt
33
33
  The shared list of shell behaviors to keep when customizing is in [customizing the shell](../skills/nocobase-app-development/references/frontend/references/shell.md).
34
34
 
35
35
  The signed-in region waits in `client/routing/session-preparation.tsx` before mounting `AppLayout`: the authentication provider first reconnects active realtime subscriptions as the current session and waits for the open notification, including permission invalidation. A ten-second timeout shows a localized retry/sign-out screen. Keep this boundary inside `RequiredAuthentication`; guest login must remain reachable without realtime. Readiness is only for initial session preparation. Subsequent permission notifications and reconnects still use normal fail-closed route unmounting, including portal and effect cleanup.
36
+
37
+ ## List context
38
+
39
+ Keep the identity-keyed `SessionPreparation` boundary: mounted private list conditions and ancestor query memory must be discarded when the signed-in user changes. Public list state uses `useTableUrlState`; tabbed datasets use `useListTabs`, with complete owned parameter keys and permission-filtered tabs. Follow the synchronized [list navigation rules](../skills/nocobase-app-development/references/frontend/references/child-routes.md#list-tab-breadcrumbs-and-private-conditions). Default back links use an empty ancestor query when no source visit exists; explicit return destinations remain application-owned.
package/AGENTS.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is a NocoBase 3 application. Do not apply globally installed NocoBase 2 Skills. You are building the application itself — its pages, its API, its database tables. Everything under this directory is application-owned source code that you may edit directly.
4
4
 
5
- Do not create a plugin to add a feature. Plugins are separately published packages for capabilities shared across several applications; building one for this application's own feature adds a package boundary, a version, and a release process to work that belongs in `client/` and `server/`. Create one only when the user explicitly asks for a reusable published package. Ordinary internal workspace packages under `packages/` may share code without becoming plugins or being published; follow [internal packages](.agents/rules/workspace.md) when the application needs that boundary.
5
+ Build application-specific features directly in the application by default. Use a plugin when the user requests one or when code reuse or modular organization calls for a separately registered, maintainable feature. Local plugins belong under `packages/`; the App builds and packages registered local plugins for deployment, so publishing is optional and only needed when distributing the plugin through a package registry. Follow [the project plugin development Skill](.agents/skills/nocobase-app-plugin-development/SKILL.md). Ordinary internal packages can share code without plugin registration; follow [internal packages](.agents/rules/workspace.md) when that is the boundary the application needs.
6
6
 
7
7
  ## Default template scope
8
8
 
@@ -28,6 +28,8 @@ For creating or maintaining a plugin in this App, read [the project plugin devel
28
28
 
29
29
  `.agents/skills/nocobase-app-upgrade/` is a separate Skill for a separate job: merging a newer release of the template this application was generated from. Read it when the task is upgrading the template rather than building a feature — including when the user asks to upgrade NocoBase or its core version, because a newer template release is how this application moves to a newer NocoBase — and read it before touching anything — an upgrade done by copying the newest template over this application destroys the work that made it this application.
30
30
 
31
+ When upgrading from an older installed Skill, also read the target template package’s `AGENTS.md` and `README.MD` and fetch its declared `@nocobase/app-skills` package before editing. Follow the target’s upgrade requirements in the same upgrade, including application-owned lists and installed Registry copies. Confirm the target baseline only after conflicts are resolved and dependency, source, behavior and retained-data checks pass; see the [upgrade Skill](.agents/skills/nocobase-app-upgrade/SKILL.md).
32
+
31
33
  `.agents/skills/nocobase-deployment/` covers moving this application from source to a production server: building for the target platform, what the archive does and does not carry, migrations and business data, production configuration, and what to verify afterwards. Read it before building for deployment, and when an application starts in production but does not work.
32
34
 
33
35
  ## Where things go
@@ -93,7 +95,7 @@ Put a package your **server** code (`server/`, `database/`, `cli/`) imports in `
93
95
 
94
96
  ### Plugins
95
97
 
96
- **Read a plugin's Skill before building what it already does.** Building a permission system, a notification sender, or a job scheduler by hand when a registered plugin already provides one is the most expensive mistake available here. Register and update plugins with `pnpm nocobase plugin register` and `pnpm nocobase plugin update`; customize a plugin's page through its options, a source extension or `client/route-overrides.ts`, never by redeclaring its route. `.agents/skills/` is generated output; never edit a file there. See [plugins](.agents/skills/nocobase-app-development/references/plugins.md).
98
+ **Read a plugin's Skill before building what it already does.** Building a permission system, a notification sender, or a job scheduler by hand when a registered plugin already provides one is the most expensive mistake available here. Register and update plugins with `pnpm nocobase plugin register` and `pnpm nocobase plugin update`; customize a plugin's page through its options or an explicitly registered route component override, never by redeclaring its route. `.agents/skills/` is generated output; never edit a file there. See [plugins](.agents/skills/nocobase-app-development/references/plugins.md).
97
99
 
98
100
  ### Running the application
99
101
 
@@ -118,6 +120,10 @@ When the built-in mechanism genuinely cannot express what is being asked, changi
118
120
  - Comment what you changed and why the built-in path did not fit. On upgrade, an agent reconciling the template's version needs to know whether your change is still needed.
119
121
  - Record what the next agent needs to know in the matching `.agents/rules/` file, or a new one linked from the index below, so the guidance describes the application as it actually is without growing this file. If the framework's shared guidance is wrong for every application, update the source `@nocobase/app-skills` package instead of editing its synchronized copy.
120
122
 
123
+ ## Standard submissions
124
+
125
+ Standard Repository writes require an operation key through the registered submission provider. Forms share `useSubmission` across all entries before asynchronous validation; custom effectful APIs explicitly join the receipt transaction. Follow [submission protection](.agents/skills/nocobase-app-development/references/submission.md) and [source upgrades](.agents/skills/nocobase-app-upgrade/references/submission-upgrade.md). Merge copied source and verify retained customizations on upgrade.
126
+
121
127
  ## Index
122
128
 
123
129
  | Read | When |
package/README.MD CHANGED
@@ -4,6 +4,12 @@ A full-stack NocoBase 3 application. The server runs on `@nocobase/app-server` a
4
4
 
5
5
  You own everything in this directory. Add pages, API routes, database tables, and business logic directly here — this is your application's source code, not a framework you extend from the outside.
6
6
 
7
+ ## Upgrade this application
8
+
9
+ Ask the application agent to upgrade NocoBase using the application-local [template upgrade Skill](.agents/skills/nocobase-app-upgrade/SKILL.md). This is the supported version-selection and three-way source reconciliation entry point. Before merging, read the selected target package’s `AGENTS.md`, this section and the upgrade Skill from its declared `@nocobase/app-skills` dependency, even if the application still has an older Skill. Fetch and inspect that guidance without first installing it.
10
+
11
+ Complete the target’s requirements in that same upgrade: reconcile dependencies, copied template and Registry sources, application-owned table/card consumers and translations while preserving business customizations, grants, records and executed migrations. For list feedback, follow the target Skill’s `references/list-feedback-upgrade.md`. Do not finish with only a version bump, dependency update or Skill sync. Missing targets, unresolved business conflicts or failed acceptance stop completion; confirm `defaultTemplateVersion` only after the checks pass. See the target Skill’s `references/versioned-upgrade-verification.md` for package integrity records and verification after publication.
12
+
7
13
  ## Default and Examples
8
14
 
9
15
  Default provides the application shell and product capabilities without example plugins or business demo pages. Application-owned business API routes start empty, and new databases receive no article sample data. A bare create-app shows Home and, for unrestricted administrators only, System settings → App foundation. Foundation is installed and registered on both Client and Server; its read-only overview is available at `/foundation` under the application base path. As business capabilities are developed, the app development skill reconciles missing core administration on every iteration and registers the prepared Users and Roles and permissions contribution (`client/pages/settings/routes.ts`); each actually used configurable capability includes its administration as part of feature delivery. Its built-in Users integration lists direct Authorization Permission Sets as application roles, while access assigned to all authenticated users remains separate. Use `app-template-examples` for article management and runnable plugin demonstrations.
@@ -138,7 +144,7 @@ The three files named `runtime.ts`, `plugins.ts`, and `routes.ts` are compositio
138
144
  | `pnpm dev` | Start the API server and client with hot reload |
139
145
  | `pnpm build` | Build the client and server into `dist/` |
140
146
  | `pnpm start` | Run the built server; requires `pnpm build` first |
141
- | `pnpm build --tar` | Build, then pack `dist/` and `config.example.yml` into `storage/exports/dist.tar.gz` |
147
+ | `pnpm build --tar` | Build, then pack `dist/`, `config.example.yml` and the `Dockerfile` with its `.dockerignore` into `storage/exports/dist.tar.gz` |
142
148
 
143
149
  ### Database
144
150
 
@@ -287,10 +293,12 @@ Pass the file's path relative to `dist/client/`, with or without a leading slash
287
293
 
288
294
  `pnpm build --tar` writes `storage/exports/dist.tar.gz`, the artifact `@nocobase/app-installer` installs on a server.
289
295
 
290
- The archive has two levels: `config.example.yml` and `dist/` sit at the root, and the build is inside `dist/`. What `dist/` holds varies with the application; this is the shape:
296
+ The archive has two levels: `config.example.yml`, `dist/` and, when the application has them, the `Dockerfile` and `Dockerfile.dockerignore` sit at the root, and the build is inside `dist/`. A server builds the image from the extracted archive alone with `docker build --build-arg DIST=prebuilt .`. What `dist/` holds varies with the application; this is the shape:
291
297
 
292
298
  ```text
293
299
  dist.tar.gz
300
+ ├── Dockerfile
301
+ ├── Dockerfile.dockerignore
294
302
  ├── config.example.yml
295
303
  └── dist/
296
304
  ├── cli/
@@ -1,7 +1,7 @@
1
1
  import { useTranslation } from '@nocobase/i18n/client';
2
2
  import { ArrowLeftIcon } from 'lucide-react';
3
3
  import type { ReactElement, ReactNode } from 'react';
4
- import { Link, type To, useLocation, useResolvedPath } from 'react-router';
4
+ import { Link, type To, useResolvedPath } from 'react-router';
5
5
 
6
6
  import { useRouteSearches } from '../routing/route-context.js';
7
7
 
@@ -10,7 +10,7 @@ import { cn } from 'cn';
10
10
  export interface BackButtonProps {
11
11
  /**
12
12
  * Where it leads. Defaults to the parent route with its last visited query; a directly opened detail falls back
13
- * to the current query, as closing a route overlay does. An explicit destination is kept unchanged.
13
+ * to the default parent list. An explicit destination is kept unchanged.
14
14
  */
15
15
  readonly to?: To;
16
16
  /** The label, "Back" by default. */
@@ -33,7 +33,6 @@ export function BackButton({
33
33
  to,
34
34
  }: BackButtonProps = {}): ReactElement {
35
35
  const { t } = useTranslation();
36
- const location = useLocation();
37
36
  const parent = useResolvedPath('..');
38
37
  const searches = useRouteSearches();
39
38
 
@@ -48,8 +47,7 @@ export function BackButton({
48
47
  to ?? {
49
48
  pathname: '..',
50
49
  search:
51
- searches.get(parent.pathname.replace(/\/+$/u, '') || '/') ??
52
- location.search,
50
+ searches.get(parent.pathname.replace(/\/+$/u, '') || '/') ?? '',
53
51
  }
54
52
  }
55
53
  >
@@ -1,4 +1,5 @@
1
- import { useTranslation } from '@nocobase/i18n/client';
1
+ import type { ListState } from '@nocobase/app-client';
2
+ import { ListFeedback, ListFeedbackActions } from '#components/list-feedback';
2
3
  import {
3
4
  type ColumnDef,
4
5
  type PaginationState,
@@ -41,13 +42,17 @@ export interface DataTableProps<TData, TValue = unknown> {
41
42
  readonly onSortingChange?: OnChangeFn<SortingState>;
42
43
  readonly showSelectedCount?: boolean;
43
44
  readonly loading?: boolean;
45
+ readonly listState?: ListState;
46
+ readonly onRetry?: () => void;
47
+ readonly onClearFilters?: () => void;
48
+ readonly emptyAction?: ReactNode;
44
49
  readonly className?: string;
45
50
  /**
46
51
  * Rendered above the table and handed the table instance, so filters and
47
52
  * `DataTableViewOptions` can read and drive its state.
48
53
  */
49
54
  readonly toolbar?: (table: TableInstance<TData>) => ReactNode;
50
- /** Shown in place of the body when no row survives filtering. */
55
+ /** Shown in the table body when no row survives filtering. */
51
56
  readonly emptyMessage?: ReactNode;
52
57
  /** Set to `false` to render every row and hide the pagination footer. */
53
58
  readonly pagination?: boolean;
@@ -91,8 +96,11 @@ export function DataTable<TData, TValue = unknown>({
91
96
  onSortingChange,
92
97
  showSelectedCount = true,
93
98
  loading = false,
99
+ listState,
100
+ onRetry,
101
+ onClearFilters,
102
+ emptyAction,
94
103
  }: DataTableProps<TData, TValue>): ReactElement {
95
- const { t } = useTranslation();
96
104
  const tableData = useMemo(() => [...data], [data]);
97
105
  const [sorting, setSorting] = useState<SortingState>([]);
98
106
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
@@ -134,12 +142,56 @@ export function DataTable<TData, TValue = unknown>({
134
142
  });
135
143
 
136
144
  const rows = table.getRowModel().rows;
145
+ const requestedState =
146
+ (listState === 'ready' && rows.length === 0
147
+ ? columnFilters.length > 0
148
+ ? 'no-results'
149
+ : 'empty'
150
+ : listState) ??
151
+ (loading && data.length === 0
152
+ ? 'loading'
153
+ : rows.length
154
+ ? 'ready'
155
+ : columnFilters.length
156
+ ? 'no-results'
157
+ : 'empty');
158
+ const state =
159
+ (requestedState === 'empty' || requestedState === 'no-results') &&
160
+ ((manualPagination && columnFilters.length === 0 && (rowCount ?? 0) > 0) ||
161
+ (!manualPagination && table.getPrePaginationRowModel().rows.length > 0))
162
+ ? 'loading'
163
+ : requestedState;
164
+ const clearFilters = () => {
165
+ table.resetColumnFilters();
166
+ table.setPageIndex(0);
167
+ onClearFilters?.();
168
+ };
169
+ const hasStateActions =
170
+ (state === 'no-results' &&
171
+ (columnFilters.length > 0 || onClearFilters !== undefined)) ||
172
+ ((state === 'failed' || state === 'refresh-failed') &&
173
+ onRetry !== undefined) ||
174
+ state === 'unauthenticated';
175
+ const showRows =
176
+ rows.length > 0 && (state === 'ready' || state === 'refresh-failed');
177
+ const showFeedback = !showRows || state === 'refresh-failed';
178
+ const feedbackColumnCount = Math.max(1, table.getVisibleLeafColumns().length);
137
179
 
138
180
  return (
139
181
  <div className={cn('flex flex-col gap-4', className)}>
140
- {toolbar ? (
182
+ {toolbar || hasStateActions ? (
141
183
  <div className='flex flex-wrap items-center gap-2'>
142
- {toolbar(table)}
184
+ {toolbar?.(table)}
185
+ <ListFeedbackActions
186
+ state={state}
187
+ onClear={
188
+ columnFilters.length > 0 || onClearFilters
189
+ ? clearFilters
190
+ : undefined
191
+ }
192
+ onRetry={onRetry}
193
+ busy={loading}
194
+ />
143
195
  </div>
144
196
  ) : null}
145
197
  <div className='overflow-hidden rounded-lg border'>
@@ -173,39 +225,41 @@ export function DataTable<TData, TValue = unknown>({
173
225
  ))}
174
226
  </TableHeader>
175
227
  <TableBody>
176
- {rows.length > 0 ? (
177
- rows.map((row) => (
178
- <TableRow
179
- key={row.id}
180
- data-state={row.getIsSelected() ? 'selected' : undefined}
181
- className={onRowClick ? 'cursor-pointer' : undefined}
182
- onClick={onRowClick ? () => onRowClick(row) : undefined}
183
- >
184
- {row.getVisibleCells().map((cell) => (
185
- <TableCell key={cell.id}>
186
- {flexRender(
187
- cell.column.columnDef.cell,
188
- cell.getContext(),
189
- )}
190
- </TableCell>
191
- ))}
192
- </TableRow>
193
- ))
194
- ) : (
228
+ {showFeedback ? (
195
229
  <TableRow>
196
- <TableCell
197
- colSpan={columns.length}
198
- className='h-24 text-center text-muted-foreground'
199
- >
200
- {emptyMessage ??
201
- t('dataTable.noResults', { defaultValue: 'No results.' })}
230
+ <TableCell colSpan={feedbackColumnCount} className='p-0'>
231
+ <ListFeedback
232
+ state={state}
233
+ empty={emptyMessage}
234
+ emptyAction={emptyAction}
235
+ />
202
236
  </TableCell>
203
237
  </TableRow>
204
- )}
238
+ ) : null}
239
+ {showRows
240
+ ? rows.map((row) => (
241
+ <TableRow
242
+ key={row.id}
243
+ data-state={row.getIsSelected() ? 'selected' : undefined}
244
+ className={onRowClick ? 'cursor-pointer' : undefined}
245
+ onClick={onRowClick ? () => onRowClick(row) : undefined}
246
+ >
247
+ {row.getVisibleCells().map((cell) => (
248
+ <TableCell key={cell.id}>
249
+ {flexRender(
250
+ cell.column.columnDef.cell,
251
+ cell.getContext(),
252
+ )}
253
+ </TableCell>
254
+ ))}
255
+ </TableRow>
256
+ ))
257
+ : null}
205
258
  </TableBody>
206
259
  </Table>
207
260
  </div>
208
- {pagination ? (
261
+ {pagination &&
262
+ (state === 'ready' || (state === 'refresh-failed' && rows.length > 0)) ? (
209
263
  <DataTablePagination
210
264
  table={table}
211
265
  pageSizeOptions={pageSizeOptions}
@@ -83,6 +83,7 @@ export function DefaultAccessEditor({
83
83
  <Input
84
84
  id={`${id}-key`}
85
85
  name='ruleKey'
86
+ data-field-path='key'
86
87
  value={ruleKey}
87
88
  required
88
89
  disabled={existing || readOnly}
@@ -91,7 +92,11 @@ export function DefaultAccessEditor({
91
92
  onChange={(event) => onKeyChange(event.target.value)}
92
93
  />
93
94
  {keyError && (
94
- <p id={`${id}-key-error`} className='text-sm text-destructive'>
95
+ <p
96
+ data-feedback-field-error
97
+ id={`${id}-key-error`}
98
+ className='text-sm text-destructive'
99
+ >
95
100
  {keyError}
96
101
  </p>
97
102
  )}
@@ -108,6 +113,7 @@ export function DefaultAccessEditor({
108
113
  >
109
114
  <SelectTrigger
110
115
  id={`${id}-resource`}
116
+ data-field-path='resource'
111
117
  className='w-full'
112
118
  aria-invalid={Boolean(resourceError)}
113
119
  aria-describedby={
@@ -127,7 +133,11 @@ export function DefaultAccessEditor({
127
133
  </SelectContent>
128
134
  </Select>
129
135
  {resourceError && (
130
- <p id={`${id}-resource-error`} className='text-sm text-destructive'>
136
+ <p
137
+ data-feedback-field-error
138
+ id={`${id}-resource-error`}
139
+ className='text-sm text-destructive'
140
+ >
131
141
  {resourceError}
132
142
  </p>
133
143
  )}
@@ -187,6 +197,7 @@ export function DefaultAccessEditor({
187
197
  </Select>
188
198
  {scope.error && (
189
199
  <p
200
+ data-feedback-field-error
190
201
  id={`${id}-${scope.id}-error`}
191
202
  className='text-sm text-destructive'
192
203
  >