From 7c237c0acc0401875a43fb324ec4a64be27f68a1 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Mon, 11 Aug 2025 08:15:13 +0200 Subject: [PATCH] Improve the register page --- frontend/.env.development | 10 +- frontend/.env.production | 23 ++ frontend/package-lock.json | 111 ++++-- frontend/package.json | 46 +-- frontend/src/pages/auth/RegisterPage.tsx | 430 ++++++++++++++++++++--- frontend/vite-env.d.ts | 4 + 6 files changed, 523 insertions(+), 101 deletions(-) create mode 100644 frontend/.env.production diff --git a/frontend/.env.development b/frontend/.env.development index a8309c9b..b44b7f8f 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -12,4 +12,12 @@ VITE_ENABLE_WEBSOCKETS=false VITE_ENABLE_OFFLINE=false VITE_ENABLE_OPTIMISTIC_UPDATES=true VITE_ENABLE_DEDUPLICATION=true -VITE_ENABLE_METRICS=false \ No newline at end of file +VITE_ENABLE_METRICS=false + +# Stripe Configuration (Spanish Market) +VITE_STRIPE_PUBLISHABLE_KEY=pk_test_example_key_for_development +VITE_STRIPE_WEBHOOK_SECRET=whsec_example_webhook_secret_for_development + +# Development Flags +VITE_BYPASS_PAYMENT=true +VITE_DEV_MODE=true \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 00000000..f6bda9e6 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,23 @@ +# API Configuration +VITE_API_URL=https://api.pania.es/api/v1 +VITE_API_TIMEOUT=30000 +VITE_API_RETRIES=3 +VITE_API_RETRY_DELAY=1000 +VITE_API_LOGGING=false +VITE_API_CACHING=true +VITE_API_CACHE_TIMEOUT=300000 + +# Feature Flags +VITE_ENABLE_WEBSOCKETS=true +VITE_ENABLE_OFFLINE=true +VITE_ENABLE_OPTIMISTIC_UPDATES=true +VITE_ENABLE_DEDUPLICATION=true +VITE_ENABLE_METRICS=true + +# Stripe Configuration (Spanish Market) +VITE_STRIPE_PUBLISHABLE_KEY=pk_live_your_production_stripe_key +VITE_STRIPE_WEBHOOK_SECRET=whsec_your_production_webhook_secret + +# Development Flags (DISABLED IN PRODUCTION) +VITE_BYPASS_PAYMENT=false +VITE_DEV_MODE=false \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 766faf21..52888e6f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@hookform/resolvers": "^3.3.1", "@reduxjs/toolkit": "^1.9.5", + "@stripe/react-stripe-js": "^3.9.0", + "@stripe/stripe-js": "^7.8.0", "clsx": "^2.0.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", @@ -1213,6 +1215,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@stripe/react-stripe-js": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.9.0.tgz", + "integrity": "sha512-pN1Re7zUc3m61FFQROok685g3zsBQRzCmZDmTzO8iPU6zhLvu2JnC0LrG0FCzSp6kgGa8AQSzq4rpFSgyhkjKg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": ">=1.44.1 <8.0.0", + "react": ">=16.8.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.8.0.tgz", + "integrity": "sha512-DNXRfYUgkZlrniQORbA/wH8CdFRhiBSE0R56gYU0V5vvpJ9WZwvGrz9tBAZmfq2aTgw6SK7mNpmTizGzLWVezw==", + "license": "MIT", + "engines": { + "node": ">=12.16" + } + }, "node_modules/@tailwindcss/forms": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", @@ -1242,20 +1267,6 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, - "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -1605,13 +1616,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", - "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "version": "24.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", + "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~7.10.0" } }, "node_modules/@types/prop-types": { @@ -2559,9 +2570,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", "dev": true, "funding": [ { @@ -2579,8 +2590,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -2672,9 +2683,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001731", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", - "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "version": "1.0.30001734", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz", + "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==", "dev": true, "funding": [ { @@ -3252,9 +3263,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", - "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", + "version": "1.5.199", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", + "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", "dev": true, "license": "ISC" }, @@ -5775,7 +5786,7 @@ "postcss": "^8.2.14" } }, - "node_modules/postcss-selector-parser": { + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", @@ -5789,6 +5800,20 @@ "node": ">=4" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -6869,6 +6894,20 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7033,9 +7072,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "dev": true, "license": "MIT" }, @@ -7575,9 +7614,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { diff --git a/frontend/package.json b/frontend/package.json index daa82d6a..76b7241c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,46 +14,48 @@ "lint:fix": "eslint . --ext ts,tsx --fix" }, "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.15.0", - "@reduxjs/toolkit": "^1.9.5", - "react-redux": "^8.1.2", - "i18next": "^23.4.4", - "react-i18next": "^13.1.2", - "i18next-browser-languagedetector": "^7.1.0", - "react-hook-form": "^7.45.4", "@hookform/resolvers": "^3.3.1", - "zod": "^3.22.2", - "recharts": "^2.8.0", + "@reduxjs/toolkit": "^1.9.5", + "@stripe/react-stripe-js": "^3.9.0", + "@stripe/stripe-js": "^7.8.0", + "clsx": "^2.0.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", - "react-hot-toast": "^2.4.1", + "i18next": "^23.4.4", + "i18next-browser-languagedetector": "^7.1.0", "lucide-react": "^0.263.1", - "clsx": "^2.0.0", - "tailwind-merge": "^1.14.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.45.4", + "react-hot-toast": "^2.4.1", + "react-i18next": "^13.1.2", + "react-redux": "^8.1.2", + "react-router-dom": "^6.15.0", + "recharts": "^2.8.0", + "tailwind-merge": "^1.14.0", + "zod": "^3.22.2" }, "devDependencies": { + "@tailwindcss/forms": "^0.5.4", + "@tailwindcss/typography": "^0.5.9", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react": "^4.0.3", + "@vitest/ui": "^0.34.1", "autoprefixer": "^10.4.14", "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", "postcss": "^8.4.27", "tailwindcss": "^3.3.0", - "@tailwindcss/forms": "^0.5.4", - "@tailwindcss/typography": "^0.5.9", "typescript": "^5.0.2", "vite": "^4.4.5", - "vitest": "^0.34.1", - "@vitest/ui": "^0.34.1", - "@testing-library/react": "^13.4.0", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/user-event": "^14.4.3" + "vitest": "^0.34.1" }, "keywords": [ "bakery", @@ -70,4 +72,4 @@ }, "author": "PanIA Team", "license": "MIT" -} \ No newline at end of file +} diff --git a/frontend/src/pages/auth/RegisterPage.tsx b/frontend/src/pages/auth/RegisterPage.tsx index 7e75318a..1a3ba348 100644 --- a/frontend/src/pages/auth/RegisterPage.tsx +++ b/frontend/src/pages/auth/RegisterPage.tsx @@ -1,12 +1,48 @@ -import React, { useState } from 'react'; -import { Eye, EyeOff, Loader2, Check } from 'lucide-react'; +import React, { useState, useEffect } from 'react'; +import { Eye, EyeOff, Loader2, Check, CreditCard, Shield, ArrowRight } from 'lucide-react'; import toast from 'react-hot-toast'; +import { loadStripe } from '@stripe/stripe-js'; +import { + Elements, + CardElement, + useStripe, + useElements +} from '@stripe/react-stripe-js'; import { useAuth, RegisterRequest } from '../../api'; +// Development flags +const isDevelopment = import.meta.env.DEV; +const bypassPayment = import.meta.env.VITE_BYPASS_PAYMENT === 'true'; + +// Initialize Stripe with Spanish market configuration (only if not bypassing) +const stripePromise = !bypassPayment ? loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || '', { + locale: 'es' +}) : null; + +// Stripe card element options for Spanish market +const cardElementOptions = { + style: { + base: { + fontSize: '16px', + color: '#374151', + '::placeholder': { + color: '#9CA3AF', + }, + }, + invalid: { + color: '#EF4444', + }, + }, + hidePostalCode: false, // Keep postal code for better fraud protection +}; + +// Subscription pricing (monthly) +const SUBSCRIPTION_PRICE_EUR = 29.99; + interface RegisterPageProps { onLogin: (user: any, token: string) => void; onNavigateToLogin: () => void; @@ -15,27 +51,201 @@ interface RegisterPageProps { interface RegisterForm { fullName: string; email: string; + confirmEmail: string; password: string; confirmPassword: string; acceptTerms: boolean; + paymentCompleted: boolean; } const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin }) => { - const { register, isLoading, error } = useAuth(); + const { register, isLoading } = useAuth(); const [formData, setFormData] = useState({ fullName: '', email: '', + confirmEmail: '', password: '', confirmPassword: '', - acceptTerms: false + acceptTerms: false, + paymentCompleted: false }); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [errors, setErrors] = useState>({}); + const [passwordStrength, setPasswordStrength] = useState<{ + score: number; + checks: { [key: string]: boolean }; + message: string; + }>({ score: 0, checks: {}, message: '' }); + const [paymentStep, setPaymentStep] = useState<'form' | 'payment' | 'processing' | 'completed'>('form'); + const [paymentLoading, setPaymentLoading] = useState(false); - const validateForm = (): boolean => { + // Update password strength in real-time + useEffect(() => { + if (formData.password) { + const validation = validatePassword(formData.password); + setPasswordStrength(validation); + } + }, [formData.password]); + + // Payment processing component + const PaymentForm: React.FC<{ onPaymentSuccess: () => void }> = ({ onPaymentSuccess }) => { + const stripe = useStripe(); + const elements = useElements(); + + const handlePayment = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!stripe || !elements) { + toast.error('Stripe no está cargado correctamente'); + return; + } + + const card = elements.getElement(CardElement); + if (!card) { + toast.error('Elemento de tarjeta no encontrado'); + return; + } + + setPaymentLoading(true); + + try { + // Create payment method + const { error } = await stripe.createPaymentMethod({ + type: 'card', + card, + billing_details: { + name: formData.fullName, + email: formData.email, + }, + }); + + if (error) { + throw new Error(error.message); + } + + // Here you would typically create the subscription via your backend + // For now, we'll simulate a successful payment + toast.success('¡Pago procesado correctamente!'); + + setFormData(prev => ({ ...prev, paymentCompleted: true })); + setPaymentStep('completed'); + onPaymentSuccess(); + + } catch (error) { + console.error('Payment error:', error); + toast.error(error instanceof Error ? error.message : 'Error procesando el pago'); + } finally { + setPaymentLoading(false); + } + }; + + return ( +
+
+
+
+ +
+
+

