@nocobase/portal-template-default 1.0.4 → 1.0.6

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 (139) hide show
  1. package/README.MD +77 -0
  2. package/package.json +23 -17
  3. package/registry/nocobase-acl/demo/prompt-generator.tsx +6 -39
  4. package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +6 -42
  5. package/registry/nocobase-ai/demo/page-context-prompt-generator.tsx +1 -1
  6. package/registry/nocobase-ai/demo/prompt-card.tsx +7 -28
  7. package/registry/nocobase-ai/demo/prompt-generator.tsx +9 -30
  8. package/registry/nocobase-ai/demo/shortcut.tsx +1 -1
  9. package/registry/nocobase-ai/demo/tool-cards.tsx +10 -30
  10. package/registry/nocobase-auth-cas/README.md +6 -0
  11. package/registry/nocobase-auth-cas/cas-sign-in-button.tsx +41 -0
  12. package/registry/nocobase-auth-cas/demo.tsx +20 -0
  13. package/registry/nocobase-auth-cas/extension.tsx +46 -0
  14. package/registry/nocobase-auth-cas/index.ts +1 -0
  15. package/registry/nocobase-auth-cas/use-cas-sign-in.ts +29 -0
  16. package/registry/nocobase-auth-dingtalk/README.md +7 -0
  17. package/registry/nocobase-auth-dingtalk/auto-login-provider.tsx +154 -0
  18. package/registry/nocobase-auth-dingtalk/demo.tsx +23 -0
  19. package/registry/nocobase-auth-dingtalk/dingtalk-sign-in-button.tsx +45 -0
  20. package/registry/nocobase-auth-dingtalk/extension.tsx +55 -0
  21. package/registry/nocobase-auth-dingtalk/index.ts +4 -0
  22. package/registry/nocobase-auth-dingtalk/use-dingtalk-sign-in.ts +64 -0
  23. package/registry/nocobase-auth-ldap/README.md +6 -0
  24. package/registry/nocobase-auth-ldap/demo.tsx +21 -0
  25. package/registry/nocobase-auth-ldap/extension.tsx +46 -0
  26. package/registry/nocobase-auth-ldap/index.ts +1 -0
  27. package/registry/nocobase-auth-ldap/ldap-sign-in-form.tsx +69 -0
  28. package/registry/nocobase-auth-ldap/use-ldap-sign-in.ts +5 -0
  29. package/registry/nocobase-auth-oidc/README.md +11 -0
  30. package/registry/nocobase-auth-oidc/auto-redirect-provider.tsx +13 -0
  31. package/registry/nocobase-auth-oidc/demo.tsx +20 -0
  32. package/registry/nocobase-auth-oidc/extension.tsx +51 -0
  33. package/registry/nocobase-auth-oidc/index.ts +1 -0
  34. package/registry/nocobase-auth-oidc/oidc-sign-in-button.tsx +44 -0
  35. package/registry/nocobase-auth-oidc/use-oidc-sign-in.ts +58 -0
  36. package/registry/nocobase-auth-saml/README.md +6 -0
  37. package/registry/nocobase-auth-saml/auto-redirect-provider.tsx +13 -0
  38. package/registry/nocobase-auth-saml/demo.tsx +20 -0
  39. package/registry/nocobase-auth-saml/extension.tsx +51 -0
  40. package/registry/nocobase-auth-saml/index.ts +1 -0
  41. package/registry/nocobase-auth-saml/saml-sign-in-button.tsx +43 -0
  42. package/registry/nocobase-auth-saml/use-saml-sign-in.ts +51 -0
  43. package/registry/nocobase-auth-sms/README.md +7 -0
  44. package/registry/nocobase-auth-sms/demo.tsx +25 -0
  45. package/registry/nocobase-auth-sms/extension.tsx +46 -0
  46. package/registry/nocobase-auth-sms/index.ts +1 -0
  47. package/registry/nocobase-auth-sms/sms-sign-in-form.tsx +107 -0
  48. package/registry/nocobase-auth-sms/use-sms-sign-in.ts +88 -0
  49. package/registry/nocobase-auth-wecom/README.md +6 -0
  50. package/registry/nocobase-auth-wecom/auto-login-provider.tsx +21 -0
  51. package/registry/nocobase-auth-wecom/demo.tsx +23 -0
  52. package/registry/nocobase-auth-wecom/extension.tsx +49 -0
  53. package/registry/nocobase-auth-wecom/index.ts +1 -0
  54. package/registry/nocobase-auth-wecom/use-wecom-sign-in.ts +62 -0
  55. package/registry/nocobase-auth-wecom/wecom-sign-in-button.tsx +70 -0
  56. package/registry/nocobase-i18n/demo/prompt-generator.tsx +30 -54
  57. package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +18 -21
  58. package/registry.config.json +112 -0
  59. package/scripts/build-html.mjs +106 -0
  60. package/src/App.tsx +34 -8
  61. package/src/app/extension.ts +4 -0
  62. package/src/app/extensions.tsx +24 -1
  63. package/src/components/auth/authenticator-adapters.ts +20 -0
  64. package/src/components/auth/auto-redirect-provider.tsx +84 -0
  65. package/src/components/auth/basic-sign-in-form.tsx +101 -0
  66. package/src/components/auth/default-sign-in-page.tsx +20 -0
  67. package/src/components/auth/demo/auth-demo-page.tsx +236 -0
  68. package/src/components/auth/demo/auth-demo-prompt-generator.tsx +93 -0
  69. package/src/components/auth/demo/auth-demo-route.tsx +11 -0
  70. package/src/components/auth/demo/auth-method-demo.tsx +48 -0
  71. package/src/components/auth/demo/index.ts +4 -0
  72. package/src/components/auth/dynamic-sign-in.tsx +191 -0
  73. package/src/components/auth/forgot-password-form.tsx +13 -5
  74. package/src/components/auth/index.ts +13 -0
  75. package/src/components/auth/sign-in-form.tsx +1 -111
  76. package/src/components/auth/sign-up-form.tsx +14 -37
  77. package/src/components/auth/types.ts +38 -0
  78. package/src/components/auth/use-authenticator-sign-in.ts +14 -0
  79. package/src/components/auth/use-public-authenticators.ts +26 -0
  80. package/src/components/data-table/data-table-filter.tsx +107 -153
  81. package/src/components/demo/prompt-output.tsx +71 -0
  82. package/src/components/ui/alert-dialog.tsx +2 -0
  83. package/src/components/ui/attachment.tsx +207 -0
  84. package/src/components/ui/bubble.tsx +128 -0
  85. package/src/components/ui/button-group.tsx +87 -0
  86. package/src/components/ui/button.tsx +6 -6
  87. package/src/components/ui/calendar.tsx +0 -2
  88. package/src/components/ui/card.tsx +1 -1
  89. package/src/components/ui/carousel.tsx +2 -0
  90. package/src/components/ui/combobox.tsx +297 -0
  91. package/src/components/ui/command.tsx +43 -41
  92. package/src/components/ui/dialog.tsx +0 -2
  93. package/src/components/ui/direction.tsx +4 -0
  94. package/src/components/ui/drawer.tsx +2 -0
  95. package/src/components/ui/dropdown-menu.tsx +0 -2
  96. package/src/components/ui/empty.tsx +104 -0
  97. package/src/components/ui/field.tsx +238 -0
  98. package/src/components/ui/hover-card.tsx +2 -0
  99. package/src/components/ui/input-group.tsx +0 -2
  100. package/src/components/ui/input-otp.tsx +0 -2
  101. package/src/components/ui/input.tsx +1 -1
  102. package/src/components/ui/item.tsx +201 -0
  103. package/src/components/ui/kbd.tsx +26 -0
  104. package/src/components/ui/label.tsx +2 -0
  105. package/src/components/ui/marker.tsx +71 -0
  106. package/src/components/ui/message-scroller.tsx +131 -0
  107. package/src/components/ui/message.tsx +92 -0
  108. package/src/components/ui/native-select.tsx +61 -0
  109. package/src/components/ui/popover.tsx +0 -2
  110. package/src/components/ui/progress.tsx +2 -0
  111. package/src/components/ui/radio-group.tsx +0 -2
  112. package/src/components/ui/resizable.tsx +2 -0
  113. package/src/components/ui/scroll-area.tsx +0 -2
  114. package/src/components/ui/select.tsx +6 -4
  115. package/src/components/ui/separator.tsx +0 -2
  116. package/src/components/ui/sheet.tsx +21 -22
  117. package/src/components/ui/sidebar.tsx +1 -3
  118. package/src/components/ui/sonner.tsx +2 -0
  119. package/src/components/ui/spinner.tsx +10 -0
  120. package/src/components/ui/switch.tsx +0 -2
  121. package/src/components/ui/table.tsx +4 -2
  122. package/src/components/ui/tabs.tsx +0 -2
  123. package/src/components/ui/textarea.tsx +1 -1
  124. package/src/components/ui/toggle-group.tsx +2 -0
  125. package/src/components/ui/toggle.tsx +2 -0
  126. package/src/components/ui/tooltip.tsx +0 -2
  127. package/src/index.tsx +3 -0
  128. package/src/lib/nocobase/client.ts +32 -2
  129. package/src/lib/utils.ts +2 -1
  130. package/src/locales/en-US.ts +4 -2
  131. package/src/locales/zh-CN.ts +4 -2
  132. package/src/pages/login/index.tsx +2 -2
  133. package/src/pages/users/list.tsx +144 -16
  134. package/src/providers/auth-callback.ts +21 -0
  135. package/src/providers/auth.ts +108 -22
  136. package/src/providers/constants.ts +4 -1
  137. package/src/providers/runtime-config.ts +19 -0
  138. package/src/vite-env.d.ts +5 -0
  139. package/vite.config.ts +55 -2
package/README.MD CHANGED
@@ -24,6 +24,83 @@ namespace for shared controls. Feature-specific Registry translations remain
24
24
  inside their own extension namespace and are not mixed into application locale
25
25
  files.
26
26
 
27
+ ### Authentication UI
28
+
29
+ The default login page calls `authenticators:publicList` and renders every
30
+ enabled NocoBase authenticator that has a frontend adapter installed. Basic
31
+ username/password authentication is built into the Starter. Optional methods
32
+ are installed independently:
33
+
34
+ ```bash
35
+ pnpm exec shadcn add @nocobase/auth-sms
36
+ pnpm exec shadcn add @nocobase/auth-ldap
37
+ pnpm exec shadcn add @nocobase/auth-oidc
38
+ pnpm exec shadcn add @nocobase/auth-saml
39
+ pnpm exec shadcn add @nocobase/auth-cas
40
+ pnpm exec shadcn add @nocobase/auth-wecom
41
+ pnpm exec shadcn add @nocobase/auth-dingtalk
42
+ ```
43
+
44
+ The Starter includes an Authentication Demo for the three customization
45
+ boundaries below. Each optional authentication Registry adds its own component
46
+ Demo under the same Authentication menu when installed.
47
+
48
+ The application login page remains the customization boundary. Keep the
49
+ default dynamic page with:
50
+
51
+ ```tsx
52
+ import { DefaultSignInPage } from "@/components/auth/default-sign-in-page"
53
+
54
+ export const Login = () => <DefaultSignInPage />
55
+ ```
56
+
57
+ To replace only one authenticator UI, use `renderAuthenticator` and return the
58
+ default element for everything else:
59
+
60
+ ```tsx
61
+ import { DefaultSignInPage } from "@/components/auth/default-sign-in-page"
62
+ import { useOidcSignIn } from "@/extensions/nocobase-auth-oidc"
63
+ import { Button } from "@/components/ui/button"
64
+
65
+ function CompanyOidcButton({ authenticator }) {
66
+ const oidc = useOidcSignIn(authenticator)
67
+ return <Button onClick={oidc.signIn}>Company SSO</Button>
68
+ }
69
+
70
+ export const Login = () => (
71
+ <DefaultSignInPage
72
+ renderAuthenticator={({ authenticator, defaultElement }) =>
73
+ authenticator.name === "company-oidc" ? (
74
+ <CompanyOidcButton authenticator={authenticator} />
75
+ ) : (
76
+ defaultElement
77
+ )
78
+ }
79
+ />
80
+ )
81
+ ```
82
+
83
+ Replace `src/pages/login/index.tsx` completely when the application needs a
84
+ fully custom login page. Token callback capture, current authenticator storage,
85
+ request headers, and logout behavior remain provided by the Starter runtime and
86
+ do not depend on `DefaultSignInPage`.
87
+
88
+ ```tsx
89
+ import { AuthLayout } from "@/components/auth/auth-layout"
90
+ import { BasicSignInForm } from "@/components/auth"
91
+
92
+ export const Login = () => (
93
+ <AuthLayout title="Sign in" description="Continue to your workspace.">
94
+ <BasicSignInForm
95
+ authenticator={{
96
+ name: "basic",
97
+ authType: "Email/Password",
98
+ }}
99
+ />
100
+ </AuthLayout>
101
+ )
102
+ ```
103
+
27
104
  ## Available Scripts
