Merge branch 'main' into feat/homepage-icons

This commit is contained in:
Haitao Pan 2025-06-20 08:29:23 +08:00
commit aa4f6eeb3f
7 changed files with 154 additions and 12 deletions

View File

@ -0,0 +1,57 @@
name: Deploy Xcontrol-panel to Cloudflare Pages
on:
pull_request:
paths:
- 'ui/nextjs/**'
- '.github/workflows/deploy-xcontrol-panel.yml'
workflow_dispatch:
paths:
- 'ui/nextjs/**'
- '.github/workflows/deploy-xcontrol-panel.yml'
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ui/nextjs
steps:
- name: 📦 Checkout source code
uses: actions/checkout@v4
- name: 🧰 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: 📥 Install dependencies
run: yarn install --frozen-lockfile
- name: 🛠️ Build static site
run: yarn build # next build (output: 'export' 会自动产出 ./out)
- name: 🚀 Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
projectName: xcontrol-panel # 和 Pages 控制台里看到的项目名保持一致
directory: ui/nextjs/out # 指定到子目录 out
wranglerVersion: '4' # 升级到 Wrangler v4避免 v2 大量警告
# - name: 📤 Deploy to VPS via rsync
# env:
# RSYNC_SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
# VPS_HOST: ${{ secrets.VPS_HOST }}
# run: |
# mkdir -p ~/.ssh
# echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
# chmod 600 ~/.ssh/id_rsa
# ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
# rsync -av out/ root@"$VPS_HOST":/mnt/data/website/ --delete
- name: ✅ Confirm deployment complete
run: echo "✅ Homepage deployed to Cloudflare & VPS Site."

View File

