@allbluecn/admin-app 0.3.2 → 0.3.4

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 (209) hide show
  1. package/allblue-ui/package.json +6 -0
  2. package/allblue-ui/src/components/confirm-dialog.tsx +94 -0
  3. package/allblue-ui/src/components/infinite-scroll-sentinel.tsx +37 -0
  4. package/allblue-ui/src/components/notion-avatar.tsx +126 -0
  5. package/allblue-ui/src/components/user-avatar.tsx +82 -0
  6. package/{src/components → allblue-ui/src}/data-table/data-table-pagination.tsx +14 -5
  7. package/{src/components → allblue-ui/src}/data-table/data-table.tsx +1 -1
  8. package/{src/components → allblue-ui/src}/data-table/table-detail-layout.tsx +5 -6
  9. package/allblue-ui/src/hooks/use-load-more.ts +57 -0
  10. package/allblue-ui/src/index.ts +31 -0
  11. package/allblue-ui/src/lib/datetime.ts +226 -0
  12. package/allblue-ui/src/lib/sanitize.ts +18 -0
  13. package/allblue-ui/src/lib/utils.ts +1 -0
  14. package/{src/components → allblue-ui/src}/ui/alert-dialog.tsx +1 -3
  15. package/{src/components → allblue-ui/src}/ui/checkbox.tsx +0 -1
  16. package/{src/components → allblue-ui/src}/ui/dialog.tsx +1 -1
  17. package/{src/components → allblue-ui/src}/ui/dropdown-menu.tsx +0 -2
  18. package/{src/components → allblue-ui/src}/ui/field.tsx +2 -2
  19. package/{src/components → allblue-ui/src}/ui/input-group.tsx +3 -4
  20. package/allblue-ui/src/ui/input.tsx +19 -0
  21. package/{src/components → allblue-ui/src}/ui/pagination.tsx +1 -1
  22. package/{src/components → allblue-ui/src}/ui/select.tsx +0 -2
  23. package/{src/components → allblue-ui/src}/ui/separator.tsx +1 -0
  24. package/{src/components → allblue-ui/src}/ui/sheet.tsx +1 -2
  25. package/{src/components → allblue-ui/src}/ui/sidebar.tsx +7 -7
  26. package/{src/components → allblue-ui/src}/ui/switch.tsx +1 -0
  27. package/{src/components → allblue-ui/src}/ui/table.tsx +0 -2
  28. package/{src/components → allblue-ui/src}/ui/toast.tsx +44 -18
  29. package/{src/components → allblue-ui/src}/ui/tooltip.tsx +1 -0
  30. package/package.json +21 -18
  31. package/src/components/admin/admin-user-menu.tsx +4 -4
  32. package/src/components/admin/developer-log.tsx +16 -32
  33. package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
  34. package/src/components/ai-prompts/preview-panel.tsx +1 -1
  35. package/src/components/ai-prompts/prompt-editor.tsx +8 -9
  36. package/src/components/ai-prompts/prompt-table.tsx +6 -6
  37. package/src/components/ai-prompts/section-editor.tsx +3 -3
  38. package/src/components/ai-prompts/variable-panel.tsx +2 -2
  39. package/src/components/ai-prompts/version-history.tsx +21 -41
  40. package/src/components/ai-provider/model-list-editor.tsx +2 -3
  41. package/src/components/ai-provider/provider-form.tsx +7 -7
  42. package/src/components/ai-provider/provider-table.tsx +17 -40
  43. package/src/components/auth/admin-login-form.tsx +7 -7
  44. package/src/components/auth/icon-input.tsx +2 -2
  45. package/src/components/auth/password-input.tsx +2 -2
  46. package/src/components/component/component-form-dialog.tsx +10 -18
  47. package/src/components/component/component-import-dialog.tsx +6 -6
  48. package/src/components/component/component-preview.tsx +311 -0
  49. package/src/components/component/component-table.tsx +3 -3
  50. package/src/components/dependency/changelog-dialog.tsx +2 -2
  51. package/src/components/dependency/columns.tsx +3 -3
  52. package/src/components/dependency/dependency-table.tsx +7 -7
  53. package/src/components/dependency/edit-dialog.tsx +8 -8
  54. package/src/components/invitations/invitations-page.tsx +26 -49
  55. package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
  56. package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
  57. package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
  58. package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
  59. package/src/components/layout/sidebar-02/index.tsx +1 -1
  60. package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
  61. package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
  62. package/src/components/market/columns.tsx +4 -4
  63. package/src/components/market/market-app-form.tsx +6 -7
  64. package/src/components/market/market-app-list.tsx +14 -29
  65. package/src/components/monitoring/error-list.tsx +2 -2
  66. package/src/components/monitoring/metrics-stats.tsx +1 -1
  67. package/src/components/monitoring/request-log-table.tsx +2 -2
  68. package/src/components/oauth-providers/provider-page.tsx +20 -38
  69. package/src/components/permissions/assign-dialog.tsx +135 -0
  70. package/src/components/permissions/create-role-dialog.tsx +135 -0
  71. package/src/components/permissions/types.ts +49 -0
  72. package/src/components/user/columns.tsx +6 -6
  73. package/src/components/user/settings-activity-log.tsx +7 -7
  74. package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
  75. package/src/components/user/settings-profile/social-links.tsx +63 -0
  76. package/src/components/user/settings-profile.tsx +26 -125
  77. package/src/components/user/user-create-dialog.tsx +6 -6
  78. package/src/components/user/user-detail/error-boundary.tsx +19 -0
  79. package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
  80. package/src/components/user/user-filters.tsx +4 -4
  81. package/src/lib/auth/page-gate.ts +28 -0
  82. package/src/lib/auth.config.ts +25 -9
  83. package/src/lib/i18n/detect.ts +1 -1
  84. package/src/lib/loader-serial.ts +14 -0
  85. package/src/providers/index.tsx +1 -1
  86. package/src/routeTree.gen.ts +67 -78
  87. package/src/routes/__root.tsx +4 -4
  88. package/src/routes/_nav/audit/route.lazy.tsx +1 -2
  89. package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
  90. package/src/routes/_nav/components/route.lazy.tsx +14 -34
  91. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
  92. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
  93. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
  94. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
  95. package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
  96. package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
  97. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
  98. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
  99. package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
  100. package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
  101. package/src/routes/_nav/monitoring/health/route.tsx +14 -0
  102. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
  103. package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
  104. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
  105. package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
  106. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
  107. package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
  108. package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
  109. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
  110. package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
  111. package/src/routes/_nav/permissions/route.tsx +1 -1
  112. package/src/routes/_nav/route.tsx +5 -13
  113. package/src/routes/_nav/sso/route.lazy.tsx +2 -3
  114. package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
  115. package/src/routes/_nav/users/-components/users-table.tsx +1 -1
  116. package/src/routes/_nav/users/route.lazy.tsx +1 -1
  117. package/src/routes/login/route.lazy.tsx +1 -1
  118. package/src/routes/setup/route.lazy.tsx +2 -2
  119. package/src/server/__tests__/admin-billing.test.ts +191 -16
  120. package/src/server/__tests__/compliance.test.ts +44 -7
  121. package/src/server/__tests__/permissions.test.ts +10 -5
  122. package/src/server/__tests__/prompt-defaults.test.ts +3 -3
  123. package/src/server/admin-ai-alerts.ts +2 -1
  124. package/src/server/admin-ai-usage.ts +2 -1
  125. package/src/server/ai-memory/store.ts +6 -2
  126. package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
  127. package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
  128. package/src/server/knowledge-public/github-sync.ts +2 -1
  129. package/src/server/license-issue.ts +2 -1
  130. package/src/server/middlewares/auth-rate-limit.ts +117 -0
  131. package/src/server/observability/health-check.ts +4 -1
  132. package/src/server/observability/metrics.ts +17 -15
  133. package/src/server/observability/request-middleware.ts +2 -1
  134. package/src/server/observability/timing.ts +14 -16
  135. package/src/server/serverFns/admin-billing.ts +107 -12
  136. package/src/server/serverFns/ai-memory/list-users.ts +2 -1
  137. package/src/server/serverFns/ai-prompts/activate.ts +2 -1
  138. package/src/server/serverFns/ai-prompts/get.ts +3 -2
  139. package/src/server/serverFns/ai-prompts/list.ts +3 -2
  140. package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
  141. package/src/server/serverFns/ai-prompts/save.ts +5 -3
  142. package/src/server/serverFns/ai-provider/create.ts +4 -1
  143. package/src/server/serverFns/ai-provider/delete.ts +4 -1
  144. package/src/server/serverFns/ai-provider/get.ts +4 -1
  145. package/src/server/serverFns/ai-provider/list.ts +28 -1
  146. package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
  147. package/src/server/serverFns/ai-provider/update.ts +4 -1
  148. package/src/server/serverFns/anyhub/market.ts +8 -1
  149. package/src/server/serverFns/audit.ts +14 -1
  150. package/src/server/serverFns/auth/setup.ts +3 -1
  151. package/src/server/serverFns/compliance.ts +32 -3
  152. package/src/server/serverFns/components/import.ts +69 -14
  153. package/src/server/serverFns/components/list.ts +4 -1
  154. package/src/server/serverFns/components/mutations.ts +9 -1
  155. package/src/server/serverFns/dependency/list.ts +28 -5
  156. package/src/server/serverFns/dependency/sync.ts +7 -2
  157. package/src/server/serverFns/dependency/update.ts +4 -1
  158. package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
  159. package/src/server/serverFns/developer-logs.ts +5 -1
  160. package/src/server/serverFns/invitations.ts +7 -1
  161. package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
  162. package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
  163. package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
  164. package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
  165. package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
  166. package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
  167. package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
  168. package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
  169. package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
  170. package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
  171. package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
  172. package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
  173. package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
  174. package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
  175. package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
  176. package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
  177. package/src/server/serverFns/licenses.ts +8 -2
  178. package/src/server/serverFns/monitoring/get-errors.ts +2 -1
  179. package/src/server/serverFns/monitoring/get-health.ts +2 -1
  180. package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
  181. package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
  182. package/src/server/serverFns/monitoring/report-error.ts +2 -1
  183. package/src/server/serverFns/monitoring/reset.ts +2 -1
  184. package/src/server/serverFns/oauth-providers.ts +4 -1
  185. package/src/server/serverFns/permissions.ts +18 -11
  186. package/src/server/serverFns/sentry/config.ts +11 -8
  187. package/src/server/serverFns/sso-config.ts +17 -2
  188. package/src/server/serverFns/user/create.ts +6 -1
  189. package/src/server/serverFns/user/delete.ts +6 -1
  190. package/src/server/serverFns/user/detail.ts +5 -2
  191. package/src/server/serverFns/user/list.ts +19 -4
  192. package/src/server/serverFns/user/reset-password.ts +10 -2
  193. package/src/server/serverFns/user/update-role.ts +10 -2
  194. package/src/server/serverFns/user/update-status.ts +10 -2
  195. package/src/styles/globals.css +4 -0
  196. package/src/components/ui/input.tsx +0 -19
  197. /package/{src → allblue-ui/src}/hooks/use-mobile.ts +0 -0
  198. /package/{src → allblue-ui/src}/lib/avatar.ts +0 -0
  199. /package/{src/components → allblue-ui/src}/ui/alert.tsx +0 -0
  200. /package/{src/components → allblue-ui/src}/ui/avatar.tsx +0 -0
  201. /package/{src/components → allblue-ui/src}/ui/badge.tsx +0 -0
  202. /package/{src/components → allblue-ui/src}/ui/button.tsx +0 -0
  203. /package/{src/components → allblue-ui/src}/ui/card.tsx +0 -0
  204. /package/{src/components → allblue-ui/src}/ui/collapsible.tsx +0 -0
  205. /package/{src/components → allblue-ui/src}/ui/label.tsx +0 -0
  206. /package/{src/components → allblue-ui/src}/ui/skeleton.tsx +0 -0
  207. /package/{src/components → allblue-ui/src}/ui/textarea.tsx +0 -0
  208. /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
  209. /package/src/routes/{api.sentry-test.ts → api/sentry-test.ts} +0 -0