Suscripción PanIA Pro

+

Facturación mensual

+
+
+
€{SUBSCRIPTION_PRICE_EUR}
+
/mes
+
+
+ +
+
+ + Predicciones de demanda ilimitadas +
+
+ + Análisis de tendencias avanzado +
+
+ + Soporte técnico prioritario +
+
+ + Integración con sistemas de punto de venta +
+
+
+ +
+ + +
+ + Pago seguro con encriptación SSL. Powered by Stripe. +
+
+ + +
+ ); + }; + + // Password validation based on backend rules + const validatePassword = (password: string) => { + const checks = { + length: password.length >= 8, + uppercase: /[A-Z]/.test(password), + lowercase: /[a-z]/.test(password), + numbers: /\d/.test(password), + // symbols: /[!@#$%^&*(),.?":{}|<>]/.test(password) // Backend doesn't require symbols + }; + + const score = Object.values(checks).filter(Boolean).length; + + let message = ''; + if (score < 4) { + if (!checks.length) message += 'Mínimo 8 caracteres. '; + if (!checks.uppercase) message += 'Una mayúscula. '; + if (!checks.lowercase) message += 'Una minúscula. '; + if (!checks.numbers) message += 'Un número. '; + } else { + message = '¡Contraseña segura!'; + } + + return { score, checks, message: message.trim() }; + }; + + + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validate form but exclude payment requirement for first step const newErrors: Partial = {}; if (!formData.fullName.trim()) { @@ -50,10 +260,19 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin newErrors.email = 'El email no es válido'; } + if (!formData.confirmEmail) { + newErrors.confirmEmail = 'Confirma tu email'; + } else if (formData.email !== formData.confirmEmail) { + newErrors.confirmEmail = 'Los emails no coinciden'; + } + if (!formData.password) { newErrors.password = 'La contraseña es obligatoria'; - } else if (formData.password.length < 8) { - newErrors.password = 'La contraseña debe tener al menos 8 caracteres'; + } else { + const passwordValidation = validatePassword(formData.password); + if (passwordValidation.score < 4) { + newErrors.password = passwordValidation.message; + } } if (formData.password !== formData.confirmPassword) { @@ -65,13 +284,29 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin } setErrors(newErrors); - return Object.keys(newErrors).length === 0; + + if (Object.keys(newErrors).length > 0) return; + + // Move to payment step, or bypass if in development mode + if (bypassPayment) { + // Development bypass: simulate payment completion + setFormData(prev => ({ ...prev, paymentCompleted: true })); + setPaymentStep('completed'); + toast.success('🚀 Modo desarrollo: Pago omitido'); + // Proceed directly to registration + setTimeout(() => { + handleRegistrationComplete(); + }, 1500); + } else { + setPaymentStep('payment'); + } }; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!validateForm()) return; + const handleRegistrationComplete = async () => { + if (!bypassPayment && !formData.paymentCompleted) { + toast.error('El pago debe completarse antes del registro'); + return; + } try { const registerData: RegisterRequest = { @@ -97,6 +332,9 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin } catch (error) { console.error('Registration error:', error); toast.error(error instanceof Error ? error.message : 'Error en el registro'); + // Reset payment if registration fails + setFormData(prev => ({ ...prev, paymentCompleted: false })); + setPaymentStep('payment'); } }; @@ -116,19 +354,68 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin } }; - const getPasswordStrength = (password: string) => { - let strength = 0; - if (password.length >= 8) strength++; - if (/[a-z]/.test(password)) strength++; - if (/[A-Z]/.test(password)) strength++; - if (/\d/.test(password)) strength++; - if (/[^A-Za-z0-9]/.test(password)) strength++; - return strength; - }; - const passwordStrength = getPasswordStrength(formData.password); - const strengthLabels = ['Muy débil', 'Débil', 'Regular', 'Buena', 'Excelente']; - const strengthColors = ['bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-blue-500', 'bg-green-500']; + // Render different content based on payment step + if (paymentStep === 'payment' && !bypassPayment) { + return ( + +
+
+ {/* Logo and Header */} +
+
+ 🥖 +
+

