Improve public pages
This commit is contained in:
@@ -1,105 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, CardBody } from '../ui';
|
||||
import { PublicLayout } from '../layout';
|
||||
import { AlertCircle, RefreshCw, Home, HelpCircle } from 'lucide-react';
|
||||
|
||||
interface Props {
|
||||
error: string;
|
||||
details?: Array<{ service: string; error_message: string }>;
|
||||
onRetry: () => void;
|
||||
isRetrying?: boolean;
|
||||
}
|
||||
|
||||
export const DemoErrorScreen: React.FC<Props> = ({
|
||||
error,
|
||||
details,
|
||||
onRetry,
|
||||
isRetrying = false,
|
||||
}) => {
|
||||
return (
|
||||
<PublicLayout
|
||||
variant="centered"
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: false,
|
||||
}}
|
||||
>
|
||||
<div className="max-w-2xl mx-auto p-8">
|
||||
<Card className="shadow-xl">
|
||||
<CardBody className="p-8 text-center">
|
||||
<div className="flex justify-center mb-4">
|
||||
<AlertCircle className="w-20 h-20 text-[var(--color-error)]" />
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold text-[var(--color-error)] mb-3">
|
||||
Error en la Configuración del Demo
|
||||
</h1>
|
||||
|
||||
<p className="text-[var(--text-secondary)] mb-6 text-lg">
|
||||
{error}
|
||||
</p>
|
||||
|
||||
{details && details.length > 0 && (
|
||||
<div className="mb-6 p-4 bg-[var(--color-error)]/10 border border-[var(--color-error)] rounded-lg text-left">
|
||||
<h3 className="text-sm font-semibold text-[var(--text-primary)] mb-3">
|
||||
Detalles del error:
|
||||
</h3>
|
||||
<ul className="space-y-2">
|
||||
{details.map((detail, idx) => (
|
||||
<li key={idx} className="text-sm">
|
||||
<span className="font-medium text-[var(--text-primary)]">
|
||||
{detail.service}:
|
||||
</span>{' '}
|
||||
<span className="text-[var(--text-secondary)]">
|
||||
{detail.error_message}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-3 max-w-md mx-auto mt-6">
|
||||
<Button
|
||||
onClick={onRetry}
|
||||
disabled={isRetrying}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
>
|
||||
<RefreshCw className={`w-5 h-5 mr-2 ${isRetrying ? 'animate-spin' : ''}`} />
|
||||
{isRetrying ? 'Reintentando...' : 'Reintentar Configuración'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => window.location.href = '/demo'}
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
>
|
||||
<Home className="w-5 h-5 mr-2" />
|
||||
Volver a la Página Demo
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => window.location.href = '/contact'}
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
>
|
||||
<HelpCircle className="w-5 h-5 mr-2" />
|
||||
Contactar Soporte
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<p className="text-xs text-[var(--text-tertiary)]">
|
||||
Si el problema persiste, por favor contacta a nuestro equipo de soporte.
|
||||
</p>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</PublicLayout>
|
||||
);
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Badge, ProgressBar } from '../ui';
|
||||
import { CheckCircle, XCircle, Loader2, Clock } from 'lucide-react';
|
||||
import { ServiceProgress } from '@/api/services/demo';
|
||||
|
||||
interface Props {
|
||||
progress: Record<string, ServiceProgress>;
|
||||
}
|
||||
|
||||
const SERVICE_LABELS: Record<string, string> = {
|
||||
tenant: 'Tenant Virtual',
|
||||
inventory: 'Inventario y Productos',
|
||||
recipes: 'Recetas',
|
||||
sales: 'Historial de Ventas',
|
||||
orders: 'Pedidos de Clientes',
|
||||
suppliers: 'Proveedores',
|
||||
production: 'Producción',
|
||||
forecasting: 'Pronósticos',
|
||||
};
|
||||
|
||||
const SERVICE_DESCRIPTIONS: Record<string, string> = {
|
||||
tenant: 'Creando tu entorno demo aislado',
|
||||
inventory: 'Cargando ingredientes, recetas y datos de stock',
|
||||
recipes: 'Configurando recetas y fórmulas',
|
||||
sales: 'Importando registros de ventas históricas',
|
||||
orders: 'Configurando pedidos de clientes',
|
||||
suppliers: 'Importando datos de proveedores',
|
||||
production: 'Configurando lotes de producción',
|
||||
forecasting: 'Preparando datos de pronósticos',
|
||||
};
|
||||
|
||||
export const DemoProgressIndicator: React.FC<Props> = ({ progress }) => {
|
||||
return (
|
||||
<div className="mt-4 space-y-3">
|
||||
{Object.entries(progress).map(([serviceName, serviceProgress]) => (
|
||||
<div
|
||||
key={serviceName}
|
||||
className={`
|
||||
p-4 rounded-lg border-2 transition-all
|
||||
${
|
||||
serviceProgress.status === 'completed'
|
||||
? 'bg-green-50 dark:bg-green-900/20 border-green-500'
|
||||
: serviceProgress.status === 'failed'
|
||||
? 'bg-red-50 dark:bg-red-900/20 border-red-500'
|
||||
: serviceProgress.status === 'in_progress'
|
||||
? 'bg-blue-50 dark:bg-blue-900/20 border-blue-500'
|
||||
: 'bg-gray-50 dark:bg-gray-800/20 border-gray-300 dark:border-gray-700'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-3 flex-1">
|
||||
<div className="flex-shrink-0">
|
||||
{getStatusIcon(serviceProgress.status)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-[var(--text-primary)] truncate">
|
||||
{SERVICE_LABELS[serviceName] || serviceName}
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--text-secondary)] truncate">
|
||||
{SERVICE_DESCRIPTIONS[serviceName] || 'Procesando...'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant={getStatusVariant(serviceProgress.status)}>
|
||||
{getStatusLabel(serviceProgress.status)}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{serviceProgress.status === 'in_progress' && (
|
||||
<div className="my-2">
|
||||
<ProgressBar value={50} variant="primary" className="animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{serviceProgress.records_cloned > 0 && (
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-2">
|
||||
✓ {serviceProgress.records_cloned} registros clonados
|
||||
</p>
|
||||
)}
|
||||
|
||||
{serviceProgress.error && (
|
||||
<p className="text-xs text-[var(--color-error)] mt-2">
|
||||
Error: {serviceProgress.error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getStatusIcon(status: ServiceProgress['status']): React.ReactNode {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return <CheckCircle className="w-5 h-5 text-green-500" />;
|
||||
case 'failed':
|
||||
return <XCircle className="w-5 h-5 text-red-500" />;
|
||||
case 'in_progress':
|
||||
return <Loader2 className="w-5 h-5 text-blue-500 animate-spin" />;
|
||||
default:
|
||||
return <Clock className="w-5 h-5 text-gray-400" />;
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(status: ServiceProgress['status']): string {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'Completado';
|
||||
case 'failed':
|
||||
return 'Fallido';
|
||||
case 'in_progress':
|
||||
return 'En Progreso';
|
||||
default:
|
||||
return 'Pendiente';
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusVariant(
|
||||
status: ServiceProgress['status']
|
||||
): 'success' | 'error' | 'info' | 'default' {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'success';
|
||||
case 'failed':
|
||||
return 'error';
|
||||
case 'in_progress':
|
||||
return 'info';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
76
frontend/src/components/domain/auth/PilotBanner.tsx
Normal file
76
frontend/src/components/domain/auth/PilotBanner.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Pilot Banner Component
|
||||
* Displays when pilot=true URL parameter is detected
|
||||
* Shows 3-month free trial information and PILOT2025 coupon code
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Star, Check, Award } from 'lucide-react';
|
||||
import { Card } from '../../ui';
|
||||
|
||||
interface PilotBannerProps {
|
||||
couponCode: string;
|
||||
trialMonths: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PilotBanner: React.FC<PilotBannerProps> = ({
|
||||
couponCode,
|
||||
trialMonths,
|
||||
className = ''
|
||||
}) => {
|
||||
return (
|
||||
<Card className={`p-6 border-2 border-amber-500 dark:border-amber-600 bg-gradient-to-r from-amber-50 via-orange-50 to-amber-50 dark:from-amber-900/20 dark:via-orange-900/20 dark:to-amber-900/20 ${className}`}>
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
<div className="flex-shrink-0 w-12 h-12 rounded-full bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shadow-lg">
|
||||
<Award className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Star className="w-5 h-5 text-amber-600 dark:text-amber-400 fill-amber-600 dark:fill-amber-400" />
|
||||
<h3 className="text-lg font-bold text-amber-900 dark:text-amber-100">
|
||||
¡Programa Piloto Activado!
|
||||
</h3>
|
||||
<Star className="w-5 h-5 text-amber-600 dark:text-amber-400 fill-amber-600 dark:fill-amber-400" />
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-amber-800 dark:text-amber-200 mb-3">
|
||||
Has sido seleccionado para nuestro programa piloto exclusivo.
|
||||
Disfruta de <strong>{trialMonths} meses completamente gratis</strong> como uno de nuestros primeros 20 clientes.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div className="flex items-center gap-2 bg-white/60 dark:bg-gray-800/60 px-3 py-1.5 rounded-lg border border-amber-300 dark:border-amber-700">
|
||||
<Check className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Cupón: <span className="font-mono font-bold text-amber-700 dark:text-amber-300">{couponCode}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 bg-white/60 dark:bg-gray-800/60 px-3 py-1.5 rounded-lg border border-amber-300 dark:border-amber-700">
|
||||
<Check className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{trialMonths} meses gratis
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 bg-white/60 dark:bg-gray-800/60 px-3 py-1.5 rounded-lg border border-amber-300 dark:border-amber-700">
|
||||
<Check className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
40% descuento de por vida
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-amber-700 dark:text-amber-300 mt-3 italic">
|
||||
El cupón se aplicará automáticamente durante el registro. Tarjeta requerida para validación, sin cargo inmediato.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default PilotBanner;
|
||||
@@ -6,9 +6,11 @@ import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/au
|
||||
import { useToast } from '../../../hooks/ui/useToast';
|
||||
import { SubscriptionSelection } from './SubscriptionSelection';
|
||||
import PaymentForm from './PaymentForm';
|
||||
import PilotBanner from './PilotBanner';
|
||||
import { loadStripe } from '@stripe/stripe-js';
|
||||
import { Elements } from '@stripe/react-stripe-js';
|
||||
import { CheckCircle } from 'lucide-react';
|
||||
import { usePilotDetection } from '../../../hooks/usePilotDetection';
|
||||
|
||||
// Initialize Stripe - In production, use environment variable
|
||||
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || 'pk_test_51234567890123456789012345678901234567890123456789012345678901234567890123456789012345');
|
||||
@@ -47,20 +49,23 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
marketingConsent: false,
|
||||
analyticsConsent: false
|
||||
});
|
||||
|
||||
|
||||
const [errors, setErrors] = useState<Partial<SimpleUserRegistration>>({});
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
|
||||
const { register } = useAuthActions();
|
||||
const isLoading = useAuthLoading();
|
||||
const error = useAuthError();
|
||||
const { success: showSuccessToast, error: showErrorToast } = useToast();
|
||||
|
||||
|
||||
// Detect pilot program participation
|
||||
const { isPilot, couponCode, trialMonths } = usePilotDetection();
|
||||
|
||||
// Multi-step form state
|
||||
const [currentStep, setCurrentStep] = useState<RegistrationStep>('basic_info');
|
||||
const [currentStep, setCurrentStep] = useState<RegistrationStep>('basic_info');
|
||||
const [selectedPlan, setSelectedPlan] = useState<string>('starter');
|
||||
const [useTrial, setUseTrial] = useState<boolean>(false);
|
||||
const [useTrial, setUseTrial] = useState<boolean>(isPilot); // Auto-enable trial for pilot customers
|
||||
const [bypassPayment, setBypassPayment] = useState<boolean>(false);
|
||||
|
||||
// Helper function to determine password match status
|
||||
@@ -139,6 +144,8 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
subscription_plan: selectedPlan,
|
||||
use_trial: useTrial,
|
||||
payment_method_id: paymentMethodId,
|
||||
// Include coupon code if pilot customer
|
||||
coupon_code: isPilot ? couponCode : undefined,
|
||||
// Include consent data
|
||||
terms_accepted: formData.acceptTerms,
|
||||
privacy_accepted: formData.acceptTerms,
|
||||
@@ -148,7 +155,11 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
|
||||
await register(registrationData);
|
||||
|
||||
showSuccessToast(t('auth:register.registering', '¡Bienvenido! Tu cuenta ha sido creada correctamente.'), {
|
||||
const successMessage = isPilot
|
||||
? '¡Bienvenido al programa piloto! Tu cuenta ha sido creada con 3 meses gratis.'
|
||||
: '¡Bienvenido! Tu cuenta ha sido creada correctamente.';
|
||||
|
||||
showSuccessToast(t('auth:register.registering', successMessage), {
|
||||
title: t('auth:alerts.success_create', 'Cuenta creada exitosamente')
|
||||
});
|
||||
onSuccess?.();
|
||||
@@ -157,7 +168,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
title: t('auth:alerts.error_create', 'Error al crear la cuenta')
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handlePaymentSuccess = () => {
|
||||
handleRegistrationSubmit(); // In a real app, you would pass the payment method ID
|
||||
@@ -486,10 +497,14 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isPilot && couponCode && (
|
||||
<PilotBanner couponCode={couponCode} trialMonths={trialMonths} />
|
||||
)}
|
||||
|
||||
<SubscriptionSelection
|
||||
selectedPlan={selectedPlan}
|
||||
onPlanSelect={setSelectedPlan}
|
||||
showTrialOption={true}
|
||||
showTrialOption={!isPilot}
|
||||
onTrialSelect={setUseTrial}
|
||||
trialSelected={useTrial}
|
||||
/>
|
||||
|
||||
@@ -143,8 +143,8 @@ export const SubscriptionSelection: React.FC<SubscriptionSelectionProps> = ({
|
||||
return translations[feature] || feature.replace(/_/g, ' ');
|
||||
};
|
||||
|
||||
// Get trial days from the selected plan (default to 14 if not available)
|
||||
const trialDays = availablePlans.plans[selectedPlan]?.trial_days || 14;
|
||||
// Get trial days from the selected plan (default to 90 for pilot customers)
|
||||
const trialDays = 90; // 3 months for pilot customers
|
||||
|
||||
return (
|
||||
<div className={`space-y-4 ${className}`}>
|
||||
@@ -160,7 +160,7 @@ export const SubscriptionSelection: React.FC<SubscriptionSelectionProps> = ({
|
||||
{t('auth:subscription.trial_title', 'Prueba gratuita')}
|
||||
</h3>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{t('auth:subscription.trial_description', `Obtén ${trialDays} días de prueba gratuita - sin tarjeta de crédito requerida`)}
|
||||
{t('auth:subscription.trial_description', `Obtén 3 meses de prueba gratuita - tarjeta requerida para validación`)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,7 +218,7 @@ export const SubscriptionSelection: React.FC<SubscriptionSelectionProps> = ({
|
||||
{metadata.trial_days > 0 && (
|
||||
<Badge variant="success" className="text-xs px-2 py-0.5">
|
||||
<Zap className="w-3 h-3 mr-1" />
|
||||
{metadata.trial_days} días gratis
|
||||
3 meses gratis
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -194,10 +194,9 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
id: 'company',
|
||||
title: t('common:footer.sections.company', 'Empresa'),
|
||||
links: [
|
||||
{ id: 'about', label: t('common:footer.links.about', 'Acerca de'), href: '/about', external: true },
|
||||
{ id: 'blog', label: t('common:footer.links.blog', 'Blog'), href: 'https://blog.panaderia-ia.com', external: true },
|
||||
{ id: 'careers', label: t('common:footer.links.careers', 'Carreras'), href: 'https://careers.panaderia-ia.com', external: true },
|
||||
{ id: 'press', label: t('common:footer.links.press', 'Prensa'), href: '/press', external: true },
|
||||
{ id: 'about', label: t('common:footer.links.about', 'Acerca de'), href: '/about' },
|
||||
{ id: 'blog', label: t('common:footer.links.blog', 'Blog'), href: '/blog' },
|
||||
{ id: 'careers', label: t('common:footer.links.careers', 'Carreras'), href: '/careers' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, ThemeToggle } from '../../ui';
|
||||
import { CompactLanguageSelector } from '../../ui/LanguageSelector';
|
||||
|
||||
@@ -62,6 +63,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
navigationItems = [],
|
||||
variant = 'default',
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const headerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// Default navigation items
|
||||
@@ -180,7 +182,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
size="md"
|
||||
className="hidden sm:inline-flex font-medium hover:bg-[var(--bg-secondary)] transition-all duration-200"
|
||||
>
|
||||
Iniciar Sesión
|
||||
{t('common:header.login')}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
@@ -188,8 +190,8 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
size="md"
|
||||
className="bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-dark)] hover:opacity-90 text-white font-semibold shadow-lg hover:shadow-xl transition-all duration-200 px-6"
|
||||
>
|
||||
<span className="hidden sm:inline">Comenzar Gratis</span>
|
||||
<span className="sm:hidden">Registro</span>
|
||||
<span className="hidden sm:inline">{t('common:header.start_free')}</span>
|
||||
<span className="sm:hidden">{t('common:header.register')}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -210,7 +212,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="p-2"
|
||||
aria-label="Abrir menú de navegación"
|
||||
aria-label={t('common:header.open_menu')}
|
||||
onClick={() => {
|
||||
// TODO: Implement mobile menu
|
||||
console.log('Mobile menu toggle');
|
||||
@@ -237,7 +239,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
{showLanguageSelector && (
|
||||
<div className="py-2 border-b border-[var(--border-primary)] sm:hidden">
|
||||
<div className="text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
Idioma
|
||||
{t('common:header.language')}
|
||||
</div>
|
||||
<CompactLanguageSelector className="w-full" />
|
||||
</div>
|
||||
@@ -252,7 +254,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
size="md"
|
||||
className="w-full font-medium border border-[var(--border-primary)] hover:bg-[var(--bg-secondary)]"
|
||||
>
|
||||
Iniciar Sesión
|
||||
{t('common:header.login')}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
@@ -260,7 +262,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
size="md"
|
||||
className="w-full bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-dark)] hover:opacity-90 text-white font-semibold shadow-lg"
|
||||
>
|
||||
Comenzar Gratis
|
||||
{t('common:header.start_free')}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -261,7 +261,7 @@ export const PricingSection: React.FC = () => {
|
||||
<div className={`mt-2 px-3 py-1 text-sm font-medium rounded-full inline-block ${
|
||||
isPopular ? 'bg-white/20 text-white' : 'bg-[var(--color-success)]/10 text-[var(--color-success)]'
|
||||
}`}>
|
||||
{plan.trial_days} días gratis
|
||||
3 meses gratis
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -346,7 +346,7 @@ export const PricingSection: React.FC = () => {
|
||||
</Link>
|
||||
|
||||
<p className={`text-xs text-center mt-3 ${isPopular ? 'text-white/70' : 'text-[var(--text-secondary)]'}`}>
|
||||
{plan.trial_days} días gratis • Sin tarjeta requerida
|
||||
3 meses gratis • Tarjeta requerida para validación
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user