mirror of
https://github.com/zuiidea/antd-admin
synced 2026-10-30 22:13:45 +00:00
feat(basic): remove Lingui bootstrap and English shell (batch A)
Made-with: Cursor
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
const config = {
|
||||
locales: ["en", "zh"],
|
||||
sourceLocale: "en",
|
||||
catalogs: [
|
||||
{
|
||||
path: "src/locales/{locale}/messages",
|
||||
include: ["src"],
|
||||
},
|
||||
],
|
||||
format: "po",
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Flex, theme } from "antd";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { BarChart3 } from "lucide-react";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
@@ -7,7 +6,6 @@ import type { ReactElement } from "react";
|
||||
* Table empty state: icon in soft tile + bold title + secondary description (dashboard-style).
|
||||
*/
|
||||
export function DataTableEmpty(): ReactElement {
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
return (
|
||||
@@ -46,7 +44,7 @@ export function DataTableEmpty(): ReactElement {
|
||||
lineHeight: token.lineHeightLG,
|
||||
}}
|
||||
>
|
||||
{t`No data`}
|
||||
No data
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
@@ -56,7 +54,7 @@ export function DataTableEmpty(): ReactElement {
|
||||
lineHeight: token.lineHeight,
|
||||
}}
|
||||
>
|
||||
{t`Nothing to show in this list yet`}
|
||||
Nothing to show in this list yet
|
||||
</span>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Flex, Typography, theme } from "antd";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { GitHub } from "@/components/Icon";
|
||||
|
||||
const ANTD_ADMIN_REPO = "https://github.com/zuiidea/antd-admin";
|
||||
|
||||
export function AppFooter() {
|
||||
const { token } = theme.useToken();
|
||||
const { t } = useLingui();
|
||||
const iconSize = Math.max(12, Math.round(Number(token.fontSizeSM)));
|
||||
|
||||
return (
|
||||
@@ -20,7 +18,7 @@ export function AppFooter() {
|
||||
}}
|
||||
>
|
||||
<Typography.Text type="secondary" style={{ fontSize: token.fontSizeSM, marginBottom: 0 }}>
|
||||
{t`Powered by`}
|
||||
Powered by
|
||||
</Typography.Text>
|
||||
<a
|
||||
href={ANTD_ADMIN_REPO}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Layout, Button, Space, theme, Breadcrumb, Flex, Divider, Grid } from "antd";
|
||||
import type { ItemType } from "antd/es/breadcrumb/Breadcrumb";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { useSettingsStore, type Locale } from "@/stores/settings";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
import { Link, useLocation, useMatches } from "@tanstack/react-router";
|
||||
import { Home, Languages, PanelLeft, ShieldAlert, Users } from "lucide-react";
|
||||
import { Home, PanelLeft, ShieldAlert, Users } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Theme } from "@/components/Icon";
|
||||
@@ -32,11 +31,8 @@ export type HeaderProps = {
|
||||
export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProps) {
|
||||
const toggleSidebar = useSettingsStore((s) => s.toggleSidebar);
|
||||
const toggleDarkMode = useSettingsStore((s) => s.toggleDarkMode);
|
||||
const locale = useSettingsStore((s) => s.locale);
|
||||
const setLocale = useSettingsStore((s) => s.setLocale);
|
||||
const location = useLocation();
|
||||
const matches = useMatches();
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
const screens = Grid.useBreakpoint();
|
||||
const isMobile = !screens.lg;
|
||||
@@ -74,14 +70,7 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
|
||||
const leafIcon: LucideIcon =
|
||||
firstSegmentPath === "/users" ? Users : firstSegmentPath === "/403" ? ShieldAlert : Home;
|
||||
|
||||
const leafLabel =
|
||||
leafLabelKey === "Dashboard"
|
||||
? t`Dashboard`
|
||||
: leafLabelKey === "Users"
|
||||
? t`Users`
|
||||
: leafLabelKey === "403"
|
||||
? t`403`
|
||||
: leafLabelKey;
|
||||
const leafLabel = leafLabelKey;
|
||||
|
||||
const breadcrumbItems: ItemType[] = [];
|
||||
|
||||
@@ -89,11 +78,11 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
|
||||
|
||||
if (onDashboard) {
|
||||
breadcrumbItems.push({
|
||||
title: crumb(Home, t`Dashboard`),
|
||||
title: crumb(Home, "Dashboard"),
|
||||
});
|
||||
} else {
|
||||
breadcrumbItems.push({
|
||||
title: crumb(Home, t`Dashboard`, "/dashboard"),
|
||||
title: crumb(Home, "Dashboard", "/dashboard"),
|
||||
});
|
||||
|
||||
breadcrumbItems.push({
|
||||
@@ -113,11 +102,6 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
|
||||
const showBreadcrumb =
|
||||
showBreadcrumbProp && !hideBreadcrumbFromRoute && breadcrumbItems.length > 0;
|
||||
|
||||
const toggleLocale = () => {
|
||||
const next: Locale = locale === "en" ? "zh" : "en";
|
||||
setLocale(next);
|
||||
};
|
||||
|
||||
return (
|
||||
<AntHeader
|
||||
style={{
|
||||
@@ -135,7 +119,7 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
|
||||
size="small"
|
||||
onClick={toggleSidebar}
|
||||
icon={<PanelLeft size={token.size} />}
|
||||
aria-label={t`Toggle sidebar`}
|
||||
aria-label="Toggle sidebar"
|
||||
/>
|
||||
) : null}
|
||||
{showBreadcrumb ? (
|
||||
@@ -146,17 +130,11 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
|
||||
) : null}
|
||||
</Flex>
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={toggleLocale}
|
||||
icon={<Languages size={token.size} />}
|
||||
aria-label={t`Switch language`}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={toggleDarkMode}
|
||||
icon={<Theme size={token.size} />}
|
||||
aria-label={t`Toggle Theme`}
|
||||
aria-label="Toggle Theme"
|
||||
/>
|
||||
</Space>
|
||||
</AntHeader>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Menu, Layout, theme, Flex, Grid, Drawer, Button } from "antd";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useLocation } from "@tanstack/react-router";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import {
|
||||
Book,
|
||||
Briefcase,
|
||||
@@ -26,14 +24,14 @@ import { UserMenu } from "../UserMenu";
|
||||
import "./index.css";
|
||||
|
||||
const { Sider } = Layout;
|
||||
/** API menu `name` → Lingui descriptors (extracted into catalogs). */
|
||||
const MENU_LABELS: Record<string, ReturnType<typeof msg>> = {
|
||||
Platform: msg`Platform`,
|
||||
Projects: msg`Projects`,
|
||||
Dashboard: msg`Dashboard`,
|
||||
Users: msg`Users`,
|
||||
"Design Engineering": msg`Design Engineering`,
|
||||
"Sales & Marketing": msg`Sales & Marketing`,
|
||||
/** API menu `name` → English labels for known keys; unknown keys pass through as `menu.name`. */
|
||||
const MENU_LABELS: Record<string, string> = {
|
||||
Platform: "Platform",
|
||||
Projects: "Projects",
|
||||
Dashboard: "Dashboard",
|
||||
Users: "Users",
|
||||
"Design Engineering": "Design Engineering",
|
||||
"Sales & Marketing": "Sales & Marketing",
|
||||
};
|
||||
|
||||
type AntMenuItem = Required<MenuProps>["items"][number];
|
||||
@@ -64,7 +62,6 @@ function renderMenuIcon(icon: string | null, size = 16) {
|
||||
|
||||
function buildMenuItems(
|
||||
menus: MenuItemType[],
|
||||
t: ReturnType<typeof useLingui>["t"],
|
||||
token: ReturnType<typeof theme.useToken>["token"],
|
||||
collapsed = false,
|
||||
iconSize = 16,
|
||||
@@ -76,11 +73,11 @@ function buildMenuItems(
|
||||
const items: AntMenuItem[] = [];
|
||||
|
||||
for (const menu of sorted) {
|
||||
const label = MENU_LABELS[menu.name] ? t(MENU_LABELS[menu.name]) : menu.name;
|
||||
const label = MENU_LABELS[menu.name] ?? menu.name;
|
||||
const key = menu.id;
|
||||
|
||||
if (menu.kind === "group") {
|
||||
const built = buildMenuItems(menu.children, t, token, collapsed, iconSize, parentKeys);
|
||||
const built = buildMenuItems(menu.children, token, collapsed, iconSize, parentKeys);
|
||||
Object.assign(keyToPath, built.keyToPath);
|
||||
Object.assign(pathToKeyChain, built.pathToKeyChain);
|
||||
if (built.items.length > 0) {
|
||||
@@ -131,7 +128,7 @@ function buildMenuItems(
|
||||
|
||||
let children: AntMenuItem[] | undefined;
|
||||
if (menu.children?.length) {
|
||||
const built = buildMenuItems(menu.children, t, token, collapsed, iconSize, nextParents);
|
||||
const built = buildMenuItems(menu.children, token, collapsed, iconSize, nextParents);
|
||||
Object.assign(keyToPath, built.keyToPath);
|
||||
Object.assign(pathToKeyChain, built.pathToKeyChain);
|
||||
children = built.items.length ? built.items : undefined;
|
||||
@@ -157,14 +154,13 @@ export function Sidebar() {
|
||||
const toggleSidebar = useSettingsStore((s) => s.toggleSidebar);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
const screens = Grid.useBreakpoint();
|
||||
const isMobile = !screens.lg;
|
||||
const mobileOpen = collapsed;
|
||||
const builtMenu = useMemo(
|
||||
() => buildMenuItems(menus, t, token, !isMobile && collapsed, token.size),
|
||||
[menus, t, token, isMobile, collapsed, token.size],
|
||||
() => buildMenuItems(menus, token, !isMobile && collapsed, token.size),
|
||||
[menus, token, isMobile, collapsed, token.size],
|
||||
);
|
||||
const { selectedKey, routeOpenKeys } = useMemo(() => {
|
||||
const chain = builtMenu.pathToKeyChain[location.pathname] ?? [];
|
||||
@@ -191,7 +187,7 @@ export function Sidebar() {
|
||||
const userMenuItems: MenuProps["items"] = [
|
||||
{
|
||||
key: "logout",
|
||||
label: t`Sign Out`,
|
||||
label: "Sign Out",
|
||||
onClick: () => {
|
||||
if (isMobile) {
|
||||
setSidebarCollapsed(false);
|
||||
@@ -265,7 +261,7 @@ export function Sidebar() {
|
||||
className="sidebar-collapsed-brand__toggle"
|
||||
onClick={toggleSidebar}
|
||||
icon={<PanelLeft size={token.size} />}
|
||||
aria-label={t`Toggle sidebar`}
|
||||
aria-label="Toggle sidebar"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -313,7 +309,7 @@ export function Sidebar() {
|
||||
size="small"
|
||||
onClick={toggleSidebar}
|
||||
icon={<PanelLeft size={token.size} />}
|
||||
aria-label={t`Toggle sidebar`}
|
||||
aria-label="Toggle sidebar"
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { ArrowLeft, Home, SearchX } from "lucide-react";
|
||||
import { Button, Flex, Result, Space, theme } from "antd";
|
||||
|
||||
/** Shared 404 UI for `/404` route and root `notFoundComponent`. */
|
||||
export function NotFound() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const goDashboard = () => {
|
||||
@@ -52,14 +50,14 @@ export function NotFound() {
|
||||
<SearchX size={64} strokeWidth={1.25} color={token.colorTextQuaternary} aria-hidden />
|
||||
}
|
||||
title="404"
|
||||
subTitle={t`Sorry, the page you visited does not exist.`}
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
extra={
|
||||
<Space wrap size="middle">
|
||||
<Button type="primary" icon={<Home size={16} aria-hidden />} onClick={goDashboard}>
|
||||
{t`Back to Home`}
|
||||
Back to Home
|
||||
</Button>
|
||||
<Button icon={<ArrowLeft size={16} aria-hidden />} onClick={goBack}>
|
||||
{t`Go back`}
|
||||
Go back
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
declare module "*.po" {
|
||||
import type { Messages } from "@lingui/core";
|
||||
export const messages: Messages;
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2026-04-07 15:30+0800\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: en\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:464
|
||||
#~ msgid "{n} rows"
|
||||
#~ msgstr "{n} rows"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:386
|
||||
msgid "{total} rows"
|
||||
msgstr "{total} rows"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:35
|
||||
msgid "+180.1% from last month"
|
||||
msgstr "+180.1% from last month"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:41
|
||||
msgid "+19% from last month"
|
||||
msgstr "+19% from last month"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:29
|
||||
msgid "+20.1% from last month"
|
||||
msgstr "+20.1% from last month"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:47
|
||||
msgid "+201 since last hour"
|
||||
msgstr "+201 since last hour"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:78
|
||||
msgid "403"
|
||||
msgstr "403"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:45
|
||||
msgid "Active Now"
|
||||
msgstr "Active Now"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:58
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:64
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:164
|
||||
msgid "Are you absolutely sure?"
|
||||
msgstr "Are you absolutely sure?"
|
||||
|
||||
#: src/routes/login/index.tsx:185
|
||||
msgid "Auto login"
|
||||
msgstr "Auto login"
|
||||
|
||||
#: src/components/NotFound/index.tsx:54
|
||||
#: src/routes/_auth/403/index.tsx:40
|
||||
msgid "Back to Home"
|
||||
msgstr "Back to Home"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:32
|
||||
#: src/routes/_auth/users/index.tsx:168
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:134
|
||||
msgid "Chart Placeholder"
|
||||
msgstr "Chart Placeholder"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:106
|
||||
msgid "Create failed"
|
||||
msgstr "Create failed"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:89
|
||||
msgid "Create User"
|
||||
msgstr "Create User"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:100
|
||||
msgid "Created successfully"
|
||||
msgstr "Created successfully"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:74
|
||||
#: src/components/Layout/Header/index.tsx:87
|
||||
#: src/components/Layout/Header/index.tsx:91
|
||||
#: src/components/Layout/Sidebar/index.tsx:32
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:166
|
||||
#: src/routes/_auth/users/index.tsx:257
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:156
|
||||
msgid "Delete failed"
|
||||
msgstr "Delete failed"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:149
|
||||
msgid "Deleted successfully"
|
||||
msgstr "Deleted successfully"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:142
|
||||
msgid "Deleting…"
|
||||
msgstr "Deleting…"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:35
|
||||
msgid "Design Engineering"
|
||||
msgstr "Design Engineering"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:247
|
||||
msgid "Edit"
|
||||
msgstr "Edit"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:29
|
||||
msgid "Edit User"
|
||||
msgstr "Edit User"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:59
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:65
|
||||
msgid "Editor"
|
||||
msgstr "Editor"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:63
|
||||
#: src/routes/_auth/users/index.tsx:202
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: src/routes/_auth/users.tsx:353
|
||||
#~ msgid "Filter by role"
|
||||
#~ msgstr "Filter by role"
|
||||
|
||||
#: src/routes/login/index.tsx:192
|
||||
msgid "Forgot password?"
|
||||
msgstr "Forgot password?"
|
||||
|
||||
#: src/components/NotFound/index.tsx:57
|
||||
msgid "Go back"
|
||||
msgstr "Go back"
|
||||
|
||||
#: src/routes/login/index.tsx:68
|
||||
msgid "Login failed"
|
||||
msgstr "Login failed"
|
||||
|
||||
#: src/routes/login/index.tsx:64
|
||||
msgid "Login successful"
|
||||
msgstr "Login successful"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:92
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:93
|
||||
msgid "More filters"
|
||||
msgstr "More filters"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:29
|
||||
msgid "New User"
|
||||
msgstr "New User"
|
||||
|
||||
#: src/components/DataTable/DataTableEmpty.tsx:49
|
||||
msgid "No data"
|
||||
msgstr "No data"
|
||||
|
||||
#: src/components/DataTable/DataTableEmpty.tsx:59
|
||||
msgid "Nothing to show in this list yet"
|
||||
msgstr "Nothing to show in this list yet"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:31
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:115
|
||||
msgid "Overview"
|
||||
msgstr "Overview"
|
||||
|
||||
#: src/routes/404/index.tsx:87
|
||||
#~ msgid "Page not found"
|
||||
#~ msgstr "Page not found"
|
||||
|
||||
#: src/routes/login/index.tsx:171
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:30
|
||||
msgid "Platform"
|
||||
msgstr "Platform"
|
||||
|
||||
#: src/routes/login/index.tsx:172
|
||||
msgid "Please enter password"
|
||||
msgstr "Please enter password"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:46
|
||||
#: src/routes/login/index.tsx:164
|
||||
msgid "Please enter username"
|
||||
msgstr "Please enter username"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:53
|
||||
msgid "Please select roles"
|
||||
msgstr "Please select roles"
|
||||
|
||||
#: src/components/Layout/AppFooter/index.tsx:36
|
||||
msgid "Powered by"
|
||||
msgstr "Powered by"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:31
|
||||
msgid "Projects"
|
||||
msgstr "Projects"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:142
|
||||
msgid "Recent Sales"
|
||||
msgstr "Recent Sales"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:58
|
||||
msgid "Role"
|
||||
msgstr "Role"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:52
|
||||
#: src/routes/_auth/users/index.tsx:209
|
||||
msgid "Roles"
|
||||
msgstr "Roles"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:39
|
||||
msgid "Sales"
|
||||
msgstr "Sales"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:36
|
||||
msgid "Sales & Marketing"
|
||||
msgstr "Sales & Marketing"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:43
|
||||
msgid "Search User"
|
||||
msgstr "Search User"
|
||||
|
||||
#: src/routes/_auth/users.tsx:341
|
||||
#~ msgid "Search username or email"
|
||||
#~ msgstr "Search username or email"
|
||||
|
||||
#: src/routes/login/index.tsx:204
|
||||
msgid "Sign In"
|
||||
msgstr "Sign In"
|
||||
|
||||
#: src/routes/login.tsx:100
|
||||
#~ msgid "Sign in with your credentials."
|
||||
#~ msgstr "Sign in with your credentials."
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:194
|
||||
msgid "Sign Out"
|
||||
msgstr "Sign Out"
|
||||
|
||||
#: src/components/NotFound/index.tsx:50
|
||||
msgid "Sorry, the page you visited does not exist."
|
||||
msgstr "Sorry, the page you visited does not exist."
|
||||
|
||||
#: src/routes/_auth/403/index.tsx:37
|
||||
msgid "Sorry, you don't have permission to access this page."
|
||||
msgstr "Sorry, you don't have permission to access this page."
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:33
|
||||
msgid "Subscriptions"
|
||||
msgstr "Subscriptions"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:145
|
||||
#: src/routes/login/index.tsx:225
|
||||
msgid "Switch language"
|
||||
msgstr "Switch language"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:165
|
||||
msgid "This action cannot be undone. This will permanently delete the user."
|
||||
msgstr "This action cannot be undone. This will permanently delete the user."
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:133
|
||||
#: src/components/Layout/Sidebar/index.tsx:263
|
||||
#: src/components/Layout/Sidebar/index.tsx:311
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "Toggle sidebar"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:151
|
||||
#: src/routes/login/index.tsx:232
|
||||
msgid "Toggle Theme"
|
||||
msgstr "Toggle Theme"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:27
|
||||
msgid "Total Revenue"
|
||||
msgstr "Total Revenue"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:132
|
||||
msgid "Update failed"
|
||||
msgstr "Update failed"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:122
|
||||
msgid "Updated successfully"
|
||||
msgstr "Updated successfully"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:115
|
||||
msgid "Updating…"
|
||||
msgstr "Updating…"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:34
|
||||
msgid "User List"
|
||||
msgstr "User List"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:45
|
||||
#: src/routes/_auth/users/index.tsx:182
|
||||
#: src/routes/login/index.tsx:163
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:76
|
||||
#: src/components/Layout/Sidebar/index.tsx:33
|
||||
msgid "Users"
|
||||
msgstr "Users"
|
||||
|
||||
#: src/routes/login.tsx:101
|
||||
#~ msgid "Welcome back"
|
||||
#~ msgstr "Welcome back"
|
||||
@@ -1,317 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2026-04-07 15:30+0800\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: zh\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:464
|
||||
#~ msgid "{n} rows"
|
||||
#~ msgstr "{n} 行"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:386
|
||||
msgid "{total} rows"
|
||||
msgstr "共 {total} 条"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:35
|
||||
msgid "+180.1% from last month"
|
||||
msgstr "较上月 +180.1%"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:41
|
||||
msgid "+19% from last month"
|
||||
msgstr "较上月 +19%"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:29
|
||||
msgid "+20.1% from last month"
|
||||
msgstr "较上月 +20.1%"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:47
|
||||
msgid "+201 since last hour"
|
||||
msgstr "较上一小时 +201"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:78
|
||||
msgid "403"
|
||||
msgstr "403"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:45
|
||||
msgid "Active Now"
|
||||
msgstr "当前在线"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:58
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:64
|
||||
msgid "Admin"
|
||||
msgstr "管理员"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:164
|
||||
msgid "Are you absolutely sure?"
|
||||
msgstr "确定要执行此操作吗?"
|
||||
|
||||
#: src/routes/login/index.tsx:185
|
||||
msgid "Auto login"
|
||||
msgstr "自动登录"
|
||||
|
||||
#: src/components/NotFound/index.tsx:54
|
||||
#: src/routes/_auth/403/index.tsx:40
|
||||
msgid "Back to Home"
|
||||
msgstr "返回首页"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:32
|
||||
#: src/routes/_auth/users/index.tsx:168
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:134
|
||||
msgid "Chart Placeholder"
|
||||
msgstr "图表占位"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:106
|
||||
msgid "Create failed"
|
||||
msgstr "创建失败"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:89
|
||||
msgid "Create User"
|
||||
msgstr "创建用户"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:100
|
||||
msgid "Created successfully"
|
||||
msgstr "创建成功"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:74
|
||||
#: src/components/Layout/Header/index.tsx:87
|
||||
#: src/components/Layout/Header/index.tsx:91
|
||||
#: src/components/Layout/Sidebar/index.tsx:32
|
||||
msgid "Dashboard"
|
||||
msgstr "仪表盘"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:166
|
||||
#: src/routes/_auth/users/index.tsx:257
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:156
|
||||
msgid "Delete failed"
|
||||
msgstr "删除失败"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:149
|
||||
msgid "Deleted successfully"
|
||||
msgstr "删除成功"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:142
|
||||
msgid "Deleting…"
|
||||
msgstr "正在删除…"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:35
|
||||
msgid "Design Engineering"
|
||||
msgstr "设计工程"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:247
|
||||
msgid "Edit"
|
||||
msgstr "编辑"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:29
|
||||
msgid "Edit User"
|
||||
msgstr "编辑用户"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:59
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:65
|
||||
msgid "Editor"
|
||||
msgstr "编辑者"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:63
|
||||
#: src/routes/_auth/users/index.tsx:202
|
||||
msgid "Email"
|
||||
msgstr "邮箱"
|
||||
|
||||
#: src/routes/_auth/users.tsx:353
|
||||
#~ msgid "Filter by role"
|
||||
#~ msgstr "按角色筛选"
|
||||
|
||||
#: src/routes/login/index.tsx:192
|
||||
msgid "Forgot password?"
|
||||
msgstr "忘记密码?"
|
||||
|
||||
#: src/components/NotFound/index.tsx:57
|
||||
msgid "Go back"
|
||||
msgstr "返回上一页"
|
||||
|
||||
#: src/routes/login/index.tsx:68
|
||||
msgid "Login failed"
|
||||
msgstr "登录失败"
|
||||
|
||||
#: src/routes/login/index.tsx:64
|
||||
msgid "Login successful"
|
||||
msgstr "登录成功"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:92
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:93
|
||||
msgid "More filters"
|
||||
msgstr "更多筛选"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:29
|
||||
msgid "New User"
|
||||
msgstr "新建用户"
|
||||
|
||||
#: src/components/DataTable/DataTableEmpty.tsx:49
|
||||
msgid "No data"
|
||||
msgstr "暂无数据"
|
||||
|
||||
#: src/components/DataTable/DataTableEmpty.tsx:59
|
||||
msgid "Nothing to show in this list yet"
|
||||
msgstr "当前列表为空,暂无可展示的记录"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:31
|
||||
msgid "OK"
|
||||
msgstr "确定"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:115
|
||||
msgid "Overview"
|
||||
msgstr "概览"
|
||||
|
||||
#: src/routes/404/index.tsx:87
|
||||
#~ msgid "Page not found"
|
||||
#~ msgstr "页面不存在"
|
||||
|
||||
#: src/routes/login/index.tsx:171
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:30
|
||||
msgid "Platform"
|
||||
msgstr "平台"
|
||||
|
||||
#: src/routes/login/index.tsx:172
|
||||
msgid "Please enter password"
|
||||
msgstr "请输入密码"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:46
|
||||
#: src/routes/login/index.tsx:164
|
||||
msgid "Please enter username"
|
||||
msgstr "请输入用户名"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:53
|
||||
msgid "Please select roles"
|
||||
msgstr "请选择角色"
|
||||
|
||||
#: src/components/Layout/AppFooter/index.tsx:36
|
||||
msgid "Powered by"
|
||||
msgstr "由"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:31
|
||||
msgid "Projects"
|
||||
msgstr "项目"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:142
|
||||
msgid "Recent Sales"
|
||||
msgstr "近期销售"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:58
|
||||
msgid "Role"
|
||||
msgstr "角色"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:52
|
||||
#: src/routes/_auth/users/index.tsx:209
|
||||
msgid "Roles"
|
||||
msgstr "角色"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:39
|
||||
msgid "Sales"
|
||||
msgstr "销售额"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:36
|
||||
msgid "Sales & Marketing"
|
||||
msgstr "销售与市场"
|
||||
|
||||
#: src/routes/_auth/users/-Toolbar.tsx:43
|
||||
msgid "Search User"
|
||||
msgstr "搜索用户"
|
||||
|
||||
#: src/routes/_auth/users.tsx:341
|
||||
#~ msgid "Search username or email"
|
||||
#~ msgstr "搜索用户名或邮箱"
|
||||
|
||||
#: src/routes/login/index.tsx:204
|
||||
msgid "Sign In"
|
||||
msgstr "登录"
|
||||
|
||||
#: src/routes/login.tsx:100
|
||||
#~ msgid "Sign in with your credentials."
|
||||
#~ msgstr "凭用户名与密码登录"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:194
|
||||
msgid "Sign Out"
|
||||
msgstr "退出登录"
|
||||
|
||||
#: src/components/NotFound/index.tsx:50
|
||||
msgid "Sorry, the page you visited does not exist."
|
||||
msgstr "抱歉,您访问的页面不存在。"
|
||||
|
||||
#: src/routes/_auth/403/index.tsx:37
|
||||
msgid "Sorry, you don't have permission to access this page."
|
||||
msgstr "抱歉,您没有权限访问此页面。"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:33
|
||||
msgid "Subscriptions"
|
||||
msgstr "订阅"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:145
|
||||
#: src/routes/login/index.tsx:225
|
||||
msgid "Switch language"
|
||||
msgstr "切换语言"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:165
|
||||
msgid "This action cannot be undone. This will permanently delete the user."
|
||||
msgstr "此操作无法撤销,将永久删除该用户。"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:133
|
||||
#: src/components/Layout/Sidebar/index.tsx:263
|
||||
#: src/components/Layout/Sidebar/index.tsx:311
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "折叠侧栏"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:151
|
||||
#: src/routes/login/index.tsx:232
|
||||
msgid "Toggle Theme"
|
||||
msgstr "切换主题"
|
||||
|
||||
#: src/routes/_auth/dashboard/index.tsx:27
|
||||
msgid "Total Revenue"
|
||||
msgstr "总收入"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:132
|
||||
msgid "Update failed"
|
||||
msgstr "更新失败"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:122
|
||||
msgid "Updated successfully"
|
||||
msgstr "更新成功"
|
||||
|
||||
#: src/routes/_auth/users/index.tsx:115
|
||||
msgid "Updating…"
|
||||
msgstr "正在更新…"
|
||||
|
||||
#: src/components/Layout/Sidebar/index.tsx:34
|
||||
msgid "User List"
|
||||
msgstr "用户列表"
|
||||
|
||||
#: src/routes/_auth/users/-FormModal.tsx:45
|
||||
#: src/routes/_auth/users/index.tsx:182
|
||||
#: src/routes/login/index.tsx:163
|
||||
msgid "Username"
|
||||
msgstr "用户名"
|
||||
|
||||
#: src/components/Layout/Header/index.tsx:76
|
||||
#: src/components/Layout/Sidebar/index.tsx:33
|
||||
msgid "Users"
|
||||
msgstr "用户"
|
||||
|
||||
#: src/routes/login.tsx:101
|
||||
#~ msgid "Welcome back"
|
||||
#~ msgstr "欢迎回来"
|
||||
@@ -2,17 +2,9 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import { i18n } from "@lingui/core";
|
||||
import { messages as enMessages } from "./locales/en/messages.po";
|
||||
import { messages as zhMessages } from "./locales/zh/messages.po";
|
||||
import { useSettingsStore } from "./stores/settings";
|
||||
import { useAuthStore } from "./stores/auth";
|
||||
import { fetchSessionAndApplyToStore } from "./utils/session";
|
||||
|
||||
i18n.load("en", enMessages);
|
||||
i18n.load("zh", zhMessages);
|
||||
i18n.activate(useSettingsStore.getState().locale);
|
||||
|
||||
const router = createRouter({ routeTree });
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useLayoutEffect } from "react";
|
||||
import { createRootRoute, Outlet } from "@tanstack/react-router";
|
||||
import { ConfigProvider, App } from "antd";
|
||||
import enUS from "antd/locale/en_US";
|
||||
import zhCN from "antd/locale/zh_CN";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { i18n } from "@lingui/core";
|
||||
import { I18nProvider } from "@lingui/react";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
import { useAppTheme } from "@/hooks/useAppTheme";
|
||||
import { NotFound } from "@/components/NotFound";
|
||||
import "@/index.css";
|
||||
|
||||
const antdLocaleMap = { en: enUS, zh: zhCN } as const;
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: 1, refetchOnWindowFocus: false },
|
||||
@@ -20,14 +15,12 @@ const queryClient = new QueryClient({
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
const locale = useSettingsStore((s) => s.locale);
|
||||
const darkMode = useSettingsStore((s) => s.darkMode);
|
||||
const configProviderProps = useAppTheme();
|
||||
|
||||
useEffect(() => {
|
||||
i18n.activate(locale);
|
||||
document.documentElement.lang = locale === "zh" ? "zh" : "en";
|
||||
}, [locale]);
|
||||
useLayoutEffect(() => {
|
||||
document.documentElement.lang = "en";
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", darkMode ? "dark" : "light");
|
||||
@@ -35,13 +28,11 @@ function RootComponent() {
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<I18nProvider i18n={i18n}>
|
||||
<ConfigProvider {...configProviderProps} locale={antdLocaleMap[locale]}>
|
||||
<App>
|
||||
<Outlet />
|
||||
</App>
|
||||
</ConfigProvider>
|
||||
</I18nProvider>
|
||||
<ConfigProvider {...configProviderProps} locale={enUS}>
|
||||
<App>
|
||||
<Outlet />
|
||||
</App>
|
||||
</ConfigProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { Home, ShieldAlert } from "lucide-react";
|
||||
import { Button, Flex, Result, theme } from "antd";
|
||||
|
||||
@@ -9,7 +8,6 @@ export const Route = createFileRoute("/_auth/403/")({
|
||||
|
||||
function ForbiddenPage() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const goDashboard = () => {
|
||||
@@ -29,10 +27,10 @@ function ForbiddenPage() {
|
||||
<ShieldAlert size={64} strokeWidth={1.25} color={token.colorTextQuaternary} aria-hidden />
|
||||
}
|
||||
title="403"
|
||||
subTitle={t`Sorry, you don't have permission to access this page.`}
|
||||
subTitle="Sorry, you don't have permission to access this page."
|
||||
extra={
|
||||
<Button type="primary" icon={<Home size={16} aria-hidden />} onClick={goDashboard}>
|
||||
{t`Back to Home`}
|
||||
Back to Home
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useMemo } from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Card, Col, Row, Typography, Avatar, theme, Flex, Skeleton } from "antd";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { DollarSign, Users, CreditCard, Activity } from "lucide-react";
|
||||
import "./index.css";
|
||||
|
||||
@@ -115,7 +114,6 @@ function DashboardSkeleton() {
|
||||
}
|
||||
|
||||
function DashboardPage() {
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
const { isPending } = useQuery({
|
||||
queryKey: ["dashboard"],
|
||||
@@ -133,31 +131,31 @@ function DashboardPage() {
|
||||
const stats = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t`Total Revenue`,
|
||||
title: "Total Revenue",
|
||||
value: "$45,231.89",
|
||||
description: t`+20.1% from last month`,
|
||||
description: "+20.1% from last month",
|
||||
icon: <DollarSign style={{ color: token.colorTextSecondary }} />,
|
||||
},
|
||||
{
|
||||
title: t`Subscriptions`,
|
||||
title: "Subscriptions",
|
||||
value: "+2350",
|
||||
description: t`+180.1% from last month`,
|
||||
description: "+180.1% from last month",
|
||||
icon: <Users style={{ color: token.colorTextSecondary }} />,
|
||||
},
|
||||
{
|
||||
title: t`Sales`,
|
||||
title: "Sales",
|
||||
value: "+12,234",
|
||||
description: t`+19% from last month`,
|
||||
description: "+19% from last month",
|
||||
icon: <CreditCard style={{ color: token.colorTextSecondary }} />,
|
||||
},
|
||||
{
|
||||
title: t`Active Now`,
|
||||
title: "Active Now",
|
||||
value: "+573",
|
||||
description: t`+201 since last hour`,
|
||||
description: "+201 since last hour",
|
||||
icon: <Activity style={{ color: token.colorTextSecondary }} />,
|
||||
},
|
||||
],
|
||||
[t, token.colorTextSecondary],
|
||||
[token.colorTextSecondary],
|
||||
);
|
||||
|
||||
const recentSales = useMemo(
|
||||
@@ -230,7 +228,7 @@ function DashboardPage() {
|
||||
<Card
|
||||
className="dash-card-interactive"
|
||||
style={{ ...cardHoverStyle, height: "100%" }}
|
||||
title={<Title level={5} style={{ margin: 0 }}>{t`Overview`}</Title>}
|
||||
title={<Title level={5} style={{ margin: 0 }}>Overview</Title>}
|
||||
>
|
||||
<Flex
|
||||
className="dash-chart-placeholder"
|
||||
@@ -247,7 +245,7 @@ function DashboardPage() {
|
||||
}}
|
||||
>
|
||||
<Text type="secondary" style={{ fontSize: token.fontSizeSM }}>
|
||||
{t`Chart Placeholder`}
|
||||
Chart Placeholder
|
||||
</Text>
|
||||
</Flex>
|
||||
</Card>
|
||||
@@ -256,7 +254,7 @@ function DashboardPage() {
|
||||
<Card
|
||||
className="dash-card-interactive"
|
||||
style={{ ...cardHoverStyle, height: "100%" }}
|
||||
title={<Title level={5} style={{ margin: 0 }}>{t`Recent Sales`}</Title>}
|
||||
title={<Title level={5} style={{ margin: 0 }}>Recent Sales</Title>}
|
||||
>
|
||||
<Flex
|
||||
vertical
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Form, Input, Select } from "antd";
|
||||
import type { FormInstance } from "antd/es/form";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import type { CreateUserRequest, User } from "@/api/schemas";
|
||||
import { BaseFormModal } from "@/components/FormModal";
|
||||
|
||||
@@ -21,14 +20,12 @@ export function FormModal({
|
||||
onCancel,
|
||||
onFinish,
|
||||
}: FormModalProps) {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<BaseFormModal<CreateUserRequest>
|
||||
open={open}
|
||||
title={editingUser ? t`Edit User` : t`New User`}
|
||||
okText={t`OK`}
|
||||
cancelText={t`Cancel`}
|
||||
title={editingUser ? "Edit User" : "New User"}
|
||||
okText="OK"
|
||||
cancelText="Cancel"
|
||||
form={form}
|
||||
confirmLoading={confirmLoading}
|
||||
onCancel={onCancel}
|
||||
@@ -36,25 +33,25 @@ export function FormModal({
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t`Username`}
|
||||
rules={[{ required: true, message: t`Please enter username` }]}
|
||||
label="Username"
|
||||
rules={[{ required: true, message: "Please enter username" }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="roles"
|
||||
label={t`Roles`}
|
||||
rules={[{ required: true, message: t`Please select roles` }]}
|
||||
label="Roles"
|
||||
rules={[{ required: true, message: "Please select roles" }]}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
options={[
|
||||
{ label: t`Admin`, value: "admin" },
|
||||
{ label: t`Editor`, value: "editor" },
|
||||
{ label: "Admin", value: "admin" },
|
||||
{ label: "Editor", value: "editor" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="email" label={t`Email`}>
|
||||
<Form.Item name="email" label="Email">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</BaseFormModal>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, Input, Select, theme } from "antd";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { Plus, UserRound } from "lucide-react";
|
||||
import { forwardRef, useMemo } from "react";
|
||||
import { FilterToolbar } from "@/components/FilterToolbar";
|
||||
@@ -29,7 +28,6 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const slots = useMemo(
|
||||
@@ -40,7 +38,7 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
|
||||
children: (
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder={t`Search User`}
|
||||
placeholder="Search User"
|
||||
style={{ width: FILTER_CONTROL_WIDTH }}
|
||||
value={keywordInput}
|
||||
onChange={(e) => onKeywordChange(e.target.value)}
|
||||
@@ -55,29 +53,20 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
|
||||
children: (
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t`Role`}
|
||||
placeholder="Role"
|
||||
style={{ width: FILTER_CONTROL_WIDTH }}
|
||||
prefix={<UserRound size={token.fontSize} />}
|
||||
value={roleValue}
|
||||
onChange={(v) => onRoleChange(v ?? "")}
|
||||
options={[
|
||||
{ label: t`Admin`, value: "admin" },
|
||||
{ label: t`Editor`, value: "editor" },
|
||||
{ label: "Admin", value: "admin" },
|
||||
{ label: "Editor", value: "editor" },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
keywordInput,
|
||||
onClearSearch,
|
||||
onKeywordChange,
|
||||
onRoleChange,
|
||||
onSearch,
|
||||
roleValue,
|
||||
t,
|
||||
token.fontSize,
|
||||
],
|
||||
[keywordInput, onClearSearch, onKeywordChange, onRoleChange, onSearch, roleValue, token.fontSize],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -86,11 +75,11 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
|
||||
slots={slots}
|
||||
actions={
|
||||
<Button type="primary" icon={<Plus size={token.fontSize} />} onClick={onCreateClick}>
|
||||
{t`Create User`}
|
||||
Create User
|
||||
</Button>
|
||||
}
|
||||
moreFiltersLabel={t`More filters`}
|
||||
moreFiltersTitle={t`More filters`}
|
||||
moreFiltersLabel="More filters"
|
||||
moreFiltersTitle="More filters"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { Avatar, Button, Space, Form, App, Dropdown, theme, Tag, Flex } from "antd";
|
||||
import type { TablePaginationConfig } from "antd/es/table/interface";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { httpClient } from "@/utils/http";
|
||||
import { USER_ENDPOINTS } from "@/api/user";
|
||||
@@ -38,7 +37,6 @@ function UsersPage() {
|
||||
const search = Route.useSearch();
|
||||
const navigate = useNavigate({ from: Route.fullPath });
|
||||
const { message, modal } = App.useApp();
|
||||
const { t } = useLingui();
|
||||
const { token } = theme.useToken();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const pageShellRef = useRef<HTMLDivElement>(null);
|
||||
@@ -101,7 +99,7 @@ function UsersPage() {
|
||||
createLifecycle: {
|
||||
onSuccess: () => {
|
||||
message.success({
|
||||
content: t`Created successfully`,
|
||||
content: "Created successfully",
|
||||
key: MESSAGE_KEY_USER_CREATE,
|
||||
});
|
||||
setModalOpen(false);
|
||||
@@ -109,7 +107,7 @@ function UsersPage() {
|
||||
},
|
||||
onError: () => {
|
||||
message.error({
|
||||
content: t`Create failed`,
|
||||
content: "Create failed",
|
||||
key: MESSAGE_KEY_USER_CREATE,
|
||||
});
|
||||
},
|
||||
@@ -117,14 +115,14 @@ function UsersPage() {
|
||||
updateLifecycle: {
|
||||
onMutate: () => {
|
||||
message.loading({
|
||||
content: t`Updating…`,
|
||||
content: "Updating…",
|
||||
key: MESSAGE_KEY_USER_UPDATE,
|
||||
duration: 0,
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success({
|
||||
content: t`Updated successfully`,
|
||||
content: "Updated successfully",
|
||||
key: MESSAGE_KEY_USER_UPDATE,
|
||||
});
|
||||
setModalOpen(false);
|
||||
@@ -133,7 +131,7 @@ function UsersPage() {
|
||||
},
|
||||
onError: () => {
|
||||
message.error({
|
||||
content: t`Update failed`,
|
||||
content: "Update failed",
|
||||
key: MESSAGE_KEY_USER_UPDATE,
|
||||
});
|
||||
},
|
||||
@@ -141,20 +139,20 @@ function UsersPage() {
|
||||
deleteLifecycle: {
|
||||
onMutate: () => {
|
||||
message.loading({
|
||||
content: t`Deleting…`,
|
||||
content: "Deleting…",
|
||||
key: MESSAGE_KEY_USER_DELETE,
|
||||
duration: 0,
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success({
|
||||
content: t`Deleted successfully`,
|
||||
content: "Deleted successfully",
|
||||
key: MESSAGE_KEY_USER_DELETE,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
message.error({
|
||||
content: t`Delete failed`,
|
||||
content: "Delete failed",
|
||||
key: MESSAGE_KEY_USER_DELETE,
|
||||
});
|
||||
},
|
||||
@@ -163,11 +161,11 @@ function UsersPage() {
|
||||
|
||||
const confirmDelete = (record: User) => {
|
||||
modal.confirm({
|
||||
title: t`Are you absolutely sure?`,
|
||||
content: t`This action cannot be undone. This will permanently delete the user.`,
|
||||
okText: t`Delete`,
|
||||
title: "Are you absolutely sure?",
|
||||
content: "This action cannot be undone. This will permanently delete the user.",
|
||||
okText: "Delete",
|
||||
okType: "danger",
|
||||
cancelText: t`Cancel`,
|
||||
cancelText: "Cancel",
|
||||
onOk: () => deleteMutation.mutate(record.id),
|
||||
});
|
||||
};
|
||||
@@ -181,7 +179,7 @@ function UsersPage() {
|
||||
sortOrder: search.sortField === "id" ? search.sortOrder : null,
|
||||
},
|
||||
{
|
||||
title: t`Username`,
|
||||
title: "Username",
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
sorter: true,
|
||||
@@ -207,14 +205,14 @@ function UsersPage() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t`Email`,
|
||||
title: "Email",
|
||||
dataIndex: "email",
|
||||
key: "email",
|
||||
sorter: true,
|
||||
sortOrder: search.sortField === "email" ? search.sortOrder : null,
|
||||
},
|
||||
{
|
||||
title: t`Roles`,
|
||||
title: "Roles",
|
||||
dataIndex: "roles",
|
||||
key: "roles",
|
||||
sorter: true,
|
||||
@@ -252,7 +250,7 @@ function UsersPage() {
|
||||
{
|
||||
key: "edit",
|
||||
icon: <Pencil size={token.fontSize} />,
|
||||
label: t`Edit`,
|
||||
label: "Edit",
|
||||
onClick: () => {
|
||||
setEditingUser(record);
|
||||
form.setFieldsValue(record);
|
||||
@@ -262,7 +260,7 @@ function UsersPage() {
|
||||
{
|
||||
key: "delete",
|
||||
icon: <Trash2 size={token.fontSize} />,
|
||||
label: t`Delete`,
|
||||
label: "Delete",
|
||||
danger: true,
|
||||
onClick: () => confirmDelete(record),
|
||||
},
|
||||
@@ -391,7 +389,7 @@ function UsersPage() {
|
||||
current: currentPage,
|
||||
pageSize: search.limit,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => t`${total} rows`,
|
||||
showTotal: (total) => `${total} rows`,
|
||||
onChange: (page, pageSize) => {
|
||||
void navigate({
|
||||
search: {
|
||||
@@ -403,7 +401,7 @@ function UsersPage() {
|
||||
},
|
||||
}
|
||||
: false,
|
||||
[showPagination, data?.total, currentPage, search, navigate, t],
|
||||
[showPagination, data?.total, currentPage, search, navigate],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
|
||||
import { Form, Input, Button, Card, App, theme, Typography, Flex, Checkbox, Space } from "antd";
|
||||
import type { CSSProperties } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { httpClient } from "@/utils/http";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
@@ -11,7 +10,6 @@ import { LoginRequestSchema, AuthTokensSchema } from "@/api/schemas";
|
||||
import { fetchSessionAndApplyToStore } from "@/utils/session";
|
||||
import type { LoginRequest } from "@/api/schemas";
|
||||
import { APP_BRAND_NAME, APP_FAVICON_SRC } from "@/utils/constants";
|
||||
import { Languages } from "lucide-react";
|
||||
import { Theme } from "@/components/Icon";
|
||||
import { AppFooter } from "@/components/Layout/AppFooter";
|
||||
import { Aurora } from "@/components/Aurora";
|
||||
@@ -30,10 +28,7 @@ export const Route = createFileRoute("/login/")({
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const { message } = App.useApp();
|
||||
const { t } = useLingui();
|
||||
const setTokens = useAuthStore((s) => s.setTokens);
|
||||
const locale = useSettingsStore((s) => s.locale);
|
||||
const setLocale = useSettingsStore((s) => s.setLocale);
|
||||
const toggleDarkMode = useSettingsStore((s) => s.toggleDarkMode);
|
||||
const darkMode = useSettingsStore((s) => s.darkMode);
|
||||
const { token } = theme.useToken();
|
||||
@@ -47,18 +42,14 @@ function LoginPage() {
|
||||
await fetchSessionAndApplyToStore();
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success(t`Login successful`);
|
||||
message.success("Login successful");
|
||||
void navigate({ to: "/dashboard" });
|
||||
},
|
||||
onError: (err) => {
|
||||
message.error(err instanceof Error ? err.message : t`Login failed`);
|
||||
message.error(err instanceof Error ? err.message : "Login failed");
|
||||
},
|
||||
});
|
||||
|
||||
const toggleLocale = () => {
|
||||
setLocale(locale === "en" ? "zh" : "en");
|
||||
};
|
||||
|
||||
const shellStyle: CSSProperties = {
|
||||
position: "relative",
|
||||
isolation: "isolate",
|
||||
@@ -146,12 +137,12 @@ function LoginPage() {
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={<span style={{ fontWeight: 500 }}>{t`Username`}</span>}
|
||||
rules={[{ required: true, message: t`Please enter username` }]}
|
||||
label={<span style={{ fontWeight: 500 }}>Username</span>}
|
||||
rules={[{ required: true, message: "Please enter username" }]}
|
||||
>
|
||||
<Input
|
||||
id="login-username"
|
||||
aria-label={t`Username`}
|
||||
aria-label="Username"
|
||||
placeholder="admin"
|
||||
size="large"
|
||||
/>
|
||||
@@ -159,13 +150,13 @@ function LoginPage() {
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={<span style={{ fontWeight: 500 }}>{t`Password`}</span>}
|
||||
rules={[{ required: true, message: t`Please enter password` }]}
|
||||
label={<span style={{ fontWeight: 500 }}>Password</span>}
|
||||
rules={[{ required: true, message: "Please enter password" }]}
|
||||
style={{ marginBottom: token.marginLG }}
|
||||
>
|
||||
<Input.Password
|
||||
id="login-password"
|
||||
aria-label={t`Password`}
|
||||
aria-label="Password"
|
||||
placeholder="admin"
|
||||
size="large"
|
||||
/>
|
||||
@@ -178,14 +169,14 @@ function LoginPage() {
|
||||
wrap="wrap"
|
||||
>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>{t`Auto login`}</Checkbox>
|
||||
<Checkbox>Auto login</Checkbox>
|
||||
</Form.Item>
|
||||
<Typography.Link
|
||||
href="#"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
style={{ fontSize: token.fontSizeSM }}
|
||||
>
|
||||
{t`Forgot password?`}
|
||||
Forgot password?
|
||||
</Typography.Link>
|
||||
</Flex>
|
||||
|
||||
@@ -197,7 +188,7 @@ function LoginPage() {
|
||||
block
|
||||
size="large"
|
||||
>
|
||||
{t`Sign In`}
|
||||
Sign In
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
@@ -213,19 +204,12 @@ function LoginPage() {
|
||||
>
|
||||
<Flex justify="center" style={{ marginBottom: token.marginSM }}>
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={toggleLocale}
|
||||
icon={<Languages size={token.size} />}
|
||||
aria-label={t`Switch language`}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={toggleDarkMode}
|
||||
icon={<Theme size={token.size} />}
|
||||
aria-label={t`Toggle Theme`}
|
||||
aria-label="Toggle Theme"
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { createPersistentStore } from "./createPersistentStore";
|
||||
|
||||
export type Locale = "en" | "zh";
|
||||
|
||||
function getInitialDarkMode() {
|
||||
if (typeof window === "undefined") {
|
||||
return false;
|
||||
@@ -12,29 +10,24 @@ function getInitialDarkMode() {
|
||||
interface SettingsState {
|
||||
darkMode: boolean;
|
||||
sidebarCollapsed: boolean;
|
||||
locale: Locale;
|
||||
toggleDarkMode: () => void;
|
||||
toggleSidebar: () => void;
|
||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||
setLocale: (locale: Locale) => void;
|
||||
}
|
||||
|
||||
export const useSettingsStore = createPersistentStore<SettingsState>(
|
||||
(set) => ({
|
||||
darkMode: getInitialDarkMode(),
|
||||
sidebarCollapsed: false,
|
||||
locale: "en",
|
||||
toggleDarkMode: () => set((s) => ({ darkMode: !s.darkMode })),
|
||||
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
|
||||
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
|
||||
setLocale: (locale) => set({ locale }),
|
||||
}),
|
||||
{
|
||||
name: "settings-storage",
|
||||
name: "settings-storage-basic",
|
||||
partialize: (state) => ({
|
||||
darkMode: state.darkMode,
|
||||
sidebarCollapsed: state.sidebarCollapsed,
|
||||
locale: state.locale,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user