@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.
- package/package.json +9 -9
- package/src/components/admin/admin-user-menu.tsx +12 -6
- package/src/components/admin/developer-log.tsx +42 -44
- package/src/components/ai-provider/model-list-editor.tsx +5 -4
- package/src/components/ai-provider/provider-form.tsx +63 -64
- package/src/components/ai-provider/provider-table.tsx +35 -36
- package/src/components/auth/admin-login-form.tsx +23 -24
- package/src/components/auth/oauth-section.tsx +3 -5
- package/src/components/auth/password-input.tsx +2 -3
- package/src/components/common/placeholder-page.tsx +2 -4
- package/src/components/component/component-form-dialog.tsx +48 -49
- package/src/components/component/component-import-dialog.tsx +5 -5
- package/src/components/component/component-table.tsx +13 -14
- package/src/components/data-table/data-table-pagination.tsx +4 -6
- package/src/components/data-table/table-detail-layout.tsx +2 -3
- package/src/components/dependency/changelog-dialog.tsx +3 -4
- package/src/components/dependency/columns.tsx +32 -24
- package/src/components/dependency/dependency-table.tsx +37 -29
- package/src/components/dependency/edit-dialog.tsx +24 -25
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
- package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
- package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
- package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
- package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
- package/src/components/market/columns.tsx +20 -21
- package/src/components/market/market-app-form.tsx +45 -38
- package/src/components/market/market-app-list.tsx +16 -17
- package/src/components/monitoring/error-list.tsx +18 -19
- package/src/components/monitoring/health-cards.tsx +11 -13
- package/src/components/monitoring/metrics-stats.tsx +11 -12
- package/src/components/monitoring/request-log-table.tsx +8 -9
- package/src/components/ui/pagination.tsx +0 -2
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/user/columns.tsx +9 -9
- package/src/components/user/settings-activity-log.tsx +39 -29
- package/src/components/user/settings-profile.tsx +36 -36
- package/src/components/user/user-create-dialog.tsx +4 -4
- package/src/components/user/user-filters.tsx +16 -18
- package/src/lib/i18n/config.ts +0 -32
- package/src/providers/index.tsx +6 -16
- package/src/routes/__root.tsx +9 -23
- package/src/routes/_nav/apps/route.lazy.tsx +2 -3
- package/src/routes/_nav/audit/route.lazy.tsx +10 -11
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
- package/src/routes/_nav/billing/route.lazy.tsx +3 -4
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
- package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
- package/src/routes/_nav/components/route.lazy.tsx +6 -6
- package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
- package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
- package/src/routes/_nav/docs/route.lazy.tsx +2 -3
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
- package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
- package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
- package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
- package/src/routes/_nav/settings/route.lazy.tsx +2 -3
- package/src/routes/_nav/sso/route.lazy.tsx +20 -18
- package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
- package/src/routes/_nav/users/route.lazy.tsx +4 -5
- package/src/routes/setup/route.lazy.tsx +15 -16
- package/src/start.ts +2 -2
- package/src/lib/i18n/client.ts +0 -32
- package/src/lib/i18n/locales/en/admin-ai.json +0 -128
- package/src/lib/i18n/locales/en/admin-billing.json +0 -56
- package/src/lib/i18n/locales/en/admin-users.json +0 -88
- package/src/lib/i18n/locales/en/common.json +0 -877
- package/src/lib/i18n/locales/en/enterprise.json +0 -208
- package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
- package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
- package/src/lib/i18n/locales/zh/admin-users.json +0 -88
- package/src/lib/i18n/locales/zh/common.json +0 -877
- package/src/lib/i18n/locales/zh/enterprise.json +0 -208
- package/src/lib/i18n/server.ts +0 -18
- package/src/lib/i18n/types.ts +0 -5
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { LogOut, User, Mail, Users, BarChart3, Shield, Settings, ArrowRight, ArrowLeft, Target } from "lucide-react";
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
19
20
|
import { useState } from "react";
|
|
20
|
-
import { useTranslation } from 'react-i18next'
|
|
21
21
|
import { signIn, signOut } from "@/lib/auth-client";
|
|
22
22
|
import { Button } from "@/components/ui/button";
|
|
23
23
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
@@ -61,7 +61,6 @@ function maskEmail(email: string): string {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
64
|
-
const { t } = useTranslation('common')
|
|
65
64
|
const [password, setPassword] = useState("");
|
|
66
65
|
const [email, setEmail] = useState("");
|
|
67
66
|
const [stayLoggedIn, setStayLoggedIn] = useState(false);
|
|
@@ -71,15 +70,15 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
71
70
|
e.preventDefault();
|
|
72
71
|
|
|
73
72
|
if (!isAdmin && !email) {
|
|
74
|
-
toast.error(
|
|
73
|
+
toast.error(m.common_login_error_enterEmail());
|
|
75
74
|
return;
|
|
76
75
|
}
|
|
77
76
|
if (!isAdmin && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(email)) {
|
|
78
|
-
toast.error(
|
|
77
|
+
toast.error(m.common_login_error_validEmail());
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
81
80
|
if (!password) {
|
|
82
|
-
toast.error(
|
|
81
|
+
toast.error(m.common_login_error_enterPassword());
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
84
|
|
|
@@ -99,7 +98,7 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
99
98
|
// form.submit() 已触发导航,以下代码在页面卸载前执行
|
|
100
99
|
// 若 csrf fetch 失败会抛错进入 catch
|
|
101
100
|
} catch {
|
|
102
|
-
toast.error(
|
|
101
|
+
toast.error(m.common_login_error_invalidCredentials());
|
|
103
102
|
setLoading(false);
|
|
104
103
|
}
|
|
105
104
|
}
|
|
@@ -122,7 +121,7 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
122
121
|
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-lg bg-primary">
|
|
123
122
|
<Target className="h-10 w-10 text-primary-foreground" />
|
|
124
123
|
</div>
|
|
125
|
-
<h1 className="text-3xl font-semibold">{
|
|
124
|
+
<h1 className="text-3xl font-semibold">{m.common_login_adminPanel()}</h1>
|
|
126
125
|
<Card className="h-16 w-full bg-muted/50 border">
|
|
127
126
|
<CardContent className="flex h-full items-center gap-3 px-3 py-3">
|
|
128
127
|
<UserAvatar
|
|
@@ -133,30 +132,30 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
133
132
|
size="lg"
|
|
134
133
|
/>
|
|
135
134
|
<div className="min-w-0 text-left">
|
|
136
|
-
<p className="truncate text-base font-medium">{user?.name || user?.email ||
|
|
135
|
+
<p className="truncate text-base font-medium">{user?.name || user?.email || m.common_login_admin()}</p>
|
|
137
136
|
<p className="truncate text-xm text-muted-foreground">{maskEmail(user?.email || "")}</p>
|
|
138
137
|
</div>
|
|
139
138
|
</CardContent>
|
|
140
139
|
</Card>
|
|
141
140
|
</div>
|
|
142
141
|
<form onSubmit={handleLogin} className="space-y-6">
|
|
143
|
-
<PasswordInput label={
|
|
142
|
+
<PasswordInput label={m.common_login_password_label()} placeholder={m.common_login_password_placeholder()} value={password} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)} />
|
|
144
143
|
<Field orientation="horizontal" className="gap-2">
|
|
145
144
|
<FieldLabel>
|
|
146
|
-
<Checkbox checked={stayLoggedIn} onCheckedChange={(v) => setStayLoggedIn(v === true)} />
|
|
147
|
-
<span className="text-sm">{
|
|
145
|
+
<Checkbox checked={stayLoggedIn} onCheckedChange={(v: boolean) => setStayLoggedIn(v === true)} />
|
|
146
|
+
<span className="text-sm">{m.common_login_rememberMe()}</span>
|
|
148
147
|
</FieldLabel>
|
|
149
148
|
</Field>
|
|
150
149
|
<RainbowButton type="submit" className="w-full" disabled={loading}>
|
|
151
|
-
{loading ?
|
|
150
|
+
{loading ? m.common_login_loading() : m.common_login_loginAdmin()}
|
|
152
151
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
153
152
|
</RainbowButton>
|
|
154
153
|
<div className="flex gap-2">
|
|
155
154
|
<Button type="button" variant="outline" className="flex-1" onClick={handleSwitchAccount}>
|
|
156
|
-
<Users className="w-4 h-4 mr-2" />{
|
|
155
|
+
<Users className="w-4 h-4 mr-2" />{m.common_login_switchAccount()}
|
|
157
156
|
</Button>
|
|
158
157
|
<Button type="button" variant="outline" className="flex-1" onClick={handleLogout}>
|
|
159
|
-
<LogOut className="w-4 h-4 mr-2" />{
|
|
158
|
+
<LogOut className="w-4 h-4 mr-2" />{m.common_login_logout()}
|
|
160
159
|
</Button>
|
|
161
160
|
</div>
|
|
162
161
|
</form>
|
|
@@ -171,16 +170,16 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
171
170
|
<AuthLayout>
|
|
172
171
|
<AuthLayoutLeft>
|
|
173
172
|
<div className="space-y-4 text-center">
|
|
174
|
-
<AuthHeader title={
|
|
173
|
+
<AuthHeader title={m.common_login_noPermission()} titleClassName="text-destructive" />
|
|
175
174
|
<p className="text-muted-foreground">
|
|
176
|
-
{
|
|
175
|
+
{m.common_login_notAdmin({ name: user.name || user.email || '' })}
|
|
177
176
|
</p>
|
|
178
177
|
<Button className="w-full" onClick={() => window.open(`${import.meta.env.VITE_WEB_URL ?? '/'}dashboard`, "_self")}>
|
|
179
178
|
<ArrowLeft className="h-4 w-4" />
|
|
180
|
-
{
|
|
179
|
+
{m.common_login_backToWorkspace()}
|
|
181
180
|
</Button>
|
|
182
181
|
<Button variant="outline" className="w-full" onClick={handleSwitchAccount}>
|
|
183
|
-
{
|
|
182
|
+
{m.common_login_switchAndLogin()}
|
|
184
183
|
</Button>
|
|
185
184
|
</div>
|
|
186
185
|
</AuthLayoutLeft>
|
|
@@ -192,18 +191,18 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
|
|
|
192
191
|
return (
|
|
193
192
|
<AuthLayout>
|
|
194
193
|
<AuthLayoutLeft>
|
|
195
|
-
<AuthHeader title={
|
|
194
|
+
<AuthHeader title={m.common_login_adminPanel()} subtitle={m.common_login_subtitle()} />
|
|
196
195
|
<form onSubmit={handleLogin} className="space-y-6">
|
|
197
|
-
<IconInput icon={Mail} label={
|
|
198
|
-
<PasswordInput label={
|
|
196
|
+
<IconInput icon={Mail} label={m.common_login_email_label()} placeholder={m.common_login_email_placeholder()} value={email} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)} />
|
|
197
|
+
<PasswordInput label={m.common_login_password_label()} placeholder={m.common_login_password_placeholder()} value={password} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)} />
|
|
199
198
|
<Field orientation="horizontal" className="gap-2">
|
|
200
199
|
<FieldLabel>
|
|
201
|
-
<Checkbox checked={stayLoggedIn} onCheckedChange={(v) => setStayLoggedIn(v === true)} />
|
|
202
|
-
<span className="text-sm">{
|
|
200
|
+
<Checkbox checked={stayLoggedIn} onCheckedChange={(v: boolean) => setStayLoggedIn(v === true)} />
|
|
201
|
+
<span className="text-sm">{m.common_login_rememberMe()}</span>
|
|
203
202
|
</FieldLabel>
|
|
204
203
|
</Field>
|
|
205
204
|
<RainbowButton type="submit" className="w-full" disabled={loading}>
|
|
206
|
-
{loading ?
|
|
205
|
+
{loading ? m.common_login_loggingIn() : m.common_login_loginAdmin()}
|
|
207
206
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
208
207
|
</RainbowButton>
|
|
209
208
|
</form>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { m } from '@/paraglide/messages.js'
|
|
1
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
3
|
// AllBlue - 理想之海
|
|
3
4
|
// Copyright (C) 2026 AllBlue Contributors
|
|
@@ -15,8 +16,6 @@
|
|
|
15
16
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
17
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
18
|
|
|
18
|
-
import { useTranslation } from 'react-i18next'
|
|
19
|
-
|
|
20
19
|
interface OAuthSectionProps {
|
|
21
20
|
providers: { name: string; icon: React.ReactNode }[]
|
|
22
21
|
separatorText?: string
|
|
@@ -24,8 +23,7 @@ interface OAuthSectionProps {
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export function OAuthSection({ providers, separatorText, actionText = '登录' }: OAuthSectionProps) {
|
|
27
|
-
const
|
|
28
|
-
const resolvedActionText = actionText === '登录' ? t('login.action') : actionText
|
|
26
|
+
const resolvedActionText = actionText === '登录' ? m.common_login_action() : actionText
|
|
29
27
|
return (
|
|
30
28
|
<div className="space-y-3">
|
|
31
29
|
{providers.map((provider, index) => (
|
|
@@ -35,7 +33,7 @@ export function OAuthSection({ providers, separatorText, actionText = '登录' }
|
|
|
35
33
|
className="w-full inline-flex items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent transition-colors"
|
|
36
34
|
>
|
|
37
35
|
{provider.icon}
|
|
38
|
-
{
|
|
36
|
+
{m.common_login_oauthWith({ provider: provider.name, action: resolvedActionText })}
|
|
39
37
|
</button>
|
|
40
38
|
{index < providers.length - 1 && separatorText && (
|
|
41
39
|
<div className="relative flex items-center justify-center my-3">
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useState } from 'react'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { Input } from '@/components/ui/input'
|
|
21
21
|
import { Label } from '@/components/ui/label'
|
|
22
22
|
import { Lock } from 'lucide-react'
|
|
@@ -26,7 +26,6 @@ interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputEle
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function PasswordInput({ label, id, className, ...props }: PasswordInputProps) {
|
|
29
|
-
const { t } = useTranslation('common')
|
|
30
29
|
const inputId = id || label.toLowerCase()
|
|
31
30
|
const [isVisible, setIsVisible] = useState(false)
|
|
32
31
|
|
|
@@ -47,7 +46,7 @@ export function PasswordInput({ label, id, className, ...props }: PasswordInputP
|
|
|
47
46
|
type="button"
|
|
48
47
|
onClick={() => setIsVisible(!isVisible)}
|
|
49
48
|
className="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground outline-none transition-colors hover:text-foreground focus:z-10 focus:ring-[3px] focus:ring-ring/50"
|
|
50
|
-
aria-label={isVisible ?
|
|
49
|
+
aria-label={isVisible ? m.common_login_hidePassword() : m.common_login_showPassword()}
|
|
51
50
|
>
|
|
52
51
|
{isVisible ? (
|
|
53
52
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import type { LucideIcon } from 'lucide-react'
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
21
20
|
type PlaceholderPageProps = {
|
|
22
21
|
title: string
|
|
23
22
|
description?: string
|
|
@@ -25,7 +24,6 @@ type PlaceholderPageProps = {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export function PlaceholderPage({ title, description, icon: Icon }: PlaceholderPageProps) {
|
|
28
|
-
const { t } = useTranslation('common')
|
|
29
27
|
return (
|
|
30
28
|
<div className="flex h-full min-h-[60vh] items-center justify-center">
|
|
31
29
|
<div className="flex flex-col items-center gap-4 rounded-xl border bg-card p-12 text-center shadow-sm">
|
|
@@ -34,7 +32,7 @@ export function PlaceholderPage({ title, description, icon: Icon }: PlaceholderP
|
|
|
34
32
|
</div>
|
|
35
33
|
<h1 className="text-xl font-semibold">{title}</h1>
|
|
36
34
|
<p className="text-sm text-muted-foreground">
|
|
37
|
-
{description ??
|
|
35
|
+
{description ?? m.common_common_comingSoon()}
|
|
38
36
|
</p>
|
|
39
37
|
</div>
|
|
40
38
|
</div>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useState, useEffect } from 'react'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { Button } from '@/components/ui/button'
|
|
21
21
|
import { Input } from '@/components/ui/input'
|
|
22
22
|
import { Label } from '@/components/ui/label'
|
|
@@ -51,7 +51,6 @@ interface ComponentFormDialogProps {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }: ComponentFormDialogProps) {
|
|
54
|
-
const { t } = useTranslation('common')
|
|
55
54
|
const isEdit = !!component
|
|
56
55
|
const [name, setName] = useState('')
|
|
57
56
|
const [description, setDescription] = useState('')
|
|
@@ -85,7 +84,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
85
84
|
|
|
86
85
|
async function handleSave() {
|
|
87
86
|
if (!name.trim()) {
|
|
88
|
-
toast.error(
|
|
87
|
+
toast.error(m.common_component_form_nameRequired())
|
|
89
88
|
return
|
|
90
89
|
}
|
|
91
90
|
setSaving(true)
|
|
@@ -109,7 +108,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
109
108
|
defaultProps: propsState,
|
|
110
109
|
},
|
|
111
110
|
})
|
|
112
|
-
toast.success(
|
|
111
|
+
toast.success(m.common_component_form_updated())
|
|
113
112
|
} else {
|
|
114
113
|
await createComponentFn({
|
|
115
114
|
data: {
|
|
@@ -119,12 +118,12 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
119
118
|
description: description.trim() || undefined,
|
|
120
119
|
},
|
|
121
120
|
})
|
|
122
|
-
toast.success(
|
|
121
|
+
toast.success(m.common_component_form_created())
|
|
123
122
|
}
|
|
124
123
|
onSuccess()
|
|
125
124
|
onOpenChange(false)
|
|
126
125
|
} catch (e) {
|
|
127
|
-
toast.error(e instanceof Error ? e.message :
|
|
126
|
+
toast.error(e instanceof Error ? e.message : m.common_component_form_saveFailed())
|
|
128
127
|
} finally {
|
|
129
128
|
setSaving(false)
|
|
130
129
|
}
|
|
@@ -132,18 +131,18 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
132
131
|
|
|
133
132
|
function categoryToDisplay(k: string): string {
|
|
134
133
|
const map: Record<string, string> = {
|
|
135
|
-
base:
|
|
136
|
-
block:
|
|
137
|
-
template:
|
|
134
|
+
base: m.common_component_categories_base(),
|
|
135
|
+
block: m.common_component_categories_block(),
|
|
136
|
+
template: m.common_component_categories_template(),
|
|
138
137
|
}
|
|
139
138
|
return map[k] ?? k
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
function categoryFromKey(label: string | null | undefined): string | undefined {
|
|
143
142
|
if (!label) return undefined
|
|
144
|
-
const zhBase =
|
|
145
|
-
const zhBlock =
|
|
146
|
-
const zhTemplate =
|
|
143
|
+
const zhBase = m.common_component_categories_base()
|
|
144
|
+
const zhBlock = m.common_component_categories_block()
|
|
145
|
+
const zhTemplate = m.common_component_categories_template()
|
|
147
146
|
if (label === zhBase) return 'base'
|
|
148
147
|
if (label === zhBlock) return 'block'
|
|
149
148
|
if (label === zhTemplate) return 'template'
|
|
@@ -152,7 +151,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
152
151
|
|
|
153
152
|
function renderPreview() {
|
|
154
153
|
if (!component) {
|
|
155
|
-
return <span className="text-xs text-muted-foreground">{
|
|
154
|
+
return <span className="text-xs text-muted-foreground">{m.common_component_form_previewUnavailable()}</span>
|
|
156
155
|
}
|
|
157
156
|
const config = getComponent(component.prototypeKey ?? '')
|
|
158
157
|
const Comp = config?.component
|
|
@@ -180,7 +179,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
180
179
|
if (schema.length === 0) return null
|
|
181
180
|
return (
|
|
182
181
|
<div className="col-span-2 mt-2 space-y-2 border-t pt-3">
|
|
183
|
-
<Label className="text-muted-foreground">{
|
|
182
|
+
<Label className="text-muted-foreground">{m.common_component_form_defaultProps()}</Label>
|
|
184
183
|
<div className="grid grid-cols-2 gap-3">
|
|
185
184
|
{schema.map((prop) => {
|
|
186
185
|
const val = propsState[prop.key] ?? prop.default
|
|
@@ -191,11 +190,11 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
191
190
|
{prop.type === 'boolean' ? (
|
|
192
191
|
<Switch checked={!!val} onCheckedChange={setVal} />
|
|
193
192
|
) : prop.type === 'number' ? (
|
|
194
|
-
<Input type="number" value={Number(val)} onChange={(e) => setVal(Number(e.target.value))} />
|
|
193
|
+
<Input type="number" value={Number(val)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(Number(e.target.value))} />
|
|
195
194
|
) : prop.type === 'color' ? (
|
|
196
195
|
<div className="flex gap-2">
|
|
197
|
-
<input type="color" value={String(val)} onChange={(e) => setVal(e.target.value)} className="size-8 shrink-0 rounded border" />
|
|
198
|
-
<Input value={String(val)} onChange={(e) => setVal(e.target.value)} className="font-mono text-xs" />
|
|
196
|
+
<input type="color" value={String(val)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)} className="size-8 shrink-0 rounded border" />
|
|
197
|
+
<Input value={String(val)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)} className="font-mono text-xs" />
|
|
199
198
|
</div>
|
|
200
199
|
) : prop.type === 'select' && prop.options ? (
|
|
201
200
|
<Select value={String(val)} onValueChange={setVal}>
|
|
@@ -207,7 +206,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
207
206
|
</SelectContent>
|
|
208
207
|
</Select>
|
|
209
208
|
) : (
|
|
210
|
-
<Input value={String(val ?? '')} onChange={(e) => setVal(e.target.value)} />
|
|
209
|
+
<Input value={String(val ?? '')} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)} />
|
|
211
210
|
)}
|
|
212
211
|
</div>
|
|
213
212
|
)
|
|
@@ -221,13 +220,13 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
221
220
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
222
221
|
<DialogContent className="sm:max-w-4xl">
|
|
223
222
|
<DialogHeader>
|
|
224
|
-
<DialogTitle>{isEdit ?
|
|
223
|
+
<DialogTitle>{isEdit ? m.common_component_form_edit() : m.common_component_form_create()}</DialogTitle>
|
|
225
224
|
</DialogHeader>
|
|
226
225
|
|
|
227
226
|
<div className="flex min-h-[300px] gap-4">
|
|
228
227
|
{/* 左侧预览区 */}
|
|
229
228
|
<div className="flex w-60 shrink-0 flex-col gap-1.5">
|
|
230
|
-
<Label className="text-muted-foreground">{
|
|
229
|
+
<Label className="text-muted-foreground">{m.common_component_form_preview()}</Label>
|
|
231
230
|
<div className="flex flex-1 items-center justify-center overflow-hidden rounded-lg border bg-muted/30">
|
|
232
231
|
{renderPreview()}
|
|
233
232
|
</div>
|
|
@@ -242,94 +241,94 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
242
241
|
{/* 右侧表单 */}
|
|
243
242
|
<div className="grid flex-1 grid-cols-2 gap-x-4 gap-y-3">
|
|
244
243
|
<div className="col-span-2 space-y-1.5">
|
|
245
|
-
<Label>{
|
|
246
|
-
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder={
|
|
244
|
+
<Label>{m.common_component_form_name()}</Label>
|
|
245
|
+
<Input value={name} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)} placeholder={m.common_component_form_namePlaceholder()} />
|
|
247
246
|
</div>
|
|
248
247
|
|
|
249
248
|
<div className="col-span-2 space-y-1.5">
|
|
250
|
-
<Label>{
|
|
249
|
+
<Label>{m.common_component_form_description()}</Label>
|
|
251
250
|
<Textarea
|
|
252
251
|
value={description}
|
|
253
|
-
onChange={(e) => setDescription(e.target.value)}
|
|
254
|
-
placeholder={
|
|
252
|
+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
|
|
253
|
+
placeholder={m.common_component_form_descriptionPlaceholder()}
|
|
255
254
|
rows={2}
|
|
256
255
|
/>
|
|
257
256
|
</div>
|
|
258
257
|
|
|
259
258
|
<div className="space-y-1.5">
|
|
260
|
-
<Label>{
|
|
259
|
+
<Label>{m.common_component_form_category()}</Label>
|
|
261
260
|
<Select value={category} onValueChange={setCategory}>
|
|
262
261
|
<SelectTrigger className="w-full">
|
|
263
262
|
<SelectValue />
|
|
264
263
|
</SelectTrigger>
|
|
265
264
|
<SelectContent>
|
|
266
|
-
<SelectItem key="base" value="base">{
|
|
267
|
-
<SelectItem key="block" value="block">{
|
|
268
|
-
<SelectItem key="template" value="template">{
|
|
265
|
+
<SelectItem key="base" value="base">{m.common_component_categories_base()}</SelectItem>
|
|
266
|
+
<SelectItem key="block" value="block">{m.common_component_categories_block()}</SelectItem>
|
|
267
|
+
<SelectItem key="template" value="template">{m.common_component_categories_template()}</SelectItem>
|
|
269
268
|
</SelectContent>
|
|
270
269
|
</Select>
|
|
271
270
|
</div>
|
|
272
271
|
|
|
273
272
|
<div className="space-y-1.5">
|
|
274
|
-
<Label>{
|
|
273
|
+
<Label>{m.common_component_form_type()}</Label>
|
|
275
274
|
<Select value={type} onValueChange={setType}>
|
|
276
275
|
<SelectTrigger className="w-full">
|
|
277
276
|
<SelectValue />
|
|
278
277
|
</SelectTrigger>
|
|
279
278
|
<SelectContent>
|
|
280
|
-
<SelectItem key="component" value="component">{
|
|
281
|
-
<SelectItem key="block" value="block">{
|
|
282
|
-
<SelectItem key="template" value="template">{
|
|
279
|
+
<SelectItem key="component" value="component">{m.common_component_types_base()}</SelectItem>
|
|
280
|
+
<SelectItem key="block" value="block">{m.common_component_types_block()}</SelectItem>
|
|
281
|
+
<SelectItem key="template" value="template">{m.common_component_types_template()}</SelectItem>
|
|
283
282
|
</SelectContent>
|
|
284
283
|
</Select>
|
|
285
284
|
</div>
|
|
286
285
|
|
|
287
286
|
<div className="space-y-1.5">
|
|
288
|
-
<Label>{
|
|
289
|
-
<Input value={subCategory} onChange={(e) => setSubCategory(e.target.value)} placeholder={
|
|
287
|
+
<Label>{m.common_component_form_subCategory()}</Label>
|
|
288
|
+
<Input value={subCategory} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSubCategory(e.target.value)} placeholder={m.common_component_form_subCategoryPlaceholder()} />
|
|
290
289
|
</div>
|
|
291
290
|
|
|
292
291
|
<div className="space-y-1.5">
|
|
293
|
-
<Label>{
|
|
294
|
-
<Input value={tags} onChange={(e) => setTags(e.target.value)} placeholder={
|
|
292
|
+
<Label>{m.common_component_form_tags()}</Label>
|
|
293
|
+
<Input value={tags} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTags(e.target.value)} placeholder={m.common_component_form_tagsPlaceholder()} />
|
|
295
294
|
</div>
|
|
296
295
|
|
|
297
296
|
<div className="col-span-2 space-y-1.5">
|
|
298
|
-
<Label>{
|
|
299
|
-
<Input value={path} onChange={(e) => setPath(e.target.value)} placeholder={
|
|
300
|
-
<p className="text-[10px] text-muted-foreground">{
|
|
297
|
+
<Label>{m.common_component_form_path()}</Label>
|
|
298
|
+
<Input value={path} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPath(e.target.value)} placeholder={m.common_component_form_pathPlaceholder()} className="font-mono text-xs" />
|
|
299
|
+
<p className="text-[10px] text-muted-foreground">{m.common_component_form_pathHint()}</p>
|
|
301
300
|
</div>
|
|
302
301
|
|
|
303
302
|
<div className="space-y-1.5">
|
|
304
|
-
<Label>{
|
|
303
|
+
<Label>{m.common_component_form_defaultWidth()}</Label>
|
|
305
304
|
<Input
|
|
306
305
|
type="number"
|
|
307
306
|
value={defaultWidth}
|
|
308
|
-
onChange={(e) => setDefaultWidth(Number(e.target.value))}
|
|
307
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDefaultWidth(Number(e.target.value))}
|
|
309
308
|
/>
|
|
310
309
|
</div>
|
|
311
310
|
|
|
312
311
|
<div className="space-y-1.5">
|
|
313
|
-
<Label>{
|
|
312
|
+
<Label>{m.common_component_form_defaultHeight()}</Label>
|
|
314
313
|
<Input
|
|
315
314
|
type="number"
|
|
316
315
|
value={defaultHeight}
|
|
317
|
-
onChange={(e) => setDefaultHeight(Number(e.target.value))}
|
|
316
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDefaultHeight(Number(e.target.value))}
|
|
318
317
|
/>
|
|
319
318
|
</div>
|
|
320
319
|
|
|
321
320
|
<div className="space-y-1.5">
|
|
322
|
-
<Label>{
|
|
321
|
+
<Label>{m.common_component_form_sortOrder()}</Label>
|
|
323
322
|
<Input
|
|
324
323
|
type="number"
|
|
325
324
|
value={sortOrder}
|
|
326
|
-
onChange={(e) => setSortOrder(Number(e.target.value))}
|
|
325
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSortOrder(Number(e.target.value))}
|
|
327
326
|
/>
|
|
328
327
|
</div>
|
|
329
328
|
|
|
330
329
|
<div className="flex items-center gap-2 pt-6">
|
|
331
330
|
<Switch checked={isHidden} onCheckedChange={setIsHidden} />
|
|
332
|
-
<Label className="cursor-pointer" onClick={() => setIsHidden(!isHidden)}>{
|
|
331
|
+
<Label className="cursor-pointer" onClick={() => setIsHidden(!isHidden)}>{m.common_component_form_hidden()}</Label>
|
|
333
332
|
</div>
|
|
334
333
|
|
|
335
334
|
{renderPropsEditor()}
|
|
@@ -337,9 +336,9 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
337
336
|
</div>
|
|
338
337
|
|
|
339
338
|
<DialogFooter>
|
|
340
|
-
<Button variant="outline" onClick={() => onOpenChange(false)}>{
|
|
339
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>{m.common_common_cancel()}</Button>
|
|
341
340
|
<Button onClick={handleSave} disabled={saving}>
|
|
342
|
-
{saving ?
|
|
341
|
+
{saving ? m.common_status_saving() : m.common_button_save()}
|
|
343
342
|
</Button>
|
|
344
343
|
</DialogFooter>
|
|
345
344
|
</DialogContent>
|
|
@@ -144,7 +144,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
|
|
|
144
144
|
<div className="flex gap-2">
|
|
145
145
|
<Input
|
|
146
146
|
value={packageName}
|
|
147
|
-
onChange={(e) => setPackageName(e.target.value)}
|
|
147
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPackageName(e.target.value)}
|
|
148
148
|
placeholder="如:lucide-react"
|
|
149
149
|
className="font-mono text-xs"
|
|
150
150
|
/>
|
|
@@ -160,7 +160,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
|
|
|
160
160
|
<div className="flex gap-2">
|
|
161
161
|
<Input
|
|
162
162
|
value={importPath}
|
|
163
|
-
onChange={(e) => setImportPath(e.target.value)}
|
|
163
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setImportPath(e.target.value)}
|
|
164
164
|
placeholder="@magicui/safari 或 @/components/magicui/safari"
|
|
165
165
|
className="font-mono text-xs"
|
|
166
166
|
/>
|
|
@@ -180,7 +180,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
|
|
|
180
180
|
<Label>导出名</Label>
|
|
181
181
|
<Input
|
|
182
182
|
value={exportName}
|
|
183
|
-
onChange={(e) => setExportName(e.target.value)}
|
|
183
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setExportName(e.target.value)}
|
|
184
184
|
placeholder="default 或具名"
|
|
185
185
|
className="font-mono text-xs"
|
|
186
186
|
/>
|
|
@@ -189,7 +189,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
|
|
|
189
189
|
<Label>组件标识 *</Label>
|
|
190
190
|
<Input
|
|
191
191
|
value={componentKey}
|
|
192
|
-
onChange={(e) => setComponentKey(e.target.value)}
|
|
192
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setComponentKey(e.target.value)}
|
|
193
193
|
placeholder="如:my-button"
|
|
194
194
|
className="font-mono text-xs"
|
|
195
195
|
/>
|
|
@@ -201,7 +201,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
|
|
|
201
201
|
<Label>显示名称 *</Label>
|
|
202
202
|
<Input
|
|
203
203
|
value={displayName}
|
|
204
|
-
onChange={(e) => setDisplayName(e.target.value)}
|
|
204
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDisplayName(e.target.value)}
|
|
205
205
|
placeholder="如:我的按钮"
|
|
206
206
|
/>
|
|
207
207
|
</div>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { Edit, Trash2 } from 'lucide-react'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { Badge } from '@/components/ui/badge'
|
|
21
21
|
import { Button } from '@/components/ui/button'
|
|
22
22
|
import {
|
|
@@ -62,16 +62,15 @@ export function ComponentTable({
|
|
|
62
62
|
onEdit: (item: ComponentItem) => void
|
|
63
63
|
onDelete: (item: ComponentItem) => void
|
|
64
64
|
}) {
|
|
65
|
-
const { t } = useTranslation('common')
|
|
66
65
|
const categoryLabel = (k: string) => {
|
|
67
|
-
const map: Record<string, string> = { base:
|
|
66
|
+
const map: Record<string, string> = { base: m.common_component_categories_base(), block: m.common_component_categories_block(), template: m.common_component_categories_template() }
|
|
68
67
|
return map[k] ?? k
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
if (items.length === 0) {
|
|
72
71
|
return (
|
|
73
72
|
<div className="flex h-40 items-center justify-center rounded-lg border border-dashed">
|
|
74
|
-
<p className="text-sm text-muted-foreground">{
|
|
73
|
+
<p className="text-sm text-muted-foreground">{m.common_component_table_empty()}</p>
|
|
75
74
|
</div>
|
|
76
75
|
)
|
|
77
76
|
}
|
|
@@ -81,13 +80,13 @@ export function ComponentTable({
|
|
|
81
80
|
<Table>
|
|
82
81
|
<TableHeader>
|
|
83
82
|
<TableRow>
|
|
84
|
-
<TableHead className="w-[28%]">{
|
|
85
|
-
<TableHead>{
|
|
86
|
-
<TableHead>{
|
|
87
|
-
<TableHead>{
|
|
88
|
-
<TableHead>{
|
|
89
|
-
<TableHead>{
|
|
90
|
-
<TableHead className="text-right">{
|
|
83
|
+
<TableHead className="w-[28%]">{m.common_component_table_columns_component()}</TableHead>
|
|
84
|
+
<TableHead>{m.common_component_table_columns_category()}</TableHead>
|
|
85
|
+
<TableHead>{m.common_component_table_columns_type()}</TableHead>
|
|
86
|
+
<TableHead>{m.common_component_table_columns_tags()}</TableHead>
|
|
87
|
+
<TableHead>{m.common_component_table_columns_source()}</TableHead>
|
|
88
|
+
<TableHead>{m.common_component_table_columns_status()}</TableHead>
|
|
89
|
+
<TableHead className="text-right">{m.common_component_table_columns_actions()}</TableHead>
|
|
91
90
|
</TableRow>
|
|
92
91
|
</TableHeader>
|
|
93
92
|
<TableBody>
|
|
@@ -124,12 +123,12 @@ export function ComponentTable({
|
|
|
124
123
|
</TableCell>
|
|
125
124
|
<TableCell>
|
|
126
125
|
<span className={cn('text-xs', item.isHidden ? 'text-muted-foreground' : 'text-foreground')}>
|
|
127
|
-
{item.isHidden ?
|
|
126
|
+
{item.isHidden ? m.common_component_table_status_hidden() : m.common_component_table_status_visible()}
|
|
128
127
|
</span>
|
|
129
128
|
</TableCell>
|
|
130
129
|
<TableCell className="text-right">
|
|
131
130
|
<div className="flex items-center justify-end gap-1">
|
|
132
|
-
<Button variant="ghost" size="icon-sm" onClick={() => onEdit(item)} aria-label={
|
|
131
|
+
<Button variant="ghost" size="icon-sm" onClick={() => onEdit(item)} aria-label={m.common_common_edit()}>
|
|
133
132
|
<Edit className="size-4" />
|
|
134
133
|
</Button>
|
|
135
134
|
<Button
|
|
@@ -137,7 +136,7 @@ export function ComponentTable({
|
|
|
137
136
|
size="icon-sm"
|
|
138
137
|
onClick={() => onDelete(item)}
|
|
139
138
|
className="text-destructive hover:bg-destructive/10"
|
|
140
|
-
aria-label={
|
|
139
|
+
aria-label={m.common_common_delete()}
|
|
141
140
|
>
|
|
142
141
|
<Trash2 className="size-4" />
|
|
143
142
|
</Button>
|