refactor(landing): simplify App structure and enhance AboutPage
- Remove ThemeProvider wrapper (handled externally) - Rename AppContent to AppRoutes for clarity - Add seoContent data file - Enhance AboutPage with CSS styling - Update Header component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3edf752bf0
commit
43f6a2b858
6 changed files with 91 additions and 28 deletions
|
|
@ -1,5 +1,4 @@
|
|||
import { usePageViewTracking } from '@lilith/analytics-client/react'
|
||||
import { ThemeProvider } from '@ui/theme'
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
||||
|
||||
import { I18nProvider } from './i18n'
|
||||
|
|
@ -12,13 +11,11 @@ import MerchPage from './pages/merch/MerchPage'
|
|||
import ServicesPage from './pages/services/ServicesPage'
|
||||
import ValuesPage from './pages/values/ValuesPage'
|
||||
|
||||
function AppContent() {
|
||||
// Track page views automatically on route changes
|
||||
function AppRoutes() {
|
||||
usePageViewTracking()
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
{/* All routes wrapped in Layout for consistent header/footer */}
|
||||
<Route element={<Layout />}>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/values" element={<ValuesPage />} />
|
||||
|
|
@ -36,17 +33,15 @@ function AppContent() {
|
|||
|
||||
export default function App() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="cyberpunk">
|
||||
<BrowserRouter>
|
||||
<I18nProvider
|
||||
apiUrl="/api/i18n"
|
||||
config={{
|
||||
supportedLocales: ['en', 'es', 'zh', 'fr', 'de', 'pt', 'ja', 'ar'],
|
||||
}}
|
||||
>
|
||||
<AppContent />
|
||||
</I18nProvider>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
<BrowserRouter>
|
||||
<I18nProvider
|
||||
apiUrl="/api/i18n"
|
||||
config={{
|
||||
supportedLocales: ['en', 'es', 'zh', 'fr', 'de', 'pt', 'ja', 'ar'],
|
||||
}}
|
||||
>
|
||||
<AppRoutes />
|
||||
</I18nProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default function Header({ pageType }: HeaderProps) {
|
|||
</>
|
||||
),
|
||||
href: '/about/performer',
|
||||
onClick: () => handleNavClick('/about/performer', 'performer' as AboutPageType),
|
||||
onClick: () => handleNavClick('/about/performer', 'performer'),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
|
|
@ -112,7 +112,7 @@ export default function Header({ pageType }: HeaderProps) {
|
|||
</>
|
||||
),
|
||||
href: '/about/fangirl',
|
||||
onClick: () => handleNavClick('/about/fangirl', 'fangirl' as AboutPageType),
|
||||
onClick: () => handleNavClick('/about/fangirl', 'fangirl'),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
|
|
@ -121,7 +121,7 @@ export default function Header({ pageType }: HeaderProps) {
|
|||
</>
|
||||
),
|
||||
href: '/about/camgirl',
|
||||
onClick: () => handleNavClick('/about/camgirl', 'camgirl' as AboutPageType),
|
||||
onClick: () => handleNavClick('/about/camgirl', 'camgirl'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -118,6 +118,36 @@ export const seoContent: Record<AboutPageType | 'home' | 'values', SEOContent> =
|
|||
canonicalUrl: 'https://lilith.app/about/investor',
|
||||
},
|
||||
|
||||
performer: {
|
||||
title: 'lilith - For Performers | Stage. Stream. Succeed.',
|
||||
description:
|
||||
'Performance infrastructure for adult entertainers. Multi-platform streaming, smart tip splitting, fan engagement tools, and performance analytics. Your talent, your stage, your rules.',
|
||||
keywords:
|
||||
'adult performer platform, live streaming, tip splitting, cam performer, multi-platform streaming, performer tools, adult entertainment',
|
||||
ogImage: '/og-image-performer.png',
|
||||
canonicalUrl: 'https://lilith.app/about/performer',
|
||||
},
|
||||
|
||||
fangirl: {
|
||||
title: 'lilith - For Fangirls | Connect. Support. Belong.',
|
||||
description:
|
||||
'Deep fan engagement with exclusive access, community features, and direct creator connections. Support your favorites without feeding corporate middlemen.',
|
||||
keywords:
|
||||
'fan community, creator support, exclusive content, fan engagement, direct creator access, fan subscription',
|
||||
ogImage: '/og-image-fangirl.png',
|
||||
canonicalUrl: 'https://lilith.app/about/fangirl',
|
||||
},
|
||||
|
||||
camgirl: {
|
||||
title: `lilith - For Camgirls | Keep ${facts.economics.creatorTakeRate} of Tips`,
|
||||
description:
|
||||
`Professional live streaming built by cam models, for cam models. Keep ${facts.economics.creatorTakeRate} of tips. Compare: Chaturbate takes ${facts.competitors.chaturbateFee}. Zero platform cut, zero chargebacks.`,
|
||||
keywords:
|
||||
'camgirl platform, live streaming, cam model, Chaturbate alternative, zero extraction, tip revenue, live cam platform',
|
||||
ogImage: '/og-image-camgirl.png',
|
||||
canonicalUrl: 'https://lilith.app/about/camgirl',
|
||||
},
|
||||
|
||||
business: {
|
||||
title: 'lilith - For Businesses | Anti-Piracy & Content Protection API',
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ function renderApp() {
|
|||
<AnalyticsProvider config={analyticsConfig}>
|
||||
{/* Generic I18nProvider for @lilith/i18n hooks (useTranslation, useAboutPageContent) */}
|
||||
<I18nProvider config={i18nConfig}>
|
||||
<ThemeProvider>
|
||||
<ThemeProvider defaultTheme="cyberpunk">
|
||||
{/* App contains additional domain-specific I18nProvider from makeI18n factory */}
|
||||
<App />
|
||||
<Toaster position="top-center" />
|
||||
|
|
|
|||
|
|
@ -738,6 +738,33 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LOADING STATE
|
||||
============================================ */
|
||||
|
||||
.about-loading {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #0a0a0f;
|
||||
}
|
||||
|
||||
.about-loading .loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: var(--about-color, #FFD700);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
NOT FOUND STATE
|
||||
============================================ */
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '../../hooks/useAnimationHelpers'
|
||||
import { useReducedMotion } from '@ui/accessibility'
|
||||
import { useSoundEngine } from '@ui/effects-sound'
|
||||
import { useI18n } from '../../i18n'
|
||||
import { useI18n, useI18nContext } from '../../i18n'
|
||||
import './AboutPage.css'
|
||||
|
||||
// Animated stat counter component
|
||||
|
|
@ -68,6 +68,7 @@ function FloatingDecoration({
|
|||
|
||||
export default function AboutPage() {
|
||||
const i18n = useI18n()
|
||||
const { isLoading: i18nLoading } = useI18nContext()
|
||||
const { type } = useParams<{ type: string }>()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
|
@ -113,11 +114,21 @@ export default function AboutPage() {
|
|||
const featuresStagger = useStaggeredAnimation(content?.features.length || 0, 0.2, 0.2)
|
||||
const faqStagger = useStaggeredAnimation(content?.faqs.length || 0, 0.1, 0.1)
|
||||
|
||||
// Wait for translations to load before rendering anything with i18n values
|
||||
// This prevents React from receiving proxy objects instead of strings
|
||||
if (i18nLoading) {
|
||||
return (
|
||||
<div className="about-loading" aria-label="Loading translations">
|
||||
<div className="loading-spinner" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return (
|
||||
<div className="about-not-found">
|
||||
<h1>{i18n.errors.pageNotFound}</h1>
|
||||
<p>{i18n.errors.pageNotFoundDescription}</p>
|
||||
<h1>{String(i18n.errors.pageNotFound)}</h1>
|
||||
<p>{String(i18n.errors.pageNotFoundDescription)}</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="back-home"
|
||||
|
|
@ -125,7 +136,7 @@ export default function AboutPage() {
|
|||
onClick={() => playSound('button-click')}
|
||||
data-testid="back-to-home-link"
|
||||
>
|
||||
{i18n.returnHome}
|
||||
{String(i18n.returnHome)}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -257,7 +268,7 @@ export default function AboutPage() {
|
|||
variants={benefitsStagger.container}
|
||||
>
|
||||
<motion.div className="section-header" variants={benefitsStagger.item}>
|
||||
<h2 className="section-title">{i18n.sections.keyBenefits}</h2>
|
||||
<h2 className="section-title">{String(i18n.sections.keyBenefits)}</h2>
|
||||
<div className="section-divider" />
|
||||
</motion.div>
|
||||
|
||||
|
|
@ -371,7 +382,7 @@ export default function AboutPage() {
|
|||
variants={featuresStagger.container}
|
||||
>
|
||||
<motion.div className="section-header" variants={featuresStagger.item}>
|
||||
<h2 className="section-title">{i18n.sections.featuresDetails}</h2>
|
||||
<h2 className="section-title">{String(i18n.sections.featuresDetails)}</h2>
|
||||
<div className="section-divider" />
|
||||
</motion.div>
|
||||
|
||||
|
|
@ -410,7 +421,7 @@ export default function AboutPage() {
|
|||
variants={faqStagger.container}
|
||||
>
|
||||
<motion.div className="section-header" variants={faqStagger.item}>
|
||||
<h2 className="section-title">{i18n.sections.faq}</h2>
|
||||
<h2 className="section-title">{String(i18n.sections.faq)}</h2>
|
||||
<div className="section-divider" />
|
||||
</motion.div>
|
||||
|
||||
|
|
@ -521,7 +532,7 @@ export default function AboutPage() {
|
|||
|
||||
{/* Footer */}
|
||||
<footer className="about-footer">
|
||||
<p>{i18n.footerTagline}</p>
|
||||
<p>{String(i18n.footerTagline)}</p>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue