Fix issues

This commit is contained in:
Urtzi Alfaro
2025-10-01 14:39:10 +02:00
parent 6fa655275f
commit 36b44c41f1
5 changed files with 229 additions and 324 deletions

View File

@@ -8,6 +8,7 @@ import { SubscriptionSelection } from './SubscriptionSelection';
import PaymentForm from './PaymentForm';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import { CheckCircle } from 'lucide-react';
// Initialize Stripe - In production, use environment variable
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || 'pk_test_51234567890123456789012345678901234567890123456789012345678901234567890123456789012345');
@@ -168,48 +169,69 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
};
// Render step indicator
const renderStepIndicator = () => (
<div className="flex justify-center mb-6">
<div className="flex items-center space-x-4">
<div className={`flex items-center justify-center w-8 h-8 rounded-full ${
currentStep === 'basic_info' ? 'bg-color-primary text-white' :
currentStep === 'subscription' || currentStep === 'payment' ? 'bg-color-success text-white' : 'bg-bg-secondary text-text-secondary'
}`}>
1
</div>
<div className="h-1 w-16 bg-border-primary"></div>
<div className={`flex items-center justify-center w-8 h-8 rounded-full ${
currentStep === 'subscription' ? 'bg-color-primary text-white' :
currentStep === 'payment' ? 'bg-color-success text-white' : 'bg-bg-secondary text-text-secondary'
}`}>
2
</div>
<div className="h-1 w-16 bg-border-primary"></div>
<div className={`flex items-center justify-center w-8 h-8 rounded-full ${
currentStep === 'payment' ? 'bg-color-primary text-white' : 'bg-bg-secondary text-text-secondary'
}`}>
3
const renderStepIndicator = () => {
const steps = [
{ key: 'basic_info', label: 'Información', number: 1 },
{ key: 'subscription', label: 'Plan', number: 2 },
{ key: 'payment', label: 'Pago', number: 3 }
];
const getStepIndex = (step: RegistrationStep) => {
return steps.findIndex(s => s.key === step);
};
const currentIndex = getStepIndex(currentStep);
return (
<div className="mb-6 sm:mb-8">
<div className="flex justify-center items-center">
{steps.map((step, index) => (
<React.Fragment key={step.key}>
<div className="flex flex-col items-center">
<div className={`flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 rounded-full font-semibold text-sm transition-all duration-200 ${
index < currentIndex
? 'bg-color-success text-white'
: index === currentIndex
? 'bg-color-primary text-white ring-4 ring-color-primary/20'
: 'bg-bg-secondary text-text-secondary'
}`}>
{index < currentIndex ? (
<CheckCircle className="w-5 h-5" />
) : (
step.number
)}
</div>
<span className={`mt-1 sm:mt-2 text-[10px] sm:text-xs font-medium hidden sm:block ${
index <= currentIndex ? 'text-text-primary' : 'text-text-secondary'
}`}>
{step.label}
</span>
</div>
{index < steps.length - 1 && (
<div className={`h-0.5 w-8 sm:w-16 mx-2 sm:mx-4 transition-all duration-200 ${
index < currentIndex ? 'bg-color-success' : 'bg-border-primary'
}`} />
)}
</React.Fragment>
))}
</div>
</div>
</div>
);
);
};
// Render current step
const renderCurrentStep = () => {
switch (currentStep) {
case 'basic_info':
return (
<div className="space-y-6">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-text-primary mb-2">
<div className="space-y-4 sm:space-y-6">
<div className="text-center mb-4 sm:mb-6">
<h1 className="text-2xl sm:text-3xl font-bold text-text-primary mb-2">
{t('auth:register.title', 'Crear Cuenta')}
</h1>
<p className="text-text-secondary text-lg">
<p className="text-text-secondary text-sm sm:text-base">
{t('auth:register.subtitle', 'Únete y comienza hoy mismo')}
</p>
<p className="text-sm text-text-tertiary mt-2">
Paso 1 de 3: Información Básica
</p>
</div>
<form onSubmit={(e) => { e.preventDefault(); handleNextStep(); }} className="space-y-6">
@@ -396,14 +418,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
)}
</div>
<div className="flex justify-between pt-4">
<div></div> {/* Spacer for alignment */}
<div className="flex justify-end pt-4">
<Button
type="submit"
variant="primary"
size="lg"
disabled={isLoading || !validatePassword(formData.password) || !formData.acceptTerms || passwordMatchStatus !== 'match'}
className="w-48"
className="w-full sm:w-48"
>
Siguiente
</Button>
@@ -415,16 +436,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
case 'subscription':
return (
<div className="space-y-6">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-text-primary mb-2">
<div className="text-center">
<h1 className="text-2xl sm:text-3xl font-bold text-text-primary mb-2">
{t('auth:subscription.select_plan', 'Selecciona tu plan')}
</h1>
<p className="text-text-secondary text-lg">
<p className="text-text-secondary text-sm sm:text-base">
{t('auth:subscription.choose_plan', 'Elige el plan que mejor se adapte a tu negocio')}
</p>
<p className="text-sm text-text-tertiary mt-2">
Paso 2 de 3: Plan de Suscripción
</p>
</div>
<SubscriptionSelection
@@ -435,13 +453,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
trialSelected={useTrial}
/>
<div className="flex justify-between pt-4">
<div className="flex flex-col sm:flex-row justify-between gap-3 sm:gap-4 pt-2 border-t border-border-primary">
<Button
variant="outline"
size="lg"
onClick={handlePreviousStep}
disabled={isLoading}
className="w-48"
className="w-full sm:w-48 order-2 sm:order-1"
>
Anterior
</Button>
@@ -450,7 +468,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
size="lg"
onClick={handleNextStep}
disabled={isLoading}
className="w-48"
className="w-full sm:w-48 order-1 sm:order-2"
>
Siguiente
</Button>
@@ -460,17 +478,14 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
case 'payment':
return (
<div className="space-y-6">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-text-primary mb-2">
<div className="space-y-4 sm:space-y-6">
<div className="text-center mb-4 sm:mb-6">
<h1 className="text-2xl sm:text-3xl font-bold text-text-primary mb-2">
{t('auth:payment.payment_info', 'Información de Pago')}
</h1>
<p className="text-text-secondary text-lg">
<p className="text-text-secondary text-xs sm:text-sm">
{t('auth:payment.secure_payment', 'Tu información de pago está protegida con encriptación de extremo a extremo')}
</p>
<p className="text-sm text-text-tertiary mt-2">
Paso 3 de 3: Procesamiento de Pago
</p>
</div>
<Elements stripe={stripePromise}>
@@ -482,13 +497,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
/>
</Elements>
<div className="flex justify-between pt-4">
<div className="flex justify-start pt-4">
<Button
variant="outline"
size="lg"
onClick={handlePreviousStep}
disabled={isLoading}
className="w-48"
className="w-full sm:w-48"
>
Anterior
</Button>
@@ -499,7 +514,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
};
return (
<Card className={`p-8 w-full max-w-3xl ${className || ''}`} role="main">
<Card className={`p-4 sm:p-6 lg:p-8 w-full max-w-6xl ${className || ''}`} role="main">
{renderStepIndicator()}
{renderCurrentStep()}

View File

@@ -55,25 +55,16 @@ export const SubscriptionSelection: React.FC<SubscriptionSelectionProps> = ({
};
return (
<div className={`space-y-6 ${className}`}>
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-text-primary mb-2">
{t('auth:subscription.select_plan', 'Selecciona tu plan')}
</h2>
<p className="text-text-secondary">
{t('auth:subscription.choose_plan', 'Elige el plan que mejor se adapte a tu negocio')}
</p>
</div>
<div className={`space-y-4 ${className}`}>
{showTrialOption && (
<Card className="p-4 mb-6 bg-blue-50 border-blue-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
<Star className="w-5 h-5 text-blue-600" />
<Card className="p-4 border-2 border-color-primary/30 bg-bg-primary">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<div className="flex items-center gap-3 flex-1">
<div className="p-2.5 bg-color-primary/10 rounded-lg flex-shrink-0">
<Star className="w-5 h-5 text-color-primary" />
</div>
<div>
<h3 className="font-semibold text-text-primary">
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-text-primary text-base">
{t('auth:subscription.trial_title', 'Prueba gratuita')}
</h3>
<p className="text-sm text-text-secondary">
@@ -83,151 +74,154 @@ export const SubscriptionSelection: React.FC<SubscriptionSelectionProps> = ({
</div>
<Button
variant={trialSelected ? "primary" : "outline"}
size="sm"
size="md"
onClick={handleTrialToggle}
className="w-full sm:w-auto flex-shrink-0 min-w-[100px]"
>
{trialSelected
? t('auth:subscription.trial_active', 'Activo')
{trialSelected
? t('auth:subscription.trial_active', 'Activo')
: t('auth:subscription.trial_activate', 'Activar')}
</Button>
</div>
</Card>
)}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="space-y-3">
{Object.entries(availablePlans.plans).map(([planKey, plan]) => {
const isSelected = selectedPlan === planKey;
const getPlanColor = () => {
switch (planKey) {
case 'starter': return 'border-blue-500/30 bg-blue-500/5';
case 'professional': return 'border-purple-500/30 bg-purple-500/5';
case 'enterprise': return 'border-amber-500/30 bg-amber-500/5';
default: return 'border-border-primary bg-bg-secondary';
}
};
return (
<Card
key={planKey}
className={`relative p-6 cursor-pointer transition-all duration-200 hover:shadow-lg ${
getPlanColor()
} ${isSelected ? 'ring-2 ring-color-primary' : ''}`}
className={`relative p-5 cursor-pointer transition-all duration-200 border-2 ${
isSelected
? 'border-color-primary bg-color-primary/5 shadow-lg'
: 'border-border-primary bg-bg-primary hover:border-color-primary/40 hover:shadow-md'
}`}
onClick={() => onPlanSelect(planKey)}
>
{/* Popular Badge */}
{plan.popular && (
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
<Badge variant="primary" className="px-3 py-1">
<Star className="w-3 h-3 mr-1" />
<div className="absolute -top-2.5 right-4 z-10">
<Badge variant="primary" className="px-3 py-1 text-xs font-semibold flex items-center gap-1.5 shadow-md">
<Star className="w-3.5 h-3.5 fill-current" />
{t('auth:subscription.popular', 'Más Popular')}
</Badge>
</div>
)}
<div className="text-center mb-6">
<h4 className="text-xl font-bold text-text-primary mb-2">{plan.name}</h4>
<div className="text-3xl font-bold text-color-primary mb-1">
{subscriptionService.formatPrice(plan.monthly_price)}
<span className="text-lg text-text-secondary">/mes</span>
</div>
<p className="text-sm text-text-secondary">{plan.description}</p>
</div>
{/* Horizontal Layout */}
<div className="flex flex-col md:flex-row gap-6 items-start">
{/* Left Section: Plan Info & Pricing */}
<div className="flex-shrink-0 md:w-52">
<h4 className="text-2xl font-bold text-text-primary mb-2">{plan.name}</h4>
<div className="flex items-baseline gap-1 mb-3">
<span className="text-4xl font-bold text-color-primary">
{subscriptionService.formatPrice(plan.monthly_price)}
</span>
<span className="text-base text-text-secondary font-medium">/mes</span>
</div>
<p className="text-sm text-text-secondary leading-relaxed mb-4">{plan.description}</p>
<div className="space-y-3 mb-6">
<div className="flex items-center gap-2 text-sm">
<Users className="w-4 h-4 text-color-primary" />
<span>{plan.max_users === -1 ? 'Usuarios ilimitados' : `${plan.max_users} usuarios`}</span>
{/* Plan Limits */}
<div className="space-y-2.5 pt-2 border-t border-border-primary/50">
<div className="flex items-center gap-2.5 text-sm text-text-primary">
<Users className="w-4 h-4 text-color-primary flex-shrink-0" />
<span className="font-medium">{plan.max_users === -1 ? 'Usuarios ilimitados' : `${plan.max_users} usuarios`}</span>
</div>
<div className="flex items-center gap-2.5 text-sm text-text-primary">
<MapPin className="w-4 h-4 text-color-primary flex-shrink-0" />
<span className="font-medium">{plan.max_locations === -1 ? 'Ubicaciones ilimitadas' : `${plan.max_locations} ubicación${plan.max_locations > 1 ? 'es' : ''}`}</span>
</div>
<div className="flex items-center gap-2.5 text-sm text-text-primary">
<Package className="w-4 h-4 text-color-primary flex-shrink-0" />
<span className="font-medium">{plan.max_products === -1 ? 'Productos ilimitados' : `${plan.max_products} productos`}</span>
</div>
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<MapPin className="w-4 h-4 text-color-primary" />
<span>{plan.max_locations === -1 ? 'Ubicaciones ilimitadas' : `${plan.max_locations} ubicación${plan.max_locations > 1 ? 'es' : ''}`}</span>
{/* Divider */}
<div className="hidden md:block w-px self-stretch bg-border-primary/50"></div>
{/* Right Section: Features */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-4">
<TrendingUp className="w-5 h-5 text-color-primary flex-shrink-0" />
<h5 className="text-base font-bold text-text-primary">
{t('auth:subscription.features', 'Funcionalidades Incluidas')}
</h5>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-x-6 gap-y-2.5">
{(() => {
const getPlanFeatures = (planKey: string) => {
switch (planKey) {
case 'starter':
return [
'Panel de Control Básico',
'Gestión de Inventario',
'Gestión de Pedidos',
'Gestión de Proveedores',
'Punto de Venta Básico'
];
case 'professional':
return [
'Todo lo de Starter',
'Panel Avanzado',
'Analytics de Ventas',
'Pronósticos con IA',
'Optimización de Producción'
];
case 'enterprise':
return [
'Todo lo de Professional',
'Insights Predictivos IA',
'Analytics Multi-ubicación',
'Integración ERP',
'Soporte 24/7 Prioritario',
'API Personalizada'
];
default:
return [];
}
};
return getPlanFeatures(planKey).map((feature, index) => (
<div key={index} className="flex items-start gap-2.5 text-sm">
<CheckCircle className="w-4 h-4 text-color-success flex-shrink-0 mt-0.5" />
<span className="text-text-primary leading-snug">{feature}</span>
</div>
));
})()}
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<Package className="w-4 h-4 text-color-primary" />
<span>{plan.max_products === -1 ? 'Productos ilimitados' : `${plan.max_products} productos`}</span>
{/* Action Button */}
<div className="flex-shrink-0 md:w-28 flex items-center justify-stretch md:justify-end pt-4 md:pt-0 w-full md:w-auto border-t md:border-t-0 md:border-l border-border-primary/50 md:pl-6">
<Button
variant={isSelected ? "primary" : "outline"}
className="w-full md:w-auto md:min-w-[100px]"
size="lg"
onClick={(e) => {
e.stopPropagation();
onPlanSelect(planKey);
}}
>
{isSelected ? (
<>
<CheckCircle className="w-5 h-5 md:mr-2" />
<span className="hidden md:inline">Seleccionado</span>
</>
) : (
<>
<span className="md:hidden">Seleccionar</span>
<span className="hidden md:inline">Elegir Plan</span>
<ArrowRight className="w-5 h-5 ml-2" />
</>
)}
</Button>
</div>
</div>
{/* Features Section */}
<div className="border-t border-border-color pt-4 mb-6">
<h5 className="text-sm font-semibold text-text-primary mb-3 flex items-center">
<TrendingUp className="w-4 h-4 mr-2 text-color-primary" />
{t('auth:subscription.features', 'Funcionalidades Incluidas')}
</h5>
<div className="space-y-2">
{(() => {
const getPlanFeatures = (planKey: string) => {
switch (planKey) {
case 'starter':
return [
'✓ Panel de Control Básico',
'✓ Gestión de Inventario',
'✓ Gestión de Pedidos',
'✓ Gestión de Proveedores',
'✓ Punto de Venta Básico',
'✗ Analytics Avanzados',
'✗ Pronósticos IA',
'✗ Insights Predictivos'
];
case 'professional':
return [
'✓ Panel de Control Avanzado',
'✓ Gestión de Inventario Completa',
'✓ Analytics de Ventas',
'✓ Pronósticos con IA (92% precisión)',
'✓ Análisis de Rendimiento',
'✓ Optimización de Producción',
'✓ Integración POS',
'✗ Insights Predictivos Avanzados'
];
case 'enterprise':
return [
'✓ Todas las funcionalidades Professional',
'✓ Insights Predictivos con IA',
'✓ Analytics Multi-ubicación',
'✓ Integración ERP',
'✓ API Personalizada',
'✓ Gestor de Cuenta Dedicado',
'✓ Soporte 24/7 Prioritario',
'✓ Demo Personalizada'
];
default:
return [];
}
};
return getPlanFeatures(planKey).map((feature, index) => (
<div key={index} className={`text-xs flex items-center gap-2 ${
feature.startsWith('✓')
? 'text-green-600'
: 'text-text-secondary opacity-60'
}`}>
<span>{feature}</span>
</div>
));
})()}
</div>
</div>
<Button
variant={isSelected ? "primary" : plan.popular ? "primary" : "outline"}
className="w-full"
onClick={(e) => {
e.stopPropagation();
onPlanSelect(planKey);
}}
>
{isSelected ? (
<>
<CheckCircle className="w-4 h-4 mr-2" />
{t('auth:subscription.selected', 'Seleccionado')}
</>
) : (
<>
{t('auth:subscription.select', 'Seleccionar Plan')}
<ArrowRight className="w-4 h-4 ml-2" />
</>
)}
</Button>
</Card>
);
})}