@@ -16,16 +16,16 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { useState } from 'react'
19
- import { Button } from '@/components/ui/button'
20
- import { Input } from '@/components/ui/input'
21
- import { Label } from '@/components/ui/label'
19
+ import { Button } from '@allbluecn/ui/button'
20
+ import { Input } from '@allbluecn/ui/input'
21
+ import { Label } from '@allbluecn/ui/label'
22
22
  import {
23
23
  Select,
24
24
  SelectContent,
25
25
  SelectItem,
26
26
  SelectTrigger,
27
27
  SelectValue,
28
- } from '@/components/ui/select'
28
+ } from '@allbluecn/ui/select'
29
29
  import {
30
30
  Dialog,
31
31
  DialogContent,
@@ -33,9 +33,9 @@ import {
33
33
  DialogFooter,
34
34
  DialogHeader,
35
35
  DialogTitle,
36
- } from '@/components/ui/dialog'
36
+ } from '@allbluecn/ui/dialog'
37
37
  import { importComponentFn } from '@/server/serverFns/components/import'
38
- import { toast } from '@/components/ui/toast'
38
+ import { toast } from '@allbluecn/ui/toast'
39
39
  import { Loader2, Sparkles } from 'lucide-react'
40
40
  import { cn } from '@/lib/utils'
