Improve teh securty of teh DB
This commit is contained in:
@@ -97,15 +97,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDemoLogin = () => {
|
||||
setCredentials({
|
||||
email: 'admin@bakery.com',
|
||||
password: 'admin12345',
|
||||
remember_me: false
|
||||
});
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !isLoading) {
|
||||
handleSubmit(e as any);
|
||||
@@ -290,30 +281,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
<div id="login-button-description" className="sr-only">
|
||||
Presiona Enter o haz clic para iniciar sesión con tus credenciales
|
||||
</div>
|
||||
|
||||
{/* Demo Login Section */}
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-border-primary" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-background-primary text-text-tertiary">Demo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleDemoLogin}
|
||||
disabled={isLoading}
|
||||
className="w-full focus:outline-none focus:ring-2 focus:ring-color-primary focus:ring-offset-2"
|
||||
>
|
||||
Usar credenciales de demostración
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{onRegisterClick && (
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../../ui/Button';
|
||||
import { Input } from '../../../ui/Input';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useCreateIngredient, useClassifyBatch } from '../../../../api/hooks/inventory';
|
||||
import { useValidateImportFile, useImportSalesData } from '../../../../api/hooks/sales';
|
||||
import type { ImportValidationResult } from '../../../../api/types/sales';
|
||||
import type { ImportValidationResponse } from '../../../../api/types/dataImport';
|
||||
import type { ProductSuggestionResponse } from '../../../../api/types/inventory';
|
||||
import { useAuth } from '../../../../contexts/AuthContext';
|
||||
|
||||
interface UploadSalesDataStepProps {
|
||||
@@ -52,6 +54,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
onComplete,
|
||||
isFirstStep
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [validationResult, setValidationResult] = useState<ImportValidationResponse | null>(null);
|
||||
@@ -60,6 +63,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [progressState, setProgressState] = useState<ProgressState | null>(null);
|
||||
const [showGuide, setShowGuide] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
@@ -132,7 +136,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
};
|
||||
|
||||
|
||||
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResult) => {
|
||||
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResponse) => {
|
||||
if (!currentTenant?.id) {
|
||||
setError('No hay datos de validación disponibles para generar sugerencias');
|
||||
setIsValidating(false);
|
||||
@@ -166,7 +170,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' });
|
||||
|
||||
// Convert API response to InventoryItem format - use exact backend structure plus UI fields
|
||||
const items: InventoryItem[] = classificationResponse.suggestions.map(suggestion => {
|
||||
const items: InventoryItem[] = classificationResponse.suggestions.map((suggestion: ProductSuggestionResponse) => {
|
||||
// Calculate default stock quantity based on sales data
|
||||
const defaultStock = Math.max(
|
||||
Math.ceil((suggestion.sales_data?.average_daily_sales || 1) * 7), // 1 week supply
|
||||
@@ -534,6 +538,113 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* File Format Guide */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<h3 className="font-semibold text-blue-900">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.title', 'Guía de Formato de Archivo')}
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowGuide(!showGuide)}
|
||||
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
|
||||
>
|
||||
{showGuide
|
||||
? t('onboarding:steps.inventory_setup.file_format_guide.collapse_guide', 'Ocultar Guía')
|
||||
: t('onboarding:steps.inventory_setup.file_format_guide.toggle_guide', 'Ver Guía Completa')
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Quick Summary - Always Visible */}
|
||||
<div className="text-sm text-blue-800 space-y-1">
|
||||
<p>
|
||||
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.title', 'Formatos Soportados')}:</strong>{' '}
|
||||
CSV, JSON, Excel (XLSX) • {t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.max_size', 'Tamaño máximo: 10MB')}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}:</strong>{' '}
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')},{' '}
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')},{' '}
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Detailed Guide - Collapsible */}
|
||||
{showGuide && (
|
||||
<div className="mt-4 pt-4 border-t border-blue-200 space-y-4">
|
||||
{/* Required Columns Detail */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 space-y-1 pl-4">
|
||||
<p>
|
||||
• <strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')}:</strong>{' '}
|
||||
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date_examples', 'date, fecha, data')}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
• <strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')}:</strong>{' '}
|
||||
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product_examples', 'product, producto, product_name')}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
• <strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}:</strong>{' '}
|
||||
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity_examples', 'quantity, cantidad, quantity_sold')}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Optional Columns */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.title', 'Columnas Opcionales')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 space-y-1 pl-4">
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.revenue', 'Ingresos (revenue, ingresos, ventas)')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.unit_price', 'Precio Unitario (unit_price, precio, price)')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.category', 'Categoría (category, categoria)')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.sku', 'SKU del Producto')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.location', 'Ubicación/Tienda')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date Formats */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.title', 'Formatos de Fecha Soportados')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 pl-4">
|
||||
<p>{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.formats', 'YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, y más')}</p>
|
||||
<p className="text-xs mt-1">{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.with_time', 'También se admiten formatos con hora')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Automatic Features */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.features.title', 'Características Automáticas')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 space-y-1 pl-4">
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.multilingual', 'Detección multiidioma de columnas')}</p>
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.validation', 'Validación automática con reporte detallado')}</p>
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.ai_classification', 'Clasificación de productos con IA')}</p>
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.inventory_suggestions', 'Sugerencias inteligentes de inventario')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File Upload Area */}
|
||||
<div
|
||||
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
|
||||
@@ -626,7 +737,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
<div className="mt-2">
|
||||
<p className="font-medium text-[var(--color-warning)]">Warnings:</p>
|
||||
<ul className="list-disc list-inside">
|
||||
{validationResult.warnings.map((warning, index) => (
|
||||
{validationResult.warnings.map((warning: any, index: number) => (
|
||||
<li key={index} className="text-[var(--color-warning)]">
|
||||
{typeof warning === 'string' ? warning : JSON.stringify(warning)}
|
||||
</li>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { SubscriptionPricingCards } from './SubscriptionPricingCards';
|
||||
|
||||
export const PricingSection: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-24 bg-[var(--bg-primary)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)]">
|
||||
Planes que se Adaptan a tu Negocio
|
||||
{t('landing:pricing.title', 'Planes que se Adaptan a tu Negocio')}
|
||||
</h2>
|
||||
<p className="mt-4 max-w-2xl mx-auto text-lg text-[var(--text-secondary)]">
|
||||
Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.
|
||||
{t('landing:pricing.subtitle', 'Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +29,7 @@ export const PricingSection: React.FC = () => {
|
||||
to="/plans/compare"
|
||||
className="text-[var(--color-primary)] hover:text-[var(--color-primary-dark)] font-semibold inline-flex items-center gap-2"
|
||||
>
|
||||
Ver comparación completa de características
|
||||
{t('landing:pricing.compare_link', 'Ver comparación completa de características')}
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user