@nocobase/app-template-default 1.0.0-beta.57 → 1.0.0-beta.59
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.
- package/AGENTS.md +26 -21
- package/README.MD +7 -53
- package/cli/plugins.ts +1 -7
- package/client/agent-annotations-host.ts +8 -2
- package/client/app.ts +0 -2
- package/client/components/brand-loading-mark.tsx +46 -0
- package/client/components/brand-mark-geometry.ts +7 -0
- package/client/components/brand-spinner.tsx +22 -0
- package/client/components/brand-watermark.tsx +32 -0
- package/client/components/loading.tsx +4 -3
- package/client/components/route-overlay.tsx +15 -4
- package/client/components/status-badge.tsx +29 -0
- package/client/components/ui/alert-dialog.tsx +186 -0
- package/client/components/ui/card.tsx +102 -0
- package/client/components/ui/dialog.tsx +2 -2
- package/client/components/ui/radio-group.tsx +35 -0
- package/client/components/ui/spinner.tsx +2 -20
- package/client/components/ui/toast.tsx +3 -2
- package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +3 -3
- package/client/extensions/nocobase-inbox/inbox-item.tsx +4 -3
- package/client/layouts/app-layout.tsx +8 -28
- package/client/layouts/components/app-sidebar.tsx +4 -4
- package/client/layouts/components/header-actions.tsx +3 -36
- package/client/layouts/index.ts +0 -1
- package/client/lib/toaster.ts +1 -1
- package/client/locales/en-US.ts +31 -17
- package/client/locales/zh-CN.ts +24 -14
- package/client/pages/auth/login.tsx +1 -0
- package/client/pages/auth/shared.tsx +9 -9
- package/client/pages/home.tsx +80 -7
- package/client/plugins.ts +2 -6
- package/client/routes.ts +4 -10
- package/client/routing/app-router.tsx +5 -55
- package/client/routing/route-navigation.ts +1 -1
- package/client/styles.css +244 -0
- package/client/theme/theme-settings.tsx +22 -20
- package/client/theme/themes/compact.css +34 -16
- package/client/theme/themes/default.css +34 -16
- package/config.example.yml +0 -16
- package/eslint.config.js +0 -13
- package/index.html +48 -16
- package/package.json +24 -30
- package/server/config/index.ts +0 -6
- package/server/plugins.ts +0 -4
- package/tests/components/app-layout-routes.test.tsx +1 -1
- package/tests/components/inbox.test.tsx +0 -19
- package/tests/components/layout-i18n.test.tsx +5 -92
- package/tests/components/primitive-labels.test.tsx +61 -1
- package/tests/fixtures/theme-tokens.html +1 -1
- package/tests/fixtures/theme-tokens.tsx +7 -0
- package/tests/logic/app-server.test.ts +0 -15
- package/tests/logic/client-plugin-registry.test.ts +0 -21
- package/tests/logic/client-routes.test.ts +2 -6
- package/tests/logic/client-shell.test.tsx +11 -107
- package/tests/logic/client-theme.test.tsx +17 -1
- package/tests/logic/config.test.ts +0 -1
- package/tests/logic/plugin-commands.test.ts +1 -1
- package/tsconfig.json +1 -4
- package/tsconfig.server.json +1 -6
- package/client/components/inbox-badge.ts +0 -49
- package/client/components/inbox-button.tsx +0 -132
- package/client/components/inbox-header-button.tsx +0 -25
- package/client/layouts/components/surface-empty.tsx +0 -35
- package/client/layouts/settings-layout.tsx +0 -172
- package/server/config/ai.ts +0 -27
- package/server/config/workflow.ts +0 -24
- package/tests/logic/client-settings.test.tsx +0 -841
- package/tests/logic/dev-routes-production-build.test.ts +0 -168
- package/tests/logic/plugin-page-authz.test.ts +0 -76
package/client/locales/en-US.ts
CHANGED
|
@@ -59,17 +59,15 @@ const enUS = {
|
|
|
59
59
|
'auth.marketingTitleSecond': 'NocoBase keeps it',
|
|
60
60
|
'auth.marketingTitleThird': 'reliable.',
|
|
61
61
|
'auth.marketingDescription':
|
|
62
|
-
'Give AI a flexible frontend framework to shape each experience, while NocoBase secures the data, permissions
|
|
62
|
+
'Give AI a flexible frontend framework to shape each experience, while NocoBase secures the data, permissions and governance underneath.',
|
|
63
63
|
'auth.frontend': 'AI-native frontend',
|
|
64
64
|
'auth.frontendDescription':
|
|
65
65
|
'Compose interfaces freely on a flexible framework.',
|
|
66
66
|
'auth.foundation': 'NocoBase foundation',
|
|
67
|
-
'auth.foundationDescription':
|
|
68
|
-
'Reliable data, access control, workflows and governance.',
|
|
67
|
+
'auth.foundationDescription': 'Reliable data, access control and governance.',
|
|
69
68
|
'auth.marketingFooter': 'Freedom above. Confidence below.',
|
|
70
69
|
'status.loading': 'Loading',
|
|
71
70
|
'status.loadingPage': 'Loading page',
|
|
72
|
-
'status.loadingSettings': 'Loading settings',
|
|
73
71
|
'status.denied': 'Access denied',
|
|
74
72
|
'status.pageFailed': 'Unable to load page',
|
|
75
73
|
'status.retry': 'Retry',
|
|
@@ -83,22 +81,38 @@ const enUS = {
|
|
|
83
81
|
buildFreely: 'AI builds freely.',
|
|
84
82
|
reliability: '<brand>NocoBase</brand> keeps it reliable.',
|
|
85
83
|
},
|
|
86
|
-
surface: {
|
|
87
|
-
backToApp: 'Back to app',
|
|
88
|
-
loading: 'Loading {{title}}',
|
|
89
|
-
navigation: '{{title}} navigation',
|
|
90
|
-
page: '{{title}} page',
|
|
91
|
-
},
|
|
92
|
-
settings: {
|
|
93
|
-
title: 'Settings',
|
|
94
|
-
emptyTitle: 'No settings available',
|
|
95
|
-
emptyDescription:
|
|
96
|
-
'No enabled plugin contributes a settings page you have access to.',
|
|
97
|
-
},
|
|
98
84
|
home: {
|
|
85
|
+
platform: 'NocoBase · AI-native application platform',
|
|
86
|
+
pages: {
|
|
87
|
+
title: 'Build your pages',
|
|
88
|
+
description: 'Turn your ideas into interfaces that fit your work.',
|
|
89
|
+
examples:
|
|
90
|
+
'From tables and forms to dashboards, shape each page around your business.',
|
|
91
|
+
},
|
|
92
|
+
data: {
|
|
93
|
+
title: 'Organize your data',
|
|
94
|
+
description: 'Give your business a clear, connected data model.',
|
|
95
|
+
examples:
|
|
96
|
+
'Define records, fields and relationships so your information works together.',
|
|
97
|
+
},
|
|
98
|
+
workflows: {
|
|
99
|
+
title: 'Connect your workflows',
|
|
100
|
+
description: 'Keep work moving with consistent business processes.',
|
|
101
|
+
examples:
|
|
102
|
+
'Connect approvals, notifications and automated actions to everyday work.',
|
|
103
|
+
},
|
|
104
|
+
startTitle: 'Start with one requirement',
|
|
105
|
+
startDescription:
|
|
106
|
+
'Tell your AI Agent what you want to achieve and who will use it.',
|
|
107
|
+
examplePrompt:
|
|
108
|
+
'“Build a customer management app for our sales team. We need a customer list, contact details and follow-up records, with a clear overview of each opportunity.”',
|
|
109
|
+
startHint:
|
|
110
|
+
'Begin with a small, useful flow. Refine the pages, data and rules as your needs become clearer.',
|
|
111
|
+
foundation:
|
|
112
|
+
'AI builds freely. NocoBase keeps data, permissions and workflows reliable.',
|
|
99
113
|
title: 'Start building your application',
|
|
100
114
|
description:
|
|
101
|
-
'Describe what you need to your AI Agent, then build pages, data models, and business
|
|
115
|
+
'Describe what you need to your AI Agent, then build pages, data models, and business processes.',
|
|
102
116
|
},
|
|
103
117
|
|
|
104
118
|
appearance: {
|
package/client/locales/zh-CN.ts
CHANGED
|
@@ -56,15 +56,14 @@ const zhCN: AppResource = {
|
|
|
56
56
|
'auth.marketingTitleSecond': 'NocoBase 保障',
|
|
57
57
|
'auth.marketingTitleThird': '可靠运行。',
|
|
58
58
|
'auth.marketingDescription':
|
|
59
|
-
'让 AI 在灵活的前端框架上构建体验,由 NocoBase
|
|
59
|
+
'让 AI 在灵活的前端框架上构建体验,由 NocoBase 保障底层数据、权限与治理。',
|
|
60
60
|
'auth.frontend': 'AI 原生前端',
|
|
61
61
|
'auth.frontendDescription': '在灵活的框架上自由构建界面。',
|
|
62
62
|
'auth.foundation': 'NocoBase 基础能力',
|
|
63
|
-
'auth.foundationDescription': '
|
|
63
|
+
'auth.foundationDescription': '可靠的数据、访问控制与治理。',
|
|
64
64
|
'auth.marketingFooter': '自由构建,可靠支撑。',
|
|
65
65
|
'status.loading': '加载中',
|
|
66
66
|
'status.loadingPage': '正在加载页面',
|
|
67
|
-
'status.loadingSettings': '正在加载设置',
|
|
68
67
|
'status.denied': '无权访问',
|
|
69
68
|
'status.pageFailed': '无法加载页面',
|
|
70
69
|
'status.retry': '重试',
|
|
@@ -78,18 +77,29 @@ const zhCN: AppResource = {
|
|
|
78
77
|
buildFreely: 'AI 自由构建。',
|
|
79
78
|
reliability: '<brand>NocoBase</brand> 保障可靠。',
|
|
80
79
|
},
|
|
81
|
-
surface: {
|
|
82
|
-
backToApp: '返回应用',
|
|
83
|
-
loading: '正在加载{{title}}',
|
|
84
|
-
navigation: '{{title}}导航',
|
|
85
|
-
page: '{{title}}页面',
|
|
86
|
-
},
|
|
87
|
-
settings: {
|
|
88
|
-
title: '设置',
|
|
89
|
-
emptyTitle: '暂无可用设置',
|
|
90
|
-
emptyDescription: '没有已启用的插件提供你有权访问的设置页面。',
|
|
91
|
-
},
|
|
92
80
|
home: {
|
|
81
|
+
platform: 'NocoBase · AI 原生应用平台',
|
|
82
|
+
pages: {
|
|
83
|
+
title: '构建业务页面',
|
|
84
|
+
description: '让想法变成适合你的工作界面。',
|
|
85
|
+
examples: '从表格、表单到仪表盘,围绕业务搭建每一个页面。',
|
|
86
|
+
},
|
|
87
|
+
data: {
|
|
88
|
+
title: '组织业务数据',
|
|
89
|
+
description: '建立清晰、相互关联的数据模型。',
|
|
90
|
+
examples: '定义记录、字段与关联,让分散的信息连成整体。',
|
|
91
|
+
},
|
|
92
|
+
workflows: {
|
|
93
|
+
title: '串联业务流程',
|
|
94
|
+
description: '用一致的流程,让工作顺畅推进。',
|
|
95
|
+
examples: '把审批、通知和自动化操作融入日常业务。',
|
|
96
|
+
},
|
|
97
|
+
startTitle: '从一个需求开始',
|
|
98
|
+
startDescription: '告诉 AI 助手,你想解决什么问题、谁会使用这个应用。',
|
|
99
|
+
examplePrompt:
|
|
100
|
+
'“为销售团队搭建一个客户管理应用,包含客户列表、联系人信息和跟进记录,并能清楚查看每个商机的进展。”',
|
|
101
|
+
startHint: '先完成一个实用的小流程,再随着需求逐步完善页面、数据和规则。',
|
|
102
|
+
foundation: 'AI 自由构建,NocoBase 为数据、权限和流程提供可靠底座。',
|
|
93
103
|
title: '开始构建你的应用',
|
|
94
104
|
description: '向 AI 助手描述你的需求,逐步构建页面、数据模型和业务流程。',
|
|
95
105
|
},
|
|
@@ -32,6 +32,7 @@ export default function LoginPage(): ReactElement {
|
|
|
32
32
|
{
|
|
33
33
|
content: (
|
|
34
34
|
<PasswordLoginForm
|
|
35
|
+
className='[&_[data-slot=input]]:h-12 [&_[data-slot=input-group]]:h-12 [&_button[type=submit]]:h-12'
|
|
35
36
|
error={login.error?.message}
|
|
36
37
|
footer={
|
|
37
38
|
signUpAvailable ? (
|
|
@@ -49,7 +49,7 @@ export function AuthPage({
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// The brand panel
|
|
52
|
+
// The brand panel stays dark in both color modes; its dedicated palette belongs to the theme presets.
|
|
53
53
|
function BrandPanel(): ReactElement {
|
|
54
54
|
const { t } = useTranslation();
|
|
55
55
|
const features = [
|
|
@@ -68,10 +68,10 @@ function BrandPanel(): ReactElement {
|
|
|
68
68
|
];
|
|
69
69
|
|
|
70
70
|
return (
|
|
71
|
-
<div className='relative grid h-full place-items-center overflow-hidden bg-
|
|
72
|
-
<div className='pointer-events-none absolute inset-0 [
|
|
71
|
+
<div className='relative grid h-full place-items-center overflow-hidden bg-(--auth-brand-background) p-12 text-white'>
|
|
72
|
+
<div className='pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,color-mix(in_srgb,var(--primary)_22%,transparent),transparent_65%)]' />
|
|
73
73
|
<div className='relative w-full max-w-xl'>
|
|
74
|
-
<p className='text-xs font-semibold tracking-[0.16em] text-
|
|
74
|
+
<p className='text-xs font-semibold tracking-[0.16em] text-(--auth-brand-highlight) uppercase'>
|
|
75
75
|
{t('auth.platform')}
|
|
76
76
|
</p>
|
|
77
77
|
<h2 className='mt-4 text-5xl leading-[1.05] font-semibold tracking-[-0.045em]'>
|
|
@@ -81,26 +81,26 @@ function BrandPanel(): ReactElement {
|
|
|
81
81
|
<br />
|
|
82
82
|
{t('auth.marketingTitleThird')}
|
|
83
83
|
</h2>
|
|
84
|
-
<p className='mt-5 text-sm leading-6 text-
|
|
84
|
+
<p className='mt-5 text-sm leading-6 text-(--auth-brand-muted)'>
|
|
85
85
|
{t('auth.marketingDescription')}
|
|
86
86
|
</p>
|
|
87
|
-
<div className='mt-8 overflow-hidden rounded-2xl bg-white text-
|
|
87
|
+
<div className='mt-8 overflow-hidden rounded-2xl border border-white/10 bg-white/5 text-white shadow-lg backdrop-blur-sm'>
|
|
88
88
|
<div className='space-y-5 p-6'>
|
|
89
89
|
{features.map(({ description, icon: Icon, id, title }) => (
|
|
90
90
|
<div className='flex gap-4' key={id}>
|
|
91
|
-
<span className='grid size-11 shrink-0 place-items-center rounded-xl bg-
|
|
91
|
+
<span className='grid size-11 shrink-0 place-items-center rounded-xl bg-(--auth-brand-highlight)/10 text-(--auth-brand-highlight)'>
|
|
92
92
|
<Icon aria-hidden='true' className='size-5' />
|
|
93
93
|
</span>
|
|
94
94
|
<div>
|
|
95
95
|
<p className='font-semibold'>{title}</p>
|
|
96
|
-
<p className='mt-1 text-sm leading-6 text-
|
|
96
|
+
<p className='mt-1 text-sm leading-6 text-(--auth-brand-muted)'>
|
|
97
97
|
{description}
|
|
98
98
|
</p>
|
|
99
99
|
</div>
|
|
100
100
|
</div>
|
|
101
101
|
))}
|
|
102
102
|
</div>
|
|
103
|
-
<div className='flex items-center gap-3
|
|
103
|
+
<div className='flex items-center gap-3 border-t border-white/10 bg-white/3 px-6 py-4 text-sm font-medium text-(--auth-brand-muted)'>
|
|
104
104
|
<Blocks aria-hidden='true' className='size-4' />
|
|
105
105
|
<span>{t('auth.marketingFooter')}</span>
|
|
106
106
|
</div>
|
package/client/pages/home.tsx
CHANGED
|
@@ -1,16 +1,89 @@
|
|
|
1
1
|
import { useTranslation } from '@nocobase/i18n/client';
|
|
2
2
|
import type { ReactElement } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Database,
|
|
5
|
+
MessageSquareText,
|
|
6
|
+
PanelsTopLeft,
|
|
7
|
+
ShieldCheck,
|
|
8
|
+
Sparkles,
|
|
9
|
+
Workflow,
|
|
10
|
+
} from 'lucide-react';
|
|
11
|
+
import { PageContainer } from '#components/page-container';
|
|
12
|
+
import { PageHeader } from '#components/page-header';
|
|
13
|
+
import {
|
|
14
|
+
Card,
|
|
15
|
+
CardContent,
|
|
16
|
+
CardDescription,
|
|
17
|
+
CardHeader,
|
|
18
|
+
CardTitle,
|
|
19
|
+
} from '#components/ui/card';
|
|
3
20
|
|
|
4
21
|
export default function HomePage(): ReactElement {
|
|
5
22
|
const { t } = useTranslation();
|
|
6
23
|
return (
|
|
7
|
-
<
|
|
8
|
-
<div className='
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
<PageContainer className='mx-auto max-w-5xl'>
|
|
25
|
+
<div className='flex flex-col gap-4'>
|
|
26
|
+
<div className='flex items-center gap-2 text-sm font-medium text-primary'>
|
|
27
|
+
<Sparkles aria-hidden='true' className='size-4' />
|
|
28
|
+
{t('home.platform')}
|
|
29
|
+
</div>
|
|
30
|
+
<PageHeader
|
|
31
|
+
title={t('home.title')}
|
|
32
|
+
description={t('home.description')}
|
|
33
|
+
/>
|
|
13
34
|
</div>
|
|
14
|
-
|
|
35
|
+
<div className='grid gap-4 md:grid-cols-3'>
|
|
36
|
+
{(
|
|
37
|
+
[
|
|
38
|
+
['pages', PanelsTopLeft],
|
|
39
|
+
['data', Database],
|
|
40
|
+
['workflows', Workflow],
|
|
41
|
+
] as const
|
|
42
|
+
).map(([feature, Icon]) => (
|
|
43
|
+
<Card key={feature}>
|
|
44
|
+
<CardHeader className='gap-3'>
|
|
45
|
+
<div className='flex size-10 items-center justify-center rounded-lg bg-primary/5 text-primary'>
|
|
46
|
+
<Icon aria-hidden='true' className='size-4' />
|
|
47
|
+
</div>
|
|
48
|
+
<CardTitle>
|
|
49
|
+
<h2>{t(`home.${feature}.title`)}</h2>
|
|
50
|
+
</CardTitle>
|
|
51
|
+
<CardDescription>
|
|
52
|
+
{t(`home.${feature}.description`)}
|
|
53
|
+
</CardDescription>
|
|
54
|
+
</CardHeader>
|
|
55
|
+
<CardContent>
|
|
56
|
+
<p className='text-sm leading-6 text-muted-foreground'>
|
|
57
|
+
{t(`home.${feature}.examples`)}
|
|
58
|
+
</p>
|
|
59
|
+
</CardContent>
|
|
60
|
+
</Card>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
<Card>
|
|
64
|
+
<CardHeader>
|
|
65
|
+
<CardTitle>
|
|
66
|
+
<h2 className='flex items-center gap-2'>
|
|
67
|
+
<MessageSquareText
|
|
68
|
+
aria-hidden='true'
|
|
69
|
+
className='size-4 text-primary'
|
|
70
|
+
/>
|
|
71
|
+
{t('home.startTitle')}
|
|
72
|
+
</h2>
|
|
73
|
+
</CardTitle>
|
|
74
|
+
<CardDescription>{t('home.startDescription')}</CardDescription>
|
|
75
|
+
</CardHeader>
|
|
76
|
+
<CardContent className='flex flex-col gap-4'>
|
|
77
|
+
<blockquote className='rounded-lg border border-primary/10 bg-primary/5 p-4 text-sm leading-7'>
|
|
78
|
+
{t('home.examplePrompt')}
|
|
79
|
+
</blockquote>
|
|
80
|
+
<p className='text-sm text-muted-foreground'>{t('home.startHint')}</p>
|
|
81
|
+
</CardContent>
|
|
82
|
+
</Card>
|
|
83
|
+
<p className='flex items-start gap-2 text-sm leading-6 text-muted-foreground'>
|
|
84
|
+
<ShieldCheck aria-hidden='true' className='mt-1 size-4 shrink-0' />
|
|
85
|
+
{t('home.foundation')}
|
|
86
|
+
</p>
|
|
87
|
+
</PageContainer>
|
|
15
88
|
);
|
|
16
89
|
}
|
package/client/plugins.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
defineClientPlugins,
|
|
6
6
|
type AppClientPlugins,
|
|
7
7
|
} from '@nocobase/app-client/plugins';
|
|
8
|
-
import aiEmployee from '@nocobase/app-plugin-ai-employee/client';
|
|
9
8
|
import apiKeys from '@nocobase/app-plugin-api-keys/client';
|
|
10
9
|
import authentication from '@nocobase/app-plugin-authentication/client';
|
|
11
10
|
import authorization from '@nocobase/app-plugin-authorization/client';
|
|
@@ -13,7 +12,6 @@ import databaseExplorer from '@nocobase/app-plugin-database-explorer/client';
|
|
|
13
12
|
import users from '@nocobase/app-plugin-users/client';
|
|
14
13
|
import notificationInApp from '@nocobase/app-plugin-notification-in-app/client';
|
|
15
14
|
import i18n from '@nocobase/app-plugin-i18n/client';
|
|
16
|
-
import workflow from '@nocobase/app-plugin-workflow/client';
|
|
17
15
|
import notification from '@nocobase/app-plugin-notification/client';
|
|
18
16
|
import scheduler from '@nocobase/app-plugin-scheduler/client';
|
|
19
17
|
import file from '@nocobase/app-plugin-file/client';
|
|
@@ -22,17 +20,15 @@ import file from '@nocobase/app-plugin-file/client';
|
|
|
22
20
|
// list; removing its entry and its import disables it.
|
|
23
21
|
const clientPlugins: AppClientPlugins = defineClientPlugins([
|
|
24
22
|
authentication(),
|
|
25
|
-
aiEmployee(),
|
|
26
23
|
authorization(),
|
|
27
24
|
defaultAccess(),
|
|
28
25
|
sharingRules(),
|
|
29
26
|
restrictionRules(),
|
|
30
27
|
databaseExplorer(),
|
|
31
|
-
users(
|
|
32
|
-
apiKeys(
|
|
28
|
+
users(),
|
|
29
|
+
apiKeys(),
|
|
33
30
|
i18n(),
|
|
34
31
|
notificationInApp(),
|
|
35
|
-
workflow(),
|
|
36
32
|
notification(),
|
|
37
33
|
file(),
|
|
38
34
|
scheduler(),
|
package/client/routes.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Home } from 'lucide-react';
|
|
2
2
|
import {
|
|
3
3
|
defineAppRoutes,
|
|
4
|
-
defineSettingsRoutes,
|
|
5
4
|
type AppClientRouteContribution,
|
|
6
5
|
} from '@nocobase/app-client/plugins';
|
|
7
6
|
|
|
@@ -17,9 +16,9 @@ const appRoutes: AppClientRouteContribution = defineAppRoutes([
|
|
|
17
16
|
path: '/',
|
|
18
17
|
},
|
|
19
18
|
{
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// Declares no navigation, so the application decides where its entry goes (for example a sidebar item with an
|
|
20
|
+
// unread badge). The plugin's API answers each person's own messages only, so the page needs no authorization of
|
|
21
|
+
// its own.
|
|
23
22
|
authz: 'skip',
|
|
24
23
|
auth: 'required',
|
|
25
24
|
componentLoader: () => import('./pages/inbox.js'),
|
|
@@ -65,11 +64,6 @@ const appRoutes: AppClientRouteContribution = defineAppRoutes([
|
|
|
65
64
|
},
|
|
66
65
|
]);
|
|
67
66
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const routes: readonly AppClientRouteContribution[] = [
|
|
71
|
-
appRoutes,
|
|
72
|
-
settingsRoutes,
|
|
73
|
-
];
|
|
67
|
+
const routes: readonly AppClientRouteContribution[] = [appRoutes];
|
|
74
68
|
|
|
75
69
|
export default routes;
|
|
@@ -1,52 +1,29 @@
|
|
|
1
|
-
import { useTranslation } from '@nocobase/i18n/client';
|
|
2
1
|
import {
|
|
3
2
|
GuestAuthentication,
|
|
4
3
|
RequiredAuthentication,
|
|
5
4
|
} from '@nocobase/app-plugin-authentication/client';
|
|
6
5
|
import type { AppClientRegisteredRoute } from '@nocobase/app-client/plugins';
|
|
7
|
-
import {
|
|
6
|
+
import { useMemo, type ReactElement } from 'react';
|
|
8
7
|
import { Navigate, Outlet, Route, Routes } from 'react-router';
|
|
9
8
|
|
|
10
|
-
import { Loading } from '#components/loading';
|
|
11
9
|
import { EMPTY_ARRAY } from '#lib/constants';
|
|
12
10
|
|
|
13
11
|
import { AppLayout } from '../layouts/app-layout.js';
|
|
14
12
|
import { renderRouteTree } from './route-tree.js';
|
|
15
13
|
import { StandalonePageLayout } from './standalone-page-layout.js';
|
|
16
14
|
|
|
17
|
-
// The settings centre brings its own chrome and navigation, none of which the application needs until someone opens
|
|
18
|
-
// it. Loading it lazily keeps it out of the entry chunk, the same way every page it hosts stays out.
|
|
19
|
-
const SettingsLayout = lazy(async () => ({
|
|
20
|
-
default: (await import('../layouts/settings-layout.js')).SettingsLayout,
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
15
|
export interface AppRouterProps {
|
|
24
|
-
readonly settingsRouteTree: readonly AppClientRegisteredRoute[];
|
|
25
|
-
readonly devRouteTree: readonly AppClientRegisteredRoute[];
|
|
26
16
|
readonly clientRoutes: readonly AppClientRegisteredRoute[];
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
export function AppRouter(
|
|
30
|
-
const { t } = useTranslation();
|
|
31
|
-
const { settingsRouteTree, devRouteTree, clientRoutes } = inputProps;
|
|
32
|
-
|
|
33
|
-
const settingsRoutes = useMemo(
|
|
34
|
-
() =>
|
|
35
|
-
filterRouteTree(
|
|
36
|
-
clientRoutes,
|
|
37
|
-
(route) =>
|
|
38
|
-
route.auth === 'required' && route.path.startsWith('/settings/'),
|
|
39
|
-
),
|
|
40
|
-
[clientRoutes],
|
|
41
|
-
);
|
|
19
|
+
export function AppRouter({ clientRoutes }: AppRouterProps): ReactElement {
|
|
42
20
|
const routeGroups = useMemo(
|
|
43
21
|
() => ({
|
|
44
22
|
guest: clientRoutes.filter((route) => route.auth === 'guest'),
|
|
45
23
|
optional: clientRoutes.filter((route) => route.auth === 'optional'),
|
|
46
24
|
required: filterRouteTree(
|
|
47
25
|
clientRoutes,
|
|
48
|
-
(route) =>
|
|
49
|
-
route.auth === 'required' && !route.path.startsWith('/settings/'),
|
|
26
|
+
(route) => route.auth === 'required',
|
|
50
27
|
),
|
|
51
28
|
}),
|
|
52
29
|
[clientRoutes],
|
|
@@ -61,36 +38,9 @@ export function AppRouter(inputProps: AppRouterProps): ReactElement {
|
|
|
61
38
|
</RequiredAuthentication>
|
|
62
39
|
}
|
|
63
40
|
>
|
|
64
|
-
<Route
|
|
65
|
-
element={
|
|
66
|
-
<AppLayout routes={routeGroups.required} devRoutes={devRouteTree} />
|
|
67
|
-
}
|
|
68
|
-
>
|
|
41
|
+
<Route element={<AppLayout routes={routeGroups.required} />}>
|
|
69
42
|
{renderRouteTree(routeGroups.required)}
|
|
70
|
-
{/* Pages plugins declare with `defineDevRoutes()` keep their `/dev/...` paths inside the application shell.
|
|
71
|
-
A production build resolves no dev routes, so nothing is rendered here. */}
|
|
72
|
-
{renderRouteTree(devRouteTree)}
|
|
73
43
|
</Route>
|
|
74
|
-
<Route
|
|
75
|
-
path='/settings/*'
|
|
76
|
-
element={
|
|
77
|
-
<Suspense
|
|
78
|
-
fallback={
|
|
79
|
-
<Loading
|
|
80
|
-
className='min-h-svh'
|
|
81
|
-
label={t('status.loadingSettings', {
|
|
82
|
-
defaultValue: 'Loading settings',
|
|
83
|
-
})}
|
|
84
|
-
/>
|
|
85
|
-
}
|
|
86
|
-
>
|
|
87
|
-
<SettingsLayout
|
|
88
|
-
routeTree={settingsRouteTree}
|
|
89
|
-
routes={settingsRoutes}
|
|
90
|
-
/>
|
|
91
|
-
</Suspense>
|
|
92
|
-
}
|
|
93
|
-
/>
|
|
94
44
|
</Route>
|
|
95
45
|
|
|
96
46
|
<Route
|
|
@@ -114,7 +64,7 @@ export function AppRouter(inputProps: AppRouterProps): ReactElement {
|
|
|
114
64
|
);
|
|
115
65
|
}
|
|
116
66
|
|
|
117
|
-
/** Pure groups can span
|
|
67
|
+
/** Pure groups can span shells; a page and its descendants always share their shell. */
|
|
118
68
|
function filterRouteTree(
|
|
119
69
|
routes: readonly AppClientRegisteredRoute[],
|
|
120
70
|
predicate: (route: AppClientRegisteredRoute) => boolean,
|
|
@@ -58,7 +58,7 @@ export function selectedNavigationId(
|
|
|
58
58
|
return selected ? routeKey(selected) : undefined;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
function navigationPages(
|
|
62
62
|
items: readonly RouteNavigationItem[],
|
|
63
63
|
): AppClientRegisteredRoute[] {
|
|
64
64
|
return items.flatMap(({ route, children }) => [
|