@allbluecn/admin-app 0.1.0 → 0.2.1

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 (86) hide show
  1. package/package.json +9 -9
  2. package/src/components/admin/admin-user-menu.tsx +12 -6
  3. package/src/components/admin/developer-log.tsx +42 -44
  4. package/src/components/ai-provider/model-list-editor.tsx +5 -4
  5. package/src/components/ai-provider/provider-form.tsx +63 -64
  6. package/src/components/ai-provider/provider-table.tsx +35 -36
  7. package/src/components/auth/admin-login-form.tsx +23 -24
  8. package/src/components/auth/oauth-section.tsx +3 -5
  9. package/src/components/auth/password-input.tsx +2 -3
  10. package/src/components/common/placeholder-page.tsx +2 -4
  11. package/src/components/component/component-form-dialog.tsx +48 -49
  12. package/src/components/component/component-import-dialog.tsx +5 -5
  13. package/src/components/component/component-table.tsx +13 -14
  14. package/src/components/data-table/data-table-pagination.tsx +4 -6
  15. package/src/components/data-table/table-detail-layout.tsx +2 -3
  16. package/src/components/dependency/changelog-dialog.tsx +3 -4
  17. package/src/components/dependency/columns.tsx +32 -24
  18. package/src/components/dependency/dependency-table.tsx +37 -29
  19. package/src/components/dependency/edit-dialog.tsx +24 -25
  20. package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
  21. package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
  22. package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
  23. package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
  24. package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
  25. package/src/components/market/columns.tsx +20 -21
  26. package/src/components/market/market-app-form.tsx +45 -38
  27. package/src/components/market/market-app-list.tsx +16 -17
  28. package/src/components/monitoring/error-list.tsx +18 -19
  29. package/src/components/monitoring/health-cards.tsx +11 -13
  30. package/src/components/monitoring/metrics-stats.tsx +11 -12
  31. package/src/components/monitoring/request-log-table.tsx +8 -9
  32. package/src/components/ui/pagination.tsx +0 -2
  33. package/src/components/ui/sidebar.tsx +1 -1
  34. package/src/components/user/columns.tsx +9 -9
  35. package/src/components/user/settings-activity-log.tsx +39 -29
  36. package/src/components/user/settings-profile.tsx +36 -36
  37. package/src/components/user/user-create-dialog.tsx +4 -4
  38. package/src/components/user/user-filters.tsx +16 -18
  39. package/src/lib/i18n/config.ts +0 -32
  40. package/src/providers/index.tsx +6 -16
  41. package/src/routes/__root.tsx +9 -23
  42. package/src/routes/_nav/apps/route.lazy.tsx +2 -3
  43. package/src/routes/_nav/audit/route.lazy.tsx +10 -11
  44. package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
  45. package/src/routes/_nav/billing/route.lazy.tsx +3 -4
  46. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
  47. package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
  48. package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
  49. package/src/routes/_nav/components/route.lazy.tsx +6 -6
  50. package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
  51. package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
  52. package/src/routes/_nav/docs/route.lazy.tsx +2 -3
  53. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
  54. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
  55. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
  56. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
  57. package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
  58. package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
  59. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
  60. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
  61. package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
  62. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
  63. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
  64. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
  65. package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
  66. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
  67. package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
  68. package/src/routes/_nav/settings/route.lazy.tsx +2 -3
  69. package/src/routes/_nav/sso/route.lazy.tsx +20 -18
  70. package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
  71. package/src/routes/_nav/users/route.lazy.tsx +4 -5
  72. package/src/routes/setup/route.lazy.tsx +15 -16
  73. package/src/start.ts +2 -2
  74. package/src/lib/i18n/client.ts +0 -32
  75. package/src/lib/i18n/locales/en/admin-ai.json +0 -128
  76. package/src/lib/i18n/locales/en/admin-billing.json +0 -56
  77. package/src/lib/i18n/locales/en/admin-users.json +0 -88
  78. package/src/lib/i18n/locales/en/common.json +0 -877
  79. package/src/lib/i18n/locales/en/enterprise.json +0 -208
  80. package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
  81. package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
  82. package/src/lib/i18n/locales/zh/admin-users.json +0 -88
  83. package/src/lib/i18n/locales/zh/common.json +0 -877
  84. package/src/lib/i18n/locales/zh/enterprise.json +0 -208
  85. package/src/lib/i18n/server.ts +0 -18
  86. package/src/lib/i18n/types.ts +0 -5
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import type React from 'react'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
21
21
  import { describePath } from '@/lib/monitoring-descriptions'