28
105
 
29
106
  ### Running the development server.
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@nocobase/portal-template-default",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
6
  "@ai-sdk/react": "2.0.0",
7
7
  "@base-ui/react": "^1.6.0",
8
8
  "@dicebear/core": "^9.4.3",
9
+ "@eslint/js": "^9.25.0",
9
10
  "@fontsource-variable/geist": "^5.3.0",
10
11
  "@hookform/resolvers": "^5.0.1",
11
12
  "@nocobase/ai-employee-avatars": "^1.0.2",
@@ -17,18 +18,31 @@
17
18
  "@refinedev/react-table": "^6.0.0",
18
19
  "@refinedev/rest": "^2.1.0",
19
20
  "@refinedev/simple-rest": "^6.0.0",
21
+ "@tailwindcss/postcss": "^4.1.4",
20
22
  "@tailwindcss/vite": "^4.1.8",
21
23
  "@tanstack/react-query": "^5.81.5",
22
24
  "@tanstack/react-table": "^8.2.6",
25
+ "@types/node": "^20",
26
+ "@types/react": "^19.1.0",
27
+ "@types/react-dom": "^19.1.0",
28
+ "@typescript-eslint/eslint-plugin": "^5.57.1",
29
+ "@typescript-eslint/parser": "^5.57.1",
30
+ "@vitejs/plugin-react": "^4.4.1",
23
31
  "ai": "5.0.0",
