@nocobase/app-template-default 1.0.0-beta.87 → 1.0.0-beta.89

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 (107) hide show
  1. package/.agents/rules/components.md +3 -1
  2. package/.agents/rules/notification-templates.md +5 -0
  3. package/.agents/rules/shell.md +9 -3
  4. package/AGENTS.md +4 -2
  5. package/README.MD +11 -5
  6. package/client/components/back-button.tsx +37 -12
  7. package/client/components/body-portal.tsx +25 -0
  8. package/client/components/brand-loading-mark.tsx +5 -2
  9. package/client/components/brand-mark-geometry.ts +3 -3
  10. package/client/components/breadcrumbs.tsx +60 -3
  11. package/client/components/data-table/data-table.tsx +90 -19
  12. package/client/components/data-table/pagination.tsx +17 -2
  13. package/client/components/flow-diagram.tsx +2 -2
  14. package/client/components/list-feedback.tsx +31 -22
  15. package/client/components/loading.tsx +127 -16
  16. package/client/components/pending-button.tsx +111 -0
  17. package/client/components/role-list.tsx +7 -0
  18. package/client/components/route-child-page.tsx +72 -1
  19. package/client/components/route-overlay.tsx +149 -15
  20. package/client/components/task-progress.tsx +155 -0
  21. package/client/components/use-route-overlay.ts +8 -11
  22. package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +62 -32
  23. package/client/extensions/nocobase-file-component-ui/locales/de-DE.ts +2 -0
  24. package/client/extensions/nocobase-file-component-ui/locales/en-US.ts +1 -0
  25. package/client/extensions/nocobase-file-component-ui/locales/es-ES.ts +1 -0
  26. package/client/extensions/nocobase-file-component-ui/locales/fr-FR.ts +1 -0
  27. package/client/extensions/nocobase-file-component-ui/locales/id-ID.ts +1 -0
  28. package/client/extensions/nocobase-file-component-ui/locales/ja-JP.ts +2 -0
  29. package/client/extensions/nocobase-file-component-ui/locales/pt-BR.ts +1 -0
  30. package/client/extensions/nocobase-file-component-ui/locales/vi-VN.ts +1 -0
  31. package/client/extensions/nocobase-file-component-ui/locales/zh-CN.ts +1 -0
  32. package/client/extensions/nocobase-file-component-ui/locales/zh-TW.ts +1 -0
  33. package/client/extensions/nocobase-workflow-management/README.md +5 -5
  34. package/client/extensions/nocobase-workflow-management/definition-list.tsx +2 -9
  35. package/client/extensions/nocobase-workflow-management/execution-list.tsx +2 -4
  36. package/client/layouts/app-layout.tsx +8 -8
  37. package/client/layouts/components/sidebar-footer.tsx +18 -25
  38. package/client/lib/navigation-transform.ts +156 -22
  39. package/client/locales/system/en-US.ts +26 -5
  40. package/client/locales/system/settings-en-US.ts +17 -0
  41. package/client/locales/system/settings-zh-CN.ts +15 -0
  42. package/client/locales/system/zh-CN.ts +26 -5
  43. package/client/pages/auth/register.tsx +39 -22
  44. package/client/pages/auth/shared.tsx +1 -3
  45. package/client/pages/settings/default-access.tsx +1 -1
  46. package/client/pages/settings/hooks.tsx +3 -68
  47. package/client/pages/settings/notification-routes.ts +109 -0
  48. package/client/pages/settings/notification-templates.tsx +26 -0
  49. package/client/pages/settings/permission-management-page.tsx +15 -90
  50. package/client/pages/settings/roles.tsx +27 -27
  51. package/client/pages/settings/routes.ts +107 -101
  52. package/client/pages/settings/settings-home.tsx +109 -0
  53. package/client/pages/settings/settings-module-page.tsx +158 -0
  54. package/client/pages/settings/shell-routes.ts +53 -0
  55. package/client/pages/settings/user-detail.tsx +3 -17
  56. package/client/pages/settings/user-form.tsx +9 -15
  57. package/client/pages/settings/users.tsx +11 -27
  58. package/client/routes.ts +17 -1
  59. package/client/routing/app-router.tsx +1 -14
  60. package/client/routing/client-route.tsx +199 -30
  61. package/client/routing/navigation-destination.ts +42 -0
  62. package/client/routing/route-context.tsx +30 -3
  63. package/client/routing/route-leave-boundary.tsx +261 -0
  64. package/client/routing/route-navigation.ts +72 -8
  65. package/client/routing/route-tree.tsx +7 -12
  66. package/client/runtime.ts +2 -2
  67. package/client/styles.css +18 -1
  68. package/config.example.yml +1 -1
  69. package/package.json +13 -13
  70. package/server/config/auth.ts +1 -1
  71. package/server/providers/index.ts +2 -0
  72. package/server/providers/notification-management.ts +27 -0
  73. package/tests/client/account-permissions.test.tsx +1 -1
  74. package/tests/client/auth-i18n.test.tsx +1 -1
  75. package/tests/client/authenticated-client-fixture.test.tsx +37 -0
  76. package/tests/client/back-button.test.tsx +34 -0
  77. package/tests/client/client-auth.test.tsx +10 -1
  78. package/tests/client/client-route-recovery.test.tsx +93 -1
  79. package/tests/client/client-routes.test.ts +137 -4
  80. package/tests/client/client-shell.test.tsx +23 -8
  81. package/tests/client/foundation-integration.test.ts +38 -17
  82. package/tests/client/layout-i18n.test.tsx +9 -4
  83. package/tests/client/list-feedback.test.tsx +5 -5
  84. package/tests/client/list-loading.test.tsx +397 -0
  85. package/tests/client/loading.test.tsx +20 -11
  86. package/tests/client/manual-list-retry.test.tsx +2 -2
  87. package/tests/client/navigation-parent.test.tsx +172 -0
  88. package/tests/client/pending-button.test.tsx +106 -0
  89. package/tests/client/permission-management-tabs.test.tsx +458 -17
  90. package/tests/client/primitive-labels.test.tsx +2 -2
  91. package/tests/client/registration-feedback.test.tsx +143 -0
  92. package/tests/client/role-return.test.tsx +21 -3
  93. package/tests/client/roles.test.tsx +84 -3
  94. package/tests/client/route-leave-recheck.test.tsx +74 -0
  95. package/tests/client/route-presentation.test.tsx +557 -0
  96. package/tests/client/session-preparation.test.tsx +20 -17
  97. package/tests/client/settings-continuity.test.tsx +35 -11
  98. package/tests/client/settings-home.test.tsx +112 -0
  99. package/tests/client/settings-idle-list.test.tsx +124 -0
  100. package/tests/client/settings-navigation.test.ts +230 -0
  101. package/tests/client/user-drawer.test.tsx +3 -4
  102. package/tests/fixtures/dialog-footer.html +12 -0
  103. package/tests/fixtures/dialog-footer.tsx +215 -0
  104. package/tests/helpers/authenticated-client.tsx +70 -22
  105. package/tests/playwright/dialog-footer.test.ts +227 -0
  106. package/tests/server/notification-templates.test.ts +144 -0
  107. package/tests/client/permission-management-routes.test.ts +0 -64