22
22
  import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
@@ -80,11 +80,10 @@ function DurationCell({ path, duration }: { path: string; duration: number }) {
80
80
  }
81
81
 
82
82
  export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
83
- const { t } = useTranslation('common')
84
83
 
85
84
  const columns: AdminColumnDef<RequestLogEntry>[] = [
86
85
  {
87
- header: t('requestLogTable.columns.time'),
86
+ header: m.common_requestLogTable_columns_time(),
88
87
  accessorKey: 'timestamp',
89
88
  meta: { style: { width: 20, textAlign: 'center' } },
90
89
  cell: ({ row }: { row: { original: RequestLogEntry } }) => (
@@ -94,7 +93,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
94
93
  ),
95
94
  },
96
95
  {
97
- header: t('requestLogTable.columns.path'),
96
+ header: m.common_requestLogTable_columns_path(),
98
97
  accessorKey: 'path',
99
98
  meta: { style: { width: 160 } },
100
99
  cell: ({ row }: { row: { original: RequestLogEntry } }) => (
@@ -104,7 +103,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
104
103
  ),
105
104
  },
106
105
  {
107
- header: t('requestLogTable.columns.description'),
106
+ header: m.common_requestLogTable_columns_description(),
108
107
  accessorKey: 'path',
109
108
  meta: { style: { width: 28 } },
110
109
  cell: ({ row }: { row: { original: RequestLogEntry } }) => (
@@ -124,7 +123,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
124
123
  ),
125
124
  },
126
125
  {
127
- header: t('requestLogTable.columns.method'),
126
+ header: m.common_requestLogTable_columns_method(),
128
127
  accessorKey: 'method',
129
128
  meta: { style: { width: 14, textAlign: 'right' } },
130
129
  cell: ({ row }: { row: { original: RequestLogEntry } }) => {
@@ -137,7 +136,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
137
136
  },
138
137
  },
139
138
  {
140
- header: t('requestLogTable.columns.status'),
139
+ header: m.common_requestLogTable_columns_status(),
141
140
  accessorKey: 'status',
142
141
  meta: { style: { width: 14, textAlign: 'right' } },
143
142
  cell: ({ row }: { row: { original: RequestLogEntry } }) => {
@@ -150,10 +149,10 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
150
149
  {
151
150
  header: () => (
152
151
  <span className="flex items-center gap-1">
153
- {t('requestLogTable.columns.duration')}
152
+ {m.common_requestLogTable_columns_duration()}
154
153
  <Info
155
154
  className="size-3 text-muted-foreground"
156
- data-tooltip={t('requestLogTable.compareBenchmark')}
155
+ data-tooltip={m.common_requestLogTable_compareBenchmark()}
157
156
  />
158
157
  </span>
159
158
  ),
@@ -86,7 +86,6 @@ function PaginationPrevious({
86
86
  return (
87
87
  <PaginationLink
88
88
  aria-label="Go to previous page"
89
- size="default"
90
89
  className={cn("pl-1.5!", className)}
91
90
  {...props}
92
91
  >
@@ -104,7 +103,6 @@ function PaginationNext({
104
103
  return (
105
104
  <PaginationLink
106
105
  aria-label="Go to next page"
107
- size="default"
108
106
  className={cn("pr-1.5!", className)}
109
107
  {...props}
110
108
  >
@@ -281,7 +281,7 @@ function SidebarTrigger({
281
281
  variant="ghost"
282
282
  size="icon-sm"
283
283
  className={cn(className)}
284
- onClick={(event) => {
284
+ onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
285
285
  onClick?.(event)
286
286
  toggleSidebar()
287
287
  }}
@@ -73,7 +73,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
73
73
  {
74
74
  accessorKey: "username",
75
75
  header: "用户",
76
- cell: ({ row }) => {
76
+ cell: ({ row }: any) => {
77
77
  const user = row.original;
78
78
  return (
79
79
  <div className="flex items-center gap-2">
@@ -96,7 +96,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
96
96
  {
97
97
  accessorKey: "role",
98
98
  header: "角色",
99
- cell: ({ row }) => {
99
+ cell: ({ row }: any) => {
100
100
  const role = row.original.role;
101
101
  return <Badge variant={roleBadgeVariant[role] || "outline"}>{roleLabel[role] || role}</Badge>;
102
102
  },
@@ -104,7 +104,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
104
104
  {
105
105
  accessorKey: "status",
106
106
  header: "状态",
107
- cell: ({ row }) => {
107
+ cell: ({ row }: any) => {
108
108
  const status = row.original.status;
109
109
  return <Badge variant={statusBadgeVariant[status] || "outline"}>{statusLabel[status] || status}</Badge>;
110
110
  },
@@ -112,22 +112,22 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
112
112
  {
113
113
  id: "prdCount",
114
114
  header: "PRD",
115
- cell: ({ row }) => <span className="text-muted-foreground">{row.original._count.prds}</span>,
115
+ cell: ({ row }: any) => <span className="text-muted-foreground">{row.original._count.prds}</span>,
116
116
  },
117
117
  {
118
118
  id: "pluginCount",
119
119
  header: "市场应用",
120
- cell: ({ row }) => <span className="text-muted-foreground">{row.original._count.anyhubMarketApps}</span>,
120
+ cell: ({ row }: any) => <span className="text-muted-foreground">{row.original._count.anyhubMarketApps}</span>,
121
121
  },
122
122
  {
123
123
  accessorKey: "createdAt",
124
124
  header: "注册时间",
125
- cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString("zh-CN"),
125
+ cell: ({ row }: any) => new Date(row.original.createdAt).toLocaleDateString("zh-CN"),
126
126
  },
127
127
  {
128
128
  accessorKey: "lastLoginAt",
129
129
  header: "最后登录",
130
- cell: ({ row }) => {
130
+ cell: ({ row }: any) => {
131
131
  const val = row.original.lastLoginAt;
132
132
  return <span className="text-muted-foreground">{val ? new Date(val).toLocaleDateString("zh-CN") : "从未"}</span>;
133
133
  },
@@ -135,13 +135,13 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
135
135
  {
136
136
  id: "actions",
137
137
  header: "",
138
- cell: ({ row }) => {
138
+ cell: ({ row }: any) => {
139
139
  const user = row.original;
140
140
  return (
141
141
  <Button
142
142
  variant="ghost"
143
143
  size="sm"
144
- onClick={(e) => { e.stopPropagation(); onView(user); }}
144
+ onClick={(e: React.MouseEvent) => { e.stopPropagation(); onView(user); }}
145
145
  >
146
146
  <EyeIcon className="mr-1.5 h-4 w-4" />
147
147
  详情
@@ -24,7 +24,7 @@ import {
24
24
  Search,
25
25
  } from "lucide-react";
26
26
  import { useState } from "react";
27
- import { useTranslation } from 'react-i18next'
27
+ import { m } from '@/paraglide/messages.js'
28
28
  import { cn } from "@/lib/utils";
29
29
  import { Badge } from "@/components/ui/badge";
30
30
  import { Button } from "@/components/ui/button";
@@ -95,17 +95,28 @@ const actionTypes = [
95
95
  { value: "delete", key: "delete" },
96
96
  ] as const
97
97
 
98
- function formatRelativeTime(t: (k: string, p?: Record<string, number>) => string, date: Date): string {
98
+ const ACTION_TYPE_LABELS: Record<string, () => string> = {
99
+ all: m.common_activityLog_actionTypes_all,
100
+ login: m.common_activityLog_actionTypes_login,
101
+ logout: m.common_activityLog_actionTypes_logout,
102
+ password_change: m.common_activityLog_actionTypes_password_change,
103
+ profile_update: m.common_activityLog_actionTypes_profile_update,
104
+ settings_change: m.common_activityLog_actionTypes_settings_change,
105
+ export: m.common_activityLog_actionTypes_export,
106
+ delete: m.common_activityLog_actionTypes_delete,
107
+ }
108
+
109
+ function formatRelativeTime(date: Date): string {
99
110
  const now = Date.now();
100
111
  const diff = now - date.getTime();
101
112
  const minutes = Math.floor(diff / 60_000);
102
113
  const hours = Math.floor(minutes / 60);
103
114
  const days = Math.floor(hours / 24);
104
115
 
105
- if (minutes < 1) return t("activityLog.justNow");
106
- if (minutes < 60) return t("common.timeAgo.minutes", { count: minutes });
107
- if (hours < 24) return t("common.timeAgo.hours", { count: hours });
108
- if (days < 7) return t("common.timeAgo.days", { count: days });
116
+ if (minutes < 1) return m.common_activityLog_justNow();
117
+ if (minutes < 60) return m.common_common_timeAgo_minutes({ count: minutes });
118
+ if (hours < 24) return m.common_common_timeAgo_hours({ count: hours });
119
+ if (days < 7) return m.common_common_timeAgo_days({ count: days });
109
120
  return new Intl.DateTimeFormat("zh-CN", {
110
121
  month: "long",
111
122
  day: "numeric",
@@ -122,9 +133,8 @@ export default function SettingsActivityLog({
122
133
  title,
123
134
  description,
124
135
  }: SettingsActivityLogProps) {
125
- const { t } = useTranslation('common')
126
- const displayTitle = title ?? t('activityLog.title')
127
- const displayDescription = description ?? t('activityLog.description')
136
+ const displayTitle = title ?? m.common_activityLog_title()
137
+ const displayDescription = description ?? m.common_activityLog_description()
128
138
  const [searchQuery, setSearchQuery] = useState("");
129
139
  const [selectedType, setSelectedType] = useState("all");
130
140
  const [dateRange, setDateRange] = useState({
@@ -186,12 +196,12 @@ export default function SettingsActivityLog({
186
196
  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
187
197
  <Search className="size-4" />
188
198
  </div>
189
- <FieldLabel className="mb-0">{t('activityLog.search')}</FieldLabel>
199
+ <FieldLabel className="mb-0">{m.common_activityLog_search()}</FieldLabel>
190
200
  </div>
191
201
  <InputGroup>
192
202
  <InputGroupInput
193
- onChange={(e) => setSearchQuery(e.target.value)}
194
- placeholder={t('activityLog.searchPlaceholder')}
203
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
204
+ placeholder={m.common_activityLog_searchPlaceholder()}
195
205
  value={searchQuery}
196
206
  />
197
207
  </InputGroup>
@@ -203,7 +213,7 @@ export default function SettingsActivityLog({
203
213
  <Filter className="size-4" />
204
214
  </div>
205
215
  <FieldLabel className="mb-0" htmlFor="action-type">
206
- {t('activityLog.actionType')}
216
+ {m.common_activityLog_actionType()}
207
217
  </FieldLabel>
208
218
  </div>
209
219
  <Select onValueChange={setSelectedType} value={selectedType}>
@@ -213,7 +223,7 @@ export default function SettingsActivityLog({
213
223
  <SelectContent>
214
224
  {actionTypes.map((type) => (
215
225
  <SelectItem key={type.value} value={type.value}>
216
- {t(`activityLog.actionTypes.${type.key}`)}
226
+ {(ACTION_TYPE_LABELS[type.key] ?? (() => type.key))()}
217
227
  </SelectItem>
218
228
  ))}
219
229
  </SelectContent>
@@ -226,13 +236,13 @@ export default function SettingsActivityLog({
226
236
  <Calendar className="size-4" />
227
237
  </div>
228
238
  <FieldLabel className="mb-0" htmlFor="date-start">
229
- {t('activityLog.startDate')}
239
+ {m.common_activityLog_startDate()}
230
240
  </FieldLabel>
231
241
  </div>
232
242
  <InputGroup>
233
243
  <InputGroupInput
234
244
  id="date-start"
235
- onChange={(e) =>
245
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
236
246
  setDateRange((prev) => ({ ...prev, start: e.target.value }))
237
247
  }
238
248
  type="date"
@@ -247,13 +257,13 @@ export default function SettingsActivityLog({
247
257
  <Calendar className="size-4" />
248
258
  </div>
249
259
  <FieldLabel className="mb-0" htmlFor="date-end">
250
- {t('activityLog.endDate')}
260
+ {m.common_activityLog_endDate()}
251
261
  </FieldLabel>
252
262
  </div>
253
263
  <InputGroup>
254
264
  <InputGroupInput
255
265
  id="date-end"
256
- onChange={(e) =>
266
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
257
267
  setDateRange((prev) => ({ ...prev, end: e.target.value }))
258
268
  }
259
269
  type="date"
@@ -265,7 +275,7 @@ export default function SettingsActivityLog({
265
275
 
266
276
  <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
267
277
  <p className="text-muted-foreground text-sm">
268
- {t('activityLog.total', { count: filteredEntries.length })}
278
+ {m.common_activityLog_total({ count: filteredEntries.length })}
269
279
  </p>
270
280
  <Button
271
281
  className="w-full sm:w-auto"
@@ -277,12 +287,12 @@ export default function SettingsActivityLog({
277
287
  {isExporting ? (
278
288
  <>
279
289
  <Loader2 className="size-4 animate-spin" />
280
- {t('activityLog.exporting')}
290
+ {m.common_activityLog_exporting()}
281
291
  </>
282
292
  ) : (
283
293
  <>
284
294
  <Download className="size-4" />
285
- {t('activityLog.exportLog')}
295
+ {m.common_activityLog_exportLog()}
286
296
  </>
287
297
  )}
288
298
  </Button>
@@ -296,11 +306,11 @@ export default function SettingsActivityLog({
296
306
  <Search className="size-6 text-muted-foreground" />
297
307
  </div>
298
308
  <div className="flex flex-col gap-2">
299
- <p className="font-medium text-sm">{t('activityLog.empty')}</p>
309
+ <p className="font-medium text-sm">{m.common_activityLog_empty()}</p>
300
310
  <p className="text-muted-foreground text-sm">
301
311
  {searchQuery || selectedType !== "all"
302
- ? t('activityLog.emptyHint.filtered')
303
- : t('activityLog.emptyHint.default')}
312
+ ? m.common_activityLog_emptyHint_filtered()
313
+ : m.common_activityLog_emptyHint_default()}
304
314
  </p>
305
315
  </div>
306
316
  </div>
@@ -325,20 +335,20 @@ export default function SettingsActivityLog({
325
335
  entry.status === "success" ? "default" : "destructive"
326
336
  }
327
337
  >
328
- {entry.status === "success" ? t('activityLog.status.success') : t('activityLog.status.failed')}
338
+ {entry.status === "success" ? m.common_activityLog_status_success() : m.common_activityLog_status_failed()}
329
339
  </Badge>
330
340
  </div>
331
341
  <div className="flex flex-wrap items-center gap-2 text-muted-foreground text-xs">
332
342
  <span className="flex items-center gap-1">
333
343
  <MapPin className="size-3" />
334
- {entry.location || t('activityLog.unknown')}
344
+ {entry.location || m.common_activityLog_unknown()}
335
345
  </span>
336
346
  <span>•</span>
337
- <span>{entry.ipAddress || t('activityLog.unknown')}</span>
347
+ <span>{entry.ipAddress || m.common_activityLog_unknown()}</span>
338
348
  <span>•</span>
339
- <span>{entry.device || t('activityLog.unknown')}</span>
349
+ <span>{entry.device || m.common_activityLog_unknown()}</span>
340
350
  <span>•</span>
341
- <span>{formatRelativeTime(t, entry.timestamp)}</span>
351
+ <span>{formatRelativeTime(entry.timestamp)}</span>
342
352
  {entry.status === "failed" && entry.reason && (
343
353
  <>
344
354
  <span>•</span>
@@ -426,7 +426,7 @@ export default function SettingsProfile({
426
426
  <InputGroup>
427
427
  <InputGroupInput
428
428
  id="name"
429
- onChange={(e) =>
429
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
430
430
  setFormData((prev) => ({ ...prev, name: e.target.value }))
431
431
  }
432
432
  placeholder="请输入姓名"
@@ -446,7 +446,7 @@ export default function SettingsProfile({
446
446
  <InputGroupInput
447
447
  disabled={showEmailChangeForm}
448
448
  id="email"
449
- onChange={(e) =>
449
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
450
450
  setFormData((prev) => ({
451
451
  ...prev,
452
452
  email: e.target.value,
@@ -483,13 +483,13 @@ export default function SettingsProfile({
483
483
  </FieldLabel>
484
484
  <FieldContent>
485
485
  <InputGroup>
486
- <InputGroupInput
487
- id="new-email"
488
- onChange={(e) =>
489
- setEmailChangeData((prev) => ({
490
- ...prev,
491
- newEmail: e.target.value,
492
- }))
486
+ <InputGroupInput
487
+ id="new-email"
488
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
489
+ setEmailChangeData((prev) => ({
490
+ ...prev,
491
+ newEmail: e.target.value,
492
+ }))
493
493
  }
494
494
  placeholder="new.email@example.com"
495
495
  type="email"
@@ -508,13 +508,13 @@ export default function SettingsProfile({
508
508
  </FieldLabel>
509
509
  <FieldContent>
510
510
  <InputGroup>
511
- <InputGroupInput
512
- id="current-password"
513
- onChange={(e) =>
514
- setEmailChangeData((prev) => ({
515
- ...prev,
516
- currentPassword: e.target.value,
517
- }))
511
+ <InputGroupInput
512
+ id="current-password"
513
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
514
+ setEmailChangeData((prev) => ({
515
+ ...prev,
516
+ currentPassword: e.target.value,
517
+ }))
518
518
  }
519
519
  placeholder="请输入当前密码"
520
520
  type="password"
@@ -554,7 +554,7 @@ export default function SettingsProfile({
554
554
  <FieldContent>
555
555
  <Textarea
556
556
  id="bio"
557
- onChange={(e) =>
557
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
558
558
  setFormData((prev) => ({ ...prev, bio: e.target.value }))
559
559
  }
560
560
  placeholder="介绍一下自己…"
@@ -576,13 +576,13 @@ export default function SettingsProfile({
576
576
  <FieldLabel htmlFor="location">所在地</FieldLabel>
577
577
  <FieldContent>
578
578
  <InputGroup>
579
- <InputGroupInput
580
- id="location"
581
- onChange={(e) =>
582
- setFormData((prev) => ({
583
- ...prev,
584
- location: e.target.value,
585
- }))
579
+ <InputGroupInput
580
+ id="location"
581
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
582
+ setFormData((prev) => ({
583
+ ...prev,
584
+ location: e.target.value,
585
+ }))
586
586
  }
587
587
  placeholder="城市, 国家"
588
588
  readOnly={readOnly}
@@ -598,13 +598,13 @@ export default function SettingsProfile({
598
598
  <FieldLabel htmlFor="website">网站</FieldLabel>
599
599
  <FieldContent>
600
600
  <InputGroup>
601
- <InputGroupInput
602
- id="website"
603
- onChange={(e) =>
604
- setFormData((prev) => ({
605
- ...prev,
606
- website: e.target.value,
607
- }))
601
+ <InputGroupInput
602
+ id="website"
603
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
604
+ setFormData((prev) => ({
605
+ ...prev,
606
+ website: e.target.value,
607
+ }))
608
608
  }
609
609
  placeholder="https://example.com"
610
610
  readOnly={readOnly}
@@ -640,11 +640,11 @@ export default function SettingsProfile({
640
640
  </FieldLabel>
641
641
  <FieldContent>
642
642
  <InputGroup>
643
- <InputGroupInput
644
- id={`social-${platform.id}`}
645
- onChange={(e) =>
646
- updateSocialLink(platform.id, e.target.value)
647
- }
643
+ <InputGroupInput
644
+ id={`social-${platform.id}`}
645
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
646
+ updateSocialLink(platform.id, e.target.value)
647
+ }
648
648
  placeholder={platform.placeholder}
649
649
  readOnly={readOnly}
650
650
  type="url"
@@ -87,7 +87,7 @@ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps)
87
87
  type="email"
88
88
  required
89
89
  value={form.email}
90
- onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
90
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, email: e.target.value }))}
91
91
  />
92
92
  </div>
93
93
  <div className="grid gap-2">
@@ -96,7 +96,7 @@ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps)
96
96
  id="username"
97
97
  required
98
98
  value={form.username}
99
- onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))}
99
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, username: e.target.value }))}
100
100
  />
101
101
  </div>
102
102
  <div className="grid gap-2">
@@ -106,12 +106,12 @@ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps)
106
106
  type="password"
107
107
  required
108
108
  value={form.password}
109
- onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
109
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, password: e.target.value }))}
110
110
  />
111
111
  </div>
112
112
  <div className="grid gap-2">
113
113
  <Label>角色</Label>
114
- <Select value={form.role} onValueChange={(v) => setForm((f) => ({ ...f, role: v as typeof form.role }))}>
114
+ <Select value={form.role} onValueChange={(v: string) => setForm((f) => ({ ...f, role: v as typeof form.role }))}>
115
115
  <SelectTrigger>
116
116
  <SelectValue />
117
117
  </SelectTrigger>
@@ -15,10 +15,9 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
-
19
18
  import { useNavigate, useSearch } from "@tanstack/react-router";
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { useCallback } from "react";
21
- import { useTranslation } from 'react-i18next'
22
21
  import { Input } from "@/components/ui/input";
23
22
  import { Button } from "@/components/ui/button";
24
23
  import {
@@ -37,7 +36,6 @@ interface UserFiltersProps {
37
36
  }
38
37
 
39
38
  export function UserFilters({ search, role, status }: UserFiltersProps) {
40
- const { t } = useTranslation('common')
41
39
  const navigate = useNavigate()
42
40
 
43
41
  const updateParam = useCallback(
@@ -64,10 +62,10 @@ export function UserFilters({ search, role, status }: UserFiltersProps) {
64
62
  <div className="relative flex-1 max-w-sm">
65
63
  <SearchIcon className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
66
64
  <Input
67
- placeholder={t('userFilters.searchPlaceholder')}
65
+ placeholder={m.common_userFilters_searchPlaceholder()}
68
66
  className="pl-8"
69
67
  defaultValue={search || ""}
70
- onChange={(e) => {
68
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
71
69
  const value = e.target.value || undefined
72
70
  const next: Record<string, string | undefined> = { search: value, role, status }
73
71
  if (!value) delete next.search
@@ -78,38 +76,38 @@ export function UserFilters({ search, role, status }: UserFiltersProps) {
78
76
 
79
77
  <Select
80
78
  value={role || "all"}
81
- onValueChange={(v) => updateParam("role", v)}
79
+ onValueChange={(v: string) => updateParam("role", v)}
82
80
  >
83
81
  <SelectTrigger className="w-32.5">
84
- <SelectValue placeholder={t('userFilters.rolePlaceholder')} />
82
+ <SelectValue placeholder={m.common_userFilters_rolePlaceholder()} />
85
83
  </SelectTrigger>
86
84
  <SelectContent>
87
- <SelectItem value="all">{t('userFilters.role.all')}</SelectItem>
88
- <SelectItem value="SUPER_ADMIN">{t('userFilters.role.SUPER_ADMIN')}</SelectItem>
89
- <SelectItem value="ADMIN">{t('userFilters.role.ADMIN')}</SelectItem>
90
- <SelectItem value="USER">{t('userFilters.role.USER')}</SelectItem>
85
+ <SelectItem value="all">{m.common_userFilters_role_all()}</SelectItem>
86
+ <SelectItem value="SUPER_ADMIN">{m.common_userFilters_role_SUPER_ADMIN()}</SelectItem>
87
+ <SelectItem value="ADMIN">{m.common_userFilters_role_ADMIN()}</SelectItem>
88
+ <SelectItem value="USER">{m.common_userFilters_role_USER()}</SelectItem>
91
89
  </SelectContent>
92
90
  </Select>
93
91
 
94
92
  <Select
95
93
  value={status || "all"}
96
- onValueChange={(v) => updateParam("status", v)}
94
+ onValueChange={(v: string) => updateParam("status", v)}
97
95
  >
98
96
  <SelectTrigger className="w-32.5">
99
- <SelectValue placeholder={t('userFilters.statusPlaceholder')} />
97
+ <SelectValue placeholder={m.common_userFilters_statusPlaceholder()} />
100
98
  </SelectTrigger>
101
99
  <SelectContent>
102
- <SelectItem value="all">{t('userFilters.status.all')}</SelectItem>
103
- <SelectItem value="ACTIVE">{t('userFilters.status.ACTIVE')}</SelectItem>
104
- <SelectItem value="INACTIVE">{t('userFilters.status.INACTIVE')}</SelectItem>
105
- <SelectItem value="BANNED">{t('userFilters.status.BANNED')}</SelectItem>
100
+ <SelectItem value="all">{m.common_userFilters_status_all()}</SelectItem>
101
+ <SelectItem value="ACTIVE">{m.common_userFilters_status_ACTIVE()}</SelectItem>
102
+ <SelectItem value="INACTIVE">{m.common_userFilters_status_INACTIVE()}</SelectItem>
103
+ <SelectItem value="BANNED">{m.common_userFilters_status_BANNED()}</SelectItem>
106
104
  </SelectContent>
107
105
  </Select>
108
106
 
109
107
  {hasFilters && (
110
108
  <Button variant="ghost" size="sm" onClick={clearFilters}>
111
109
  <XIcon className="mr-1 h-4 w-4" />
112
- {t('userFilters.clearFilters')}
110
+ {m.common_userFilters_clearFilters()}
113
111
  </Button>
114
112
  )}
115
113
  </div>
@@ -1,39 +1,7 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- import zhCommon from './locales/zh/common.json'
3
- import enCommon from './locales/en/common.json'
4
- import zhUsers from './locales/zh/admin-users.json'
5
- import enUsers from './locales/en/admin-users.json'
6
- import zhAi from './locales/zh/admin-ai.json'
7
- import enAi from './locales/en/admin-ai.json'
8
- import zhBilling from './locales/zh/admin-billing.json'
9
- import enBilling from './locales/en/admin-billing.json'
10
- import zhEnterprise from './locales/zh/enterprise.json'
11
- import enEnterprise from './locales/en/enterprise.json'
12
-
13
2
  export const supportedLngs = ['zh', 'en'] as const
14
3
  export type AppLocale = (typeof supportedLngs)[number]
15
4
  export const fallbackLng: AppLocale = 'zh'
16
- export const defaultNs = 'common'
17
-
18
- export const resources = {
19
- zh: {
20
- common: zhCommon,
21
- 'admin-users': zhUsers,
22
- 'admin-ai': zhAi,
23
- 'admin-billing': zhBilling,
24
- enterprise: zhEnterprise,
25
- },
26
- en: {
27
- common: enCommon,
28
- 'admin-users': enUsers,
29
- 'admin-ai': enAi,
30
- 'admin-billing': enBilling,
31
- enterprise: enEnterprise,
32
- },
33
- } as const
34
-
35
- export const namespaces = Object.keys(resources.zh) as Array<keyof typeof resources.zh>
36
- export type NamespaceKey = (typeof namespaces)[number]
37
5
 
38
6
  export function isSupportedLocale(lng: string): lng is AppLocale {
39
7
  return (supportedLngs as readonly string[]).includes(lng)