feat: Landing page pública + Registro com seleção de plano
- Nova Landing Page institucional em / - Seções: Hero, Features, Pricing, FAQ, CTA, Footer - Pricing integrado com API de planos - Register.jsx agora suporta seleção de plano - Parâmetro ?plan=slug na URL do registro - Traduções EN, ES, PT-BR para landing - PayPal configurado no servidor (sandbox) Versão: 1.54.0
This commit is contained in:
parent
c99bca9404
commit
984855e36c
38
CHANGELOG.md
38
CHANGELOG.md
@ -5,6 +5,44 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
|
|||||||
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
|
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
|
||||||
|
|
||||||
|
|
||||||
|
## [1.54.0] - 2025-12-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 🏠 **Landing Page Pública WebMoney** - Nova página inicial institucional
|
||||||
|
- **Navbar** com links para seções e botões login/registro
|
||||||
|
- **Hero Section** com animação de preview do dashboard
|
||||||
|
- **Features Section** com 6 recursos principais:
|
||||||
|
- Múltiplas Contas, Categorias Inteligentes, Importação Bancária
|
||||||
|
- Relatórios Detalhados, Metas e Orçamentos, Multi-moeda
|
||||||
|
- **Pricing Section** integrada com API de planos
|
||||||
|
- Exibe planos Free, Pro Mensual, Pro Anual
|
||||||
|
- Destaque para plano mais popular
|
||||||
|
- Mostra período de trial e desconto anual
|
||||||
|
- **FAQ Section** com accordion interativo
|
||||||
|
- **CTA Section** com chamada para registro
|
||||||
|
- **Footer** completo com links úteis
|
||||||
|
|
||||||
|
- 📝 **Registro com Seleção de Plano**
|
||||||
|
- Cards de planos no formulário de registro
|
||||||
|
- Suporte a parâmetro `?plan=slug` na URL
|
||||||
|
- Redirecionamento para PayPal após registro (planos pagos)
|
||||||
|
- Texto dinâmico do botão baseado no plano selecionado
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 🔄 **Rota inicial alterada** - "/" agora mostra Landing Page em vez de redirecionar para login
|
||||||
|
- 🌍 **Traduções** adicionadas para Landing em EN, ES e PT-BR
|
||||||
|
- Namespace `landing.*` com todas as seções
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
- Novo componente: `frontend/src/pages/Landing.jsx`
|
||||||
|
- Novo CSS: `frontend/src/pages/Landing.css`
|
||||||
|
- Atualizado: `frontend/src/App.jsx` (rota "/" e import Register)
|
||||||
|
- Atualizado: `frontend/src/pages/Register.jsx` (seleção de plano)
|
||||||
|
- Arquivos de tradução atualizados: `en.json`, `es.json`, `pt-BR.json`
|
||||||
|
- PayPal configurado no servidor (sandbox mode)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.53.0] - 2025-12-17
|
## [1.53.0] - 2025-12-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import ProtectedRoute from './components/ProtectedRoute';
|
|||||||
import Layout from './components/Layout';
|
import Layout from './components/Layout';
|
||||||
import CookieConsent from './components/CookieConsent';
|
import CookieConsent from './components/CookieConsent';
|
||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
|
import Landing from './pages/Landing';
|
||||||
import Dashboard from './pages/Dashboard';
|
import Dashboard from './pages/Dashboard';
|
||||||
import Accounts from './pages/Accounts';
|
import Accounts from './pages/Accounts';
|
||||||
import CostCenters from './pages/CostCenters';
|
import CostCenters from './pages/CostCenters';
|
||||||
@ -27,6 +28,7 @@ import Pricing from './pages/Pricing';
|
|||||||
import Billing from './pages/Billing';
|
import Billing from './pages/Billing';
|
||||||
import Users from './pages/Users';
|
import Users from './pages/Users';
|
||||||
import SiteSettings from './pages/SiteSettings';
|
import SiteSettings from './pages/SiteSettings';
|
||||||
|
import Register from './pages/Register';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -35,6 +37,7 @@ function App() {
|
|||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/register" element={<Register />} />
|
||||||
<Route
|
<Route
|
||||||
path="/dashboard"
|
path="/dashboard"
|
||||||
element={
|
element={
|
||||||
@ -243,7 +246,7 @@ function App() {
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/" element={<Navigate to="/dashboard" />} />
|
<Route path="/" element={<Landing />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<CookieConsent />
|
<CookieConsent />
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|||||||
@ -2123,5 +2123,93 @@
|
|||||||
"cancelNote2": "Your data will not be deleted",
|
"cancelNote2": "Your data will not be deleted",
|
||||||
"cancelNote3": "You can reactivate your subscription at any time",
|
"cancelNote3": "You can reactivate your subscription at any time",
|
||||||
"confirmCancel": "Yes, Cancel"
|
"confirmCancel": "Yes, Cancel"
|
||||||
|
},
|
||||||
|
"landing": {
|
||||||
|
"nav": {
|
||||||
|
"features": "Features",
|
||||||
|
"pricing": "Pricing",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"login": "Login",
|
||||||
|
"register": "Start Now"
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"title": "Take Control of Your",
|
||||||
|
"titleHighlight": "Finances",
|
||||||
|
"subtitle": "Intelligent financial management for individuals and businesses. Track income, expenses, and achieve your financial goals.",
|
||||||
|
"cta": "Start Free",
|
||||||
|
"ctaSecondary": "View Plans",
|
||||||
|
"previewBalance": "Total Balance",
|
||||||
|
"previewIncome": "Income",
|
||||||
|
"previewExpense": "Expenses"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "Everything You Need",
|
||||||
|
"subtitle": "Powerful tools to manage your money",
|
||||||
|
"item1": {
|
||||||
|
"title": "Multiple Accounts",
|
||||||
|
"description": "Manage bank accounts, cards, and cash in one place"
|
||||||
|
},
|
||||||
|
"item2": {
|
||||||
|
"title": "Smart Categories",
|
||||||
|
"description": "Automatic categorization with keywords and subcategories"
|
||||||
|
},
|
||||||
|
"item3": {
|
||||||
|
"title": "Bank Import",
|
||||||
|
"description": "Import statements from Excel, CSV, OFX, and PDF"
|
||||||
|
},
|
||||||
|
"item4": {
|
||||||
|
"title": "Detailed Reports",
|
||||||
|
"description": "Charts and analysis to understand your spending"
|
||||||
|
},
|
||||||
|
"item5": {
|
||||||
|
"title": "Goals & Budgets",
|
||||||
|
"description": "Define goals and control monthly spending"
|
||||||
|
},
|
||||||
|
"item6": {
|
||||||
|
"title": "Multi-currency",
|
||||||
|
"description": "Manage finances in different currencies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pricing": {
|
||||||
|
"title": "Simple Plans, Fair Prices",
|
||||||
|
"subtitle": "Choose the plan that fits your needs",
|
||||||
|
"monthly": "Monthly",
|
||||||
|
"annual": "Annual",
|
||||||
|
"popular": "Most Popular",
|
||||||
|
"perMonth": "/month",
|
||||||
|
"perYear": "/year",
|
||||||
|
"startFree": "Start Free",
|
||||||
|
"subscribe": "Subscribe Now",
|
||||||
|
"billedAnnually": "Billed annually"
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Frequently Asked Questions",
|
||||||
|
"q1": "Is my data secure?",
|
||||||
|
"a1": "Yes! We use bank-level encryption (SSL/TLS) and your data is stored on secure servers with regular backups. We never share your information with third parties.",
|
||||||
|
"q2": "Can I cancel anytime?",
|
||||||
|
"a2": "Yes, you can cancel your subscription at any time without fees. You'll keep access until the end of the period you already paid.",
|
||||||
|
"q3": "Which banks are compatible?",
|
||||||
|
"a3": "You can import statements from any bank that exports to Excel, CSV, OFX, or PDF. We have predefined mappings for major banks.",
|
||||||
|
"q4": "How does the free trial work?",
|
||||||
|
"a4": "You get full access to all features during the trial period. No credit card required to start. At the end, choose the plan that fits your needs."
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"title": "Ready to Transform Your Finances?",
|
||||||
|
"subtitle": "Join thousands of users who have already taken control of their money.",
|
||||||
|
"button": "Create Free Account"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"description": "Smart Financial Management for individuals and businesses.",
|
||||||
|
"product": "Product",
|
||||||
|
"company": "Company",
|
||||||
|
"legal": "Legal",
|
||||||
|
"features": "Features",
|
||||||
|
"pricing": "Pricing",
|
||||||
|
"about": "About Us",
|
||||||
|
"contact": "Contact",
|
||||||
|
"privacy": "Privacy Policy",
|
||||||
|
"terms": "Terms of Use",
|
||||||
|
"rights": "All rights reserved."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2125,5 +2125,93 @@
|
|||||||
"cancelNote2": "Tus datos no se eliminarán",
|
"cancelNote2": "Tus datos no se eliminarán",
|
||||||
"cancelNote3": "Puedes reactivar tu suscripción en cualquier momento",
|
"cancelNote3": "Puedes reactivar tu suscripción en cualquier momento",
|
||||||
"confirmCancel": "Sí, Cancelar"
|
"confirmCancel": "Sí, Cancelar"
|
||||||
|
},
|
||||||
|
"landing": {
|
||||||
|
"nav": {
|
||||||
|
"features": "Funcionalidades",
|
||||||
|
"pricing": "Precios",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"login": "Iniciar Sesión",
|
||||||
|
"register": "Empezar Ahora"
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"title": "Toma el Control de tus",
|
||||||
|
"titleHighlight": "Finanzas",
|
||||||
|
"subtitle": "Gestión financiera inteligente para personas y empresas. Controla ingresos, gastos y alcanza tus metas financieras.",
|
||||||
|
"cta": "Comenzar Gratis",
|
||||||
|
"ctaSecondary": "Ver Planes",
|
||||||
|
"previewBalance": "Saldo Total",
|
||||||
|
"previewIncome": "Ingresos",
|
||||||
|
"previewExpense": "Gastos"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "Todo lo que Necesitas",
|
||||||
|
"subtitle": "Herramientas potentes para gestionar tu dinero",
|
||||||
|
"item1": {
|
||||||
|
"title": "Múltiples Cuentas",
|
||||||
|
"description": "Gestiona cuentas bancarias, tarjetas y efectivo en un solo lugar"
|
||||||
|
},
|
||||||
|
"item2": {
|
||||||
|
"title": "Categorías Inteligentes",
|
||||||
|
"description": "Categorización automática con palabras clave y subcategorías"
|
||||||
|
},
|
||||||
|
"item3": {
|
||||||
|
"title": "Importación Bancaria",
|
||||||
|
"description": "Importa extractos de Excel, CSV, OFX y PDF"
|
||||||
|
},
|
||||||
|
"item4": {
|
||||||
|
"title": "Reportes Detallados",
|
||||||
|
"description": "Gráficos y análisis para entender tus gastos"
|
||||||
|
},
|
||||||
|
"item5": {
|
||||||
|
"title": "Metas y Presupuestos",
|
||||||
|
"description": "Define objetivos y controla gastos mensuales"
|
||||||
|
},
|
||||||
|
"item6": {
|
||||||
|
"title": "Multi-moneda",
|
||||||
|
"description": "Gestiona finanzas en diferentes monedas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pricing": {
|
||||||
|
"title": "Planes Simples, Precios Justos",
|
||||||
|
"subtitle": "Elige el plan que se adapte a tus necesidades",
|
||||||
|
"monthly": "Mensual",
|
||||||
|
"annual": "Anual",
|
||||||
|
"popular": "Más Popular",
|
||||||
|
"perMonth": "/mes",
|
||||||
|
"perYear": "/año",
|
||||||
|
"startFree": "Comenzar Gratis",
|
||||||
|
"subscribe": "Suscribirse Ahora",
|
||||||
|
"billedAnnually": "Facturado anualmente"
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Preguntas Frecuentes",
|
||||||
|
"q1": "¿Mis datos están seguros?",
|
||||||
|
"a1": "¡Sí! Utilizamos cifrado de nivel bancario (SSL/TLS) y tus datos se almacenan en servidores seguros con copias de seguridad regulares. Nunca compartimos tu información con terceros.",
|
||||||
|
"q2": "¿Puedo cancelar cuando quiera?",
|
||||||
|
"a2": "Sí, puedes cancelar tu suscripción en cualquier momento sin cargos. Mantendrás el acceso hasta el final del período que ya pagaste.",
|
||||||
|
"q3": "¿Qué bancos son compatibles?",
|
||||||
|
"a3": "Puedes importar extractos de cualquier banco que exporte a Excel, CSV, OFX o PDF. Tenemos mapeos predefinidos para los principales bancos.",
|
||||||
|
"q4": "¿Cómo funciona la prueba gratuita?",
|
||||||
|
"a4": "Tienes acceso completo a todas las funcionalidades durante el período de prueba. No se requiere tarjeta de crédito para empezar. Al final, elige el plan que se adapte a tus necesidades."
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"title": "¿Listo para Transformar tus Finanzas?",
|
||||||
|
"subtitle": "Únete a miles de usuarios que ya tomaron el control de su dinero.",
|
||||||
|
"button": "Crear Cuenta Gratis"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"description": "Gestión Financiera Inteligente para personas y empresas.",
|
||||||
|
"product": "Producto",
|
||||||
|
"company": "Empresa",
|
||||||
|
"legal": "Legal",
|
||||||
|
"features": "Funcionalidades",
|
||||||
|
"pricing": "Precios",
|
||||||
|
"about": "Sobre Nosotros",
|
||||||
|
"contact": "Contacto",
|
||||||
|
"privacy": "Política de Privacidad",
|
||||||
|
"terms": "Términos de Uso",
|
||||||
|
"rights": "Todos los derechos reservados."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2143,5 +2143,93 @@
|
|||||||
"cancelNote2": "Seus dados não serão excluídos",
|
"cancelNote2": "Seus dados não serão excluídos",
|
||||||
"cancelNote3": "Você pode reativar sua assinatura a qualquer momento",
|
"cancelNote3": "Você pode reativar sua assinatura a qualquer momento",
|
||||||
"confirmCancel": "Sim, Cancelar"
|
"confirmCancel": "Sim, Cancelar"
|
||||||
|
},
|
||||||
|
"landing": {
|
||||||
|
"nav": {
|
||||||
|
"features": "Recursos",
|
||||||
|
"pricing": "Preços",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"login": "Entrar",
|
||||||
|
"register": "Começar Agora"
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"title": "Assuma o Controle das suas",
|
||||||
|
"titleHighlight": "Finanças",
|
||||||
|
"subtitle": "Gestão financeira inteligente para pessoas e empresas. Acompanhe receitas, despesas e alcance seus objetivos financeiros.",
|
||||||
|
"cta": "Começar Grátis",
|
||||||
|
"ctaSecondary": "Ver Planos",
|
||||||
|
"previewBalance": "Saldo Total",
|
||||||
|
"previewIncome": "Receitas",
|
||||||
|
"previewExpense": "Despesas"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "Tudo que Você Precisa",
|
||||||
|
"subtitle": "Ferramentas poderosas para gerenciar seu dinheiro",
|
||||||
|
"item1": {
|
||||||
|
"title": "Múltiplas Contas",
|
||||||
|
"description": "Gerencie contas bancárias, cartões e dinheiro em um só lugar"
|
||||||
|
},
|
||||||
|
"item2": {
|
||||||
|
"title": "Categorias Inteligentes",
|
||||||
|
"description": "Categorização automática com palavras-chave e subcategorias"
|
||||||
|
},
|
||||||
|
"item3": {
|
||||||
|
"title": "Importação Bancária",
|
||||||
|
"description": "Importe extratos de Excel, CSV, OFX e PDF"
|
||||||
|
},
|
||||||
|
"item4": {
|
||||||
|
"title": "Relatórios Detalhados",
|
||||||
|
"description": "Gráficos e análises para entender seus gastos"
|
||||||
|
},
|
||||||
|
"item5": {
|
||||||
|
"title": "Metas e Orçamentos",
|
||||||
|
"description": "Defina objetivos e controle gastos mensais"
|
||||||
|
},
|
||||||
|
"item6": {
|
||||||
|
"title": "Multi-moeda",
|
||||||
|
"description": "Gerencie finanças em diferentes moedas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pricing": {
|
||||||
|
"title": "Planos Simples, Preços Justos",
|
||||||
|
"subtitle": "Escolha o plano que atende às suas necessidades",
|
||||||
|
"monthly": "Mensal",
|
||||||
|
"annual": "Anual",
|
||||||
|
"popular": "Mais Popular",
|
||||||
|
"perMonth": "/mês",
|
||||||
|
"perYear": "/ano",
|
||||||
|
"startFree": "Começar Grátis",
|
||||||
|
"subscribe": "Assinar Agora",
|
||||||
|
"billedAnnually": "Cobrado anualmente"
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Perguntas Frequentes",
|
||||||
|
"q1": "Meus dados estão seguros?",
|
||||||
|
"a1": "Sim! Usamos criptografia de nível bancário (SSL/TLS) e seus dados são armazenados em servidores seguros com backups regulares. Nunca compartilhamos suas informações com terceiros.",
|
||||||
|
"q2": "Posso cancelar quando quiser?",
|
||||||
|
"a2": "Sim, você pode cancelar sua assinatura a qualquer momento sem taxas. Você manterá o acesso até o final do período que já pagou.",
|
||||||
|
"q3": "Quais bancos são compatíveis?",
|
||||||
|
"a3": "Você pode importar extratos de qualquer banco que exporte para Excel, CSV, OFX ou PDF. Temos mapeamentos predefinidos para os principais bancos.",
|
||||||
|
"q4": "Como funciona o período de teste?",
|
||||||
|
"a4": "Você tem acesso completo a todos os recursos durante o período de teste. Não é necessário cartão de crédito para começar. No final, escolha o plano que atende às suas necessidades."
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"title": "Pronto para Transformar suas Finanças?",
|
||||||
|
"subtitle": "Junte-se a milhares de usuários que já assumiram o controle do seu dinheiro.",
|
||||||
|
"button": "Criar Conta Grátis"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"description": "Gestão Financeira Inteligente para pessoas e empresas.",
|
||||||
|
"product": "Produto",
|
||||||
|
"company": "Empresa",
|
||||||
|
"legal": "Legal",
|
||||||
|
"features": "Recursos",
|
||||||
|
"pricing": "Preços",
|
||||||
|
"about": "Sobre Nós",
|
||||||
|
"contact": "Contato",
|
||||||
|
"privacy": "Política de Privacidade",
|
||||||
|
"terms": "Termos de Uso",
|
||||||
|
"rights": "Todos os direitos reservados."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
349
frontend/src/pages/Landing.css
Normal file
349
frontend/src/pages/Landing.css
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
/* Landing Page Styles */
|
||||||
|
|
||||||
|
/* Variables */
|
||||||
|
:root {
|
||||||
|
--landing-primary: #3b82f6;
|
||||||
|
--landing-primary-dark: #1e40af;
|
||||||
|
--landing-secondary: #10b981;
|
||||||
|
--landing-dark: #0f172a;
|
||||||
|
--landing-dark-lighter: #1e293b;
|
||||||
|
--landing-text: #f1f5f9;
|
||||||
|
--landing-text-muted: #94a3b8;
|
||||||
|
--landing-border: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-page {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
color: var(--landing-text);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar */
|
||||||
|
.landing-navbar {
|
||||||
|
background: rgba(15, 23, 42, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid var(--landing-border);
|
||||||
|
padding: 1rem 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-navbar.scrolled {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-navbar .nav-link {
|
||||||
|
color: var(--landing-text-muted) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.5rem 1rem !important;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-navbar .nav-link:hover {
|
||||||
|
color: var(--landing-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero Section */
|
||||||
|
.hero-section {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(ellipse at 20% 20%, rgba(59, 130, 246, 0.15), transparent 50%),
|
||||||
|
radial-gradient(ellipse at 80% 80%, rgba(16, 185, 129, 0.1), transparent 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section .container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dashboard Preview */
|
||||||
|
.hero-image {
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-preview {
|
||||||
|
background: var(--landing-dark-lighter);
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--landing-border);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||||
|
transform: rotateY(-5deg) rotateX(5deg);
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-preview:hover {
|
||||||
|
transform: rotateY(0) rotateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--landing-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-dots .dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-dots .dot.red { background: #ef4444; }
|
||||||
|
.preview-dots .dot.yellow { background: #f59e0b; }
|
||||||
|
.preview-dots .dot.green { background: #22c55e; }
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-card {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-card i {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-card.balance i { color: var(--landing-primary); }
|
||||||
|
.preview-card.income i { color: #22c55e; }
|
||||||
|
.preview-card.expense i { color: #ef4444; }
|
||||||
|
|
||||||
|
.preview-card.income span { color: #22c55e; }
|
||||||
|
.preview-card.expense span { color: #ef4444; }
|
||||||
|
|
||||||
|
/* Features Section */
|
||||||
|
.features-section {
|
||||||
|
background: var(--landing-dark-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
border: 1px solid var(--landing-border);
|
||||||
|
transition: transform 0.3s ease, border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
border-color: var(--landing-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
background: linear-gradient(135deg, var(--landing-primary), var(--landing-secondary));
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon i {
|
||||||
|
font-size: 28px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card h4 {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--landing-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pricing Section */
|
||||||
|
.pricing-section {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card {
|
||||||
|
background: var(--landing-dark-lighter);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
border: 1px solid var(--landing-border);
|
||||||
|
transition: transform 0.3s ease, border-color 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.featured {
|
||||||
|
border-color: var(--landing-primary);
|
||||||
|
box-shadow: 0 0 40px rgba(59, 130, 246, 0.2);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(135deg, var(--landing-primary), var(--landing-primary-dark));
|
||||||
|
color: white;
|
||||||
|
padding: 6px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
border-bottom: 1px solid var(--landing-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-header h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price .currency {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--landing-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price .amount {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--landing-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price .period {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--landing-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.billing-note {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--landing-text-muted);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 24px 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features li {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--landing-border);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAQ Section */
|
||||||
|
.faq-section {
|
||||||
|
background: var(--landing-dark-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-section .accordion-item {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
border: 1px solid var(--landing-border);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-section .accordion-button {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
color: var(--landing-text);
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-section .accordion-button:not(.collapsed) {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
color: var(--landing-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-section .accordion-button::after {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-section .accordion-button:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: var(--landing-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-section .accordion-body {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
color: var(--landing-text-muted);
|
||||||
|
padding: 0 24px 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTA Section */
|
||||||
|
.cta-section {
|
||||||
|
background: linear-gradient(135deg, var(--landing-primary), var(--landing-primary-dark));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.landing-footer {
|
||||||
|
background: var(--landing-dark);
|
||||||
|
border-top: 1px solid var(--landing-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-footer a {
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-footer a:hover {
|
||||||
|
color: var(--landing-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-section .row {
|
||||||
|
min-height: auto;
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price .amount {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
385
frontend/src/pages/Landing.jsx
Normal file
385
frontend/src/pages/Landing.jsx
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import api from '../services/api';
|
||||||
|
import logo from '../assets/logo-white.png';
|
||||||
|
import './Landing.css';
|
||||||
|
|
||||||
|
export default function Landing() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { user, isAuthenticated } = useAuth();
|
||||||
|
const [plans, setPlans] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
// Se já está autenticado, vai para dashboard
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated && user) {
|
||||||
|
navigate('/dashboard');
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, user, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadPlans();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadPlans = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get('/plans');
|
||||||
|
if (response.data.success) {
|
||||||
|
setPlans(response.data.data.plans);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading plans:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToSection = (id) => {
|
||||||
|
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
icon: 'bi-wallet2',
|
||||||
|
title: t('landing.features.accounts.title'),
|
||||||
|
description: t('landing.features.accounts.description'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bi-graph-up-arrow',
|
||||||
|
title: t('landing.features.analytics.title'),
|
||||||
|
description: t('landing.features.analytics.description'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bi-tags',
|
||||||
|
title: t('landing.features.categories.title'),
|
||||||
|
description: t('landing.features.categories.description'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bi-cloud-upload',
|
||||||
|
title: t('landing.features.import.title'),
|
||||||
|
description: t('landing.features.import.description'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bi-arrow-repeat',
|
||||||
|
title: t('landing.features.recurring.title'),
|
||||||
|
description: t('landing.features.recurring.description'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bi-shield-check',
|
||||||
|
title: t('landing.features.security.title'),
|
||||||
|
description: t('landing.features.security.description'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{
|
||||||
|
question: t('landing.faq.q1'),
|
||||||
|
answer: t('landing.faq.a1'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t('landing.faq.q2'),
|
||||||
|
answer: t('landing.faq.a2'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t('landing.faq.q3'),
|
||||||
|
answer: t('landing.faq.a3'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t('landing.faq.q4'),
|
||||||
|
answer: t('landing.faq.a4'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="landing-page">
|
||||||
|
{/* Navigation */}
|
||||||
|
<nav className="navbar navbar-expand-lg navbar-dark fixed-top landing-navbar">
|
||||||
|
<div className="container">
|
||||||
|
<Link to="/" className="navbar-brand d-flex align-items-center">
|
||||||
|
<img src={logo} alt="WebMoney" height="40" className="me-2" />
|
||||||
|
<span className="fw-bold">WebMoney</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNav"
|
||||||
|
>
|
||||||
|
<span className="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul className="navbar-nav mx-auto">
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className="nav-link btn btn-link" onClick={() => scrollToSection('features')}>
|
||||||
|
{t('landing.nav.features')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className="nav-link btn btn-link" onClick={() => scrollToSection('pricing')}>
|
||||||
|
{t('landing.nav.pricing')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className="nav-link btn btn-link" onClick={() => scrollToSection('faq')}>
|
||||||
|
{t('landing.nav.faq')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="d-flex align-items-center gap-3">
|
||||||
|
<Link to="/login" className="btn btn-outline-light">
|
||||||
|
{t('landing.nav.login')}
|
||||||
|
</Link>
|
||||||
|
<Link to="/register" className="btn btn-primary">
|
||||||
|
{t('landing.nav.register')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="hero-section">
|
||||||
|
<div className="hero-bg"></div>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row align-items-center min-vh-100 py-5">
|
||||||
|
<div className="col-lg-6">
|
||||||
|
<h1 className="display-3 fw-bold text-white mb-4">
|
||||||
|
{t('landing.hero.title')}
|
||||||
|
</h1>
|
||||||
|
<p className="lead text-white-50 mb-4">
|
||||||
|
{t('landing.hero.subtitle')}
|
||||||
|
</p>
|
||||||
|
<div className="d-flex gap-3 flex-wrap">
|
||||||
|
<Link to="/register" className="btn btn-primary btn-lg">
|
||||||
|
<i className="bi bi-rocket-takeoff me-2"></i>
|
||||||
|
{t('landing.hero.cta')}
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-light btn-lg"
|
||||||
|
onClick={() => scrollToSection('features')}
|
||||||
|
>
|
||||||
|
{t('landing.hero.learnMore')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust badges */}
|
||||||
|
<div className="mt-5 d-flex align-items-center gap-4 text-white-50">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<i className="bi bi-shield-check fs-4 me-2 text-success"></i>
|
||||||
|
<span>{t('landing.hero.secure')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<i className="bi bi-credit-card fs-4 me-2 text-primary"></i>
|
||||||
|
<span>PayPal</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-lg-6 d-none d-lg-block">
|
||||||
|
<div className="hero-image">
|
||||||
|
<div className="dashboard-preview">
|
||||||
|
<div className="preview-header">
|
||||||
|
<div className="preview-dots">
|
||||||
|
<span className="dot red"></span>
|
||||||
|
<span className="dot yellow"></span>
|
||||||
|
<span className="dot green"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="preview-content">
|
||||||
|
<div className="preview-card balance">
|
||||||
|
<i className="bi bi-wallet2"></i>
|
||||||
|
<span>€12,450.00</span>
|
||||||
|
</div>
|
||||||
|
<div className="preview-card income">
|
||||||
|
<i className="bi bi-arrow-down-circle"></i>
|
||||||
|
<span>+€3,200</span>
|
||||||
|
</div>
|
||||||
|
<div className="preview-card expense">
|
||||||
|
<i className="bi bi-arrow-up-circle"></i>
|
||||||
|
<span>-€1,850</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Features Section */}
|
||||||
|
<section id="features" className="features-section py-5">
|
||||||
|
<div className="container py-5">
|
||||||
|
<div className="text-center mb-5">
|
||||||
|
<h2 className="display-5 fw-bold">{t('landing.features.title')}</h2>
|
||||||
|
<p className="lead text-muted">{t('landing.features.subtitle')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row g-4">
|
||||||
|
{features.map((feature, index) => (
|
||||||
|
<div key={index} className="col-md-6 col-lg-4">
|
||||||
|
<div className="feature-card h-100">
|
||||||
|
<div className="feature-icon">
|
||||||
|
<i className={`bi ${feature.icon}`}></i>
|
||||||
|
</div>
|
||||||
|
<h4>{feature.title}</h4>
|
||||||
|
<p className="text-muted">{feature.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Pricing Section */}
|
||||||
|
<section id="pricing" className="pricing-section py-5">
|
||||||
|
<div className="container py-5">
|
||||||
|
<div className="text-center mb-5">
|
||||||
|
<h2 className="display-5 fw-bold">{t('landing.pricing.title')}</h2>
|
||||||
|
<p className="lead text-muted">{t('landing.pricing.subtitle')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="text-center py-5">
|
||||||
|
<div className="spinner-border text-primary" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="row justify-content-center g-4">
|
||||||
|
{plans.map((plan) => (
|
||||||
|
<div key={plan.id} className="col-lg-4 col-md-6">
|
||||||
|
<div className={`pricing-card h-100 ${plan.is_featured ? 'featured' : ''}`}>
|
||||||
|
{plan.is_featured && (
|
||||||
|
<div className="featured-badge">
|
||||||
|
<i className="bi bi-star-fill me-1"></i>
|
||||||
|
{t('landing.pricing.popular')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="pricing-header">
|
||||||
|
<h3>{plan.name}</h3>
|
||||||
|
<div className="price">
|
||||||
|
{plan.is_free ? (
|
||||||
|
<span className="amount">{t('landing.pricing.free')}</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="currency">€</span>
|
||||||
|
<span className="amount">{plan.monthly_price?.toFixed(2) || plan.price}</span>
|
||||||
|
<span className="period">/{t('landing.pricing.month')}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{plan.billing_period === 'annual' && !plan.is_free && (
|
||||||
|
<p className="billing-note">
|
||||||
|
{t('landing.pricing.billedAnnually', { price: plan.price })}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="pricing-features">
|
||||||
|
{(plan.features || []).map((feature, idx) => (
|
||||||
|
<li key={idx}>
|
||||||
|
<i className="bi bi-check-circle-fill text-success me-2"></i>
|
||||||
|
{feature}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="pricing-footer">
|
||||||
|
<Link
|
||||||
|
to={`/register?plan=${plan.slug}`}
|
||||||
|
className={`btn w-100 ${plan.is_featured ? 'btn-primary' : 'btn-outline-primary'}`}
|
||||||
|
>
|
||||||
|
{plan.is_free ? t('landing.pricing.startFree') : t('landing.pricing.subscribe')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ Section */}
|
||||||
|
<section id="faq" className="faq-section py-5">
|
||||||
|
<div className="container py-5">
|
||||||
|
<div className="text-center mb-5">
|
||||||
|
<h2 className="display-5 fw-bold">{t('landing.faq.title')}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row justify-content-center">
|
||||||
|
<div className="col-lg-8">
|
||||||
|
<div className="accordion" id="faqAccordion">
|
||||||
|
{faqs.map((faq, index) => (
|
||||||
|
<div key={index} className="accordion-item">
|
||||||
|
<h2 className="accordion-header">
|
||||||
|
<button
|
||||||
|
className={`accordion-button ${index !== 0 ? 'collapsed' : ''}`}
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target={`#faq${index}`}
|
||||||
|
>
|
||||||
|
{faq.question}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id={`faq${index}`}
|
||||||
|
className={`accordion-collapse collapse ${index === 0 ? 'show' : ''}`}
|
||||||
|
data-bs-parent="#faqAccordion"
|
||||||
|
>
|
||||||
|
<div className="accordion-body">
|
||||||
|
{faq.answer}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<section className="cta-section py-5">
|
||||||
|
<div className="container py-5 text-center">
|
||||||
|
<h2 className="display-5 fw-bold text-white mb-4">
|
||||||
|
{t('landing.cta.title')}
|
||||||
|
</h2>
|
||||||
|
<p className="lead text-white-50 mb-4">
|
||||||
|
{t('landing.cta.subtitle')}
|
||||||
|
</p>
|
||||||
|
<Link to="/register" className="btn btn-light btn-lg">
|
||||||
|
<i className="bi bi-person-plus me-2"></i>
|
||||||
|
{t('landing.cta.button')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="landing-footer py-4">
|
||||||
|
<div className="container">
|
||||||
|
<div className="row align-items-center">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<img src={logo} alt="WebMoney" height="30" className="me-2" />
|
||||||
|
<span className="text-muted">© 2025 WebMoney. {t('landing.footer.rights')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 text-md-end mt-3 mt-md-0">
|
||||||
|
<a href="#" className="text-muted me-3">{t('landing.footer.privacy')}</a>
|
||||||
|
<a href="#" className="text-muted me-3">{t('landing.footer.terms')}</a>
|
||||||
|
<a href="mailto:support@webmoney.app" className="text-muted">{t('landing.footer.contact')}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,12 +1,18 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import Footer from '../components/Footer';
|
import Footer from '../components/Footer';
|
||||||
|
import api from '../services/api';
|
||||||
import logo from '../assets/logo-white.png';
|
import logo from '../assets/logo-white.png';
|
||||||
|
|
||||||
const Register = () => {
|
const Register = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const { register } = useAuth();
|
const { register } = useAuth();
|
||||||
|
const [plans, setPlans] = useState([]);
|
||||||
|
const [selectedPlan, setSelectedPlan] = useState(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
@ -15,6 +21,31 @@ const Register = () => {
|
|||||||
});
|
});
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPlans, setLoadingPlans] = useState(true);
|
||||||
|
|
||||||
|
// Carregar planos
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlans = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get('/plans');
|
||||||
|
setPlans(response.data.data || response.data);
|
||||||
|
|
||||||
|
// Verificar se há plano na URL
|
||||||
|
const planSlug = searchParams.get('plan');
|
||||||
|
if (planSlug && response.data.data) {
|
||||||
|
const plan = response.data.data.find(p => p.slug === planSlug);
|
||||||
|
if (plan) {
|
||||||
|
setSelectedPlan(plan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading plans:', error);
|
||||||
|
} finally {
|
||||||
|
setLoadingPlans(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlans();
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
@ -33,8 +64,26 @@ const Register = () => {
|
|||||||
setErrors({});
|
setErrors({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await register(formData);
|
const response = await register({
|
||||||
|
...formData,
|
||||||
|
plan_id: selectedPlan?.id,
|
||||||
|
});
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
// Se for plano pago, redirecionar para pagamento
|
||||||
|
if (selectedPlan && selectedPlan.price > 0) {
|
||||||
|
try {
|
||||||
|
const subscriptionResponse = await api.post('/subscription/subscribe', {
|
||||||
|
plan_id: selectedPlan.id,
|
||||||
|
});
|
||||||
|
if (subscriptionResponse.data.approve_url) {
|
||||||
|
window.location.href = subscriptionResponse.data.approve_url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (subError) {
|
||||||
|
console.error('Subscription error:', subError);
|
||||||
|
// Continuar para o dashboard mesmo sem subscrição
|
||||||
|
}
|
||||||
|
}
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -53,15 +102,57 @@ const Register = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row justify-content-center align-items-center min-vh-100">
|
<div className="row justify-content-center align-items-center min-vh-100">
|
||||||
<div className="col-md-6">
|
<div className="col-lg-8">
|
||||||
<div className="card shadow-lg border-0">
|
<div className="card shadow-lg border-0">
|
||||||
<div className="card-body p-5">
|
<div className="card-body p-5">
|
||||||
<div className="text-center mb-4">
|
<div className="text-center mb-4">
|
||||||
<img src={logo} alt="WebMoney" className="mb-3" style={{ height: '80px', width: 'auto' }} />
|
<Link to="/">
|
||||||
|
<img src={logo} alt="WebMoney" className="mb-3" style={{ height: '80px', width: 'auto' }} />
|
||||||
|
</Link>
|
||||||
<h2 className="fw-bold text-primary">WebMoney</h2>
|
<h2 className="fw-bold text-primary">WebMoney</h2>
|
||||||
<p className="text-muted">Crie sua conta</p>
|
<p className="text-muted">{t('auth.createAccount', 'Crea tu cuenta')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Plan Selection */}
|
||||||
|
{!loadingPlans && plans.length > 0 && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="form-label fw-semibold">
|
||||||
|
<i className="bi bi-box me-2"></i>
|
||||||
|
{t('register.selectPlan', 'Selecciona un plan')}
|
||||||
|
</label>
|
||||||
|
<div className="row g-3">
|
||||||
|
{plans.map((plan) => (
|
||||||
|
<div key={plan.id} className="col-md-4">
|
||||||
|
<div
|
||||||
|
className={`card h-100 cursor-pointer ${selectedPlan?.id === plan.id ? 'border-primary bg-primary bg-opacity-10' : ''}`}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => setSelectedPlan(plan)}
|
||||||
|
>
|
||||||
|
<div className="card-body text-center p-3">
|
||||||
|
<h6 className="card-title mb-1">{plan.name}</h6>
|
||||||
|
<p className="h5 mb-1 text-primary">
|
||||||
|
{plan.price > 0 ? `€${plan.price}` : t('pricing.free', 'Gratis')}
|
||||||
|
{plan.price > 0 && <small className="text-muted fs-6">/{plan.billing_period === 'yearly' ? t('pricing.year', 'año') : t('pricing.month', 'mes')}</small>}
|
||||||
|
</p>
|
||||||
|
{plan.trial_days > 0 && (
|
||||||
|
<small className="text-success">
|
||||||
|
<i className="bi bi-gift me-1"></i>
|
||||||
|
{plan.trial_days} {t('common.days', 'días')} {t('pricing.trial', 'de prueba')}
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
{selectedPlan?.id === plan.id && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<i className="bi bi-check-circle-fill text-primary"></i>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{errors.general && (
|
{errors.general && (
|
||||||
<div className="alert alert-danger" role="alert">
|
<div className="alert alert-danger" role="alert">
|
||||||
<i className="bi bi-exclamation-circle me-2"></i>
|
<i className="bi bi-exclamation-circle me-2"></i>
|
||||||
@ -70,106 +161,122 @@ const Register = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="mb-3">
|
<div className="row">
|
||||||
<label htmlFor="name" className="form-label">
|
<div className="col-md-6 mb-3">
|
||||||
Nombre completo
|
<label htmlFor="name" className="form-label">
|
||||||
</label>
|
{t('profile.name', 'Nombre completo')}
|
||||||
<input
|
</label>
|
||||||
type="text"
|
<input
|
||||||
className={`form-control ${errors.name ? 'is-invalid' : ''}`}
|
type="text"
|
||||||
id="name"
|
className={`form-control ${errors.name ? 'is-invalid' : ''}`}
|
||||||
name="name"
|
id="name"
|
||||||
value={formData.name}
|
name="name"
|
||||||
onChange={handleChange}
|
value={formData.name}
|
||||||
placeholder="Tu nombre"
|
onChange={handleChange}
|
||||||
required
|
placeholder={t('profile.namePlaceholder', 'Tu nombre')}
|
||||||
/>
|
required
|
||||||
{errors.name && (
|
/>
|
||||||
<div className="invalid-feedback">{errors.name}</div>
|
{errors.name && (
|
||||||
)}
|
<div className="invalid-feedback">{errors.name}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
|
<label htmlFor="email" className="form-label">
|
||||||
|
{t('auth.email', 'Email')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
className={`form-control ${errors.email ? 'is-invalid' : ''}`}
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
autoComplete="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="tu@email.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<div className="invalid-feedback">{errors.email}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="row">
|
||||||
<label htmlFor="email" className="form-label">
|
<div className="col-md-6 mb-3">
|
||||||
Email
|
<label htmlFor="password" className="form-label">
|
||||||
</label>
|
{t('auth.password', 'Contraseña')}
|
||||||
<input
|
</label>
|
||||||
type="email"
|
<input
|
||||||
className={`form-control ${errors.email ? 'is-invalid' : ''}`}
|
type="password"
|
||||||
id="email"
|
className={`form-control ${errors.password ? 'is-invalid' : ''}`}
|
||||||
name="email"
|
id="password"
|
||||||
autoComplete="email"
|
name="password"
|
||||||
value={formData.email}
|
autoComplete="new-password"
|
||||||
onChange={handleChange}
|
value={formData.password}
|
||||||
placeholder="tu@email.com"
|
onChange={handleChange}
|
||||||
required
|
placeholder={t('profile.passwordHint', 'Mínimo 8 caracteres')}
|
||||||
/>
|
required
|
||||||
{errors.email && (
|
/>
|
||||||
<div className="invalid-feedback">{errors.email}</div>
|
{errors.password && (
|
||||||
)}
|
<div className="invalid-feedback">{errors.password}</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label htmlFor="password" className="form-label">
|
<label htmlFor="password_confirmation" className="form-label">
|
||||||
Contraseña
|
{t('profile.confirmPassword', 'Confirmar contraseña')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
className={`form-control ${errors.password ? 'is-invalid' : ''}`}
|
className={`form-control ${errors.password_confirmation ? 'is-invalid' : ''}`}
|
||||||
id="password"
|
id="password_confirmation"
|
||||||
name="password"
|
name="password_confirmation"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
value={formData.password}
|
value={formData.password_confirmation}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Mínimo 8 caracteres"
|
placeholder={t('register.repeatPassword', 'Repite tu contraseña')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.password && (
|
{errors.password_confirmation && (
|
||||||
<div className="invalid-feedback">{errors.password}</div>
|
<div className="invalid-feedback">{errors.password_confirmation}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="password_confirmation" className="form-label">
|
|
||||||
Confirmar contraseña
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
className={`form-control ${errors.password_confirmation ? 'is-invalid' : ''}`}
|
|
||||||
id="password_confirmation"
|
|
||||||
name="password_confirmation"
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={formData.password_confirmation}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="Repite tu contraseña"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
{errors.password_confirmation && (
|
|
||||||
<div className="invalid-feedback">{errors.password_confirmation}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary w-100 py-2"
|
className="btn btn-primary w-100 py-2 mt-3"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||||
Registrando...
|
{t('common.processing', 'Procesando...')}
|
||||||
|
</>
|
||||||
|
) : selectedPlan && selectedPlan.price > 0 ? (
|
||||||
|
<>
|
||||||
|
<i className="bi bi-credit-card me-2"></i>
|
||||||
|
{t('register.continueToPayment', 'Continuar al pago')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Crear Cuenta'
|
t('register.createAccount', 'Crear Cuenta')
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{selectedPlan && selectedPlan.price > 0 && (
|
||||||
|
<p className="text-center text-muted mt-2 small">
|
||||||
|
<i className="bi bi-shield-check me-1"></i>
|
||||||
|
{t('pricing.paypalSecure', 'Pago seguro con PayPal')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="text-center mt-4">
|
<div className="text-center mt-4">
|
||||||
<p className="mb-0">
|
<p className="mb-0">
|
||||||
¿Ya tienes cuenta?{' '}
|
{t('register.alreadyHaveAccount', '¿Ya tienes cuenta?')}{' '}
|
||||||
<Link to="/login" className="text-decoration-none fw-semibold">
|
<Link to="/login" className="text-decoration-none fw-semibold">
|
||||||
Inicia sesión aquí
|
{t('register.loginHere', 'Inicia sesión aquí')}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user