@ -2,6 +2,8 @@ import Hero from '@components/Hero'
import Features from '@components/Features'
import OpenSource from '@components/OpenSource'
import DownloadSection from '@components/DownloadSection'
import Terms from '@components/Terms'
import Contact from '@components/Contact'
import Footer from '@components/Footer'
import NavBar from '@components/NavBar'
@ -14,6 +16,8 @@ export default function Page() {
<Features />
<OpenSource />
<DownloadSection />
<Terms />
<Contact />
</main>
<Footer />
</>

View File

@ -0,0 +1,17 @@
'use client'
import { useLanguage } from '../i18n/LanguageProvider'
import { translations } from '../i18n/translations'
export default function Contact() {
const { language } = useLanguage()
const t = translations[language]
return (
<section id="contact" className="py-20 bg-gray-900 text-white">
<div className="max-w-3xl mx-auto px-4 text-center">
<h2 className="text-3xl font-bold mb-6">{t.contactTitle}</h2>
<p className="text-xl">manbuzhe2008@gmail.com</p>
</div>
</section>
)
}

View File

@ -1,9 +1,13 @@
'use client'
import { useState } from 'react'
import LanguageToggle from './LanguageToggle'
import { useLanguage } from '../i18n/LanguageProvider'
import { translations } from '../i18n/translations'
export default function NavBar() {
const [menuOpen, setMenuOpen] = useState(false)
const { language } = useLanguage()
const nav = translations[language].nav
return (
<nav className="fixed top-0 w-full z-50 bg-black/30 backdrop-blur border-b border-white/10">
@ -15,10 +19,10 @@ export default function NavBar() {
{/* 桌面导航 */}
<div className="hidden md:flex items-center gap-6 text-sm text-white">
<a href="#features" className="hover:text-purple-300">Features</a>
<a href="#open-sources" className="hover:text-purple-300">Open Source</a>
<a href="#download" className="hover:text-purple-300">Download</a>
<a href="#contact" className="hover:text-purple-300">Contact</a>
<a href="#features" className="hover:text-purple-300">{nav.features}</a>
<a href="#open-sources" className="hover:text-purple-300">{nav.openSource}</a>
<a href="#download" className="hover:text-purple-300">{nav.download}</a>
<a href="#contact" className="hover:text-purple-300">{nav.contact}</a>
<LanguageToggle />
</div>
@ -54,28 +58,28 @@ export default function NavBar() {
onClick={() => setMenuOpen(false)}
className="block py-2 text-white hover:text-purple-300"
>
Features
{nav.features}
</a>
<a
href="#open-sources"
onClick={() => setMenuOpen(false)}
className="block py-2 text-white hover:text-purple-300"
>
Open Source
{nav.openSource}
</a>
<a
href="#download"
onClick={() => setMenuOpen(false)}
className="block py-2 text-white hover:text-purple-300"
>
Download
{nav.download}
</a>
<a
href="#contact"
onClick={() => setMenuOpen(false)}
className="block py-2 text-white hover:text-purple-300"
>
Contact
{nav.contact}
</a>
<div className="pt-2">
<LanguageToggle />

View File

@ -0,0 +1,23 @@
'use client'
import { useLanguage } from '../i18n/LanguageProvider'
import { translations } from '../i18n/translations'
export default function Terms() {
const { language } = useLanguage()
const t = translations[language]
return (
<section id="terms" className="py-20 bg-gray-950 text-white">
<div className="max-w-3xl mx-auto px-4">
<h2 className="text-3xl font-bold mb-6">{t.termsTitle}</h2>
<ul className="list-disc pl-5 space-y-2">
{t.termsPoints.map((p, idx) => (
<li key={idx} className="text-gray-300">
{p}
</li>
))}
</ul>
</div>
</section>
)
}

View File

@ -11,6 +11,15 @@ export type Translation = {
downloadTitle: string
downloadSubtitle: string
footerLinks: [string, string, string]
nav: {
features: string
openSource: string
download: string
contact: string
}
termsTitle: string
termsPoints: string[]
contactTitle: string
}
export const translations: Record<'en' | 'zh', Translation> = {
@ -27,6 +36,20 @@ export const translations: Record<'en' | 'zh', Translation> = {
downloadTitle: 'Download',
downloadSubtitle: 'Select your platform',
footerLinks: ['Privacy Policy', 'Terms of Service', 'Contact Us'],
nav: {
features: 'Features',
openSource: 'Open Source',
download: 'Download',
contact: 'Contact',
},
termsTitle: 'Terms of Service',
termsPoints: [
'A free, open-source version for self-hosting on Windows, Linux, and macOS',
'Affordable 1-on-1 consulting for technical setup',
'A premium plan with cloud sync, mobile support, and device linking',
'A future SaaS version for users who want one-click deployment with no setup required',
],
contactTitle: 'Contact Us',
},
zh: {
hero: {
@ -41,5 +64,19 @@ export const translations: Record<'en' | 'zh', Translation> = {
downloadTitle: '下载',
downloadSubtitle: '选择适合的平台',
footerLinks: ['隐私政策', '服务条款', '联系我们'],
nav: {
features: '功能特性',
openSource: '开源项目',
download: '下载',
contact: '联系我们',
},
termsTitle: '服务条款',
termsPoints: [
'提供在 Windows、Linux 和 macOS 上可自托管的免费开源版本',
'提供经济实惠的 1 对 1 技术部署咨询服务',
'提供带云同步、移动端支持和设备绑定的高级版计划',
'未来将推出无需设置、一键部署的 SaaS 版本',
],
contactTitle: '联系我们',
},
}

View File

@ -522,10 +522,10 @@ lru-cache@^10.2.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lucide-react@^0.517.0:
version "0.517.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.517.0.tgz#195ed8bd18e72462b012ba4e36d4474e480ff44c"
integrity sha512-TQCwwbwIuVG6SSutUC2Ol6PRXcuZndqoVAnDa7S7xb/RWPaiKTvLwX7byUKeh0pUgvtFh0NZZwFIDuMSeB7Iwg==
lucide-react@^0.292.0:
version "0.292.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.292.0.tgz#c8a06b2ccd8a348a88669def3c0291c035de2884"
integrity sha512-rRgUkpEHWpa5VCT66YscInCQmQuPCB1RFRzkkxMxg4b+jaL0V12E3riWWR2Sh5OIiUhCwGW/ZExuEO4Az32E6Q==
merge2@^1.3.0:
version "1.4.1"