feat(web): add /pricing page with subscription tier comparison
Complete public pricing page showing all 4 subscription plans (FREE, AGENT_PRO, INVESTOR, ENTERPRISE) with billing cycle toggle, feature comparison table, VND formatting, and Vietnamese/English i18n support. Also adds pricing link to public navigation header and footer. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { Menu, X } from 'lucide-react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { LanguageSwitcher } from '@/components/ui/language-switcher';
|
||||
import { Link } from '@/i18n/navigation';
|
||||
@@ -12,6 +14,25 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
||||
const pathname = usePathname();
|
||||
const { user } = useAuthStore();
|
||||
const t = useTranslations();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
const navLinks = [
|
||||
{
|
||||
href: '/' as const,
|
||||
label: t('nav.home'),
|
||||
isActive: pathname === '/' || !!pathname.match(/^\/(vi|en)\/?$/),
|
||||
},
|
||||
{
|
||||
href: '/search' as const,
|
||||
label: t('nav.search'),
|
||||
isActive: pathname.includes('/search'),
|
||||
},
|
||||
{
|
||||
href: '/pricing' as const,
|
||||
label: t('nav.pricing'),
|
||||
isActive: pathname.includes('/pricing'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -24,29 +45,22 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
||||
<span className="text-lg font-bold text-primary">{t('common.goodgo')}</span>
|
||||
</Link>
|
||||
|
||||
<nav aria-label={t('nav.mainNav')} className="flex items-center space-x-1">
|
||||
<Link
|
||||
href="/"
|
||||
className={cn(
|
||||
'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground',
|
||||
pathname === '/' || pathname.match(/^\/(vi|en)\/?$/)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{t('nav.home')}
|
||||
</Link>
|
||||
<Link
|
||||
href="/search"
|
||||
className={cn(
|
||||
'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground',
|
||||
pathname.includes('/search')
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{t('nav.search')}
|
||||
</Link>
|
||||
{/* Desktop nav */}
|
||||
<nav aria-label={t('nav.mainNav')} className="hidden items-center space-x-1 sm:flex">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className={cn(
|
||||
'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground',
|
||||
link.isActive
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="ml-auto flex items-center space-x-2">
|
||||
@@ -62,18 +76,73 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link href="/login">
|
||||
<Link href="/login" className="hidden sm:inline-flex">
|
||||
<Button variant="ghost" size="sm">
|
||||
{t('common.login')}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/register">
|
||||
<Link href="/register" className="hidden sm:inline-flex">
|
||||
<Button size="sm">{t('common.register')}</Button>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
{/* Mobile menu toggle */}
|
||||
<button
|
||||
aria-label={mobileMenuOpen ? t('nav.closeMenu') : t('nav.openMenu')}
|
||||
className="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground sm:hidden"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
{mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu */}
|
||||
{mobileMenuOpen && (
|
||||
<nav
|
||||
aria-label={t('nav.mainNav')}
|
||||
className="border-t px-4 pb-4 pt-2 sm:hidden"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className={cn(
|
||||
'block rounded-md px-3 py-2.5 text-sm font-medium transition-colors',
|
||||
link.isActive
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
||||
)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-3 space-y-2 border-t pt-3">
|
||||
{user ? (
|
||||
<>
|
||||
<p className="px-3 text-sm text-muted-foreground">{user.fullName}</p>
|
||||
<Link href="/dashboard" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button size="sm" className="w-full">{t('common.dashboard')}</Button>
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<Link href="/login" className="flex-1" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
{t('common.login')}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/register" className="flex-1" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button size="sm" className="w-full">{t('common.register')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<main id="main-content" role="main">
|
||||
@@ -110,6 +179,7 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold">{t('footer.support')}</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li><Link href="/pricing" className="hover:text-foreground">{t('nav.pricing')}</Link></li>
|
||||
<li><Link href="/login" className="hover:text-foreground">{t('common.login')}</Link></li>
|
||||
<li><Link href="/register" className="hover:text-foreground">{t('common.register')}</Link></li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user