24
32
  "class-variance-authority": "^0.7.1",
25
33
  "clsx": "^2.1.1",
26
34
  "date-fns": "^4.1.0",
27
35
  "dayjs": "^1.10.7",
36
+ "dingtalk-jsapi": "3.1.1",
28
37
  "echarts": "^5.5.0",
29
38
  "echarts-for-react": "3.0.2",
30
39
  "embla-carousel-react": "^8.6.0",
40
+ "eslint": "^9.25.0",
41
+ "eslint-plugin-react-hooks": "^5.2.0",
42
+ "eslint-plugin-react-refresh": "^0.4.19",
31
43
  "eventsource-parser": "3.1.0",
44
+ "globals": "^16.0.0",
45
+ "i18next": "^25.3.2",
32
46
  "ignore": "^7.0.6",
33
47
  "input-otp": "^1.4.2",
34
48
  "lucide-react": "^0.487.0",
@@ -46,25 +60,12 @@
46
60
  "shadcn": "^4.13.1",
47
61
  "sonner": "^2.0.3",
48
62
  "tailwind-merge": "^3.2.0",
49
- "tw-animate-css": "^1.2.5",
50
- "zod": "^3.24.3",
51
- "@eslint/js": "^9.25.0",
52
- "@tailwindcss/postcss": "^4.1.4",
53
- "@types/node": "^20",
54
- "@types/react": "^19.1.0",
55
- "@types/react-dom": "^19.1.0",
56
- "@typescript-eslint/eslint-plugin": "^5.57.1",
57
- "@typescript-eslint/parser": "^5.57.1",
58
- "@vitejs/plugin-react": "^4.4.1",
59
- "eslint": "^9.25.0",
60
- "eslint-plugin-react-hooks": "^5.2.0",
61
- "eslint-plugin-react-refresh": "^0.4.19",
62
- "globals": "^16.0.0",
63
- "i18next": "^25.3.2",
64
63
  "tailwindcss": "^4.1.4",
64
+ "tw-animate-css": "^1.2.5",
65
65
  "typescript": "^5.8.3",
66
66
  "typescript-eslint": "^8.30.1",
67
- "vite": "^6.3.5"
67
+ "vite": "^6.3.5",
68
+ "zod": "^3.25.76"
68
69
  },
69
70
  "publishConfig": {
70
71
  "access": "public"
@@ -77,6 +78,7 @@
77
78
  "postinstall": "node ./scripts/registry.mjs install-missing",
78
79
  "dev": "vite",
79
80
  "build": "tsc && refine build",
81
+ "build:html": "node ./scripts/build-html.mjs",
80
82
  "registry:install": "node ./scripts/registry.mjs install-missing",
81
83
  "registry:preview": "node ./scripts/registry.mjs preview",
82
84
  "registry:build": "node ./scripts/registry.mjs build && shadcn build ./registry.json -o ./public/r",
@@ -96,5 +98,9 @@
96
98
  "last 1 firefox version",
97
99
  "last 1 safari version"
98
100
  ]
101
+ },
102
+ "dependencies": {
103
+ "@shadcn/react": "^0.2.1",
104
+ "cmdk": "^1.1.1"
99
105
  }
100
106
  }
@@ -1,7 +1,7 @@
1
- import { Check, ChevronDown, Copy, Sparkles } from "lucide-react";
1
+ import { ChevronDown, Sparkles } from "lucide-react";
2
2
  import { useMemo, useState } from "react";