41
41
 
@@ -0,0 +1,311 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { memo, useMemo, type CSSProperties, type FC } from 'react'
19
+ import { Image as ImageIcon } from 'lucide-react'
20
+ import { cn } from '@/lib/utils'
21
+
22
+ interface ComponentPreviewProps {
23
+ type: string
24
+ name: string
25
+ prototypeKey?: string | null
26
+ defaultProps?: Record<string, unknown> | null
27
+ defaultStyle?: Record<string, unknown> | null
28
+ size: 'sm' | 'md' | 'grid'
29
+ className?: string
30
+ }
31
+
32
+ interface PrototypeRendererProps {
33
+ props?: Record<string, unknown> | null
34
+ style?: CSSProperties
35
+ }
36
+
37
+ function PrototypeButton({ props, style }: PrototypeRendererProps) {
38
+ return (
39
+ <div
40
+ style={{
41
+ display: 'flex',
42
+ width: '100%',
43
+ height: '100%',
44
+ boxSizing: 'border-box',
45
+ alignItems: 'center',
46
+ justifyContent: 'center',
47
+ padding: '8px 16px',
48
+ borderRadius: '6px',
49
+ background: 'var(--primary)',
50
+ color: 'var(--primary-foreground)',
51
+ fontSize: '14px',
52
+ fontWeight: 500,
53
+ cursor: 'default',
54
+ border: '1px solid var(--primary)',
55
+ ...style,
56
+ }}
57
+ >
58
+ {textOf(props) || '按钮'}
59
+ </div>
60
+ )
61
+ }
62
+
63
+ function PrototypeInput({ props, style }: PrototypeRendererProps) {
64
+ return (
65
+ <div
66
+ style={{
67
+ width: '100%',
68
+ height: '100%',
69
+ boxSizing: 'border-box',
70
+ padding: '8px 12px',
71
+ border: '1px solid var(--input)',
72
+ borderRadius: '6px',
73
+ background: 'var(--background)',
74
+ fontSize: '14px',
75
+ color: 'var(--muted-foreground)',
76
+ cursor: 'default',
77
+ ...style,
78
+ }}
79
+ >
80
+ {textOf(props) || '输入框'}
81
+ </div>
82
+ )
83
+ }
84
+
85
+ function PrototypeText({ props, style }: PrototypeRendererProps) {
86
+ return (
87
+ <div
88
+ style={{
89
+ width: '100%',
90
+ height: '100%',
91
+ boxSizing: 'border-box',
92
+ padding: '4px 0',
93
+ fontSize: '14px',
94
+ color: 'var(--foreground)',
95
+ lineHeight: 1.6,
96
+ wordBreak: 'break-word',
97
+ ...style,
98
+ }}
99
+ >
100
+ {textOf(props) || '文本内容'}
101
+ </div>
102
+ )
103
+ }
104
+
105
+ function PrototypeCard({ props, style }: PrototypeRendererProps) {
106
+ return (
107
+ <div
108
+ style={{
109
+ width: '100%',
110
+ height: '100%',
111
+ boxSizing: 'border-box',
112
+ borderRadius: '8px',
113
+ border: '1px solid var(--border)',
114
+ background: 'var(--card)',
115
+ boxShadow: '0 1px 3px rgba(0,0,0,0.08)',
116
+ padding: '16px',
117
+ display: 'flex',
118
+ flexDirection: 'column',
119
+ gap: '8px',
120
+ ...style,
121
+ }}
122
+ >
123
+ <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--muted-foreground)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
124
+ {titleOf(props) || '卡片标题'}
125
+ </div>
126
+ <div style={{ flex: 1, borderRadius: '4px', background: 'var(--muted)', minHeight: '24px' }} />
127
+ </div>
128
+ )
129
+ }
130
+
131
+ function PrototypeImage({ props, style }: PrototypeRendererProps) {
132
+ return (
133
+ <div
134
+ style={{
135
+ width: '100%',
136
+ height: '100%',
137
+ boxSizing: 'border-box',
138
+ background: 'var(--muted)',
139
+ border: '1px dashed var(--border)',
140
+ borderRadius: '6px',
141
+ display: 'flex',
142
+ alignItems: 'center',
143
+ justifyContent: 'center',
144
+ color: 'var(--muted-foreground)',
145
+ fontSize: '12px',
146
+ ...style,
147
+ }}
148
+ >
149
+ <div className="flex flex-col items-center justify-center gap-3">
150
+ <ImageIcon size={24} />
151
+ <span>{textOf(props) || '图片'}</span>
152
+ </div>
153
+ </div>
154
+ )
155
+ }
156
+
157
+ function PrototypeGeneric({ props, style }: PrototypeRendererProps) {
158
+ return (
159
+ <div
160
+ style={{
161
+ width: '100%',
162
+ height: '100%',
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ justifyContent: 'center',
166
+ background: 'var(--muted)',
167
+ border: '1px dashed var(--border)',
168
+ borderRadius: '4px',
169
+ fontSize: '12px',
170
+ color: 'var(--muted-foreground)',
171
+ overflow: 'hidden',
172
+ ...style,
173
+ }}
174
+ >
175
+ {textOf(props) || '自定义组件'}
176
+ </div>
177
+ )
178
+ }
179
+
180
+ const PROTOTYPE_RENDERERS: Record<string, FC<PrototypeRendererProps>> = {
181
+ button: PrototypeButton,
182
+ input: PrototypeInput,
183
+ text: PrototypeText,
184
+ card: PrototypeCard,
185
+ image: PrototypeImage,
186
+ }
187
+
188
+ const SAFE_STYLE_KEYS = new Set([
189
+ 'background', 'backgroundColor', 'color', 'borderRadius',
190
+ 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
191
+ 'margin', 'fontSize', 'fontWeight', 'fontFamily', 'textAlign',
192
+ 'border', 'borderTop', 'borderRight', 'borderBottom', 'borderLeft',
193
+ 'borderColor', 'borderWidth', 'borderStyle',
194
+ 'boxShadow', 'opacity',
195
+ 'width', 'height', 'minWidth', 'minHeight',
196
+ 'display', 'flexDirection', 'alignItems', 'justifyContent', 'gap',
197
+ 'overflow', 'textOverflow', 'whiteSpace',
198
+ ])
199
+
200
+ function textOf(props: Record<string, unknown> | null | undefined): string {
201
+ if (!props) return ''
202
+ for (const key of ['text', 'label', 'title', 'content', 'placeholder', 'value', 'alt', 'children']) {
203
+ const val = props[key]
204
+ if (typeof val === 'string' && val.trim()) return val
205
+ }
206
+ return ''
207
+ }
208
+
209
+ function titleOf(props: Record<string, unknown> | null | undefined): string {
210
+ if (!props) return ''
211
+ const val = props.title
212
+ return typeof val === 'string' && val.trim() ? val : ''
213
+ }
214
+
215
+ function extractStyle(defaultStyle: Record<string, unknown> | null | undefined): Record<string, unknown> {
216
+ if (!defaultStyle) return {}
217
+ const safe: Record<string, unknown> = {}
218
+ for (const [key, value] of Object.entries(defaultStyle)) {
219
+ if (SAFE_STYLE_KEYS.has(key)) safe[key] = value
220
+ }
221
+ return safe
222
+ }
223
+
224
+ const SIZE_CLASSES: Record<string, string> = {
225
+ sm: 'w-15 h-7 text-[9px]',
226
+ md: 'w-full min-h-40',
227
+ grid: 'w-full aspect-[4/3]',
228
+ }
229
+
230
+ function ComponentPreviewInner({
231
+ type,
232
+ name,
233
+ prototypeKey,
234
+ defaultProps,
235
+ defaultStyle,
236
+ size,
237
+ className,
238
+ }: ComponentPreviewProps) {
239
+ const style = useMemo(() => extractStyle(defaultStyle), [defaultStyle])
240
+ const text = useMemo(() => textOf(defaultProps) || name, [defaultProps, name])
241
+ const prototypeRenderer = prototypeKey ? PROTOTYPE_RENDERERS[prototypeKey] : undefined
242
+
243
+ if (type === 'prototype') {
244
+ const Renderer = prototypeRenderer ?? PrototypeGeneric
245
+ return (
246
+ <div
247
+ className={cn(
248
+ 'flex items-center justify-center overflow-hidden rounded border border-border/40 bg-background p-1',
249
+ SIZE_CLASSES[size],
250
+ className
251
+ )}
252
+ >
253
+ <Renderer props={defaultProps ?? undefined} style={style as CSSProperties} />
254
+ </div>
255
+ )
256
+ }
257
+
258
+ if (type === 'component') {
259
+ return (
260
+ <div
261
+ className={cn(
262
+ 'flex items-center justify-center overflow-hidden rounded border border-border/40 bg-background',
263
+ SIZE_CLASSES[size],
264
+ className
265
+ )}
266
+ style={style as React.CSSProperties}
267
+ >
268
+ <span className="truncate px-1 leading-tight text-center">{text}</span>
269
+ </div>
270
+ )
271
+ }
272
+
273
+ if (type === 'block') {
274
+ return (
275
+ <div
276
+ className={cn(
277
+ 'flex flex-col items-center justify-center overflow-hidden rounded border border-border/40 bg-background p-1',
278
+ SIZE_CLASSES[size],
279
+ className
280
+ )}
281
+ style={style as React.CSSProperties}
282
+ >
283
+ <div className="w-full h-1 rounded-sm bg-muted-foreground/20 mb-0.5" />
284
+ <div className="flex-1 w-full rounded-sm bg-muted-foreground/10" />
285
+ <span className="text-[8px] text-muted-foreground/60 mt-0.5 uppercase tracking-wider">
286
+ block
287
+ </span>
288
+ </div>
289
+ )
290
+ }
291
+
292
+ return (
293
+ <div
294
+ className={cn(
295
+ 'flex flex-col overflow-hidden rounded border border-border/40 bg-background p-0.5 gap-0.5',
296
+ SIZE_CLASSES[size],
297
+ className
298
+ )}
299
+ style={style as React.CSSProperties}
300
+ >
301
+ <div className="w-full h-1 rounded-sm bg-muted-foreground/20" />
302
+ <div className="flex w-full flex-1 gap-0.5">
303
+ <div className="w-1/4 h-full rounded-sm bg-muted-foreground/10" />
304
+ <div className="flex-1 h-full rounded-sm bg-muted-foreground/5" />
305
+ </div>
306
+ <span className="text-[7px] text-muted-foreground/50 uppercase">template</span>
307
+ </div>
308
+ )
309
+ }
310
+
311
+ export const ComponentPreview = memo(ComponentPreviewInner)
@@ -17,8 +17,8 @@
17
17
 
