Fix issues
This commit is contained in:
@@ -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()}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -82,12 +82,7 @@ spec:
|
||||
name: pos-integration-secrets
|
||||
- secretRef:
|
||||
name: whatsapp-secrets
|
||||
env:
|
||||
- name: TRAINING_PERSISTENCE_PATH
|
||||
value: "/app/training_state"
|
||||
volumeMounts:
|
||||
- name: training-state
|
||||
mountPath: /app/training_state
|
||||
- name: tmp-storage
|
||||
mountPath: /tmp
|
||||
resources:
|
||||
@@ -114,9 +109,6 @@ spec:
|
||||
periodSeconds: 15
|
||||
failureThreshold: 5
|
||||
volumes:
|
||||
- name: training-state
|
||||
persistentVolumeClaim:
|
||||
claimName: training-state-pvc
|
||||
- name: tmp-storage
|
||||
emptyDir:
|
||||
sizeLimit: 2Gi
|
||||
@@ -140,20 +132,3 @@ spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: training-service
|
||||
app.kubernetes.io/component: microservice
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: training-state-pvc
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: training-service
|
||||
app.kubernetes.io/component: storage
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
storageClassName: standard
|
||||
|
||||
@@ -311,85 +311,6 @@ patches:
|
||||
kind: Deployment
|
||||
name: external-service
|
||||
patch: |-
|
||||
- op: add
|
||||
path: /spec/template/spec/initContainers
|
||||
value:
|
||||
- name: wait-for-external-db
|
||||
image: postgres:13-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
until pg_isready -h $EXTERNAL_DB_HOST -p $EXTERNAL_DB_PORT -U $EXTERNAL_DB_USER; do
|
||||
echo "Waiting for external database..."
|
||||
sleep 2
|
||||
done
|
||||
echo "External database is ready!"
|
||||
env:
|
||||
- name: EXTERNAL_DB_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: EXTERNAL_DB_HOST
|
||||
- name: EXTERNAL_DB_PORT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: DB_PORT
|
||||
- name: EXTERNAL_DB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-secrets
|
||||
key: EXTERNAL_DB_USER
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-secrets
|
||||
key: EXTERNAL_DB_PASSWORD
|
||||
- name: wait-for-rabbitmq
|
||||
image: busybox:1.35
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
until nc -z $RABBITMQ_HOST $RABBITMQ_PORT; do
|
||||
echo "Waiting for RabbitMQ..."
|
||||
sleep 2
|
||||
done
|
||||
echo "RabbitMQ is ready!"
|
||||
env:
|
||||
- name: RABBITMQ_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: RABBITMQ_HOST
|
||||
- name: RABBITMQ_PORT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: RABBITMQ_PORT
|
||||
- name: wait-for-redis
|
||||
image: redis:7-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
until redis-cli -h $REDIS_HOST -p $REDIS_PORT ping; do
|
||||
echo "Waiting for Redis..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Redis is ready!"
|
||||
env:
|
||||
- name: REDIS_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: REDIS_HOST
|
||||
- name: REDIS_PORT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: REDIS_PORT
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/resources
|
||||
value:
|
||||
|
||||
@@ -85,34 +85,34 @@ class NotificationService(StandardFastAPIService):
|
||||
}
|
||||
|
||||
# Define custom health checks for notification service components
|
||||
async def check_email_service():
|
||||
"""Check email service health - service is ready even if credentials are invalid"""
|
||||
try:
|
||||
if not self.email_service:
|
||||
return False
|
||||
# Service is considered healthy if it's initialized, even if credentials fail
|
||||
# This allows the pod to be ready while external services may have config issues
|
||||
await self.email_service.health_check()
|
||||
return True
|
||||
except Exception as e:
|
||||
#async def check_email_service():
|
||||
# """Check email service health - service is ready even if credentials are invalid"""
|
||||
# try:
|
||||
# if not self.email_service:
|
||||
# return False
|
||||
# # Service is considered healthy if it's initialized, even if credentials fail
|
||||
# # This allows the pod to be ready while external services may have config issues
|
||||
# await self.email_service.health_check()
|
||||
# return True
|
||||
# except Exception as e:
|
||||
# Log but don't fail readiness - email service config issues shouldn't block the pod
|
||||
self.logger.error("Email service health check failed", error=str(e))
|
||||
# self.logger.error("Email service health check failed", error=str(e))
|
||||
# Return True to indicate service is ready (initialized) even if credentials are wrong
|
||||
return True
|
||||
# return True
|
||||
|
||||
async def check_whatsapp_service():
|
||||
"""Check WhatsApp service health - service is ready even if credentials are invalid"""
|
||||
try:
|
||||
if not self.whatsapp_service:
|
||||
return False
|
||||
#async def check_whatsapp_service():
|
||||
# """Check WhatsApp service health - service is ready even if credentials are invalid"""
|
||||
# try:
|
||||
# if not self.whatsapp_service:
|
||||
# return False
|
||||
# Service is considered healthy if it's initialized, even if credentials fail
|
||||
await self.whatsapp_service.health_check()
|
||||
return True
|
||||
except Exception as e:
|
||||
# await self.whatsapp_service.health_check()
|
||||
# return True
|
||||
# except Exception as e:
|
||||
# Log but don't fail readiness - WhatsApp config issues shouldn't block the pod
|
||||
self.logger.error("WhatsApp service health check failed", error=str(e))
|
||||
# self.logger.error("WhatsApp service health check failed", error=str(e))
|
||||
# Return True to indicate service is ready (initialized) even if credentials are wrong
|
||||
return True
|
||||
# return True
|
||||
|
||||
async def check_sse_service():
|
||||
"""Check SSE service health"""
|
||||
@@ -125,14 +125,14 @@ class NotificationService(StandardFastAPIService):
|
||||
self.logger.error("SSE service health check failed", error=str(e))
|
||||
return False
|
||||
|
||||
async def check_messaging():
|
||||
"""Check messaging service health"""
|
||||
try:
|
||||
from app.services.messaging import notification_publisher
|
||||
return bool(notification_publisher and notification_publisher.connected)
|
||||
except Exception as e:
|
||||
self.logger.error("Messaging health check failed", error=str(e))
|
||||
return False
|
||||
#async def check_messaging():
|
||||
# """Check messaging service health"""
|
||||
# try:
|
||||
# from app.services.messaging import notification_publisher
|
||||
# return bool(notification_publisher and notification_publisher.connected)
|
||||
# except Exception as e:
|
||||
# self.logger.error("Messaging health check failed", error=str(e))
|
||||
# return False
|
||||
|
||||
super().__init__(
|
||||
service_name="notification-service",
|
||||
@@ -145,10 +145,10 @@ class NotificationService(StandardFastAPIService):
|
||||
database_manager=database_manager,
|
||||
expected_tables=notification_expected_tables,
|
||||
custom_health_checks={
|
||||
"email_service": check_email_service,
|
||||
"whatsapp_service": check_whatsapp_service,
|
||||
# "email_service": check_email_service,
|
||||
# "whatsapp_service": check_whatsapp_service,
|
||||
"sse_service": check_sse_service,
|
||||
"messaging": check_messaging
|
||||
# "messaging": check_messaging
|
||||
},
|
||||
enable_messaging=True,
|
||||
custom_metrics=notification_custom_metrics
|
||||
|
||||
Reference in New Issue
Block a user