Improve onboarding
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { Button } from '../../../../components/ui/Button';
|
||||
import { Card, CardHeader, CardBody } from '../../../../components/ui/Card';
|
||||
import { Badge } from '../../../../components/ui/Badge';
|
||||
import { Tooltip } from '../../../../components/ui/Tooltip';
|
||||
import { Button, StatsGrid, StatusCard, getStatusColor, SearchAndFilter, EmptyState } from '../../../../components/ui';
|
||||
import { useTenant } from '../../../../stores/tenant.store';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import {
|
||||
@@ -13,12 +10,6 @@ import {
|
||||
Building2,
|
||||
Settings,
|
||||
Users,
|
||||
Calendar,
|
||||
MapPin,
|
||||
Phone,
|
||||
Mail,
|
||||
Globe,
|
||||
MoreHorizontal,
|
||||
ArrowRight,
|
||||
Crown,
|
||||
Shield,
|
||||
@@ -31,6 +22,8 @@ const OrganizationsPage: React.FC = () => {
|
||||
const user = useAuthUser();
|
||||
const { currentTenant, availableTenants, switchTenant } = useTenant();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [roleFilter, setRoleFilter] = useState('');
|
||||
|
||||
const handleAddNewOrganization = () => {
|
||||
navigate('/app/onboarding?new=true');
|
||||
@@ -44,30 +37,14 @@ const OrganizationsPage: React.FC = () => {
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleManageTenant = (tenantId: string) => {
|
||||
// Navigate to tenant settings
|
||||
const handleManageTenant = () => {
|
||||
navigate(`/app/database/bakery-config`);
|
||||
};
|
||||
|
||||
const handleManageTeam = (tenantId: string) => {
|
||||
// Navigate to team management
|
||||
const handleManageTeam = () => {
|
||||
navigate(`/app/database/team`);
|
||||
};
|
||||
|
||||
const getRoleIcon = (ownerId: string) => {
|
||||
if (user?.id === ownerId) {
|
||||
return <Crown className="w-4 h-4 text-[var(--color-warning)]" />;
|
||||
}
|
||||
return <Shield className="w-4 h-4 text-[var(--color-primary)]" />;
|
||||
};
|
||||
|
||||
const getRoleLabel = (ownerId: string) => {
|
||||
if (user?.id === ownerId) {
|
||||
return 'Propietario';
|
||||
}
|
||||
return 'Miembro';
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
@@ -76,208 +53,197 @@ const OrganizationsPage: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const isOwner = (ownerId: string) => user?.id === ownerId;
|
||||
|
||||
// Filter organizations based on search and role
|
||||
const filteredTenants = useMemo(() => {
|
||||
let filtered = availableTenants || [];
|
||||
|
||||
if (searchTerm) {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
filtered = filtered.filter(tenant =>
|
||||
tenant.name.toLowerCase().includes(searchLower) ||
|
||||
tenant.business_type?.toLowerCase().includes(searchLower) ||
|
||||
tenant.city?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
}
|
||||
|
||||
if (roleFilter) {
|
||||
filtered = filtered.filter(tenant => {
|
||||
if (roleFilter === 'owner') return isOwner(tenant.owner_id);
|
||||
if (roleFilter === 'member') return !isOwner(tenant.owner_id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [availableTenants, searchTerm, roleFilter, user?.id]);
|
||||
|
||||
// Calculate stats
|
||||
const stats = useMemo(() => [
|
||||
{
|
||||
title: 'Organizaciones Totales',
|
||||
value: availableTenants?.length || 0,
|
||||
variant: 'default' as const,
|
||||
icon: Building2,
|
||||
},
|
||||
{
|
||||
title: 'Como Propietario',
|
||||
value: availableTenants?.filter(t => isOwner(t.owner_id)).length || 0,
|
||||
variant: 'warning' as const,
|
||||
icon: Crown,
|
||||
},
|
||||
{
|
||||
title: 'Como Miembro',
|
||||
value: availableTenants?.filter(t => !isOwner(t.owner_id)).length || 0,
|
||||
variant: 'info' as const,
|
||||
icon: Shield,
|
||||
},
|
||||
], [availableTenants, user?.id]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4 sm:p-6">
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title={t('settings:organization.title', 'Mis Organizaciones')}
|
||||
description={t('settings:organization.description', 'Gestiona tus panaderías y negocios')}
|
||||
actions={
|
||||
<Button
|
||||
onClick={handleAddNewOrganization}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Nueva Organización
|
||||
</Button>
|
||||
}
|
||||
title="Organizaciones"
|
||||
description="Gestiona tus organizaciones y configuraciones"
|
||||
actions={[
|
||||
{
|
||||
id: "add-organization",
|
||||
label: "Nueva Organización",
|
||||
variant: "primary" as const,
|
||||
icon: Plus,
|
||||
onClick: handleAddNewOrganization,
|
||||
tooltip: "Crear nueva organización",
|
||||
size: "md"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Statistics */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardBody className="text-center">
|
||||
<Building2 className="w-8 h-8 text-[var(--color-primary)] mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
{availableTenants?.length || 0}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">Organizaciones Totales</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{/* Stats Grid */}
|
||||
<StatsGrid
|
||||
stats={stats}
|
||||
columns={3}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<CardBody className="text-center">
|
||||
<Crown className="w-8 h-8 text-[var(--color-warning)] mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
{availableTenants?.filter(t => t.owner_id === user?.id).length || 0}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">Propietario</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{/* Search and Filter Controls */}
|
||||
<SearchAndFilter
|
||||
searchValue={searchTerm}
|
||||
onSearchChange={setSearchTerm}
|
||||
searchPlaceholder="Buscar por nombre, tipo o ciudad..."
|
||||
filters={[
|
||||
{
|
||||
key: 'role',
|
||||
label: 'Rol',
|
||||
type: 'dropdown',
|
||||
value: roleFilter,
|
||||
onChange: (value) => setRoleFilter(value as string),
|
||||
placeholder: 'Todos los roles',
|
||||
options: [
|
||||
{ value: 'owner', label: 'Propietario' },
|
||||
{ value: 'member', label: 'Miembro' }
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<CardBody className="text-center">
|
||||
<Shield className="w-8 h-8 text-[var(--color-primary)] mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
{availableTenants?.filter(t => t.owner_id !== user?.id).length || 0}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">Miembro</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
{/* Organizations Grid */}
|
||||
{filteredTenants && filteredTenants.length > 0 ? (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredTenants.map((tenant) => {
|
||||
const isActive = currentTenant?.id === tenant.id;
|
||||
const isOwnerRole = isOwner(tenant.owner_id);
|
||||
|
||||
{/* Organizations List */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Tus Organizaciones</h3>
|
||||
const statusConfig = {
|
||||
color: isActive ? getStatusColor('completed') : getStatusColor('default'),
|
||||
text: isActive ? 'Activa' : 'Inactiva',
|
||||
icon: isOwnerRole ? Crown : Shield,
|
||||
isCritical: false,
|
||||
isHighlight: isActive
|
||||
};
|
||||
|
||||
{availableTenants && availableTenants.length > 0 ? (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{availableTenants.map((tenant) => (
|
||||
<Card
|
||||
return (
|
||||
<StatusCard
|
||||
key={tenant.id}
|
||||
className={`transition-all duration-200 hover:shadow-lg ${
|
||||
currentTenant?.id === tenant.id
|
||||
? 'ring-2 ring-[var(--color-primary)]/20 bg-[var(--color-primary)]/5'
|
||||
: 'hover:border-[var(--color-primary)]/30'
|
||||
}`}
|
||||
>
|
||||
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-4">
|
||||
<div className="flex items-start gap-3 flex-1 min-w-0">
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<Building2 className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h4 className="font-semibold text-[var(--text-primary)] truncate">
|
||||
{tenant.name}
|
||||
</h4>
|
||||
{currentTenant?.id === tenant.id && (
|
||||
<Badge variant="primary" size="sm">Activa</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
{getRoleIcon(tenant.owner_id)}
|
||||
<span>{getRoleLabel(tenant.owner_id)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1">
|
||||
<Tooltip content="Configurar organización">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleManageTenant(tenant.id)}
|
||||
className="w-8 h-8 p-0"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{user?.id === tenant.owner_id && (
|
||||
<Tooltip content="Gestionar equipo">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleManageTeam(tenant.id)}
|
||||
className="w-8 h-8 p-0"
|
||||
>
|
||||
<Users className="w-4 h-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardBody className="pt-0">
|
||||
{/* Organization details */}
|
||||
<div className="space-y-2 mb-4">
|
||||
{tenant.business_type && (
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
<Badge variant="outline" size="sm">{tenant.business_type}</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tenant.address && (
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<MapPin className="w-4 h-4 flex-shrink-0" />
|
||||
<span className="truncate">{tenant.address}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tenant.city && (
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<MapPin className="w-4 h-4 flex-shrink-0" />
|
||||
<span>{tenant.city}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tenant.phone && (
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<Phone className="w-4 h-4 flex-shrink-0" />
|
||||
<span>{tenant.phone}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<Calendar className="w-4 h-4 flex-shrink-0" />
|
||||
<span>Creada el {formatDate(tenant.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
{currentTenant?.id !== tenant.id ? (
|
||||
<Button
|
||||
onClick={() => handleSwitchToTenant(tenant.id)}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
Cambiar a esta organización
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => navigate('/app/dashboard')}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
Ver dashboard
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Card>
|
||||
<CardBody className="text-center py-12">
|
||||
<Building2 className="w-16 h-16 text-[var(--text-tertiary)] mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
No tienes organizaciones
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] mb-6">
|
||||
Crea tu primera organización para comenzar a usar Bakery IA
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleAddNewOrganization}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Crear Primera Organización
|
||||
</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
id={tenant.id}
|
||||
statusIndicator={statusConfig}
|
||||
title={tenant.name}
|
||||
subtitle={tenant.business_type || 'Organización'}
|
||||
primaryValue={isOwnerRole ? 'Propietario' : 'Miembro'}
|
||||
primaryValueLabel=""
|
||||
secondaryInfo={
|
||||
tenant.city ? {
|
||||
label: 'Ubicación',
|
||||
value: tenant.city
|
||||
} : undefined
|
||||
}
|
||||
metadata={[
|
||||
`Creada ${formatDate(tenant.created_at)}`,
|
||||
...(tenant.address ? [tenant.address] : []),
|
||||
...(tenant.phone ? [tenant.phone] : [])
|
||||
]}
|
||||
onClick={() => {
|
||||
if (!isActive) {
|
||||
handleSwitchToTenant(tenant.id);
|
||||
}
|
||||
}}
|
||||
actions={[
|
||||
// Primary action - Switch or View Dashboard
|
||||
{
|
||||
label: isActive ? 'Ver Dashboard' : 'Cambiar',
|
||||
icon: isActive ? Eye : ArrowRight,
|
||||
variant: isActive ? 'outline' : 'primary',
|
||||
priority: 'primary',
|
||||
onClick: () => {
|
||||
if (isActive) {
|
||||
navigate('/app/dashboard');
|
||||
} else {
|
||||
handleSwitchToTenant(tenant.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Settings action
|
||||
{
|
||||
label: 'Configuración',
|
||||
icon: Settings,
|
||||
priority: 'secondary',
|
||||
onClick: () => {
|
||||
if (!isActive) {
|
||||
handleSwitchToTenant(tenant.id);
|
||||
}
|
||||
handleManageTenant();
|
||||
}
|
||||
},
|
||||
// Team management - only for owners
|
||||
...(isOwnerRole ? [{
|
||||
label: 'Equipo',
|
||||
icon: Users,
|
||||
priority: 'secondary' as const,
|
||||
highlighted: true,
|
||||
onClick: () => {
|
||||
if (!isActive) {
|
||||
handleSwitchToTenant(tenant.id);
|
||||
}
|
||||
handleManageTeam();
|
||||
}
|
||||
}] : [])
|
||||
]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
icon={Building2}
|
||||
title="No se encontraron organizaciones"
|
||||
description={searchTerm || roleFilter
|
||||
? "Intenta ajustar los filtros de búsqueda"
|
||||
: "Crea tu primera organización para comenzar a usar Bakery IA"
|
||||
}
|
||||
actionLabel="Nueva Organización"
|
||||
actionIcon={Plus}
|
||||
onAction={handleAddNewOrganization}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -86,80 +86,20 @@ const SubscriptionPage: React.FC = () => {
|
||||
subscriptionService.fetchAvailablePlans()
|
||||
]);
|
||||
|
||||
// FIX: Handle demo mode or missing subscription data
|
||||
// CRITICAL: No more mock data - show real errors instead
|
||||
if (!usage || !usage.usage) {
|
||||
// If no usage data, likely a demo tenant - create mock data
|
||||
const mockUsage: UsageSummary = {
|
||||
plan: 'starter',
|
||||
status: 'active',
|
||||
billing_cycle: 'monthly',
|
||||
monthly_price: 0,
|
||||
next_billing_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
usage: {
|
||||
users: {
|
||||
current: 1,
|
||||
limit: 5,
|
||||
unlimited: false,
|
||||
usage_percentage: 20
|
||||
},
|
||||
locations: {
|
||||
current: 1,
|
||||
limit: 1,
|
||||
unlimited: false,
|
||||
usage_percentage: 100
|
||||
},
|
||||
products: {
|
||||
current: 0,
|
||||
limit: 50,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
},
|
||||
recipes: {
|
||||
current: 0,
|
||||
limit: 50,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
},
|
||||
suppliers: {
|
||||
current: 0,
|
||||
limit: 20,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
},
|
||||
training_jobs_today: {
|
||||
current: 0,
|
||||
limit: 1,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
},
|
||||
forecasts_today: {
|
||||
current: 0,
|
||||
limit: 10,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
},
|
||||
api_calls_this_hour: {
|
||||
current: 0,
|
||||
limit: 100,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
},
|
||||
file_storage_used_gb: {
|
||||
current: 0,
|
||||
limit: 5,
|
||||
unlimited: false,
|
||||
usage_percentage: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
setUsageSummary(mockUsage);
|
||||
} else {
|
||||
setUsageSummary(usage);
|
||||
throw new Error('No subscription found. Please contact support or create a new subscription.');
|
||||
}
|
||||
|
||||
setUsageSummary(usage);
|
||||
setAvailablePlans(plans);
|
||||
} catch (error) {
|
||||
console.error('Error loading subscription data:', error);
|
||||
showToast.error("No se pudo cargar la información de suscripción");
|
||||
showToast.error(
|
||||
error instanceof Error && error.message.includes('No subscription')
|
||||
? error.message
|
||||
: "No se pudo cargar la información de suscripción. Por favor, contacte con soporte."
|
||||
);
|
||||
} finally {
|
||||
setSubscriptionLoading(false);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ const DemoPage = () => {
|
||||
// Helper function to calculate estimated progress based on elapsed time
|
||||
const calculateEstimatedProgress = (tier: string, startTime: number): number => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const duration = tier === 'enterprise' ? 75000 : 30000; // ms
|
||||
const duration = tier === 'enterprise' ? 90000 : 40000; // ms (90s for enterprise, 40s for professional)
|
||||
const linearProgress = Math.min(95, (elapsed / duration) * 100);
|
||||
// Logarithmic curve for natural feel - starts fast, slows down
|
||||
return Math.min(95, Math.round(linearProgress * (1 - Math.exp(-elapsed / 10000))));
|
||||
@@ -150,17 +150,16 @@ const DemoPage = () => {
|
||||
|
||||
const getLoadingMessage = (tier, progress) => {
|
||||
if (tier === 'enterprise') {
|
||||
if (progress < 15) return 'Preparando entorno enterprise...';
|
||||
if (progress < 35) return 'Creando obrador central en Madrid...';
|
||||
if (progress < 55) return 'Configurando outlets en Barcelona, Valencia y Bilbao...';
|
||||
if (progress < 75) return 'Generando rutas de distribución optimizadas...';
|
||||
if (progress < 90) return 'Configurando red de distribución...';
|
||||
return 'Finalizando configuración enterprise...';
|
||||
if (progress < 20) return 'Iniciando tu demostración...';
|
||||
if (progress < 50) return 'Creando tu panadería central...';
|
||||
if (progress < 80) return 'Configurando tus sucursales...';
|
||||
if (progress < 95) return 'Preparando datos finales...';
|
||||
return 'Casi listo...';
|
||||
} else {
|
||||
if (progress < 30) return 'Preparando tu panadería...';
|
||||
if (progress < 60) return 'Configurando inventario y recetas...';
|
||||
if (progress < 85) return 'Generando datos de ventas y producción...';
|
||||
return 'Finalizando configuración...';
|
||||
if (progress < 25) return 'Iniciando tu panadería...';
|
||||
if (progress < 70) return 'Cargando productos y datos...';
|
||||
if (progress < 95) return 'Preparando datos finales...';
|
||||
return 'Casi listo...';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -806,11 +805,8 @@ const DemoPage = () => {
|
||||
<ModalHeader
|
||||
title={
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-[var(--color-primary)]/30 border-t-[var(--color-primary)]"></div>
|
||||
<div className="absolute inset-0 rounded-full bg-[var(--color-primary)]/10 animate-pulse"></div>
|
||||
</div>
|
||||
<span className="text-xl font-bold bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] bg-clip-text text-transparent">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-[var(--color-primary)]/30 border-t-[var(--color-primary)]"></div>
|
||||
<span className="text-xl font-bold text-[var(--text-primary)]">
|
||||
Configurando Tu Demo
|
||||
</span>
|
||||
</div>
|
||||
@@ -819,145 +815,40 @@ const DemoPage = () => {
|
||||
/>
|
||||
<ModalBody padding="xl">
|
||||
<div className="space-y-8">
|
||||
{/* Overall Progress Section with Enhanced Visual */}
|
||||
<div className="text-center space-y-4">
|
||||
<div className="flex justify-between items-baseline mb-3">
|
||||
<span className="text-sm font-medium text-[var(--text-secondary)]">Progreso Total</span>
|
||||
<span className="text-3xl font-bold bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] bg-clip-text text-transparent">
|
||||
{/* Overall Progress Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-lg font-medium text-[var(--text-primary)]">
|
||||
{getLoadingMessage(creatingTier, cloneProgress.overall)}
|
||||
</span>
|
||||
<span className="text-2xl font-bold text-[var(--color-primary)]">
|
||||
{cloneProgress.overall}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full bg-[var(--bg-tertiary)] rounded-full h-4 overflow-hidden shadow-inner">
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-3 overflow-hidden">
|
||||
<div
|
||||
className="relative bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-primary-light)] to-[var(--color-secondary)] h-4 rounded-full transition-all duration-500 ease-out shadow-lg"
|
||||
className="bg-[var(--color-primary)] h-3 rounded-full transition-all duration-500"
|
||||
style={{ width: `${cloneProgress.overall}%` }}
|
||||
>
|
||||
{/* Shimmer Effect */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent animate-shimmer"></div>
|
||||
{/* Glow Effect */}
|
||||
<div className="absolute inset-0 rounded-full shadow-[0_0_20px_rgba(217,119,6,0.5)]"></div>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
|
||||
{estimatedRemainingSeconds !== null && estimatedRemainingSeconds > 0 && (
|
||||
<div className="flex items-center justify-center gap-2 mt-4">
|
||||
<Clock className="w-4 h-4 text-[var(--text-tertiary)]" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
Aproximadamente <span className="font-semibold text-[var(--color-primary)]">{estimatedRemainingSeconds}s</span> restantes
|
||||
</span>
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Aproximadamente {estimatedRemainingSeconds}s restantes</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-base text-[var(--text-secondary)] font-medium mt-4">
|
||||
{getLoadingMessage(creatingTier, cloneProgress.overall)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Enterprise Detailed Progress with Enhanced Visuals */}
|
||||
{creatingTier === 'enterprise' && (
|
||||
<div className="space-y-5 mt-8">
|
||||
{/* Parent Tenant */}
|
||||
<div className="relative overflow-hidden rounded-2xl p-5 bg-gradient-to-br from-[var(--color-info)]/10 via-[var(--color-info)]/5 to-transparent dark:from-[var(--color-info)]/20 dark:via-[var(--color-info)]/10 border border-[var(--color-info)]/30 shadow-lg">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rounded-full bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] shadow-lg animate-pulse"></div>
|
||||
<span className="font-bold text-[var(--color-info-dark)] dark:text-[var(--color-info-light)] text-lg">
|
||||
Obrador Central
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-bold text-xl text-[var(--color-info)] dark:text-[var(--color-info-light)]">
|
||||
{cloneProgress.parent}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-3 overflow-hidden shadow-inner">
|
||||
<div
|
||||
className="bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] h-3 rounded-full transition-all duration-500 shadow-lg relative overflow-hidden"
|
||||
style={{ width: `${cloneProgress.parent}%` }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Child Outlets with Grid Layout */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{['Barcelona', 'Valencia', 'Bilbao'].map((city, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative overflow-hidden rounded-xl p-4 bg-gradient-to-br from-[var(--color-success)]/10 via-[var(--color-success)]/5 to-transparent dark:from-[var(--color-success)]/20 dark:via-[var(--color-success)]/10 border border-[var(--color-success)]/30 shadow-md hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs font-bold text-[var(--color-success-dark)] dark:text-[var(--color-success-light)] uppercase tracking-wide">
|
||||
{city}
|
||||
</span>
|
||||
<span className="text-sm font-bold text-[var(--color-success-dark)] dark:text-[var(--color-success-light)]">
|
||||
{cloneProgress.children[index]}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-2.5 overflow-hidden shadow-inner">
|
||||
<div
|
||||
className="bg-gradient-to-r from-[var(--color-success)] to-[var(--color-success-dark)] h-2.5 rounded-full transition-all duration-500 relative overflow-hidden"
|
||||
style={{ width: `${cloneProgress.children[index]}%` }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Distribution System */}
|
||||
<div className="relative overflow-hidden rounded-2xl p-5 bg-gradient-to-br from-[var(--color-secondary)]/10 via-[var(--color-secondary)]/5 to-transparent dark:from-[var(--color-secondary)]/20 dark:via-[var(--color-secondary)]/10 border border-[var(--color-secondary)]/30 shadow-lg">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Truck className="w-5 h-5 text-[var(--color-secondary)] animate-bounce" />
|
||||
<span className="font-bold text-[var(--color-secondary-dark)] dark:text-[var(--color-secondary-light)] text-lg">
|
||||
Sistema de Distribución
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-bold text-xl text-[var(--color-secondary)] dark:text-[var(--color-secondary-light)]">
|
||||
{cloneProgress.distribution}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-3 overflow-hidden shadow-inner">
|
||||
<div
|
||||
className="bg-gradient-to-r from-[var(--color-secondary)] to-[var(--color-secondary-dark)] h-3 rounded-full transition-all duration-500 shadow-lg relative overflow-hidden"
|
||||
style={{ width: `${cloneProgress.distribution}%` }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Professional Progress Indicator */}
|
||||
{creatingTier === 'professional' && cloneProgress.overall < 100 && (
|
||||
<div className="text-center py-6">
|
||||
<div className="flex justify-center items-center gap-2 mb-4">
|
||||
<div className="w-3 h-3 bg-[var(--color-primary)] rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
|
||||
<div className="w-3 h-3 bg-[var(--color-primary)] rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
|
||||
<div className="w-3 h-3 bg-[var(--color-primary)] rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
|
||||
</div>
|
||||
<p className="text-sm text-[var(--text-tertiary)] font-medium">
|
||||
Procesando servicios en paralelo...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Information Box with Enhanced Design */}
|
||||
<div className="mt-6 rounded-xl p-4 bg-gradient-to-r from-[var(--bg-secondary)] to-[var(--bg-tertiary)] border border-[var(--border-primary)] shadow-inner">
|
||||
<div className="flex items-start gap-3">
|
||||
<Activity className="w-5 h-5 text-[var(--color-info)] flex-shrink-0 mt-0.5 animate-pulse" />
|
||||
<p className="text-sm text-[var(--text-secondary)] leading-relaxed">
|
||||
{creatingTier === 'enterprise'
|
||||
? 'Creando obrador central, outlets y sistema de distribución con datos reales de ejemplo...'
|
||||
: 'Personalizando tu panadería con inventario, recetas, y datos de ventas realistas...'}
|
||||
</p>
|
||||
</div>
|
||||
{/* Information Box */}
|
||||
<div className="rounded-lg p-4 bg-[var(--bg-secondary)] border border-[var(--border-primary)]">
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
{creatingTier === 'enterprise'
|
||||
? 'Estamos preparando tu panadería con una tienda principal y 3 sucursales conectadas'
|
||||
: 'Estamos preparando tu panadería con productos, recetas y ventas de ejemplo'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
Reference in New Issue
Block a user