@allbluecn/admin-app 0.3.2 → 0.3.3

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 (198) hide show
  1. package/package.json +19 -17
  2. package/src/components/admin/admin-user-menu.tsx +4 -4
  3. package/src/components/admin/developer-log.tsx +16 -32
  4. package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
  5. package/src/components/ai-prompts/preview-panel.tsx +1 -1
  6. package/src/components/ai-prompts/prompt-editor.tsx +8 -9
  7. package/src/components/ai-prompts/prompt-table.tsx +6 -6
  8. package/src/components/ai-prompts/section-editor.tsx +3 -3
  9. package/src/components/ai-prompts/variable-panel.tsx +2 -2
  10. package/src/components/ai-prompts/version-history.tsx +21 -41
  11. package/src/components/ai-provider/model-list-editor.tsx +2 -3
  12. package/src/components/ai-provider/provider-form.tsx +7 -7
  13. package/src/components/ai-provider/provider-table.tsx +17 -40
  14. package/src/components/auth/admin-login-form.tsx +7 -7
  15. package/src/components/auth/icon-input.tsx +2 -2
  16. package/src/components/auth/password-input.tsx +2 -2
  17. package/src/components/component/component-form-dialog.tsx +10 -18
  18. package/src/components/component/component-import-dialog.tsx +6 -6
  19. package/src/components/component/component-preview.tsx +311 -0
  20. package/src/components/component/component-table.tsx +3 -3
  21. package/src/components/dependency/changelog-dialog.tsx +2 -2
  22. package/src/components/dependency/columns.tsx +3 -3
  23. package/src/components/dependency/dependency-table.tsx +7 -7
  24. package/src/components/dependency/edit-dialog.tsx +8 -8
  25. package/src/components/invitations/invitations-page.tsx +26 -49
  26. package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
  27. package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
  28. package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
  29. package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
  30. package/src/components/layout/sidebar-02/index.tsx +1 -1
  31. package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
  32. package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
  33. package/src/components/market/columns.tsx +4 -4
  34. package/src/components/market/market-app-form.tsx +6 -7
  35. package/src/components/market/market-app-list.tsx +14 -29
  36. package/src/components/monitoring/error-list.tsx +2 -2
  37. package/src/components/monitoring/metrics-stats.tsx +1 -1
  38. package/src/components/monitoring/request-log-table.tsx +2 -2
  39. package/src/components/oauth-providers/provider-page.tsx +20 -38
  40. package/src/components/permissions/assign-dialog.tsx +135 -0
  41. package/src/components/permissions/create-role-dialog.tsx +135 -0
  42. package/src/components/permissions/types.ts +49 -0
  43. package/src/components/user/columns.tsx +6 -6
  44. package/src/components/user/settings-activity-log.tsx +7 -7
  45. package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
  46. package/src/components/user/settings-profile/social-links.tsx +63 -0
  47. package/src/components/user/settings-profile.tsx +26 -125
  48. package/src/components/user/user-create-dialog.tsx +6 -6
  49. package/src/components/user/user-detail/error-boundary.tsx +19 -0
  50. package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
  51. package/src/components/user/user-filters.tsx +4 -4
  52. package/src/lib/auth/page-gate.ts +28 -0
  53. package/src/lib/auth.config.ts +25 -9
  54. package/src/lib/i18n/detect.ts +1 -1
  55. package/src/lib/loader-serial.ts +14 -0
  56. package/src/providers/index.tsx +1 -1
  57. package/src/routeTree.gen.ts +67 -78
  58. package/src/routes/__root.tsx +4 -4
  59. package/src/routes/_nav/audit/route.lazy.tsx +1 -2
  60. package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
  61. package/src/routes/_nav/components/route.lazy.tsx +14 -34
  62. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
  63. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
  64. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
  65. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
  66. package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
  67. package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
  68. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
  69. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
  70. package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
  71. package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
  72. package/src/routes/_nav/monitoring/health/route.tsx +14 -0
  73. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
  74. package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
  75. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
  76. package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
  77. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
  78. package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
  79. package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
  80. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
  81. package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
  82. package/src/routes/_nav/permissions/route.tsx +1 -1
  83. package/src/routes/_nav/route.tsx +5 -13
  84. package/src/routes/_nav/sso/route.lazy.tsx +2 -3
  85. package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
  86. package/src/routes/_nav/users/-components/users-table.tsx +1 -1
  87. package/src/routes/_nav/users/route.lazy.tsx +1 -1
  88. package/src/routes/login/route.lazy.tsx +1 -1
  89. package/src/routes/setup/route.lazy.tsx +2 -2
  90. package/src/server/__tests__/admin-billing.test.ts +191 -16
  91. package/src/server/__tests__/compliance.test.ts +44 -7
  92. package/src/server/__tests__/permissions.test.ts +10 -5
  93. package/src/server/__tests__/prompt-defaults.test.ts +3 -3
  94. package/src/server/admin-ai-alerts.ts +2 -1
  95. package/src/server/admin-ai-usage.ts +2 -1
  96. package/src/server/ai-memory/store.ts +6 -2
  97. package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
  98. package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
  99. package/src/server/knowledge-public/github-sync.ts +2 -1
  100. package/src/server/license-issue.ts +2 -1
  101. package/src/server/middlewares/auth-rate-limit.ts +117 -0
  102. package/src/server/observability/health-check.ts +4 -1
  103. package/src/server/observability/metrics.ts +17 -15
  104. package/src/server/observability/request-middleware.ts +2 -1
  105. package/src/server/observability/timing.ts +14 -16
  106. package/src/server/serverFns/admin-billing.ts +107 -12
  107. package/src/server/serverFns/ai-memory/list-users.ts +2 -1
  108. package/src/server/serverFns/ai-prompts/activate.ts +2 -1
  109. package/src/server/serverFns/ai-prompts/get.ts +3 -2
  110. package/src/server/serverFns/ai-prompts/list.ts +3 -2
  111. package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
  112. package/src/server/serverFns/ai-prompts/save.ts +5 -3
  113. package/src/server/serverFns/ai-provider/create.ts +4 -1
  114. package/src/server/serverFns/ai-provider/delete.ts +4 -1
  115. package/src/server/serverFns/ai-provider/get.ts +4 -1
  116. package/src/server/serverFns/ai-provider/list.ts +28 -1
  117. package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
  118. package/src/server/serverFns/ai-provider/update.ts +4 -1
  119. package/src/server/serverFns/anyhub/market.ts +8 -1
  120. package/src/server/serverFns/audit.ts +14 -1
  121. package/src/server/serverFns/auth/setup.ts +3 -1
  122. package/src/server/serverFns/compliance.ts +32 -3
  123. package/src/server/serverFns/components/import.ts +69 -14
  124. package/src/server/serverFns/components/list.ts +4 -1
  125. package/src/server/serverFns/components/mutations.ts +9 -1
  126. package/src/server/serverFns/dependency/list.ts +28 -5
  127. package/src/server/serverFns/dependency/sync.ts +7 -2
  128. package/src/server/serverFns/dependency/update.ts +4 -1
  129. package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
  130. package/src/server/serverFns/developer-logs.ts +5 -1
  131. package/src/server/serverFns/invitations.ts +7 -1
  132. package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
  133. package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
  134. package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
  135. package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
  136. package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
  137. package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
  138. package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
  139. package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
  140. package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
  141. package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
  142. package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
  143. package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
  144. package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
  145. package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
  146. package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
  147. package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
  148. package/src/server/serverFns/licenses.ts +8 -2
  149. package/src/server/serverFns/monitoring/get-errors.ts +2 -1
  150. package/src/server/serverFns/monitoring/get-health.ts +2 -1
  151. package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
  152. package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
  153. package/src/server/serverFns/monitoring/report-error.ts +2 -1
  154. package/src/server/serverFns/monitoring/reset.ts +2 -1
  155. package/src/server/serverFns/oauth-providers.ts +4 -1
  156. package/src/server/serverFns/permissions.ts +18 -11
  157. package/src/server/serverFns/sentry/config.ts +11 -8
  158. package/src/server/serverFns/sso-config.ts +17 -2
  159. package/src/server/serverFns/user/create.ts +6 -1
  160. package/src/server/serverFns/user/delete.ts +6 -1
  161. package/src/server/serverFns/user/detail.ts +5 -2
  162. package/src/server/serverFns/user/list.ts +19 -4
  163. package/src/server/serverFns/user/reset-password.ts +10 -2
  164. package/src/server/serverFns/user/update-role.ts +10 -2
  165. package/src/server/serverFns/user/update-status.ts +10 -2
  166. package/src/styles/globals.css +4 -0
  167. package/src/components/data-table/data-table-pagination.tsx +0 -112
  168. package/src/components/data-table/data-table.tsx +0 -218
  169. package/src/components/data-table/table-detail-layout.tsx +0 -98
  170. package/src/components/ui/alert-dialog.tsx +0 -187
  171. package/src/components/ui/alert.tsx +0 -75
  172. package/src/components/ui/avatar.tsx +0 -106
  173. package/src/components/ui/badge.tsx +0 -51
  174. package/src/components/ui/button.tsx +0 -57
  175. package/src/components/ui/card.tsx +0 -102
  176. package/src/components/ui/checkbox.tsx +0 -28
  177. package/src/components/ui/collapsible.tsx +0 -19
  178. package/src/components/ui/dialog.tsx +0 -155
  179. package/src/components/ui/dropdown-menu.tsx +0 -267
  180. package/src/components/ui/field.tsx +0 -236
  181. package/src/components/ui/input-group.tsx +0 -158
  182. package/src/components/ui/input.tsx +0 -19
  183. package/src/components/ui/label.tsx +0 -17
  184. package/src/components/ui/pagination.tsx +0 -130
  185. package/src/components/ui/select.tsx +0 -234
  186. package/src/components/ui/separator.tsx +0 -22
  187. package/src/components/ui/sheet.tsx +0 -135
  188. package/src/components/ui/sidebar.tsx +0 -721
  189. package/src/components/ui/skeleton.tsx +0 -13
  190. package/src/components/ui/switch.tsx +0 -29
  191. package/src/components/ui/table.tsx +0 -115
  192. package/src/components/ui/textarea.tsx +0 -17
  193. package/src/components/ui/toast.tsx +0 -282
  194. package/src/components/ui/tooltip.tsx +0 -63
  195. package/src/hooks/use-mobile.ts +0 -19
  196. package/src/lib/avatar.ts +0 -41
  197. /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
  198. /package/src/routes/{api.sentry-test.ts → api/sentry-test.ts} +0 -0
