|
|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { ChevronRight, ChevronLeft, Check, Store, Users, Settings, Zap } from 'lucide-react';
|
|
|
|
|
import { ChevronRight, ChevronLeft, Check, Store, Users, Settings, Zap, Upload } from 'lucide-react';
|
|
|
|
|
import { Button, Card, Input } from '../../../../components/ui';
|
|
|
|
|
import { PageHeader } from '../../../../components/layout';
|
|
|
|
|
|
|
|
|
|
@@ -7,31 +7,38 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
const [currentStep, setCurrentStep] = useState(1);
|
|
|
|
|
const [formData, setFormData] = useState({
|
|
|
|
|
bakery: {
|
|
|
|
|
name: '',
|
|
|
|
|
type: 'traditional',
|
|
|
|
|
name: 'Panadería Artesanal El Buen Pan',
|
|
|
|
|
type: 'artisan',
|
|
|
|
|
size: 'medium',
|
|
|
|
|
location: '',
|
|
|
|
|
phone: '',
|
|
|
|
|
email: ''
|
|
|
|
|
location: 'Av. Principal 123, Centro Histórico',
|
|
|
|
|
phone: '+1 234 567 8900',
|
|
|
|
|
email: 'info@elbuenpan.com'
|
|
|
|
|
},
|
|
|
|
|
team: {
|
|
|
|
|
ownerName: '',
|
|
|
|
|
ownerName: 'María García López',
|
|
|
|
|
teamSize: '5-10',
|
|
|
|
|
roles: [],
|
|
|
|
|
experience: 'intermediate'
|
|
|
|
|
experience: 'experienced'
|
|
|
|
|
},
|
|
|
|
|
operations: {
|
|
|
|
|
openingHours: {
|
|
|
|
|
start: '07:00',
|
|
|
|
|
start: '06:00',
|
|
|
|
|
end: '20:00'
|
|
|
|
|
},
|
|
|
|
|
daysOpen: 6,
|
|
|
|
|
specialties: [],
|
|
|
|
|
specialties: ['bread', 'pastries', 'cakes'],
|
|
|
|
|
dailyProduction: 'medium'
|
|
|
|
|
},
|
|
|
|
|
upload: {
|
|
|
|
|
salesData: null,
|
|
|
|
|
inventoryData: null,
|
|
|
|
|
recipeData: null,
|
|
|
|
|
useTemplates: true,
|
|
|
|
|
skipUpload: false
|
|
|
|
|
},
|
|
|
|
|
goals: {
|
|
|
|
|
primaryGoals: [],
|
|
|
|
|
expectedRevenue: '',
|
|
|
|
|
primaryGoals: ['increase-sales', 'reduce-waste', 'improve-efficiency'],
|
|
|
|
|
expectedRevenue: '15000-30000',
|
|
|
|
|
timeline: '6months'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@@ -60,6 +67,13 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 4,
|
|
|
|
|
title: 'Carga de Datos',
|
|
|
|
|
description: 'Importa tus datos existentes para acelerar la configuración inicial',
|
|
|
|
|
icon: Upload,
|
|
|
|
|
fields: ['files', 'templates', 'validation']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 5,
|
|
|
|
|
title: 'Objetivos',
|
|
|
|
|
description: 'Metas y expectativas para tu panadería',
|
|
|
|
|
icon: Zap,
|
|
|
|
|
@@ -135,7 +149,8 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
|
|
|
|
|
const handleFinish = () => {
|
|
|
|
|
console.log('Onboarding completed:', formData);
|
|
|
|
|
// Handle completion logic
|
|
|
|
|
// Navigate to dashboard - onboarding is complete
|
|
|
|
|
window.location.href = '/app/dashboard';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const isStepComplete = (stepId: number) => {
|
|
|
|
|
@@ -148,6 +163,9 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
case 3:
|
|
|
|
|
return formData.operations.specialties.length > 0;
|
|
|
|
|
case 4:
|
|
|
|
|
return formData.upload.useTemplates || formData.upload.skipUpload ||
|
|
|
|
|
(formData.upload.salesData || formData.upload.inventoryData || formData.upload.recipeData);
|
|
|
|
|
case 5:
|
|
|
|
|
return formData.goals.primaryGoals.length > 0;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
@@ -158,34 +176,76 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
switch (currentStep) {
|
|
|
|
|
case 1:
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
|
|
|
|
<div className="space-y-8">
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<label className="block text-lg font-semibold text-[var(--text-primary)] mb-3">
|
|
|
|
|
Nombre de la Panadería *
|
|
|
|
|
</label>
|
|
|
|
|
<Input
|
|
|
|
|
value={formData.bakery.name}
|
|
|
|
|
onChange={(e) => handleInputChange('bakery', 'name', e.target.value)}
|
|
|
|
|
placeholder="Ej: Panadería San Miguel"
|
|
|
|
|
/>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Input
|
|
|
|
|
value={formData.bakery.name}
|
|
|
|
|
onChange={(e) => handleInputChange('bakery', 'name', e.target.value)}
|
|
|
|
|
placeholder="Ej: Panadería Artesanal El Buen Pan"
|
|
|
|
|
className="w-full transition-all duration-300 focus:scale-[1.02] focus:shadow-lg"
|
|
|
|
|
/>
|
|
|
|
|
{formData.bakery.name && (
|
|
|
|
|
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
|
|
|
<Check className="w-5 h-5 text-[var(--color-success)]" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
|
|
|
|
<label className="block text-lg font-semibold text-[var(--text-primary)] mb-4">
|
|
|
|
|
Tipo de Panadería
|
|
|
|
|
</label>
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
{bakeryTypes.map((type) => (
|
|
|
|
|
<label key={type.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)]">
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
name="bakeryType"
|
|
|
|
|
value={type.value}
|
|
|
|
|
checked={formData.bakery.type === type.value}
|
|
|
|
|
onChange={(e) => handleInputChange('bakery', 'type', e.target.value)}
|
|
|
|
|
className="text-[var(--color-info)]"
|
|
|
|
|
/>
|
|
|
|
|
<span className="text-sm">{type.label}</span>
|
|
|
|
|
<label
|
|
|
|
|
key={type.value}
|
|
|
|
|
className={`
|
|
|
|
|
group relative flex items-center space-x-4 p-4 border-2 rounded-xl cursor-pointer transition-all duration-300 hover:scale-105 hover:shadow-lg
|
|
|
|
|
${formData.bakery.type === type.value
|
|
|
|
|
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/5 shadow-lg'
|
|
|
|
|
: 'border-[var(--border-secondary)] bg-[var(--bg-secondary)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-tertiary)]'
|
|
|
|
|
}
|
|
|
|
|
`}
|
|
|
|
|
>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
name="bakeryType"
|
|
|
|
|
value={type.value}
|
|
|
|
|
checked={formData.bakery.type === type.value}
|
|
|
|
|
onChange={(e) => handleInputChange('bakery', 'type', e.target.value)}
|
|
|
|
|
className="sr-only"
|
|
|
|
|
/>
|
|
|
|
|
<div className={`
|
|
|
|
|
w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all duration-300
|
|
|
|
|
${formData.bakery.type === type.value
|
|
|
|
|
? 'border-[var(--color-primary)] bg-[var(--color-primary)]'
|
|
|
|
|
: 'border-[var(--border-tertiary)] group-hover:border-[var(--color-primary)]'
|
|
|
|
|
}
|
|
|
|
|
`}>
|
|
|
|
|
{formData.bakery.type === type.value && (
|
|
|
|
|
<div className="w-2 h-2 bg-white rounded-full"></div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span className={`
|
|
|
|
|
font-medium transition-colors duration-300
|
|
|
|
|
${formData.bakery.type === type.value ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)] group-hover:text-[var(--text-primary)]'}
|
|
|
|
|
`}>
|
|
|
|
|
{type.label}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Selection indicator */}
|
|
|
|
|
{formData.bakery.type === type.value && (
|
|
|
|
|
<div className="absolute top-2 right-2">
|
|
|
|
|
<Check className="w-4 h-4 text-[var(--color-primary)]" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
@@ -356,6 +416,136 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-8">
|
|
|
|
|
<div className="text-center p-6 bg-[var(--color-info)]/10 rounded-xl border border-[var(--color-info)]/20">
|
|
|
|
|
<Upload className="w-12 h-12 text-[var(--color-info)] mx-auto mb-4" />
|
|
|
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
|
|
|
|
Acelera tu configuración
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-[var(--text-secondary)]">
|
|
|
|
|
Importa tus datos existentes para configurar automáticamente productos, inventario y recetas.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Upload Options */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
|
{/* Sales Data Upload */}
|
|
|
|
|
<div className="border-2 border-dashed border-[var(--border-secondary)] rounded-xl p-6 text-center transition-all duration-300 hover:border-[var(--color-primary)] hover:bg-[var(--color-primary)]/5">
|
|
|
|
|
<div className="w-12 h-12 bg-[var(--color-success)]/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<svg className="w-6 h-6 text-[var(--color-success)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Datos de Ventas</h4>
|
|
|
|
|
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
|
|
|
|
CSV con historial de ventas
|
|
|
|
|
</p>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".csv,.xlsx,.xls"
|
|
|
|
|
onChange={(e) => handleInputChange('upload', 'salesData', e.target.files?.[0] || null)}
|
|
|
|
|
className="hidden"
|
|
|
|
|
id="sales-upload"
|
|
|
|
|
/>
|
|
|
|
|
<label htmlFor="sales-upload" className="btn btn-outline text-sm cursor-pointer">
|
|
|
|
|
{formData.upload.salesData ? 'Archivo cargado ✓' : 'Seleccionar archivo'}
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Inventory Data Upload */}
|
|
|
|
|
<div className="border-2 border-dashed border-[var(--border-secondary)] rounded-xl p-6 text-center transition-all duration-300 hover:border-[var(--color-primary)] hover:bg-[var(--color-primary)]/5">
|
|
|
|
|
<div className="w-12 h-12 bg-[var(--color-warning)]/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<svg className="w-6 h-6 text-[var(--color-warning)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Inventario</h4>
|
|
|
|
|
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
|
|
|
|
Lista de productos e ingredientes
|
|
|
|
|
</p>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".csv,.xlsx,.xls"
|
|
|
|
|
onChange={(e) => handleInputChange('upload', 'inventoryData', e.target.files?.[0] || null)}
|
|
|
|
|
className="hidden"
|
|
|
|
|
id="inventory-upload"
|
|
|
|
|
/>
|
|
|
|
|
<label htmlFor="inventory-upload" className="btn btn-outline text-sm cursor-pointer">
|
|
|
|
|
{formData.upload.inventoryData ? 'Archivo cargado ✓' : 'Seleccionar archivo'}
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Recipe Data Upload */}
|
|
|
|
|
<div className="border-2 border-dashed border-[var(--border-secondary)] rounded-xl p-6 text-center transition-all duration-300 hover:border-[var(--color-primary)] hover:bg-[var(--color-primary)]/5">
|
|
|
|
|
<div className="w-12 h-12 bg-[var(--color-info)]/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<svg className="w-6 h-6 text-[var(--color-info)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Recetas</h4>
|
|
|
|
|
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
|
|
|
|
Recetas y fórmulas existentes
|
|
|
|
|
</p>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".csv,.xlsx,.xls,.pdf"
|
|
|
|
|
onChange={(e) => handleInputChange('upload', 'recipeData', e.target.files?.[0] || null)}
|
|
|
|
|
className="hidden"
|
|
|
|
|
id="recipe-upload"
|
|
|
|
|
/>
|
|
|
|
|
<label htmlFor="recipe-upload" className="btn btn-outline text-sm cursor-pointer">
|
|
|
|
|
{formData.upload.recipeData ? 'Archivo cargado ✓' : 'Seleccionar archivo'}
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Alternative Options */}
|
|
|
|
|
<div className="space-y-4 pt-6 border-t border-[var(--border-secondary)]">
|
|
|
|
|
<h4 className="font-semibold text-[var(--text-primary)] mb-4">Opciones alternativas</h4>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<label className="flex items-center space-x-3 p-4 border rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)] transition-all duration-200">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={formData.upload.useTemplates}
|
|
|
|
|
onChange={(e) => handleInputChange('upload', 'useTemplates', e.target.checked)}
|
|
|
|
|
className="text-[var(--color-primary)] rounded"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="font-medium text-[var(--text-primary)]">
|
|
|
|
|
Usar plantillas predefinidas
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-sm text-[var(--text-secondary)]">
|
|
|
|
|
Comenzar con productos y recetas típicas de panadería
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label className="flex items-center space-x-3 p-4 border rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)] transition-all duration-200">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={formData.upload.skipUpload}
|
|
|
|
|
onChange={(e) => handleInputChange('upload', 'skipUpload', e.target.checked)}
|
|
|
|
|
className="text-[var(--color-primary)] rounded"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="font-medium text-[var(--text-primary)]">
|
|
|
|
|
Configurar manualmente después
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-sm text-[var(--text-secondary)]">
|
|
|
|
|
Saltar este paso y agregar datos posteriormente
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case 5:
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<div>
|
|
|
|
|
@@ -418,79 +608,158 @@ const OnboardingSetupPage: React.FC = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="p-6 max-w-4xl mx-auto">
|
|
|
|
|
<PageHeader
|
|
|
|
|
title="Configuración Inicial"
|
|
|
|
|
description="Configura tu panadería paso a paso para comenzar"
|
|
|
|
|
/>
|
|
|
|
|
<div className="p-4 sm:p-6 max-w-5xl mx-auto">
|
|
|
|
|
<div className="text-center mb-8 sm:mb-12">
|
|
|
|
|
<div className="inline-flex items-center justify-center w-14 h-14 sm:w-16 sm:h-16 bg-[var(--color-primary)] rounded-full mb-4 sm:mb-6 shadow-lg">
|
|
|
|
|
<svg className="w-6 h-6 sm:w-8 sm:h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<h1 className="text-2xl sm:text-3xl font-bold text-[var(--text-primary)] mb-3 sm:mb-4">
|
|
|
|
|
Configuración Inicial
|
|
|
|
|
</h1>
|
|
|
|
|
<p className="text-base sm:text-lg text-[var(--text-secondary)] max-w-2xl mx-auto px-4">
|
|
|
|
|
Configura tu panadería paso a paso para comenzar a usar la plataforma de manera óptima
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Progress Steps */}
|
|
|
|
|
<Card className="p-6 mb-8">
|
|
|
|
|
<div className="flex items-center justify-between mb-6">
|
|
|
|
|
{steps.map((step, index) => (
|
|
|
|
|
<div key={step.id} className="flex items-center">
|
|
|
|
|
<div className={`flex items-center justify-center w-10 h-10 rounded-full ${
|
|
|
|
|
step.id === currentStep
|
|
|
|
|
? 'bg-blue-600 text-white'
|
|
|
|
|
: step.id < currentStep || isStepComplete(step.id)
|
|
|
|
|
? 'bg-green-600 text-white'
|
|
|
|
|
: 'bg-[var(--bg-quaternary)] text-[var(--text-secondary)]'
|
|
|
|
|
}`}>
|
|
|
|
|
{step.id < currentStep || (step.id === currentStep && isStepComplete(step.id)) ? (
|
|
|
|
|
<Check className="w-5 h-5" />
|
|
|
|
|
) : (
|
|
|
|
|
<step.icon className="w-5 h-5" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{index < steps.length - 1 && (
|
|
|
|
|
<div className={`w-full h-1 mx-4 ${
|
|
|
|
|
step.id < currentStep ? 'bg-green-600' : 'bg-[var(--bg-quaternary)]'
|
|
|
|
|
}`} />
|
|
|
|
|
)}
|
|
|
|
|
<div className="max-w-4xl mx-auto">
|
|
|
|
|
{/* Enhanced Progress Section */}
|
|
|
|
|
<Card className="p-8 mb-8 shadow-lg">
|
|
|
|
|
<div className="flex items-center justify-between mb-6">
|
|
|
|
|
<div>
|
|
|
|
|
<h3 className="text-xl font-semibold text-[var(--text-primary)] mb-2">
|
|
|
|
|
Progreso de configuración
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-[var(--text-secondary)]">
|
|
|
|
|
Completando paso {currentStep} de {steps.length}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<h2 className="text-xl font-semibold text-[var(--text-primary)] mb-2">
|
|
|
|
|
Paso {currentStep}: {steps[currentStep - 1].title}
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-[var(--text-secondary)]">
|
|
|
|
|
{steps[currentStep - 1].description}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
<div className="text-right">
|
|
|
|
|
<div className="text-3xl font-bold text-[var(--color-primary)] mb-1">
|
|
|
|
|
{Math.round((currentStep / steps.length) * 100)}%
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm text-[var(--text-tertiary)]">Completado</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Enhanced Progress Bar */}
|
|
|
|
|
<div className="relative mb-8">
|
|
|
|
|
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-3 overflow-hidden">
|
|
|
|
|
<div
|
|
|
|
|
className="bg-[var(--color-primary)] h-full rounded-full transition-all duration-700 ease-out relative"
|
|
|
|
|
style={{ width: `${(currentStep / steps.length) * 100}%` }}
|
|
|
|
|
>
|
|
|
|
|
<div className="absolute inset-0 bg-white opacity-20 rounded-full"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Step Indicators */}
|
|
|
|
|
<div className="flex justify-between mb-8">
|
|
|
|
|
{steps.map((step, index) => (
|
|
|
|
|
<div key={step.id} className="flex flex-col items-center group">
|
|
|
|
|
<div className={`
|
|
|
|
|
w-12 h-12 rounded-full flex items-center justify-center text-sm font-medium transition-all duration-300 transform hover:scale-105 shadow-md
|
|
|
|
|
${step.id <= currentStep
|
|
|
|
|
? 'bg-[var(--color-primary)] text-white'
|
|
|
|
|
: 'bg-[var(--bg-quaternary)] text-[var(--text-tertiary)] border-2 border-[var(--border-secondary)]'
|
|
|
|
|
}
|
|
|
|
|
`}>
|
|
|
|
|
{step.id < currentStep ? (
|
|
|
|
|
<Check className="w-5 h-5" />
|
|
|
|
|
) : step.id === currentStep ? (
|
|
|
|
|
<div className="w-3 h-3 bg-white rounded-full animate-pulse"></div>
|
|
|
|
|
) : (
|
|
|
|
|
<step.icon className="w-5 h-5" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Connection lines */}
|
|
|
|
|
{index < steps.length - 1 && (
|
|
|
|
|
<div className={`
|
|
|
|
|
absolute top-6 w-16 h-1 transition-all duration-500 ml-12
|
|
|
|
|
${step.id < currentStep ? 'bg-[var(--color-primary)]' : 'bg-[var(--bg-quaternary)]'}
|
|
|
|
|
`} />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<p className={`
|
|
|
|
|
text-xs mt-3 text-center max-w-20 leading-tight transition-all duration-300
|
|
|
|
|
${step.id === currentStep ? 'text-[var(--color-primary)] font-semibold' : 'text-[var(--text-tertiary)]'}
|
|
|
|
|
`}>
|
|
|
|
|
{step.title}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Current Step Info */}
|
|
|
|
|
<div className="p-6 bg-[var(--color-info)]/10 rounded-xl border border-[var(--color-info)]/20">
|
|
|
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
|
|
|
|
Paso {currentStep}: {steps[currentStep - 1].title}
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-[var(--text-secondary)]">
|
|
|
|
|
{steps[currentStep - 1].description}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Step Content */}
|
|
|
|
|
<Card className="p-8 mb-8">
|
|
|
|
|
{renderStepContent()}
|
|
|
|
|
</Card>
|
|
|
|
|
{/* Enhanced Step Content */}
|
|
|
|
|
<Card className="p-8 mb-8 shadow-lg">
|
|
|
|
|
<div className="space-y-8">
|
|
|
|
|
{renderStepContent()}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Navigation */}
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={prevStep}
|
|
|
|
|
disabled={currentStep === 1}
|
|
|
|
|
>
|
|
|
|
|
<ChevronLeft className="w-4 h-4 mr-2" />
|
|
|
|
|
Anterior
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{currentStep === steps.length ? (
|
|
|
|
|
<Button onClick={handleFinish}>
|
|
|
|
|
<Check className="w-4 h-4 mr-2" />
|
|
|
|
|
Finalizar Configuración
|
|
|
|
|
</Button>
|
|
|
|
|
) : (
|
|
|
|
|
{/* Enhanced Navigation */}
|
|
|
|
|
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-0">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={nextStep}
|
|
|
|
|
disabled={!isStepComplete(currentStep)}
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={prevStep}
|
|
|
|
|
disabled={currentStep === 1}
|
|
|
|
|
className="w-full sm:w-auto flex items-center justify-center px-6 py-3 transition-all duration-300 hover:scale-105 order-2 sm:order-1"
|
|
|
|
|
>
|
|
|
|
|
Siguiente
|
|
|
|
|
<ChevronRight className="w-4 h-4 ml-2" />
|
|
|
|
|
<ChevronLeft className="w-4 h-4 mr-2" />
|
|
|
|
|
Anterior
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center space-x-3 text-[var(--text-tertiary)] order-1 sm:order-2">
|
|
|
|
|
<div className="flex space-x-2">
|
|
|
|
|
{steps.map((_, index) => (
|
|
|
|
|
<div
|
|
|
|
|
key={index}
|
|
|
|
|
className={`
|
|
|
|
|
w-2 h-2 rounded-full transition-all duration-300
|
|
|
|
|
${index + 1 === currentStep ? 'bg-[var(--color-primary)] scale-150' : index + 1 < currentStep ? 'bg-[var(--color-success)]' : 'bg-[var(--bg-quaternary)]'}
|
|
|
|
|
`}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-sm font-medium ml-4">
|
|
|
|
|
{currentStep} / {steps.length}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{currentStep === steps.length ? (
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleFinish}
|
|
|
|
|
className="w-full sm:w-auto flex items-center justify-center px-8 py-3 bg-[var(--color-success)] hover:bg-[var(--color-success)]/80 text-white transition-all duration-300 hover:scale-105 shadow-lg order-3"
|
|
|
|
|
>
|
|
|
|
|
<Check className="w-4 h-4 mr-2" />
|
|
|
|
|
<span className="hidden sm:inline">Finalizar Configuración</span>
|
|
|
|
|
<span className="sm:hidden">Finalizar</span>
|
|
|
|
|
</Button>
|
|
|
|
|
) : (
|
|
|
|
|
<Button
|
|
|
|
|
onClick={nextStep}
|
|
|
|
|
disabled={!isStepComplete(currentStep)}
|
|
|
|
|
className="w-full sm:w-auto flex items-center justify-center px-6 py-3 transition-all duration-300 hover:scale-105 order-3"
|
|
|
|
|
>
|
|
|
|
|
Siguiente
|
|
|
|
|
<ChevronRight className="w-4 h-4 ml-2" />
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|