Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/components/layout/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Button } from '@/components/ui/Button';
import { PageTransition } from '@/components/common/PageTransition';
import { MainRoutes } from '@/router/MainRoutes';
import {
IconExternalLink,
IconSidebarAuthFiles,
IconSidebarConfig,
IconSidebarDashboard,
Expand All @@ -22,6 +23,7 @@ import {
IconSidebarProviders,
IconSidebarQuota,
IconSidebarSystem,
IconX,
} from '@/components/ui/icons';
import { INLINE_LOGO_JPEG } from '@/assets/logoInline';
import {
Expand Down Expand Up @@ -50,6 +52,17 @@ const sidebarIcons: Record<string, ReactNode> = {
system: <IconSidebarSystem size={18} />,
};

const CPA_MANAGER_PLUS_BASE_URL = 'https://seakee.github.io/CPA-Manager-Plus';
const CPA_MANAGER_PLUS_SITE_URL = `${CPA_MANAGER_PLUS_BASE_URL}/`;

const getCpampDocsBaseUrl = (language: string) =>
language === 'zh-CN' || language === 'zh-TW'
? `${CPA_MANAGER_PLUS_BASE_URL}/docs`
: `${CPA_MANAGER_PLUS_BASE_URL}/docs/en`;

const getCpampDocUrl = (language: string, path: string) =>
`${getCpampDocsBaseUrl(language)}/${path}.html`;

// Header action icons - smaller size for header buttons
const headerIconProps: SVGProps<SVGSVGElement> = {
width: 16,
Expand Down Expand Up @@ -232,6 +245,10 @@ export function MainLayout() {
'mainLayout.sidebarCollapsed',
false
);
const [migrationPromptDismissed, setMigrationPromptDismissed] = useLocalStorage(
'mainLayout.cpaManagerPlusMigrationPromptDismissed',
false
);
const [languageMenuOpen, setLanguageMenuOpen] = useState(false);
const [themeMenuOpen, setThemeMenuOpen] = useState(false);
const contentRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -243,6 +260,24 @@ export function MainLayout() {
const abbrBrandName = t('title.abbr');
const isLogsPage = location.pathname.startsWith('/logs');
const showSidebarLabels = !sidebarCollapsed || sidebarOpen;
const migrationGuideUrl = getCpampDocUrl(language, 'migration/from-cpa-manager');
const migrationPromptLinks = [
{
key: 'demo',
href: CPA_MANAGER_PLUS_SITE_URL,
label: t('migration_prompt.links.demo'),
},
{
key: 'quick_start',
href: getCpampDocUrl(language, 'guide/getting-started'),
label: t('migration_prompt.links.quick_start'),
},
{
key: 'backup',
href: getCpampDocUrl(language, 'operations/backup'),
label: t('migration_prompt.links.backup'),
},
];

// 将顶部悬浮控制区高度写入 CSS 变量,供移动端粘性元素和浮层避让。
useLayoutEffect(() => {
Expand Down Expand Up @@ -679,6 +714,51 @@ export function MainLayout() {

<div className={`content${isLogsPage ? ' content-logs' : ''}`} ref={contentRef}>
<main className={`main-content${isLogsPage ? ' main-content-logs' : ''}`}>
{!migrationPromptDismissed && (
<section className="migration-prompt" aria-label={t('migration_prompt.label')}>
<div className="migration-prompt__content">
<div className="migration-prompt__title">{t('migration_prompt.title')}</div>
<div className="migration-prompt__body">{t('migration_prompt.body')}</div>
<div
className="migration-prompt__links"
aria-label={t('migration_prompt.links_label')}
>
{migrationPromptLinks.map((link) => (
<a
key={link.key}
className="migration-prompt__secondary-link"
href={link.href}
target="_blank"
rel="noopener noreferrer"
>
<span>{link.label}</span>
<IconExternalLink size={12} />
</a>
))}
</div>
</div>
<div className="migration-prompt__actions">
<a
className="migration-prompt__link"
href={migrationGuideUrl}
target="_blank"
rel="noopener noreferrer"
>
<span>{t('migration_prompt.action')}</span>
<IconExternalLink size={14} />
</a>
<button
type="button"
className="migration-prompt__dismiss"
onClick={() => setMigrationPromptDismissed(true)}
title={t('migration_prompt.dismiss')}
aria-label={t('migration_prompt.dismiss')}
>
<IconX size={16} />
</button>
</div>
</section>
)}
<PageTransition
render={(location) => <MainRoutes location={location} />}
getRouteOrder={getRouteOrder}
Expand Down
13 changes: 13 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@
"title": "CLI Proxy API",
"subtitle": "Management Center"
},
"migration_prompt": {
"label": "CPA-Manager-Plus migration notice",
"title": "CPA-Manager-Plus is now available",
"body": "If you are considering migrating, review the demo, deployment path, and backup notes first. This panel can still be used as usual.",
"action": "View migration guide",
"links_label": "CPA-Manager-Plus evaluation links",
"links": {
"demo": "Live demo",
"quick_start": "Getting started",
"backup": "Backup notes"
},
"dismiss": "Don't show again"
},
"auto_login": {
"title": "Auto Login in Progress...",
"message": "Attempting to connect to server using locally saved connection information"
Expand Down
13 changes: 13 additions & 0 deletions src/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@
"title": "CLI Proxy API",
"subtitle": "Центр управления"
},
"migration_prompt": {
"label": "Уведомление о миграции на CPA-Manager-Plus",
"title": "CPA-Manager-Plus уже доступен",
"body": "Если вы рассматриваете переход, сначала посмотрите демо, варианты развёртывания и заметки о резервном копировании. Этой панелью по-прежнему можно пользоваться как обычно.",
"action": "Открыть руководство",
"links_label": "Ссылки для оценки CPA-Manager-Plus",
"links": {
"demo": "Демо",
"quick_start": "Быстрый старт",
"backup": "Резервное копирование"
},
"dismiss": "Больше не показывать"
},
"auto_login": {
"title": "Автовход...",
"message": "Пытаемся подключиться к серверу, используя сохранённые данные"
Expand Down
13 changes: 13 additions & 0 deletions src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@
"title": "CLI Proxy API",
"subtitle": "管理中心"
},
"migration_prompt": {
"label": "CPA-Manager-Plus 迁移提示",
"title": "CPA-Manager-Plus 已正式发布",
"body": "如果你准备升级,可以先看演示、部署方式和备份要点;当前面板仍可继续使用。",
"action": "查看迁移指南",
"links_label": "CPA-Manager-Plus 评估链接",
"links": {
"demo": "在线演示",
"quick_start": "快速开始",
"backup": "备份要点"
},
"dismiss": "不再提示"
},
"auto_login": {
"title": "正在自动登录...",
"message": "正在使用本地保存的连接信息尝试连接服务器"
Expand Down
13 changes: 13 additions & 0 deletions src/i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@
"title": "CLI Proxy API",
"subtitle": "管理中心"
},
"migration_prompt": {
"label": "CPA-Manager-Plus 遷移提示",
"title": "CPA-Manager-Plus 已正式發佈",
"body": "如果你準備升級,可以先看演示、部署方式和備份要點;目前面板仍可繼續使用。",
"action": "查看遷移指南",
"links_label": "CPA-Manager-Plus 評估連結",
"links": {
"demo": "線上演示",
"quick_start": "快速開始",
"backup": "備份要點"
},
"dismiss": "不再提示"
},
"auto_login": {
"title": "正在自動登入...",
"message": "正在使用本地儲存的連線資訊嘗試連線伺服器"
Expand Down
170 changes: 170 additions & 0 deletions src/styles/layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,153 @@
}
}

.migration-prompt {
display: flex;
align-items: center;
justify-content: space-between;
gap: $spacing-md;
width: 100%;
padding: 12px 14px 12px 16px;
border: 1px solid color-mix(in srgb, var(--primary-color) 18%, var(--border-color));
border-radius: $radius-md;
background:
linear-gradient(180deg, rgb(255 255 255 / 0.34), rgb(255 255 255 / 0)),
color-mix(in srgb, var(--bg-primary) 82%, var(--color-primary-light-9) 18%);
color: var(--text-primary);
box-shadow: 0 8px 18px -18px color-mix(in srgb, var(--primary-color) 65%, transparent);
}

.migration-prompt__content {
min-width: 0;
}

.migration-prompt__title {
font-size: 14px;
font-weight: 700;
line-height: 1.35;
color: var(--text-primary);
}

.migration-prompt__body {
margin-top: 2px;
font-size: 13px;
line-height: 1.45;
color: var(--text-secondary);
}

.migration-prompt__links {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 9px;
}

.migration-prompt__secondary-link {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 28px;
padding: 5px 8px;
border: 1px solid color-mix(in srgb, var(--border-color) 76%, transparent);
border-radius: $radius-sm;
background: color-mix(in srgb, var(--bg-primary) 64%, transparent);
color: var(--text-secondary);
font-size: 12px;
font-weight: 650;
line-height: 1.2;
text-decoration: none;
transition:
background-color $transition-fast,
border-color $transition-fast,
color $transition-fast;

&:hover {
background: color-mix(in srgb, var(--primary-color) 8%, var(--bg-primary));
border-color: color-mix(in srgb, var(--primary-color) 28%, var(--border-color));
color: var(--primary-active);
}

&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--primary-ring);
}

svg {
flex-shrink: 0;
}
}

.migration-prompt__actions {
display: inline-flex;
align-items: center;
gap: $spacing-xs;
flex-shrink: 0;
}

.migration-prompt__link {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 32px;
padding: 7px 10px;
border: 1px solid color-mix(in srgb, var(--primary-color) 22%, var(--border-color));
border-radius: $radius-md;
background: color-mix(in srgb, var(--bg-primary) 76%, var(--color-primary-light-9) 24%);
color: var(--primary-active);
font-size: 13px;
font-weight: 700;
line-height: 1.2;
text-decoration: none;
white-space: nowrap;
transition:
background-color $transition-fast,
border-color $transition-fast,
color $transition-fast;

&:hover {
background: var(--color-primary-light-9);
border-color: var(--color-primary-light-5);
color: var(--primary-color);
}

&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--primary-ring);
}

svg {
flex-shrink: 0;
}
}

.migration-prompt__dismiss {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
min-width: 32px;
padding: 0;
border: 1px solid transparent;
border-radius: $radius-md;
background: transparent;
color: var(--text-tertiary);
transition:
background-color $transition-fast,
color $transition-fast;

&:hover {
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
color: var(--text-primary);
}

&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--primary-ring);
}
}

.grid {
display: grid;
gap: $spacing-lg;
Expand Down Expand Up @@ -730,4 +877,27 @@
overflow: visible;
}
}

.migration-prompt {
align-items: flex-start;
gap: $spacing-sm;
padding: 12px;
}

.migration-prompt__actions {
align-self: stretch;
flex-direction: column-reverse;
justify-content: space-between;
}

.migration-prompt__link {
min-height: 34px;
padding: 8px;
white-space: normal;
text-align: center;
}

.migration-prompt__secondary-link {
max-width: 100%;
}
}
Loading