@@ -17,8 +17,8 @@
17
17
 
18
18
  import { useState } from 'react'
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { Input } from '@/components/ui/input'
21
- import { Label } from '@/components/ui/label'
20
+ import { Input } from '@allbluecn/ui/input'
21
+ import { Label } from '@allbluecn/ui/label'
22
22
  import { Lock } from 'lucide-react'
23
23
 
24
24
  interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
@@ -17,28 +17,28 @@
17
17
 
18
18
  import { useState, useEffect } from 'react'
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { Button } from '@/components/ui/button'
21
- import { Input } from '@/components/ui/input'
22
- import { Label } from '@/components/ui/label'
23
- import { Textarea } from '@/components/ui/textarea'
24
- import { Switch } from '@/components/ui/switch'
20
+ import { Button } from '@allbluecn/ui/button'
21
+ import { Input } from '@allbluecn/ui/input'
22
+ import { Label } from '@allbluecn/ui/label'
23
+ import { Textarea } from '@allbluecn/ui/textarea'
24
+ import { Switch } from '@allbluecn/ui/switch'
25
25
  import {
26
26
  Select,
27
27
  SelectContent,
28
28
  SelectItem,
29
29
  SelectTrigger,
30
30
  SelectValue,
31
- } from '@/components/ui/select'
31
+ } from '@allbluecn/ui/select'
32
32
  import {
33
33
  Dialog,
34
34
  DialogContent,
35
35
  DialogFooter,
36
36
  DialogHeader,
37
37
  DialogTitle,
38
- } from '@/components/ui/dialog'
39
- import { getComponent, ComponentPreview } from '@allbluecn/web-app/prototype'
38
+ } from '@allbluecn/ui/dialog'
39
+ import { ComponentPreview } from './component-preview'
40
40
  import { createComponentFn, updateComponentFn } from '@/server/serverFns/components/mutations'
41
- import { toast } from '@/components/ui/toast'
41
+ import { toast } from '@allbluecn/ui/toast'
42
42
  import type { ComponentItem } from './component-table'
43
43
 
44
44
  const CATEGORY_KEYS = ['base', 'block', 'template'] as const
@@ -153,19 +153,11 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
153
153
  if (!component) {
154
154
  return <span className="text-xs text-muted-foreground">{m.common_component_form_previewUnavailable()}</span>
155
155
  }
156
- const config = getComponent(component.prototypeKey ?? '')
157
- const Comp = config?.component
158
- if (Comp) {
159
- return (
160
- <div className="pointer-events-none flex h-full w-full items-center justify-center overflow-hidden p-3">
161
- <Comp props={propsState} style={(component.defaultStyle as Record<string, any>) ?? {}} />
162
- </div>
163
- )
164
- }
165
156
  return (
166
157
  <ComponentPreview
167
158
  type={component.type}
168
159
  name={component.name}
160
+ prototypeKey={component.prototypeKey}
169
161
  defaultProps={propsState}
170
162
  defaultStyle={component.defaultStyle as Record<string, any>}
171
163
  size="md"
@@ -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}