18
18
  import { Edit, Trash2 } from 'lucide-react'
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { Badge } from '@/components/ui/badge'
21
- import { Button } from '@/components/ui/button'
20
+ import { Badge } from '@allbluecn/ui/badge'
21
+ import { Button } from '@allbluecn/ui/button'
22
22
  import {
23
23
  Table,
24
24
  TableBody,
@@ -26,7 +26,7 @@ import {
26
26
  TableHead,
27
27
  TableHeader,
28
28
  TableRow,
29
- } from '@/components/ui/table'
29
+ } from '@allbluecn/ui/table'
30
30
  import { cn } from '@/lib/utils'
31
31
 
32
32
  export interface ComponentItem {
@@ -21,14 +21,14 @@ import ReactMarkdown from "react-markdown"
21
21
  import remarkGfm from "remark-gfm"
22
22
  import { ExternalLink, FileText } from "lucide-react"
23
23
  import type { ThirdPartyDependencyRow } from "@allbluecn/kernel"
24
- import { Button } from "@/components/ui/button"
24
+ import { Button } from "@allbluecn/ui/button"
25
25
  import {
26
26
  Dialog,
27
27
  DialogContent,
28
28
  DialogHeader,
29
29
  DialogTitle,
30
30
  DialogTrigger,
31
- } from "@/components/ui/dialog"
31
+ } from "@allbluecn/ui/dialog"
32
32
 
