@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.
- package/allblue-ui/package.json +6 -0
- package/allblue-ui/src/components/confirm-dialog.tsx +94 -0
- package/allblue-ui/src/components/infinite-scroll-sentinel.tsx +37 -0
- package/allblue-ui/src/components/notion-avatar.tsx +126 -0
- package/allblue-ui/src/components/user-avatar.tsx +82 -0
- package/{src/components → allblue-ui/src}/data-table/data-table-pagination.tsx +14 -5
- package/{src/components → allblue-ui/src}/data-table/data-table.tsx +1 -1
- package/{src/components → allblue-ui/src}/data-table/table-detail-layout.tsx +5 -6
- package/allblue-ui/src/hooks/use-load-more.ts +57 -0
- package/allblue-ui/src/index.ts +31 -0
- package/allblue-ui/src/lib/datetime.ts +226 -0
- package/allblue-ui/src/lib/sanitize.ts +18 -0
- package/allblue-ui/src/lib/utils.ts +1 -0
- package/{src/components → allblue-ui/src}/ui/alert-dialog.tsx +1 -3
- package/{src/components → allblue-ui/src}/ui/checkbox.tsx +0 -1
- package/{src/components → allblue-ui/src}/ui/dialog.tsx +1 -1
- package/{src/components → allblue-ui/src}/ui/dropdown-menu.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/field.tsx +2 -2
- package/{src/components → allblue-ui/src}/ui/input-group.tsx +3 -4
- package/allblue-ui/src/ui/input.tsx +19 -0
- package/{src/components → allblue-ui/src}/ui/pagination.tsx +1 -1
- package/{src/components → allblue-ui/src}/ui/select.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/separator.tsx +1 -0
- package/{src/components → allblue-ui/src}/ui/sheet.tsx +1 -2
- package/{src/components → allblue-ui/src}/ui/sidebar.tsx +7 -7
- package/{src/components → allblue-ui/src}/ui/switch.tsx +1 -0
- package/{src/components → allblue-ui/src}/ui/table.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/toast.tsx +44 -18
- package/{src/components → allblue-ui/src}/ui/tooltip.tsx +1 -0
- package/package.json +21 -18
- package/src/components/admin/admin-user-menu.tsx +4 -4
- package/src/components/admin/developer-log.tsx +16 -32
- package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
- package/src/components/ai-prompts/preview-panel.tsx +1 -1
- package/src/components/ai-prompts/prompt-editor.tsx +8 -9
- package/src/components/ai-prompts/prompt-table.tsx +6 -6
- package/src/components/ai-prompts/section-editor.tsx +3 -3
- package/src/components/ai-prompts/variable-panel.tsx +2 -2
- package/src/components/ai-prompts/version-history.tsx +21 -41
- package/src/components/ai-provider/model-list-editor.tsx +2 -3
- package/src/components/ai-provider/provider-form.tsx +7 -7
- package/src/components/ai-provider/provider-table.tsx +17 -40
- package/src/components/auth/admin-login-form.tsx +7 -7
- package/src/components/auth/icon-input.tsx +2 -2
- package/src/components/auth/password-input.tsx +2 -2
- package/src/components/component/component-form-dialog.tsx +10 -18
- package/src/components/component/component-import-dialog.tsx +6 -6
- package/src/components/component/component-preview.tsx +311 -0
- package/src/components/component/component-table.tsx +3 -3
- package/src/components/dependency/changelog-dialog.tsx +2 -2
- package/src/components/dependency/columns.tsx +3 -3
- package/src/components/dependency/dependency-table.tsx +7 -7
- package/src/components/dependency/edit-dialog.tsx +8 -8
- package/src/components/invitations/invitations-page.tsx +26 -49
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
- package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
- package/src/components/layout/sidebar-02/index.tsx +1 -1
- package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
- package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
- package/src/components/market/columns.tsx +4 -4
- package/src/components/market/market-app-form.tsx +6 -7
- package/src/components/market/market-app-list.tsx +14 -29
- package/src/components/monitoring/error-list.tsx +2 -2
- package/src/components/monitoring/metrics-stats.tsx +1 -1
- package/src/components/monitoring/request-log-table.tsx +2 -2
- package/src/components/oauth-providers/provider-page.tsx +20 -38
- package/src/components/permissions/assign-dialog.tsx +135 -0
- package/src/components/permissions/create-role-dialog.tsx +135 -0
- package/src/components/permissions/types.ts +49 -0
- package/src/components/user/columns.tsx +6 -6
- package/src/components/user/settings-activity-log.tsx +7 -7
- package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
- package/src/components/user/settings-profile/social-links.tsx +63 -0
- package/src/components/user/settings-profile.tsx +26 -125
- package/src/components/user/user-create-dialog.tsx +6 -6
- package/src/components/user/user-detail/error-boundary.tsx +19 -0
- package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
- package/src/components/user/user-filters.tsx +4 -4
- package/src/lib/auth/page-gate.ts +28 -0
- package/src/lib/auth.config.ts +25 -9
- package/src/lib/i18n/detect.ts +1 -1
- package/src/lib/loader-serial.ts +14 -0
- package/src/providers/index.tsx +1 -1
- package/src/routeTree.gen.ts +67 -78
- package/src/routes/__root.tsx +4 -4
- package/src/routes/_nav/audit/route.lazy.tsx +1 -2
- package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
- package/src/routes/_nav/components/route.lazy.tsx +14 -34
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
- package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
- package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
- package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/health/route.tsx +14 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
- package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
- package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
- package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
- package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
- package/src/routes/_nav/permissions/route.tsx +1 -1
- package/src/routes/_nav/route.tsx +5 -13
- package/src/routes/_nav/sso/route.lazy.tsx +2 -3
- package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
- package/src/routes/_nav/users/-components/users-table.tsx +1 -1
- package/src/routes/_nav/users/route.lazy.tsx +1 -1
- package/src/routes/login/route.lazy.tsx +1 -1
- package/src/routes/setup/route.lazy.tsx +2 -2
- package/src/server/__tests__/admin-billing.test.ts +191 -16
- package/src/server/__tests__/compliance.test.ts +44 -7
- package/src/server/__tests__/permissions.test.ts +10 -5
- package/src/server/__tests__/prompt-defaults.test.ts +3 -3
- package/src/server/admin-ai-alerts.ts +2 -1
- package/src/server/admin-ai-usage.ts +2 -1
- package/src/server/ai-memory/store.ts +6 -2
- package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
- package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
- package/src/server/knowledge-public/github-sync.ts +2 -1
- package/src/server/license-issue.ts +2 -1
- package/src/server/middlewares/auth-rate-limit.ts +117 -0
- package/src/server/observability/health-check.ts +4 -1
- package/src/server/observability/metrics.ts +17 -15
- package/src/server/observability/request-middleware.ts +2 -1
- package/src/server/observability/timing.ts +14 -16
- package/src/server/serverFns/admin-billing.ts +107 -12
- package/src/server/serverFns/ai-memory/list-users.ts +2 -1
- package/src/server/serverFns/ai-prompts/activate.ts +2 -1
- package/src/server/serverFns/ai-prompts/get.ts +3 -2
- package/src/server/serverFns/ai-prompts/list.ts +3 -2
- package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
- package/src/server/serverFns/ai-prompts/save.ts +5 -3
- package/src/server/serverFns/ai-provider/create.ts +4 -1
- package/src/server/serverFns/ai-provider/delete.ts +4 -1
- package/src/server/serverFns/ai-provider/get.ts +4 -1
- package/src/server/serverFns/ai-provider/list.ts +28 -1
- package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
- package/src/server/serverFns/ai-provider/update.ts +4 -1
- package/src/server/serverFns/anyhub/market.ts +8 -1
- package/src/server/serverFns/audit.ts +14 -1
- package/src/server/serverFns/auth/setup.ts +3 -1
- package/src/server/serverFns/compliance.ts +32 -3
- package/src/server/serverFns/components/import.ts +69 -14
- package/src/server/serverFns/components/list.ts +4 -1
- package/src/server/serverFns/components/mutations.ts +9 -1
- package/src/server/serverFns/dependency/list.ts +28 -5
- package/src/server/serverFns/dependency/sync.ts +7 -2
- package/src/server/serverFns/dependency/update.ts +4 -1
- package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
- package/src/server/serverFns/developer-logs.ts +5 -1
- package/src/server/serverFns/invitations.ts +7 -1
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
- package/src/server/serverFns/licenses.ts +8 -2
- package/src/server/serverFns/monitoring/get-errors.ts +2 -1
- package/src/server/serverFns/monitoring/get-health.ts +2 -1
- package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
- package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
- package/src/server/serverFns/monitoring/report-error.ts +2 -1
- package/src/server/serverFns/monitoring/reset.ts +2 -1
- package/src/server/serverFns/oauth-providers.ts +4 -1
- package/src/server/serverFns/permissions.ts +18 -11
- package/src/server/serverFns/sentry/config.ts +11 -8
- package/src/server/serverFns/sso-config.ts +17 -2
- package/src/server/serverFns/user/create.ts +6 -1
- package/src/server/serverFns/user/delete.ts +6 -1
- package/src/server/serverFns/user/detail.ts +5 -2
- package/src/server/serverFns/user/list.ts +19 -4
- package/src/server/serverFns/user/reset-password.ts +10 -2
- package/src/server/serverFns/user/update-role.ts +10 -2
- package/src/server/serverFns/user/update-status.ts +10 -2
- package/src/styles/globals.css +4 -0
- package/src/components/ui/input.tsx +0 -19
- /package/{src → allblue-ui/src}/hooks/use-mobile.ts +0 -0
- /package/{src → allblue-ui/src}/lib/avatar.ts +0 -0
- /package/{src/components → allblue-ui/src}/ui/alert.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/avatar.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/badge.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/button.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/card.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/collapsible.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/label.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/skeleton.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/textarea.tsx +0 -0
- /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
- /package/src/routes/{api.sentry-test.ts → api/sentry-test.ts} +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
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 { formatInTimeZone, toZonedTime, fromZonedTime } from 'date-fns-tz'
|
|
19
|
+
import { startOfDay, subDays } from 'date-fns'
|
|
20
|
+
|
|
21
|
+
const FALLBACK_TIMEZONE = 'Asia/Shanghai'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 解析生效时区:项目时区 > 用户时区 > 浏览器时区 > Asia/Shanghai
|
|
25
|
+
*/
|
|
26
|
+
export function resolveTimezone(
|
|
27
|
+
projectTz?: string | null,
|
|
28
|
+
userTz?: string | null,
|
|
29
|
+
): string {
|
|
30
|
+
const browserTz =
|
|
31
|
+
typeof Intl !== 'undefined'
|
|
32
|
+
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
33
|
+
: FALLBACK_TIMEZONE
|
|
34
|
+
return projectTz || userTz || browserTz || FALLBACK_TIMEZONE
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 时区感知的时间格式化。
|
|
39
|
+
* @param date UTC Date 对象(来自 DB)
|
|
40
|
+
* @param timezone IANA 时区,如 "Asia/Shanghai"
|
|
41
|
+
* @param fmt date-fns 格式,默认 "yyyy-MM-dd HH:mm"
|
|
42
|
+
*/
|
|
43
|
+
export function formatDate(
|
|
44
|
+
date: Date,
|
|
45
|
+
timezone: string,
|
|
46
|
+
fmt = 'yyyy-MM-dd HH:mm',
|
|
47
|
+
): string {
|
|
48
|
+
try {
|
|
49
|
+
return formatInTimeZone(date, timezone, fmt)
|
|
50
|
+
} catch {
|
|
51
|
+
return formatInTimeZone(date, FALLBACK_TIMEZONE, fmt)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 确定性时间戳格式化:固定时区 + date-fns 数字格式,不依赖运行环境 locale/时区。
|
|
57
|
+
* SSR 与客户端输出字节级一致,专治 toLocaleString() 引发的 hydration mismatch。
|
|
58
|
+
* @param date Date 对象或 ISO 字符串
|
|
59
|
+
* @param fmt date-fns 格式,默认 "yyyy-MM-dd HH:mm"
|
|
60
|
+
*/
|
|
61
|
+
export function formatTimestamp(
|
|
62
|
+
date: Date | string,
|
|
63
|
+
fmt = 'yyyy-MM-dd HH:mm',
|
|
64
|
+
): string {
|
|
65
|
+
const d = typeof date === 'string' ? new Date(date) : date
|
|
66
|
+
if (Number.isNaN(d.getTime())) return '-'
|
|
67
|
+
return formatDate(d, FALLBACK_TIMEZONE, fmt)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 相对时间(今天/昨天/具体日期),基于时区计算「今天的 0 点」。
|
|
72
|
+
* @param now 当前时间(测试注入;生产用默认 new Date())
|
|
73
|
+
*/
|
|
74
|
+
export function formatRelative(
|
|
75
|
+
date: Date,
|
|
76
|
+
timezone: string,
|
|
77
|
+
now: Date = new Date(),
|
|
78
|
+
): string {
|
|
79
|
+
try {
|
|
80
|
+
const todayStart = startOfDayInTimeZone(now, timezone)
|
|
81
|
+
const yesterdayStart = new Date(todayStart)
|
|
82
|
+
yesterdayStart.setDate(yesterdayStart.getDate() - 1)
|
|
83
|
+
|
|
84
|
+
const dateStart = startOfDayInTimeZone(date, timezone)
|
|
85
|
+
|
|
86
|
+
if (dateStart.getTime() === todayStart.getTime()) return '今天'
|
|
87
|
+
if (dateStart.getTime() === yesterdayStart.getTime()) return '昨天'
|
|
88
|
+
return formatInTimeZone(date, timezone, 'yyyy-MM-dd')
|
|
89
|
+
} catch {
|
|
90
|
+
return formatInTimeZone(date, FALLBACK_TIMEZONE, 'yyyy-MM-dd')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 获取指定时区下某天的 0 点(UTC Date 对象)。
|
|
96
|
+
*/
|
|
97
|
+
function startOfDayInTimeZone(date: Date, timezone: string): Date {
|
|
98
|
+
const zoned = toZonedTime(date, timezone)
|
|
99
|
+
const dayStart = startOfDay(zoned)
|
|
100
|
+
return fromZonedTime(dayStart, timezone)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 对话历史日期分组标签(今天/昨天/本周/具体日期)。
|
|
105
|
+
* @param date UTC Date 对象
|
|
106
|
+
* @param timezone IANA 时区
|
|
107
|
+
*/
|
|
108
|
+
export function formatChatDate(date: Date, timezone: string): string {
|
|
109
|
+
try {
|
|
110
|
+
const now = new Date()
|
|
111
|
+
const todayStart = startOfDayInTimeZone(now, timezone)
|
|
112
|
+
const yesterdayStart = subDays(todayStart, 1)
|
|
113
|
+
const weekStart = subDays(todayStart, 7)
|
|
114
|
+
|
|
115
|
+
const dateStart = startOfDayInTimeZone(date, timezone)
|
|
116
|
+
|
|
117
|
+
if (dateStart.getTime() === todayStart.getTime()) return '今天'
|
|
118
|
+
if (dateStart.getTime() === yesterdayStart.getTime()) return '昨天'
|
|
119
|
+
if (dateStart.getTime() >= weekStart.getTime()) return '本周'
|
|
120
|
+
|
|
121
|
+
const currentYear = Number(formatInTimeZone(now, timezone, 'yyyy'))
|
|
122
|
+
const dateYear = Number(formatInTimeZone(date, timezone, 'yyyy'))
|
|
123
|
+
if (dateYear === currentYear) {
|
|
124
|
+
return formatInTimeZone(date, timezone, 'M月d日')
|
|
125
|
+
}
|
|
126
|
+
return formatInTimeZone(date, timezone, 'yyyy年M月d日')
|
|
127
|
+
} catch {
|
|
128
|
+
return formatInTimeZone(date, FALLBACK_TIMEZONE, 'yyyy-MM-dd')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
// ---- 本地日历工具(自 lib/date-utils.ts 并入:按本地时区做日历网格/日键计算,无时区转换) ----
|
|
134
|
+
|
|
135
|
+
/** 解析 "yyyy-MM-dd" 前缀为本地 Date(避免 UTC 偏移) */
|
|
136
|
+
export function parseDate(s: string): Date {
|
|
137
|
+
const datePart = s.slice(0, 10)
|
|
138
|
+
const [y, m, d] = datePart.split('-').map(Number)
|
|
139
|
+
return new Date(y, m - 1, d)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 本地时区 yyyy-MM-dd 日键(与 timezone 版 formatDate 区分) */
|
|
143
|
+
export function formatDateLocal(d: Date): string {
|
|
144
|
+
const y = d.getFullYear()
|
|
145
|
+
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
146
|
+
const day = String(d.getDate()).padStart(2, '0')
|
|
147
|
+
return `${y}-${m}-${day}`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function isSameDay(a: Date, b: Date): boolean {
|
|
151
|
+
return (
|
|
152
|
+
a.getFullYear() === b.getFullYear() &&
|
|
153
|
+
a.getMonth() === b.getMonth() &&
|
|
154
|
+
a.getDate() === b.getDate()
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getMonthGrid(
|
|
159
|
+
year: number,
|
|
160
|
+
month: number,
|
|
161
|
+
): Array<{ date: Date; inMonth: boolean }> {
|
|
162
|
+
const firstDay = new Date(year, month, 1)
|
|
163
|
+
const startOffset = firstDay.getDay() // 周日=0
|
|
164
|
+
const gridStart = new Date(year, month, 1 - startOffset)
|
|
165
|
+
const result: Array<{ date: Date; inMonth: boolean }> = []
|
|
166
|
+
for (let i = 0; i < 42; i++) {
|
|
167
|
+
const date = new Date(gridStart)
|
|
168
|
+
date.setDate(gridStart.getDate() + i)
|
|
169
|
+
result.push({ date, inMonth: date.getMonth() === month })
|
|
170
|
+
}
|
|
171
|
+
return result
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getWeekGrid(
|
|
175
|
+
year: number,
|
|
176
|
+
month: number,
|
|
177
|
+
day: number,
|
|
178
|
+
): Array<{ date: Date; inMonth: boolean }> {
|
|
179
|
+
const anchor = new Date(year, month, day)
|
|
180
|
+
const startOffset = anchor.getDay()
|
|
181
|
+
const gridStart = new Date(year, month, day - startOffset)
|
|
182
|
+
const result: Array<{ date: Date; inMonth: boolean }> = []
|
|
183
|
+
for (let i = 0; i < 7; i++) {
|
|
184
|
+
const date = new Date(gridStart)
|
|
185
|
+
date.setDate(gridStart.getDate() + i)
|
|
186
|
+
result.push({ date, inMonth: date.getMonth() === month })
|
|
187
|
+
}
|
|
188
|
+
return result
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function buildTimelineRange(
|
|
192
|
+
start: Date,
|
|
193
|
+
end: Date,
|
|
194
|
+
mode: 'month' | 'week',
|
|
195
|
+
): Array<{ start: Date; label: string }> {
|
|
196
|
+
const ticks: Array<{ start: Date; label: string }> = []
|
|
197
|
+
const cursor = new Date(start.getFullYear(), start.getMonth(), 1)
|
|
198
|
+
const endDate = new Date(end.getFullYear(), end.getMonth(), 1)
|
|
199
|
+
while (cursor <= endDate) {
|
|
200
|
+
const label =
|
|
201
|
+
mode === 'month'
|
|
202
|
+
? `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, '0')}`
|
|
203
|
+
: `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, '0')}-${String(cursor.getDate()).padStart(2, '0')}`
|
|
204
|
+
ticks.push({ start: new Date(cursor), label })
|
|
205
|
+
if (mode === 'month') cursor.setMonth(cursor.getMonth() + 1)
|
|
206
|
+
else cursor.setDate(cursor.getDate() + 7)
|
|
207
|
+
}
|
|
208
|
+
return ticks
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function getRangeFromDates(dates: Date[]): { start: Date; end: Date } {
|
|
212
|
+
if (dates.length === 0) {
|
|
213
|
+
const now = new Date()
|
|
214
|
+
return {
|
|
215
|
+
start: new Date(now.getFullYear(), now.getMonth(), 1),
|
|
216
|
+
end: new Date(now.getFullYear(), now.getMonth() + 2, 0),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const times = dates.map((d) => d.getTime())
|
|
220
|
+
const min = new Date(Math.min(...times))
|
|
221
|
+
const max = new Date(Math.max(...times))
|
|
222
|
+
return {
|
|
223
|
+
start: new Date(min.getFullYear(), min.getMonth() - 1, 1),
|
|
224
|
+
end: new Date(max.getFullYear(), max.getMonth() + 1, 0),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import DOMPurify from 'isomorphic-dompurify'
|
|
2
|
+
|
|
3
|
+
// HTML 清理:默认配置(html + svg + mathMl profile 均启用),
|
|
4
|
+
// 覆盖富文本 diff、附件预览等 7 处 HTML 渲染场景。
|
|
5
|
+
export function sanitize(html: string): string {
|
|
6
|
+
return DOMPurify.sanitize(html)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// SVG 清理:头像渲染(NotionAvatar)专用。
|
|
10
|
+
// 显式复用默认配置——DOMPurify 默认 profile 已包含 SVG 滤镜元素
|
|
11
|
+
// (<defs>/<filter>/<feMorphology>/<feFlood>/<feComposite>/<feMerge>/<feMergeNode>),
|
|
12
|
+
// 同时清除 <script>/on*/javascript: 等 XSS 载荷。
|
|
13
|
+
// 禁止改成 USE_PROFILES: { svg: true }——严格 SVG profile 会移除全部 fe* 滤镜元素,
|
|
14
|
+
// 头像轮廓辉光(feMorphology+feFlood+feComposite+feMerge)会失效渲染白屏。
|
|
15
|
+
// 结构完整性由 __tests__/security/xss-sanitize.test.ts 的双向断言锁定。
|
|
16
|
+
export function sanitizeSvg(svg: string): string {
|
|
17
|
+
return DOMPurify.sanitize(svg)
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from "cn"
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import * as React from "react"
|
|
4
2
|
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
|
|
5
3
|
import { cn } from "cn"
|
|
6
4
|
|
|
7
|
-
import { Button } from "
|
|
5
|
+
import { Button } from "./button"
|
|
8
6
|
|
|
9
7
|
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
|
|
10
8
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
@@ -2,7 +2,7 @@ import * as React from "react"
|
|
|
2
2
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
|
3
3
|
import { cn } from "cn"
|
|
4
4
|
|
|
5
|
-
import { Button } from "
|
|
5
|
+
import { Button } from "./button"
|
|
6
6
|
import { XIcon } from "lucide-react"
|
|
7
7
|
|
|
8
8
|
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
|
@@ -2,8 +2,8 @@ import { useMemo } from "react"
|
|
|
2
2
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
3
|
import { cn } from "cn"
|
|
4
4
|
|
|
5
|
-
import { Label } from "
|
|
6
|
-
import { Separator } from "
|
|
5
|
+
import { Label } from "./label"
|
|
6
|
+
import { Separator } from "./separator"
|
|
7
7
|
|
|
8
8
|
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
9
9
|
return (
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
"use client"
|
|
2
1
|
|
|
3
2
|
import * as React from "react"
|
|
4
3
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
4
|
import { cn } from "cn"
|
|
6
5
|
|
|
7
|
-
import { Button } from "
|
|
8
|
-
import { Input } from "
|
|
9
|
-
import { Textarea } from "
|
|
6
|
+
import { Button } from "./button"
|
|
7
|
+
import { Input } from "./input"
|
|
8
|
+
import { Textarea } from "./textarea"
|
|
10
9
|
|
|
11
10
|
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
12
11
|
return (
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Input as InputPrimitive } from "@base-ui/react/input"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
6
|
+
return (
|
|
7
|
+
<InputPrimitive
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Input }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { cn } from "cn"
|
|
3
3
|
|
|
4
|
-
import { Button } from "
|
|
4
|
+
import { Button } from "./button"
|
|
5
5
|
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
|
|
6
6
|
|
|
7
7
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
"use client"
|
|
2
1
|
|
|
3
2
|
import * as React from "react"
|
|
4
3
|
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
|
5
4
|
import { cn } from "cn"
|
|
6
5
|
|
|
7
|
-
import { Button } from "
|
|
6
|
+
import { Button } from "./button"
|
|
8
7
|
import { XIcon } from "lucide-react"
|
|
9
8
|
|
|
10
9
|
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
|
@@ -4,23 +4,23 @@ import { useRender } from "@base-ui/react/use-render"
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
5
|
import { cn } from "cn"
|
|
6
6
|
|
|
7
|
-
import { useIsMobile } from "
|
|
8
|
-
import { Button } from "
|
|
9
|
-
import { Input } from "
|
|
10
|
-
import { Separator } from "
|
|
7
|
+
import { useIsMobile } from "../hooks/use-mobile"
|
|
8
|
+
import { Button } from "./button"
|
|
9
|
+
import { Input } from "./input"
|
|
10
|
+
import { Separator } from "./separator"
|
|
11
11
|
import {
|
|
12
12
|
Sheet,
|
|
13
13
|
SheetContent,
|
|
14
14
|
SheetDescription,
|
|
15
15
|
SheetHeader,
|
|
16
16
|
SheetTitle,
|
|
17
|
-
} from "
|
|
18
|
-
import { Skeleton } from "
|
|
17
|
+
} from "./sheet"
|
|
18
|
+
import { Skeleton } from "./skeleton"
|
|
19
19
|
import {
|
|
20
20
|
Tooltip,
|
|
21
21
|
TooltipContent,
|
|
22
22
|
TooltipTrigger,
|
|
23
|
-
} from "
|
|
23
|
+
} from "./tooltip"
|
|
24
24
|
import { PanelLeftIcon } from "lucide-react"
|
|
25
25
|
|
|
26
26
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
|
@@ -2,11 +2,17 @@ import * as React from "react"
|
|
|
2
2
|
import { Toast as ToastPrimitive } from "@base-ui/react/toast"
|
|
3
3
|
import { cn } from "cn"
|
|
4
4
|
|
|
5
|
-
import { Button } from "
|
|
5
|
+
import { Button } from "./button"
|
|
6
6
|
import { XIcon, CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
|
7
7
|
|
|
8
8
|
const toastManager = ToastPrimitive.createToastManager()
|
|
9
9
|
|
|
10
|
+
// 双端历史分叉:web 顶部居中堆叠(top-center),admin 右下角堆叠(bottom-right)。
|
|
11
|
+
// 差异为纯 class 组 + swipeDirection,通过 position 参数承载,行为逐字节等价于原实现。
|
|
12
|
+
type ToasterPosition = "top-center" | "bottom-right"
|
|
13
|
+
|
|
14
|
+
const ToastPositionContext = React.createContext<ToasterPosition>("top-center")
|
|
15
|
+
|
|
10
16
|
interface SonnerToastOptions {
|
|
11
17
|
description?: React.ReactNode
|
|
12
18
|
duration?: number
|
|
@@ -67,11 +73,14 @@ function ToastPortal({ ...props }: ToastPrimitive.Portal.Props) {
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
function ToastViewport({ className, ...props }: ToastPrimitive.Viewport.Props) {
|
|
76
|
+
const top = React.useContext(ToastPositionContext) === "top-center"
|
|
70
77
|
return (
|
|
71
78
|
<ToastPrimitive.Viewport
|
|
72
79
|
data-slot="toast-viewport"
|
|
73
80
|
className={cn(
|
|
74
|
-
|
|
81
|
+
top
|
|
82
|
+
? "pointer-events-none fixed top-4 left-0 right-0 z-50 mx-auto w-full max-w-sm outline-none sm:top-6"
|
|
83
|
+
: "pointer-events-none fixed bottom-4 right-4 z-50 w-full max-w-sm outline-none sm:bottom-6 sm:right-6",
|
|
75
84
|
className
|
|
76
85
|
)}
|
|
77
86
|
{...props}
|
|
@@ -80,17 +89,30 @@ function ToastViewport({ className, ...props }: ToastPrimitive.Viewport.Props) {
|
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
function Toast({ className, ...props }: ToastPrimitive.Root.Props) {
|
|
92
|
+
const top = React.useContext(ToastPositionContext) === "top-center"
|
|
83
93
|
return (
|
|
84
94
|
<ToastPrimitive.Root
|
|
85
95
|
data-slot="toast"
|
|
86
96
|
className={cn(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
top
|
|
98
|
+
? "group/toast pointer-events-auto absolute top-0 right-0 left-0 z-[calc(1000-var(--toast-index))] mx-auto w-full origin-top rounded-2xl border bg-popover text-popover-foreground shadow-lg will-change-transform outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
99
|
+
: "group/toast pointer-events-auto absolute right-0 bottom-0 z-[calc(1000-var(--toast-index))] w-full origin-bottom rounded-2xl border bg-popover text-popover-foreground shadow-lg will-change-transform outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
100
|
+
top
|
|
101
|
+
? "[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)+calc(var(--toast-index)*var(--gap))+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]"
|
|
102
|
+
: "[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]",
|
|
103
|
+
top
|
|
104
|
+
? "h-(--height) [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--peek))+(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms]"
|
|
105
|
+
: "h-(--height) [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms]",
|
|
106
|
+
top
|
|
107
|
+
? "after:absolute after:bottom-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']"
|
|
108
|
+
: "after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
|
91
109
|
"data-expanded:h-(--toast-height) data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(var(--offset-y))]",
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
top
|
|
111
|
+
? "data-limited:opacity-0 data-starting-style:[transform:translateY(-150%)]"
|
|
112
|
+
: "data-limited:opacity-0 data-starting-style:[transform:translateY(150%)]",
|
|
113
|
+
top
|
|
114
|
+
? "[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(-150%)]"
|
|
115
|
+
: "[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
|
|
94
116
|
"data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
|
|
95
117
|
"data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
|
|
96
118
|
"data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
|
|
@@ -230,9 +252,10 @@ function ToastIcon({ type }: { type: string | undefined }) {
|
|
|
230
252
|
|
|
231
253
|
function ToastList() {
|
|
232
254
|
const { toasts } = ToastPrimitive.useToastManager()
|
|
255
|
+
const top = React.useContext(ToastPositionContext) === "top-center"
|
|
233
256
|
|
|
234
257
|
return toasts.map((toastItem) => (
|
|
235
|
-
<Toast key={toastItem.id} toast={toastItem}>
|
|
258
|
+
<Toast key={toastItem.id} toast={toastItem} swipeDirection={top ? "up" : undefined}>
|
|
236
259
|
<ToastContent>
|
|
237
260
|
<ToastIcon type={toastItem.type} />
|
|
238
261
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
@@ -248,17 +271,20 @@ function ToastList() {
|
|
|
248
271
|
|
|
249
272
|
function Toaster({
|
|
250
273
|
children,
|
|
274
|
+
position = "top-center",
|
|
251
275
|
...props
|
|
252
|
-
}: ToastPrimitive.Provider.Props) {
|
|
276
|
+
}: ToastPrimitive.Provider.Props & { position?: ToasterPosition }) {
|
|
253
277
|
return (
|
|
254
|
-
<
|
|
255
|
-
{
|
|
256
|
-
|
|
257
|
-
<
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
278
|
+
<ToastPositionContext.Provider value={position}>
|
|
279
|
+
<ToastProvider toastManager={toastManager} {...props}>
|
|
280
|
+
{children}
|
|
281
|
+
<ToastPortal>
|
|
282
|
+
<ToastViewport>
|
|
283
|
+
<ToastList />
|
|
284
|
+
</ToastViewport>
|
|
285
|
+
</ToastPortal>
|
|
286
|
+
</ToastProvider>
|
|
287
|
+
</ToastPositionContext.Provider>
|
|
262
288
|
)
|
|
263
289
|
}
|
|
264
290
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allbluecn/admin-app",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@allbluecn/database": "0.9.
|
|
9
|
-
"@allbluecn/kernel": "0.9.
|
|
10
|
-
"@allbluecn/web-app": "0.17.2",
|
|
8
|
+
"@allbluecn/database": "0.9.2",
|
|
9
|
+
"@allbluecn/kernel": "0.9.1",
|
|
11
10
|
"@auth/core": "^0.41.2",
|
|
12
11
|
"@base-ui/react": "^1.8.0",
|
|
13
12
|
"@fontsource-variable/inter": "^5.3.0",
|
|
@@ -24,14 +23,14 @@
|
|
|
24
23
|
"@tabler/icons-react": "^3.44.0",
|
|
25
24
|
"@tanstack/charts": "^0.16.0",
|
|
26
25
|
"@tanstack/react-form": "2.0.0-alpha.2",
|
|
27
|
-
"@tanstack/react-pacer": "^0.
|
|
28
|
-
"@tanstack/react-query": "^5.
|
|
29
|
-
"@tanstack/react-router": "1.170.
|
|
30
|
-
"@tanstack/react-router-ssr-query": "^1.167.
|
|
31
|
-
"@tanstack/react-start": "
|
|
32
|
-
"@tanstack/react-table": "^9.2.
|
|
33
|
-
"@tanstack/react-virtual": "^3.14.
|
|
34
|
-
"@tanstack/store": "^0.11.
|
|
26
|
+
"@tanstack/react-pacer": "^0.24.1",
|
|
27
|
+
"@tanstack/react-query": "^5.104.1",
|
|
28
|
+
"@tanstack/react-router": "1.170.41",
|
|
29
|
+
"@tanstack/react-router-ssr-query": "^1.167.3",
|
|
30
|
+
"@tanstack/react-start": "1.168.60",
|
|
31
|
+
"@tanstack/react-table": "^9.2.6",
|
|
32
|
+
"@tanstack/react-virtual": "^3.14.13",
|
|
33
|
+
"@tanstack/store": "^0.11.2",
|
|
35
34
|
"bcryptjs": "^3.0.3",
|
|
36
35
|
"class-variance-authority": "^0.7.1",
|
|
37
36
|
"clsx": "^2.1.1",
|
|
@@ -56,8 +55,8 @@
|
|
|
56
55
|
"@inlang/paraglide-js": "^2.25.0",
|
|
57
56
|
"@inlang/plugin-message-format": "^4.4.4",
|
|
58
57
|
"@tailwindcss/vite": "^4.3.3",
|
|
59
|
-
"@tanstack/react-query-devtools": "5.
|
|
60
|
-
"@tanstack/react-router-devtools": "1.167.
|
|
58
|
+
"@tanstack/react-query-devtools": "5.104.1",
|
|
59
|
+
"@tanstack/react-router-devtools": "1.167.2",
|
|
61
60
|
"@testing-library/jest-dom": "7.0.1",
|
|
62
61
|
"@testing-library/react": "^16.3.2",
|
|
63
62
|
"@types/bcryptjs": "^2.4.6",
|
|
@@ -67,18 +66,22 @@
|
|
|
67
66
|
"@vitejs/plugin-react": "^6.0.1",
|
|
68
67
|
"@vitest/coverage-v8": "5.0.0",
|
|
69
68
|
"eslint": "^9",
|
|
69
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
70
70
|
"jsdom": "^29.1.1",
|
|
71
71
|
"nitro": "3.0.260903-beta",
|
|
72
72
|
"shadcn": "^4.21.0",
|
|
73
73
|
"tailwindcss": "^4.3.3",
|
|
74
|
-
"typescript": "
|
|
75
|
-
"
|
|
74
|
+
"typescript": "~6.0.3",
|
|
75
|
+
"typescript-eslint": "^8.70.1",
|
|
76
|
+
"vite": "^8.3.3",
|
|
76
77
|
"vitest": "5.0.0",
|
|
77
|
-
"wrangler": "^4.99.0"
|
|
78
|
+
"wrangler": "^4.99.0",
|
|
79
|
+
"@allbluecn/ui": "0.1.0"
|
|
78
80
|
},
|
|
79
81
|
"files": [
|
|
80
82
|
"src",
|
|
81
|
-
"public"
|
|
83
|
+
"public",
|
|
84
|
+
"allblue-ui"
|
|
82
85
|
],
|
|
83
86
|
"publishConfig": {
|
|
84
87
|
"access": "public"
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
|
|
18
18
|
import { LogOut } from "lucide-react";
|
|
19
19
|
import { m } from '@/paraglide/messages.js'
|
|
20
|
-
import { Badge } from "
|
|
20
|
+
import { Badge } from "@allbluecn/ui/badge";
|
|
21
21
|
import {
|
|
22
22
|
SidebarMenu,
|
|
23
23
|
SidebarMenuAction,
|
|
24
24
|
SidebarMenuButton,
|
|
25
25
|
SidebarMenuItem,
|
|
26
|
-
} from "
|
|
27
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "
|
|
26
|
+
} from "@allbluecn/ui/sidebar";
|
|
27
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@allbluecn/ui/tooltip";
|
|
28
28
|
import { useRouter, useMatches } from "@tanstack/react-router";
|
|
29
29
|
import { signOut, useSession } from "@/lib/auth-client";
|
|
30
|
-
import { UserAvatar } from "@allbluecn/
|
|
30
|
+
import { UserAvatar } from "@allbluecn/ui/components/user-avatar";
|
|
31
31
|
import type { AvatarConfig, ShapeType } from "@allbluecn/kernel";
|
|
32
32
|
|
|
33
33
|
const ROLE_LABELS: Record<string, () => string> = {
|