@@ -0,0 +1,144 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { fileURLToPath } from 'node:url';
3
+ import {
4
+ signIn,
5
+ DEFAULT_ADMIN_CREDENTIALS,
6
+ } from '@nocobase/app-plugin-authentication/testing';
7
+ import { createAppTest } from '@nocobase/app-testing/server';
8
+ import { describe, expect } from 'vitest';
9
+ import { createStandaloneServer } from '../../server/standalone.js';
10
+
11
+ const common = {
12
+ createServer: createStandaloneServer,
13
+ scope: 'test' as const,
14
+ config: {
15
+ app: { publicOrigin: 'http://localhost' },
16
+ spa: {
17
+ indexPath: fileURLToPath(new URL('../../index.html', import.meta.url)),
18
+ },
19
+ auth: {
20
+ secret: 'notification-template-test-secret-at-least-32-characters',
21
+ },
22
+ },
23
+ };
24
+ describe('bare Default', () => {
25
+ const test = createAppTest(common);
26
+ test('keeps the bare Default navigation disabled without an invitation transport', async ({
27
+ app,
28
+ }) => {
29
+ expect(
30
+ app.config.get('client.notificationTemplates.invitationConfigured'),
31
+ ).toBe(false);
32
+ });
33
+ });
34
+ describe('configured Default', () => {
35
+ const test = createAppTest({
36
+ ...common,
37
+ config: {
38
+ ...common.config,
39
+ mailers: {
40
+ default: 'system',
41
+ system: {
42
+ provider: 'smtp',
43
+ host: '127.0.0.1',
44
+ port: 9,
45
+ auth: { user: 'synthetic', pass: 'synthetic-private-password' },
46
+ },
47
+ },
48
+ },
49
+ });
50
+
51
+ test('boots the real Default invitation directory and protects all three endpoints', async ({
52
+ testApp,
53
+ app,
54
+ request,
55
+ }) => {
56
+ expect(
57
+ app.config.get('client.notificationTemplates.invitationConfigured'),
58
+ ).toBe(true);
59
+ const page = await testApp.fetch(
60
+ new Request(`http://localhost${testApp.publicBasePath}/`),
61
+ );
62
+ expect(page.status).toBe(200);
63
+ const html = await page.text();
64
+ expect(html).toContain('"invitationConfigured":true');
65
+ expect(html).not.toContain('synthetic-private-password');
66
+ const admin = await signIn(testApp, DEFAULT_ADMIN_CREDENTIALS);
67
+ expect((await request('/notifications/templates')).status).toBe(401);
68
+ const list = await admin.fetch('/notifications/templates');
69
+ expect(list.status).toBe(200);
70
+ const body = await list.json();
71
+ expect(JSON.stringify(body)).not.toContain('synthetic-private-password');
72
+ expect(body.data).toEqual(
73
+ expect.arrayContaining([
74
+ expect.objectContaining({
75
+ id: 'users.invitation',
76
+ channels: [
77
+ { name: 'system', type: 'email', configured: true, enabled: true },
78
+ ],
79
+ }),
80
+ ]),
81
+ );
82
+ const detail = await admin.fetch(
83
+ '/notifications/templates/users.invitation',
84
+ );
85
+ expect(detail.status).toBe(200);
86
+ const logsBefore = await (await admin.fetch('/notifications/logs')).json();
87
+ const preview = await admin.fetch(
88
+ '/notifications/templates/users.invitation/preview',
89
+ {
90
+ method: 'POST',
91
+ headers: {
92
+ origin: 'http://localhost',
93
+ 'content-type': 'application/json',
94
+ },
95
+ body: JSON.stringify({ locale: 'en-US' }),
96
+ },
97
+ );
98
+ expect(preview.status).toBe(200);
99
+ expect(JSON.stringify(await preview.json())).toContain('example.invalid');
100
+ expect(await (await admin.fetch('/notifications/logs')).json()).toEqual(
101
+ logsBefore,
102
+ );
103
+ const member = {
104
+ username: 'templateviewer',
105
+ email: 'templateviewer@example.invalid',
106
+ password: 'Synthetic-template-viewer-943!',
107
+ };
108
+ const created = await admin.fetch('/users', {
109
+ method: 'POST',
110
+ headers: {
111
+ origin: 'http://localhost',
112
+ 'content-type': 'application/json',
113
+ 'Idempotency-Key': `${Date.now()}.${randomUUID()}`,
114
+ },
115
+ body: JSON.stringify({
116
+ ...member,
117
+ name: 'Synthetic restricted viewer',
118
+ roleScopes: {},
119
+ }),
120
+ });
121
+ expect(created.status).toBe(201);
122
+ const restricted = await signIn(testApp, member);
123
+ for (const endpoint of [
124
+ '',
125
+ '/users.invitation',
126
+ '/users.invitation/preview',
127
+ ]) {
128
+ const response = await restricted.fetch(
129
+ `/notifications/templates${endpoint}`,
130
+ endpoint.endsWith('/preview')
131
+ ? {
132
+ method: 'POST',
133
+ headers: {
134
+ origin: 'http://localhost',
135
+ 'content-type': 'application/json',
136
+ },
137
+ body: JSON.stringify({ locale: 'en-US' }),
138
+ }
139
+ : undefined,
140
+ );
141
+ expect(response.status).toBe(403);
142
+ }
143
+ });
144
+ });
@@ -1,64 +0,0 @@
1
- import { resolveAppClientContributions } from '@nocobase/app-client/plugins';
2
- import { expect, it } from 'vitest';
3
- import settings from '../../client/pages/settings/routes.js';
4
- import {
5
- buildRouteNavigation,
6
- selectedNavigationId,
7
- routeKey,
8
- } from '../../client/routing/route-navigation.js';
9
- it('groups existing permission pages without changing their URLs or authorization and hides inaccessible children', () => {
10
- const { routes } = resolveAppClientContributions([
11
- { packageName: 'test', source: 'application', routes: [settings] },
12
- ]);
13
- const group = routes[0].children!.find(
14
- (route) => route.name === 'permissionManagement',
15
- )!;
16
- expect(group.componentLoader).toBeUndefined();
17
- expect(
18
- group.children!.map((route) => [route.name, route.path, route.authz]),
19
- ).toEqual([
20
- [
21
- 'settingsRoles',
22
- '/settings/roles',
23
- {
24
- resource: { type: 'settings', id: 'authorization.permission-sets' },
25
- action: 'read',
26
- },
27
- ],
28
- [
29
- 'settingsDefaultAccess',
30
- '/settings/default-access',
31
- {
32
- resource: { type: 'settings', id: 'authorization.default-access' },
33
- action: 'read',
34
- },
35
- ],
36
- ]);
37
- const visible = buildRouteNavigation(routes, new Set())[0].children.find(
38
- ({ route }) => route.name === 'permissionManagement',
39
- )!;
40
- expect(visible.children).toEqual([]);
41
- expect(visible.route.path).toBe('/settings/roles');
42
- expect(visible.route.navigation?.title).toBe('settings.permissionManagement');
43
- expect(selectedNavigationId(routes, '/settings/roles/member/edit')).toBe(
44
- routeKey(visible.route),
45
- );
46
- expect(selectedNavigationId(routes, '/settings/default-access')).toBe(
47
- routeKey(visible.route),
48
- );
49
- const denied = new Set([routeKey(group.children![0])]);
50
- const onlyDefault = buildRouteNavigation(routes, denied)[0].children.find(
51
- ({ route }) => route.name === 'permissionManagement',
52
- )!;
53
- expect(onlyDefault.children).toEqual([]);
54
- expect(onlyDefault.route.path).toBe('/settings/default-access');
55
- expect(selectedNavigationId(routes, '/settings/default-access', denied)).toBe(
56
- routeKey(onlyDefault.route),
57
- );
58
- denied.add(routeKey(group.children![1]));
59
- expect(
60
- buildRouteNavigation(routes, denied)[0].children.map(
61
- ({ route }) => route.name,
62
- ),
63
- ).toEqual(['settingsUsers']);
64
- });