33
33
  export function ChangelogDialog({ row }: { row: ThirdPartyDependencyRow }) {
34
34
  const [open, setOpen] = useState(false)
@@ -29,9 +29,9 @@ import {
29
29
  type ThirdPartyDependencyRow,
30
30
  type VersionStatus,
31
31
  } from "@allbluecn/kernel"
32
- import { Badge } from "@/components/ui/badge"
33
- import { Button } from "@/components/ui/button"
34
- import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
32
+ import { Badge } from "@allbluecn/ui/badge"
33
+ import { Button } from "@allbluecn/ui/button"
34
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@allbluecn/ui/tooltip"
35
35
  import { cn } from "@/lib/utils"
36
36
  import { EditDependencyDialog } from "./edit-dialog"
37
37
  import { ChangelogDialog } from "./changelog-dialog"
@@ -22,7 +22,7 @@ import {
22
22
  useTable,
23
23
  type PaginationState,
24
24
  } from "@tanstack/react-table"
25
- import { toast } from '@/components/ui/toast'
25
+ import { toast } from '@allbluecn/ui/toast'
26
26
  import { RefreshCw, Search, TriangleAlert, Loader2 } from "lucide-react"
27
27
  import {
28
28
  DEPENDENCY_CATEGORIES,
@@ -31,10 +31,10 @@ import {
31
31
  type VersionStatus,
32
32
  } from "@allbluecn/kernel"
33
33
  import { syncAllDependenciesFn, syncDependencyFn } from "@/server/serverFns/dependency/sync"
34
- import { Badge } from "@/components/ui/badge"
35
- import { Button } from "@/components/ui/button"
36
- import { Input } from "@/components/ui/input"
37
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
34
+ import { Badge } from "@allbluecn/ui/badge"
35
+ import { Button } from "@allbluecn/ui/button"
36
+ import { Input } from "@allbluecn/ui/input"
37
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@allbluecn/ui/select"
38
38
  import {
39
39
  Table,
40
40
  TableBody,
@@ -42,7 +42,7 @@ import {
42
42
  TableHead,
43
43
  TableHeader,
44
44
  TableRow,
45
- } from "@/components/ui/table"
45
+ } from "@allbluecn/ui/table"
46
46
  import {
47
47
  Pagination,
48
48
  PaginationContent,
@@ -51,7 +51,7 @@ import {
51
51
  PaginationLink,
52
52
  PaginationNext,
53
53
  PaginationPrevious,
54
- } from "@/components/ui/pagination"
54
+ } from "@allbluecn/ui/pagination"
55
55
  import { cn } from "@/lib/utils"
56
56
  import { dependencyTableFeatures, getDependencyColumns } from "./columns"
57
57
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  import { useState } from "react"
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { toast } from '@/components/ui/toast'
20
+ import { toast } from '@allbluecn/ui/toast'
21
21
  import { Loader2, Pencil } from "lucide-react"
22
22
  import {
23
23
  DEPENDENCY_CATEGORIES,
@@ -26,13 +26,13 @@ import {
26
26
  type ThirdPartyDependencyRow,
27
27
  } from "@allbluecn/kernel"
28
28
  import { updateDependencyFn } from "@/server/serverFns/dependency/update"
29
- import { Button } from "@/components/ui/button"
30
- import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
31
- import { Input } from "@/components/ui/input"
32
- import { Label } from "@/components/ui/label"
33
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
34
- import { Textarea } from "@/components/ui/textarea"
35
- import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
29
+ import { Button } from "@allbluecn/ui/button"
30
+ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@allbluecn/ui/dialog"
31
+ import { Input } from "@allbluecn/ui/input"
32
+ import { Label } from "@allbluecn/ui/label"
33
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@allbluecn/ui/select"
34
+ import { Textarea } from "@allbluecn/ui/textarea"
35
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@allbluecn/ui/tooltip"
36
36
 
37
37
  export function EditDependencyDialog({
38
38
  row,
@@ -19,15 +19,15 @@ import { useState } from "react"
19
19
  import type React from "react"
20
20
  import { m } from "@/paraglide/messages.js"
21
21
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
22
- import { toast } from "@/components/ui/toast"
22
+ import { toast } from "@allbluecn/ui/toast"
23
23
  import { Ticket, Plus, Copy, Ban, CheckCircle2, Trash2, Users } from "lucide-react"
24
24
  import { PageHeader } from "@/components/common/page-header"
25
- import { Button } from "@/components/ui/button"
26
- import { Badge } from "@/components/ui/badge"
27
- import { Input } from "@/components/ui/input"
28
- import { Label } from "@/components/ui/label"
29
- import { Switch } from "@/components/ui/switch"
30
- import { Skeleton } from "@/components/ui/skeleton"
25
+ import { Button } from "@allbluecn/ui/button"
26
+ import { Badge } from "@allbluecn/ui/badge"
27
+ import { Input } from "@allbluecn/ui/input"
28
+ import { Label } from "@allbluecn/ui/label"
29
+ import { Switch } from "@allbluecn/ui/switch"
30
+ import { Skeleton } from "@allbluecn/ui/skeleton"
31
31
  import {
32
32
  Dialog,
33
33
  DialogContent,
@@ -35,17 +35,8 @@ import {
35
35
  DialogFooter,
36
36
  DialogHeader,
37
37
  DialogTitle,
38
- } from "@/components/ui/dialog"
39
- import {
40
- AlertDialog,
41
- AlertDialogAction,
42
- AlertDialogCancel,
43
- AlertDialogContent,
44
- AlertDialogDescription,
45
- AlertDialogFooter,
46
- AlertDialogHeader,
47
- AlertDialogTitle,
48
- } from "@/components/ui/alert-dialog"
38
+ } from "@allbluecn/ui/dialog"
39
+ import { ConfirmDialog } from "@allbluecn/ui/components/confirm-dialog"
49
40
  import {
50
41
  Table,
51
42
  TableBody,
@@ -53,13 +44,13 @@ import {
53
44
  TableHead,
54
45
  TableHeader,
55
46
  TableRow,
56
- } from "@/components/ui/table"
47
+ } from "@allbluecn/ui/table"
57
48
  import {
58
49
  Tooltip,
59
50
  TooltipContent,
60
51
  TooltipTrigger,
61
- } from "@/components/ui/tooltip"
62
- import { Card, CardContent } from "@/components/ui/card"
52
+ } from "@allbluecn/ui/tooltip"
53
+ import { Card, CardContent } from "@allbluecn/ui/card"
63
54
  import {
64
55
  listInvitationCodesFn,
65
56
  createInvitationCodeFn,
@@ -68,7 +59,7 @@ import {
68
59
  getRegistrationModeFn,
69
60
  setRegistrationModeFn,
70
61
  } from "@/server/serverFns/invitations"
71
- import { formatTimestamp } from "@allbluecn/web-app/lib/datetime"
62
+ import { formatTimestamp } from "@allbluecn/ui/lib/datetime"
72
63
 
73
64
  type RegistrationMode = "invite" | "open"
74
65
 
@@ -448,35 +439,21 @@ export function InvitationsPage(): React.ReactElement {
448
439
  </DialogContent>
449
440
  </Dialog>
450
441
 
451
- <AlertDialog
442
+ <ConfirmDialog
452
443
  open={!!pendingDelete}
453
444
  onOpenChange={(open: boolean) => !open && setPendingDelete(null)}
454
- >
455
- <AlertDialogContent>
456
- <AlertDialogHeader>
457
- <AlertDialogTitle>
458
- {m["admin-invitations_delete_confirm_title"]({
459
- code: pendingDelete?.code ?? "",
460
- })}
461
- </AlertDialogTitle>
462
- <AlertDialogDescription>
463
- {m["admin-invitations_delete_confirm_desc"]()}
464
- </AlertDialogDescription>
465
- </AlertDialogHeader>
466
- <AlertDialogFooter>
467
- <AlertDialogCancel>{m.common_common_cancel()}</AlertDialogCancel>
468
- <AlertDialogAction
469
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
470
- disabled={deleteMutation.isPending}
471
- onClick={() =>
472
- pendingDelete && deleteMutation.mutate(pendingDelete.id)
473
- }
474
- >
475
- {m["admin-invitations_action_delete"]()}
476
- </AlertDialogAction>
477
- </AlertDialogFooter>
478
- </AlertDialogContent>
479
- </AlertDialog>
445
+ title={m["admin-invitations_delete_confirm_title"]({
446
+ code: pendingDelete?.code ?? "",
447
+ })}
448
+ description={m["admin-invitations_delete_confirm_desc"]()}
449
+ cancelLabel={m.common_common_cancel()}
450
+ confirmLabel={m["admin-invitations_action_delete"]()}
451
+ confirmClassName="bg-destructive text-destructive-foreground hover:bg-destructive/90"
452
+ confirmDisabled={deleteMutation.isPending}
453
+ onConfirm={() =>
454
+ pendingDelete && deleteMutation.mutate(pendingDelete.id)
455
+ }
456
+ />
480
457
 
481
458
  <Dialog
482
459
  open={!!usesViewer}
@@ -17,18 +17,18 @@
17
17
 
18
18
  import { useState, useEffect } from 'react'
19
19
  import { Loader2 } from 'lucide-react'
20
- import { toast } from '@/components/ui/toast'
21
- import { Button } from '@/components/ui/button'
22
- import { Input } from '@/components/ui/input'
23
- import { Label } from '@/components/ui/label'
24
- import { Textarea } from '@/components/ui/textarea'
20
+ import { toast } from '@allbluecn/ui/toast'
21
+ import { Button } from '@allbluecn/ui/button'
22
+ import { Input } from '@allbluecn/ui/input'
23
+ import { Label } from '@allbluecn/ui/label'
24
+ import { Textarea } from '@allbluecn/ui/textarea'
25
25
  import {
26
26
  Dialog,
27
27
  DialogContent,
28
28
  DialogFooter,
29
29
  DialogHeader,
30
30
  DialogTitle,
31
- } from '@/components/ui/dialog'
31
+ } from '@allbluecn/ui/dialog'
32
32
  import { adminCreateBookmarkFn } from '@/server/serverFns/knowledge-public/bookmarks/create'
33
33
  import { adminUpdateBookmarkFn } from '@/server/serverFns/knowledge-public/bookmarks/update'
34
34
  import { adminGetBookmarkDetailFn } from '@/server/serverFns/knowledge-public/bookmarks/detail'
@@ -17,18 +17,18 @@
17
17
 
18
18
  import { useState, useEffect } from 'react'
19
19
  import { Loader2, PlusIcon, Trash2Icon } from 'lucide-react'
20
- import { toast } from '@/components/ui/toast'
21
- import { Button } from '@/components/ui/button'
22
- import { Input } from '@/components/ui/input'
23
- import { Label } from '@/components/ui/label'
24
- import { Textarea } from '@/components/ui/textarea'
20
+ import { toast } from '@allbluecn/ui/toast'
21
+ import { Button } from '@allbluecn/ui/button'
22
+ import { Input } from '@allbluecn/ui/input'
23
+ import { Label } from '@allbluecn/ui/label'
24
+ import { Textarea } from '@allbluecn/ui/textarea'
25
25
  import {
26
26
  Dialog,
27
27
  DialogContent,
28
28
  DialogFooter,
29
29
  DialogHeader,
30
30
  DialogTitle,
31
- } from '@/components/ui/dialog'
31
+ } from '@allbluecn/ui/dialog'
32
32
  import { adminCreateComplianceFn } from '@/server/serverFns/knowledge-public/compliance/create'
33
33
  import { adminUpdateComplianceFn } from '@/server/serverFns/knowledge-public/compliance/update'
34
34
  import { adminGetComplianceDetailFn } from '@/server/serverFns/knowledge-public/compliance/detail'
@@ -18,25 +18,25 @@
18
18
  import { useState, useEffect } from 'react'
19
19
  import { m } from '@/paraglide/messages.js'
20
20
  import { Loader2 } from 'lucide-react'
21
- import { toast } from '@/components/ui/toast'
22
- import { Button } from '@/components/ui/button'
23
- import { Input } from '@/components/ui/input'
24
- import { Label } from '@/components/ui/label'
25
- import { Textarea } from '@/components/ui/textarea'
21
+ import { toast } from '@allbluecn/ui/toast'
22
+ import { Button } from '@allbluecn/ui/button'
23
+ import { Input } from '@allbluecn/ui/input'
24
+ import { Label } from '@allbluecn/ui/label'
25
+ import { Textarea } from '@allbluecn/ui/textarea'
26
26
  import {
27
27
  Select,
28
28
  SelectContent,
29
29
  SelectItem,
30
30
  SelectTrigger,
31
31
  SelectValue,
32
- } from '@/components/ui/select'
32
+ } from '@allbluecn/ui/select'
33
33
  import {
34
34
  Dialog,
35
35
  DialogContent,
36
36
  DialogFooter,
37
37
  DialogHeader,
38
38
  DialogTitle,
39
- } from '@/components/ui/dialog'
39
+ } from '@allbluecn/ui/dialog'
40
40
  import { adminCreateLegalFn } from '@/server/serverFns/knowledge-public/legal/create'
41
41
  import { adminUpdateLegalFn } from '@/server/serverFns/knowledge-public/legal/update'
42
42
  import { adminGetLegalDetailFn } from '@/server/serverFns/knowledge-public/legal/detail'