+ Finalizar Registro +

+

+ Solo un paso más para comenzar +

+

+ Suscripción segura con Stripe +

+
+ + {/* Payment Form */} +
+ + + +
+
+
+
+ ); + } + + if (paymentStep === 'completed') { + return ( +
+
+
+
+ +
+

+ ¡Bienvenido a PanIA! +

+

+ Tu cuenta ha sido creada exitosamente +

+

+ Redirigiendo al panel de control... +

+
+
+
+ ); + } return (
@@ -151,7 +438,7 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin {/* Register Form */}
-
+ {/* Full Name Field */}
+ {/* Confirm Email Field */} +
+ + + {formData.confirmEmail && formData.email === formData.confirmEmail && ( +
+ +
+ )} + {errors.confirmEmail && ( +

{errors.confirmEmail}

+ )} +
+ {/* Password Field */}
- {/* Password Strength Indicator */} + {/* Enhanced Password Strength Indicator */} {formData.password && (
-
- {[...Array(5)].map((_, i) => ( -
- ))} +
+ {Object.entries(passwordStrength.checks).map(([key, passed], index) => { + const labels = { + length: '8+ caracteres', + uppercase: 'Mayúscula', + lowercase: 'Minúscula', + numbers: 'Número' + }; + return ( +
+ {passed ? '✓' : '○'} {labels[key as keyof typeof labels]} +
+ ); + })}
-

- Seguridad: {strengthLabels[passwordStrength - 1] || 'Muy débil'} +

+ {passwordStrength.message}

)} @@ -370,10 +703,13 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin {isLoading ? ( <> - Creando cuenta... + Validando... ) : ( - 'Crear cuenta gratis' + <> + {bypassPayment ? 'Crear Cuenta (Dev)' : 'Continuar al Pago'} + + )}
@@ -395,8 +731,18 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin {/* Benefits */}
+ {bypassPayment && ( +
+

+ 🚀 Modo Desarrollo: Pago omitido para pruebas +

+
+ )}

- Al registrarte obtienes acceso completo durante 30 días gratis + {bypassPayment + ? 'Desarrollo • Pruebas • Sin pago requerido' + : 'Proceso seguro • Cancela en cualquier momento • Soporte 24/7' + }

@@ -405,11 +751,11 @@ const RegisterPage: React.FC = ({ onLogin, onNavigateToLogin
- Soporte 24/7 + Análisis de demanda
- Sin compromiso + Reduce desperdicios
diff --git a/frontend/vite-env.d.ts b/frontend/vite-env.d.ts index 7ae3b661..094b37c8 100644 --- a/frontend/vite-env.d.ts +++ b/frontend/vite-env.d.ts @@ -13,6 +13,10 @@ interface ImportMetaEnv { readonly VITE_ENABLE_OPTIMISTIC_UPDATES: string readonly VITE_ENABLE_DEDUPLICATION: string readonly VITE_ENABLE_METRICS: string + readonly VITE_STRIPE_PUBLISHABLE_KEY: string + readonly VITE_STRIPE_WEBHOOK_SECRET: string + readonly VITE_BYPASS_PAYMENT: string + readonly VITE_DEV_MODE: string } interface ImportMeta {