3
3
 
4
- import { Button } from "@/components/ui/button";
4
+ import { PromptOutput } from "@/components/demo/prompt-output";
5
5
  import {
6
6
  Card,
7
7
  CardContent,
@@ -28,8 +28,6 @@ export function AclScenarioPromptGenerator({
28
28
  }) {
29
29
  const [scene, setScene] = useState(config.defaultScene);
30
30
  const [target, setTarget] = useState(config.defaultTarget);
31
- const [copied, setCopied] = useState(false);
32
- const [copyError, setCopyError] = useState(false);
33
31
 
34
32
  const prompt = useMemo(
35
33
  () => `Build a complete NocoBase ACL scene in the NocoBase Admin Starter.
@@ -93,41 +91,10 @@ Implementation contract
93
91
  </div>
94
92
  </CardContent>
95
93
  </Card>
96
- <Card className="min-w-0 gap-0 overflow-hidden py-0">
97
- <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
98
- <div>
99
- <div className="text-sm font-medium">Generated prompt</div>
100
- <div className="text-xs text-muted-foreground">
101
- Updates as you change the business scene and permission target.
102
- </div>
103
- </div>
104
- <Button
105
- size="sm"
106
- onClick={async () => {
107
- try {
108
- await navigator.clipboard.writeText(prompt);
109
- setCopyError(false);
110
- setCopied(true);
111
- window.setTimeout(() => setCopied(false), 1500);
112
- } catch {
113
- setCopyError(true);
114
- }
115
- }}
116
- >
117
- {copied ? <Check /> : <Copy />}
118
- {copied ? "Copied" : "Copy prompt"}
119
- </Button>
120
- </div>
121
- {copyError ? (
122
- <p className="border-b px-4 py-2 text-xs text-destructive">
123
- Clipboard access failed. Select the prompt text and copy it
124
- manually.
125
- </p>
126
- ) : null}
127
- <pre className="max-h-[520px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
128
- {prompt}
129
- </pre>
130
- </Card>
94
+ <PromptOutput
95
+ description="Updates as you change the business scene and permission target."
96
+ prompt={prompt}
97
+ />
131
98
  </div>
132
99
  </details>
133
100
  );
@@ -1,7 +1,6 @@
1
- import { Check, Copy } from "lucide-react";
2
1
  import { useMemo, useState } from "react";
3
2
 
4
- import { Button } from "@/components/ui/button";
3
+ import { PromptOutput } from "@/components/demo/prompt-output";
5
4
  import {
6
5
  Card,
7
6
  CardContent,
@@ -54,8 +53,6 @@ export function RoleSwitcherPromptGenerator() {
54
53
  const [surface, setSurface] = useState(
55
54
  "Use the Starter's existing account controls and visual language. Do not create a separate demo page."
56
55
  );
57
- const [copied, setCopied] = useState(false);
58
- const [copyError, setCopyError] = useState(false);
59
56
 
60
57
  const prompt = useMemo(
61
58
  () => `Integrate the RoleSwitcher into the existing NocoBase Admin Starter.
@@ -155,44 +152,11 @@ Integration requirements
155
152
  </CardContent>
156
153
  </Card>
157
154
 
158
- <Card className="gap-0 overflow-hidden py-0">
159
- <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
160
- <div>
161
- <div className="text-sm font-medium">
162
- Generated integration prompt
163
- </div>
164
- <div className="text-xs text-muted-foreground">
165
- Updates as you change placement and presentation settings.
166
- </div>
167
- </div>
168
- <Button
169
- size="sm"
170
- onClick={async () => {
171
- try {
172
- await navigator.clipboard.writeText(prompt);
173
- setCopyError(false);
174
- setCopied(true);
175
- window.setTimeout(() => setCopied(false), 1500);
176
- } catch {
177
- setCopyError(true);
178
- }
179
- }}
180
- >
181
- {copied ? <Check /> : <Copy />}
182
- {copied ? "Copied" : "Copy prompt"}
183
- </Button>
184
- </div>
185
- {copyError ? (
186
- <p className="border-b px-4 py-2 text-xs text-destructive">
187
- Clipboard access failed. Select the prompt and copy it manually.
188
- </p>
189
- ) : null}
190
- <CardContent className="p-0">
191
- <pre className="max-h-[560px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
192
- {prompt}
193
- </pre>
194
- </CardContent>
195
- </Card>
155
+ <PromptOutput
156
+ title="Generated integration prompt"
157
+ description="Updates as you change placement and presentation settings."
158
+ prompt={prompt}
159
+ />
196
160
  </div>
197
161
  );
198
162
  }
@@ -155,7 +155,7 @@ Implementation requirements
155
155
  ]);
