Files
Sub-Router/src/themes/minimal/Home.jsx
T

208 lines
10 KiB
React

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useAuth } from '../../context/AuthContext';
import { useSite, useCurrency } from '../../context/SiteContext';
import { getSiteModels, getSitePackages, Q } from '../../api';
import { calcOfficialEquivList } from '../../utils/officialEquiv';
import RotatingEquiv from '../../components/bits/RotatingEquiv';
import CountUp from '../../components/bits/CountUp';
import FadeContent from '../../components/bits/FadeContent';
import ApiEndpoints from '../../components/ApiEndpoints';
export default function MinimalHome() {
const { t } = useTranslation();
const { user } = useAuth();
const { site } = useSite();
const { symbol, rate, fmtCNY } = useCurrency();
const [models, setModels] = useState([]);
const [packages, setPackages] = useState([]);
useEffect(() => {
getSiteModels().then(r => { if (r.data.success) setModels(r.data.data || []); }).catch(() => {});
getSitePackages().then(r => { if (r.data.success) setPackages(r.data.data || []); }).catch(() => {});
}, []);
const enabledModels = models.filter(m => m.enabled !== false);
return (
<div className="relative">
{/* Hero Section — clean and minimal */}
<section className="max-w-5xl mx-auto px-6 pt-32 pb-24">
<FadeContent blur duration={800} delay={100}>
<div className="max-w-3xl">
<h1 className="text-5xl md:text-6xl font-heading font-bold text-white leading-[1.1] tracking-tight">
{site?.name || t('home.defaultHeroTitle')}
</h1>
<p className="text-lg text-neutral-400 mt-6 leading-relaxed max-w-xl">
{t('home.heroSubtitle')}
</p>
<div className="flex items-center gap-4 mt-10">
{user ? (
<Link to="/dashboard" className="px-7 py-3 rounded-lg bg-white text-neutral-900 font-medium text-sm hover:bg-neutral-200 transition-colors">
{t('home.goToDashboard')} →
</Link>
) : (
<>
<Link to="/register" className="px-7 py-3 rounded-lg bg-white text-neutral-900 font-medium text-sm hover:bg-neutral-200 transition-colors">
{t('home.getStarted')} →
</Link>
<Link to="/pricing" className="px-7 py-3 text-neutral-400 font-medium text-sm hover:text-white transition-colors">
{t('home.viewPricing')}
</Link>
</>
)}
</div>
</div>
</FadeContent>
{/* Stats — simple inline */}
<FadeContent blur duration={800} delay={400}>
<div className="flex items-center gap-12 mt-20 border-t border-neutral-800 pt-10">
<div>
<div className="text-2xl font-bold text-white">
<CountUp from={0} to={enabledModels.length || 50} duration={2} />+
</div>
<p className="text-sm text-neutral-500 mt-0.5">{t('home.aiModels')}</p>
</div>
<div>
<div className="text-2xl font-bold text-white">
<CountUp from={0} to={99.9} duration={2.5} />%
</div>
<p className="text-sm text-neutral-500 mt-0.5">{t('home.uptime')}</p>
</div>
<div>
<div className="text-2xl font-bold text-white">
&lt;<CountUp from={200} to={50} duration={2} direction="down" />ms
</div>
<p className="text-sm text-neutral-500 mt-0.5">{t('home.latency')}</p>
</div>
</div>
</FadeContent>
</section>
<ApiEndpoints />
{/* Features — two-column layout */}
<section className="max-w-5xl mx-auto px-6 py-20">
<FadeContent blur duration={800} delay={100}>
<h2 className="text-2xl font-heading font-bold text-white mb-2">{t('home.whyChooseUs')}</h2>
<p className="text-neutral-500 mb-10">{t('home.whyChooseUsDesc')}</p>
<div className="space-y-6">
{[
{ title: t('home.lightningFast'), desc: t('home.lightningFastDesc') },
{ title: t('home.securePrivate'), desc: t('home.securePrivateDesc') },
{ title: t('home.payAsYouGo'), desc: t('home.payAsYouGoDesc') },
].map((f, i) => (
<div key={i} className="flex gap-6 items-start py-6 border-b border-neutral-800/60 last:border-0 group">
<span className="text-sm font-mono text-neutral-600 mt-0.5">0{i + 1}</span>
<div>
<h3 className="text-base font-semibold text-white mb-1 group-hover:text-neutral-300 transition-colors">{f.title}</h3>
<p className="text-sm text-neutral-500 leading-relaxed max-w-lg">{f.desc}</p>
</div>
</div>
))}
</div>
</FadeContent>
</section>
{/* Models — simple list */}
{enabledModels.length > 0 && (
<section className="max-w-5xl mx-auto px-6 py-20">
<FadeContent blur duration={800} delay={100}>
<div className="flex items-end justify-between mb-8">
<div>
<h2 className="text-2xl font-heading font-bold text-white mb-2">{t('home.availableModels')}</h2>
<p className="text-neutral-500">{t('home.availableModelsDesc', { count: enabledModels.length })}</p>
</div>
{enabledModels.length > 8 && (
<Link to="/pricing" className="text-sm text-neutral-400 hover:text-white transition-colors">
{t('home.viewAllModels', { count: enabledModels.length })} →
</Link>
)}
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{enabledModels.slice(0, 8).map((m, i) => (
<div key={m.id || i} className="px-4 py-3 rounded-lg border border-neutral-800/60 hover:border-neutral-700 transition-colors">
<span className="text-sm text-neutral-300 font-mono">{m.display_name || m.model_name}</span>
</div>
))}
</div>
</FadeContent>
</section>
)}
{/* Packages — clean cards */}
{packages.length > 0 && (
<section className="max-w-5xl mx-auto px-6 py-20">
<FadeContent blur duration={800} delay={100}>
<h2 className="text-2xl font-heading font-bold text-white mb-2">{t('home.plansPackages')}</h2>
<p className="text-neutral-500 mb-10">{t('home.choosePlan')}</p>
<div className="grid md:grid-cols-3 gap-4 max-w-4xl">
{packages.filter(p => p.enabled).slice(0, 3).map((pkg) => {
const quotaDollars = pkg.quota_amount > 0 ? pkg.quota_amount / Q : 0;
const rp = pkg.quota_reset_period || 'never';
let tqd = quotaDollars;
if (rp !== 'never' && pkg.duration > 0 && quotaDollars > 0) {
let n = rp === 'daily' ? pkg.duration : rp === 'weekly' ? Math.floor(pkg.duration / 7) : rp === 'monthly' ? Math.floor(pkg.duration / 30) : 1;
if (n < 1) n = 1;
tqd = quotaDollars * n;
}
const equiv = calcOfficialEquivList(enabledModels, tqd);
return (
<div key={pkg.id} className="rounded-xl p-6 flex flex-col border border-neutral-800/60 hover:border-neutral-700 transition-colors">
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
<div className="mt-auto pt-6">
<span className="text-3xl font-bold text-white">{fmtCNY(pkg.price)}</span>
{pkg.original_price > pkg.price && (
<span className="text-sm text-neutral-600 line-through ml-2">{fmtCNY(pkg.original_price)}</span>
)}
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
</div>
{equiv.length > 0 && (
<p className="text-xs text-amber-300/80 mt-2">🔥 <RotatingEquiv items={equiv} text={(item) => t('packages.officialEquiv', { model: item.label, amount: item.equivDollars })} /></p>
)}
<Link to={user ? '/packages' : '/register'} className="mt-4 py-2.5 rounded-lg bg-neutral-800 text-white font-medium text-sm text-center hover:bg-neutral-700 transition-colors">
{user ? t('home.subscribe') : t('home.getStarted')}
</Link>
</div>
)})}
</div>
</FadeContent>
</section>
)}
{/* CTA — simple */}
<section className="max-w-5xl mx-auto px-6 py-24">
<FadeContent blur duration={800} delay={100}>
<div className="border-t border-neutral-800 pt-16 text-center">
<h2 className="text-2xl font-heading font-bold text-white mb-3">{t('home.readyToStart')}</h2>
<p className="text-neutral-500 mb-8 max-w-md mx-auto">{t('home.readyToStartDesc')}</p>
<div className="flex items-center justify-center gap-4">
{user ? (
<Link to="/dashboard" className="px-7 py-3 rounded-lg bg-white text-neutral-900 font-medium text-sm hover:bg-neutral-200 transition-colors">
{t('home.goToDashboard')} →
</Link>
) : (
<>
<Link to="/register" className="px-7 py-3 rounded-lg bg-white text-neutral-900 font-medium text-sm hover:bg-neutral-200 transition-colors">
{t('home.createFreeAccount')} →
</Link>
<Link to="/login" className="px-7 py-3 text-neutral-400 font-medium text-sm hover:text-white transition-colors">
{t('home.signIn')}
</Link>
</>
)}
</div>
</div>
</FadeContent>
</section>
</div>
);
}