156
156
 
157
157
  return (
158
- <div className="grid items-start gap-5 xl:grid-cols-[390px_minmax(0,1fr)]">
158
+ <div className="grid items-start gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
159
159
  <Card className="gap-0 py-0">
160
160
  <CardHeader className="border-b py-4">
161
161
  <CardTitle>Scene settings</CardTitle>
@@ -1,7 +1,4 @@
1
- import { Button } from "@/components/ui/button";
2
- import { Card } from "@/components/ui/card";
3
- import { Check, Copy } from "lucide-react";
4
- import { useState } from "react";
1
+ import { PromptOutput } from "@/components/demo/prompt-output";
5
2
 
6
3
  export function PromptCard({
7
4
  title,
@@ -12,30 +9,12 @@ export function PromptCard({
12
9
  description: string;
13
10
  prompt: string;
14
11
  }) {
15
- const [copied, setCopied] = useState(false);
16
-
17
12
  return (
18
- <Card className="gap-0 overflow-hidden py-0">
19
- <div className="flex items-center justify-between gap-4 border-b px-4 py-3">
20
- <div>
21
- <div className="text-sm font-medium">{title}</div>
22
- <div className="text-xs text-muted-foreground">{description}</div>
23
- </div>
24
- <Button
25
- size="sm"
26
- onClick={async () => {
27
- await navigator.clipboard.writeText(prompt);
28
- setCopied(true);
29
- window.setTimeout(() => setCopied(false), 1500);
30
- }}
31
- >
32
- {copied ? <Check /> : <Copy />}
33
- {copied ? "Copied" : "Copy prompt"}
34
- </Button>
35
- </div>
36
- <pre className="max-h-[620px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
37
- {prompt}
38
- </pre>
39
- </Card>
13
+ <PromptOutput
14
+ title={title}
15
+ description={description}
16
+ prompt={prompt}
17
+ promptClassName="max-h-[620px]"
18
+ />
40
19
  );
41
20
  }
@@ -6,7 +6,7 @@ import {
6
6
  useAIPageElementPicker,
7
7
  type AIChatComposerAction,
8
8
  } from "../components";
9
- import { Button } from "@/components/ui/button";
9
+ import { PromptOutput } from "@/components/demo/prompt-output";
10
10
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
11
11
  import { Input } from "@/components/ui/input";
12
12
  import {
@@ -18,7 +18,7 @@ import {
18
18
  } from "@/components/ui/select";
19
19
  import { Switch } from "@/components/ui/switch";
20
20
  import { AIChatProvider, useAIChatBase } from "../providers";
21
- import { Check, Copy, Globe2, MousePointer2 } from "lucide-react";
21
+ import { Globe2, MousePointer2 } from "lucide-react";
22
22
  import { useMemo, useState } from "react";
23
23
  import type { ChatContainer } from "./container-showcase";
24
24
 
@@ -95,7 +95,6 @@ function PromptGeneratorContent() {
95
95
  const [messagePresentation, setMessagePresentation] =
96
96
  useState<MessagePresentation>("transcript");
97
97
  const [capabilities, setCapabilities] = useState(DEFAULT_CAPABILITIES);
98
- const [copied, setCopied] = useState(false);
99
98
 
100
99
  const previewActions = useMemo<AIChatComposerAction[]>(() => {
101
100
  const actions: AIChatComposerAction[] = [];
@@ -275,7 +274,7 @@ Implementation requirements:
275
274
  })();
276
275
 
277
276
  return (
278
- <div className="grid items-start gap-5 xl:grid-cols-[380px_minmax(0,1fr)]">
277
+ <div className="grid items-start gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
279
278
  <Card className="gap-0 py-0">
280
279
  <CardHeader className="border-b py-4">
281
280
  <CardTitle className="text-base">Describe the integration</CardTitle>
@@ -405,32 +404,12 @@ Implementation requirements:
405
404
  </div>
406
405
  </Card>
407
406
 
408
- <Card className="gap-0 overflow-hidden py-0">
409
- <div className="flex items-center justify-between border-b px-4 py-3">
410
- <div>
411
- <div className="text-sm font-medium">
412
- Generated implementation prompt
413
- </div>
414
- <div className="text-xs text-muted-foreground">
415
- Updates from the selected page, placement, and capabilities.
416
- </div>
417
- </div>
418
- <Button
419
- size="sm"
420
- onClick={async () => {
421
- await navigator.clipboard.writeText(prompt);
422
- setCopied(true);
423
- window.setTimeout(() => setCopied(false), 1500);
424
- }}
425
- >
426
- {copied ? <Check /> : <Copy />}
427
- {copied ? "Copied" : "Copy prompt"}
428
- </Button>
429
- </div>
430
- <pre className="max-h-[760px] min-h-[520px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
431
- {prompt}
432
- </pre>
433
- </Card>
407
+ <PromptOutput
408
+ title="Generated implementation prompt"
409
+ description="Updates from the selected page, placement, and capabilities."
410
+ prompt={prompt}
411
+ promptClassName="max-h-[760px] min-h-[520px]"
412
+ />
434
413
  </div>
435
414
  </div>
436
415
  );
@@ -678,7 +678,7 @@ Implementation requirements:
678
678
  task, model, Web search, Skills, and Tools configuration.
679
679
  </p>
680
680
  </div>
681
- <div className="grid items-start gap-5 xl:grid-cols-[520px_minmax(0,1fr)]">
681
+ <div className="grid items-start gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
682
682
  <Card className="gap-0 py-0">
683
683
  <CardHeader className="py-4">
684
684
  <CardTitle className="text-base">
@@ -2,8 +2,8 @@ import {
2
2
  ToolCallCard,
3
3
  type ToolCallPart,
4
4
  } from "../components/chat/tool-call-card";
5
+ import { PromptOutput } from "@/components/demo/prompt-output";
5
6
  import { Badge } from "@/components/ui/badge";
6
- import { Button } from "@/components/ui/button";
7
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
8
  import { Input } from "@/components/ui/input";
9
9
  import {
@@ -15,7 +15,6 @@ import {
15
15
  } from "@/components/ui/select";
16
16
  import { Switch } from "@/components/ui/switch";
17
17
  import { Textarea } from "@/components/ui/textarea";
18
- import { Check, Copy } from "lucide-react";
19
18
  import { useState } from "react";
20
19
 
21
20
  const tools = {
@@ -217,8 +216,8 @@ export function ToolCardsPage() {
217
216
  Tool Cards
218
217
  </h1>
219
218
  <p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
220
- Specialized tools render their complete business interaction.
221
- Tools without a registered renderer fall back to the shared status,
219
+ Specialized tools render their complete business interaction. Tools
220
+ without a registered renderer fall back to the shared status,
222
221
  approval, error, and input disclosure card.
223
222
  </p>
224
223
  </div>
@@ -371,7 +370,6 @@ function ToolCardCodePrompt() {
371
370
  "Show the affected support records, summarize the proposed changes, and let the user approve, request a revision, or reject the operation."
372
371
  );
373
372
  const [handlesApproval, setHandlesApproval] = useState(true);
374
- const [copied, setCopied] = useState(false);
375
373
  const referenceDefinition = referenceRenderers[reference];
376
374
  const prompt = `Implement a specialized NocoBase AI Tool Card for the tool "${
377
375
  toolName || "customTool"
@@ -410,7 +408,7 @@ Verification:
410
408
  - Verify the card in light and dark themes, narrow chat panels, expanded dialogs, pending approval, completed, and error states.`;
411
409
 
412
410
  return (
413
- <div className="grid items-start gap-5 xl:grid-cols-[380px_minmax(0,1fr)]">
411
+ <div className="grid items-start gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
414
412
  <Card className="gap-0 py-0">
415
413
  <CardHeader className="border-b py-4">
416
414
  <CardTitle className="text-base">Describe the Tool Card</CardTitle>
@@ -479,30 +477,12 @@ Verification:
479
477
  </label>
480
478
  </CardContent>
481
479
  </Card>
482
- <Card className="gap-0 py-0">
483
- <div className="flex items-center justify-between border-b px-4 py-3">
484
- <div>
485
- <div className="text-sm font-medium">Coding prompt</div>
486
- <div className="text-xs text-muted-foreground">
487
- Ready to paste into an implementation task.
488
- </div>
489
- </div>
490
- <Button
491
- size="sm"
492
- onClick={async () => {
493
- await navigator.clipboard.writeText(prompt);
494
- setCopied(true);
495
- window.setTimeout(() => setCopied(false), 1500);
496
- }}
497
- >
498
- {copied ? <Check /> : <Copy />}
499
- {copied ? "Copied" : "Copy prompt"}
500
- </Button>
501
- </div>
502
- <pre className="max-h-[720px] min-h-[560px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
503
- {prompt}
504
- </pre>
505
- </Card>
480
+ <PromptOutput
481
+ title="Coding prompt"
482
+ description="Ready to paste into an implementation task."
483
+ prompt={prompt}
484
+ promptClassName="max-h-[720px] min-h-[560px]"
485
+ />
506
486
  </div>
507
487
  );
508
488
  }
@@ -0,0 +1,6 @@
1
+ # NocoBase CAS authentication
2
+
3
+ Adds the CAS sign-in button, callback error handling, and `useCasSignIn` for
4
+ application-owned login UI.
5
+
6
+ Installing the Registry also adds a CAS component Demo under Authentication.
@@ -0,0 +1,41 @@
1
+ import { LogIn } from "lucide-react";
2
+
3
+ import type { AuthenticatorComponentProps } from "@/components/auth/types";
4
+ import {
5
+ Alert,
6
+ AlertDescription,
7
+ AlertTitle,
8
+ } from "@/components/ui/alert";
9
+ import { Button } from "@/components/ui/button";
10
+ import { resolveTranslatableText } from "@/lib/i18n";
11
+
12
+ import { useCasSignIn } from "./use-cas-sign-in";
13
+
14
+ export default function CasSignInButton({
15
+ authenticator,
16
+ onSignIn,
17
+ }: AuthenticatorComponentProps & { onSignIn?: () => void }) {
18
+ const { signIn, error } = useCasSignIn(authenticator);
19
+
20
+ return (
21
+ <div className="space-y-3">
22
+ {error && (
23
+ <Alert variant="destructive">
24
+ <AlertTitle>CAS sign-in failed</AlertTitle>
25
+ <AlertDescription>{error.message}</AlertDescription>
26
+ </Alert>
27
+ )}
28
+ <Button
29
+ type="button"
30
+ variant="outline"
31
+ className="w-full"
32
+ onClick={onSignIn ?? signIn}
33
+ >
34
+ <LogIn />
35
+ {resolveTranslatableText(
36
+ authenticator.title || authenticator.authTypeTitle || "CAS"
37
+ )}
38
+ </Button>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,20 @@
1
+ import { AuthMethodDemo } from "@/components/auth/demo";
2
+ import CasSignInButton from "./cas-sign-in-button";
3
+
4
+ const authenticator = {
5
+ name: "company-cas",
6
+ authType: "CAS",
7
+ title: "Company CAS",
8
+ };
9
+
10
+ export function CasAuthDemoPage() {
11
+ return (
12
+ <AuthMethodDemo
13
+ authType="CAS"
14
+ methodName="Central Authentication Service"
15
+ description="Redirect users to a CAS server and capture the returned NocoBase token without coupling the page UI to CAS."
16
+ >
17
+ <CasSignInButton authenticator={authenticator} onSignIn={() => {}} />
18
+ </AuthMethodDemo>
19
+ );
20
+ }
@@ -0,0 +1,46 @@
1
+ import { lazy } from "react";
2
+ import { KeyRound } from "lucide-react";
3
+ import { Route } from "react-router";
4
+
5
+ import type { AppExtension } from "@/app/extension";
6
+ import { AuthDemoRoute } from "@/components/auth/demo";
7
+
8
+ const CasSignInButton = lazy(() => import("./cas-sign-in-button"));
9
+ const CasAuthDemoPage = lazy(() =>
10
+ import("./demo").then((module) => ({ default: module.CasAuthDemoPage }))
11
+ );
12
+
13
+ const casAuthExtension: AppExtension = {
14
+ id: "nocobase-auth-cas",
15
+ resources: [
16
+ {
17
+ name: "auth-cas-demo",
18
+ list: "/auth/cas",
19
+ meta: {
20
+ parent: "auth-components",
21
+ label: "CAS",
22
+ icon: <KeyRound />,
23
+ acl: { type: "authenticated" },
24
+ },
25
+ },
26
+ ],
27
+ routes: (
28
+ <Route
29
+ path="/auth/cas"
30
+ element={
31
+ <AuthDemoRoute>
32
+ <CasAuthDemoPage />
33
+ </AuthDemoRoute>
34
+ }
35
+ />
36
+ ),
37
+ authAdapters: [
38
+ {
39
+ authType: "CAS",
40
+ placement: "button",
41
+ Component: CasSignInButton,
42
+ },
43
+ ],
44
+ };
45
+
46
+ export default casAuthExtension;
@@ -0,0 +1 @@
1
+ export { useCasSignIn } from "./use-cas-sign-in";