Imporve the UI 5

This commit is contained in:
Urtzi Alfaro
2026-01-03 15:55:24 +01:00
parent db12c57b0b
commit 47ccea4900
8 changed files with 65 additions and 24 deletions

View File

@@ -94,7 +94,7 @@ export const SubscriptionPricingCards: React.FC<SubscriptionPricingCardsProps> =
// Format limit display with emoji and user-friendly text // Format limit display with emoji and user-friendly text
const formatLimit = (value: number | string | null | undefined, unlimitedKey: string): string => { const formatLimit = (value: number | string | null | undefined, unlimitedKey: string): string => {
if (!value || value === -1 || value === 'unlimited') { if (!value || value === -1 || (typeof value === 'string' && value.toLowerCase() === 'unlimited')) {
return t(unlimitedKey); return t(unlimitedKey);
} }
return value.toString(); return value.toString();
@@ -174,16 +174,6 @@ export const SubscriptionPricingCards: React.FC<SubscriptionPricingCardsProps> =
</div> </div>
</div> </div>
{/* Selection Mode Helper Text */}
{mode === 'selection' && (
<div className="text-center mb-6">
<p className="text-sm text-[var(--text-secondary)] flex items-center justify-center gap-2">
<span className="inline-block w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
{t('ui.click_to_select', 'Haz clic en cualquier plan para seleccionarlo')}
</p>
</div>
)}
{/* Simplified Plans Grid */} {/* Simplified Plans Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 items-start lg:items-stretch"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 items-start lg:items-stretch">
{Object.entries(plans).map(([tier, plan]) => { {Object.entries(plans).map(([tier, plan]) => {
@@ -240,7 +230,7 @@ export const SubscriptionPricingCards: React.FC<SubscriptionPricingCardsProps> =
{/* Plan Header */} {/* Plan Header */}
<div className="mb-6"> <div className="mb-6">
<h3 className={`text-2xl font-bold mb-2 ${(isPopular || isSelected) ? 'text-white' : 'text-[var(--text-primary)]'}`}> <h3 className={`text-2xl font-bold mb-2 ${(isPopular || isSelected) ? 'text-white' : 'text-[var(--text-primary)]'}`}>
{plan.name} {t(`plans.${tier}.name`, plan.name)}
</h3> </h3>
<p className={`text-sm ${(isPopular || isSelected) ? 'text-white/90' : 'text-[var(--text-secondary)]'}`}> <p className={`text-sm ${(isPopular || isSelected) ? 'text-white/90' : 'text-[var(--text-secondary)]'}`}>
{plan.tagline_key ? t(plan.tagline_key) : plan.tagline || ''} {plan.tagline_key ? t(plan.tagline_key) : plan.tagline || ''}

View File

@@ -54,6 +54,12 @@
"standard_cost": "Target cost for budgeting and variance analysis", "standard_cost": "Target cost for budgeting and variance analysis",
"average_cost": "Automatically calculated from weighted average of purchases" "average_cost": "Automatically calculated from weighted average of purchases"
}, },
"type": {
"ingredient": "Ingredient",
"ingredient_desc": "Raw materials for production",
"finished_product": "Finished Product",
"finished_product_desc": "Products ready for sale"
},
"enums": { "enums": {
"product_type": { "product_type": {
"ingredient": "Ingredient", "ingredient": "Ingredient",
@@ -75,7 +81,16 @@
"pcs": "Pieces", "pcs": "Pieces",
"pkg": "Packages", "pkg": "Packages",
"bags": "Bags", "bags": "Bags",
"boxes": "Boxes" "boxes": "Boxes",
"KILOGRAMS": "Kilograms",
"GRAMS": "Grams",
"LITERS": "Liters",
"MILLILITERS": "Milliliters",
"UNITS": "Units",
"PIECES": "Pieces",
"PACKAGES": "Packages",
"BAGS": "Bags",
"BOXES": "Boxes"
}, },
"ingredient_category": { "ingredient_category": {
"flour": "Flour", "flour": "Flour",

View File

@@ -69,6 +69,7 @@
}, },
"plans": { "plans": {
"starter": { "starter": {
"name": "Starter",
"description": "Perfect for small bakeries getting started", "description": "Perfect for small bakeries getting started",
"tagline": "Start reducing waste today", "tagline": "Start reducing waste today",
"roi_badge": "Bakeries save €300-500/month on waste", "roi_badge": "Bakeries save €300-500/month on waste",
@@ -76,6 +77,7 @@
"recommended_for": "Your first bakery" "recommended_for": "Your first bakery"
}, },
"professional": { "professional": {
"name": "Professional",
"description": "For growing bakeries with multiple locations", "description": "For growing bakeries with multiple locations",
"tagline": "Grow with artificial intelligence", "tagline": "Grow with artificial intelligence",
"roi_badge": "Bakeries save €800-1,200/month on waste & ordering", "roi_badge": "Bakeries save €800-1,200/month on waste & ordering",
@@ -83,6 +85,7 @@
"recommended_for": "Expanding bakeries" "recommended_for": "Expanding bakeries"
}, },
"enterprise": { "enterprise": {
"name": "Enterprise",
"description": "For large bakery chains and franchises", "description": "For large bakery chains and franchises",
"tagline": "Complete control for your chain", "tagline": "Complete control for your chain",
"roi_badge": "Contact us for custom ROI analysis", "roi_badge": "Contact us for custom ROI analysis",
@@ -128,7 +131,6 @@
"choose_plan": "Choose Plan", "choose_plan": "Choose Plan",
"selected": "Selected", "selected": "Selected",
"plan_selected": "Plan Selected", "plan_selected": "Plan Selected",
"click_to_select": "Click on any plan to select it",
"best_value": "Best Value", "best_value": "Best Value",
"free_trial_footer": "{months} months free • Card required", "free_trial_footer": "{months} months free • Card required",
"professional_value_badge": "10x capacity • Advanced AI • Multi-location", "professional_value_badge": "10x capacity • Advanced AI • Multi-location",

View File

@@ -75,6 +75,12 @@
"standard_cost": "Costo objetivo para presupuesto y análisis de variación", "standard_cost": "Costo objetivo para presupuesto y análisis de variación",
"average_cost": "Calculado automáticamente según el promedio ponderado de compras" "average_cost": "Calculado automáticamente según el promedio ponderado de compras"
}, },
"type": {
"ingredient": "Ingrediente",
"ingredient_desc": "Materias primas para producir",
"finished_product": "Producto Terminado",
"finished_product_desc": "Productos listos para venta"
},
"enums": { "enums": {
"product_type": { "product_type": {
"ingredient": "Ingrediente", "ingredient": "Ingrediente",
@@ -96,7 +102,16 @@
"pcs": "Piezas", "pcs": "Piezas",
"pkg": "Paquetes", "pkg": "Paquetes",
"bags": "Bolsas", "bags": "Bolsas",
"boxes": "Cajas" "boxes": "Cajas",
"KILOGRAMS": "Kilogramos",
"GRAMS": "Gramos",
"LITERS": "Litros",
"MILLILITERS": "Mililitros",
"UNITS": "Unidades",
"PIECES": "Piezas",
"PACKAGES": "Paquetes",
"BAGS": "Bolsas",
"BOXES": "Cajas"
}, },
"ingredient_category": { "ingredient_category": {
"flour": "Harinas", "flour": "Harinas",

View File

@@ -69,6 +69,7 @@
}, },
"plans": { "plans": {
"starter": { "starter": {
"name": "Inicial",
"description": "Perfecto para panaderías pequeñas comenzando", "description": "Perfecto para panaderías pequeñas comenzando",
"tagline": "Empieza a reducir desperdicios hoy", "tagline": "Empieza a reducir desperdicios hoy",
"roi_badge": "Panaderías ahorran €300-500/mes en desperdicios", "roi_badge": "Panaderías ahorran €300-500/mes en desperdicios",
@@ -76,6 +77,7 @@
"recommended_for": "Tu primera panadería" "recommended_for": "Tu primera panadería"
}, },
"professional": { "professional": {
"name": "Profesional",
"description": "Para panaderías en crecimiento con múltiples ubicaciones", "description": "Para panaderías en crecimiento con múltiples ubicaciones",
"tagline": "Crece con inteligencia artificial", "tagline": "Crece con inteligencia artificial",
"roi_badge": "Panaderías ahorran €800-1,200/mes en desperdicios y pedidos", "roi_badge": "Panaderías ahorran €800-1,200/mes en desperdicios y pedidos",
@@ -83,6 +85,7 @@
"recommended_for": "Panaderías en expansión" "recommended_for": "Panaderías en expansión"
}, },
"enterprise": { "enterprise": {
"name": "Empresa",
"description": "Para cadenas de panaderías y franquicias", "description": "Para cadenas de panaderías y franquicias",
"tagline": "Control total para tu cadena", "tagline": "Control total para tu cadena",
"roi_badge": "Contacta para análisis ROI personalizado", "roi_badge": "Contacta para análisis ROI personalizado",
@@ -128,7 +131,6 @@
"choose_plan": "Elegir Plan", "choose_plan": "Elegir Plan",
"selected": "Seleccionado", "selected": "Seleccionado",
"plan_selected": "Plan Seleccionado", "plan_selected": "Plan Seleccionado",
"click_to_select": "Haz clic en cualquier plan para seleccionarlo",
"best_value": "Mejor Valor", "best_value": "Mejor Valor",
"free_trial_footer": "{months} meses gratis • Tarjeta requerida", "free_trial_footer": "{months} meses gratis • Tarjeta requerida",
"professional_value_badge": "10x capacidad • IA Avanzada • Multi-ubicación", "professional_value_badge": "10x capacidad • IA Avanzada • Multi-ubicación",

View File

@@ -69,6 +69,12 @@
"cleaning": "Garbiketa", "cleaning": "Garbiketa",
"other": "Besteak" "other": "Besteak"
}, },
"type": {
"ingredient": "Osagaia",
"ingredient_desc": "Produkzioko lehengaiak",
"finished_product": "Produktu Amaitua",
"finished_product_desc": "Saltzeko prest dauden produktuak"
},
"product_type": { "product_type": {
"ingredient": "Osagaia", "ingredient": "Osagaia",
"finished_product": "Produktu Amaitua" "finished_product": "Produktu Amaitua"
@@ -89,7 +95,16 @@
"pcs": "Piezak", "pcs": "Piezak",
"pkg": "Paketeak", "pkg": "Paketeak",
"bags": "Poltsak", "bags": "Poltsak",
"boxes": "Kutxak" "boxes": "Kutxak",
"KILOGRAMS": "Kilogramoak",
"GRAMS": "Gramoak",
"LITERS": "Litroak",
"MILLILITERS": "Mililitroak",
"UNITS": "Unitateak",
"PIECES": "Piezak",
"PACKAGES": "Paketeak",
"BAGS": "Poltsak",
"BOXES": "Kutxak"
}, },
"product_category": { "product_category": {
"bread": "Ogiak", "bread": "Ogiak",

View File

@@ -29,7 +29,7 @@ interface PasswordData {
const ProfilePage: React.FC = () => { const ProfilePage: React.FC = () => {
const user = useAuthUser(); const user = useAuthUser();
const { t } = useTranslation(['settings', 'auth']); const { t } = useTranslation(['settings', 'auth', 'subscription']);
const { data: profile, isLoading: profileLoading, error: profileError } = useAuthProfile(); const { data: profile, isLoading: profileLoading, error: profileError } = useAuthProfile();
@@ -626,7 +626,7 @@ const ProfilePage: React.FC = () => {
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] flex items-center"> <h3 className="text-lg font-semibold text-[var(--text-primary)] flex items-center">
<Crown className="w-5 h-5 mr-2 text-yellow-500" /> <Crown className="w-5 h-5 mr-2 text-yellow-500" />
Plan Actual: {subscriptionService.getPlanDisplayInfo(usageSummary.plan).name} Plan Actual: {t(`subscription:plans.${usageSummary.plan}.name`, subscriptionService.getPlanDisplayInfo(usageSummary.plan).name)}
</h3> </h3>
<Badge <Badge
variant={usageSummary.status === 'active' ? 'success' : 'default'} variant={usageSummary.status === 'active' ? 'success' : 'default'}
@@ -792,7 +792,7 @@ const ProfilePage: React.FC = () => {
)} )}
<div className="text-center mb-6"> <div className="text-center mb-6">
<h4 className="text-xl font-bold text-[var(--text-primary)] mb-2">{plan.name}</h4> <h4 className="text-xl font-bold text-[var(--text-primary)] mb-2">{t(`subscription:plans.${planKey}.name`, plan.name)}</h4>
<div className="text-3xl font-bold text-[var(--color-primary)] mb-1"> <div className="text-3xl font-bold text-[var(--color-primary)] mb-1">
{subscriptionService.formatPrice(plan.monthly_price)} {subscriptionService.formatPrice(plan.monthly_price)}
<span className="text-lg text-[var(--text-secondary)]">/mes</span> <span className="text-lg text-[var(--text-secondary)]">/mes</span>
@@ -916,11 +916,11 @@ const ProfilePage: React.FC = () => {
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg space-y-2"> <div className="p-4 bg-[var(--bg-secondary)] rounded-lg space-y-2">
<div className="flex justify-between"> <div className="flex justify-between">
<span>Plan actual:</span> <span>Plan actual:</span>
<span>{subscriptionService.getPlanDisplayInfo(usageSummary.plan).name}</span> <span>{t(`subscription:plans.${usageSummary.plan}.name`, subscriptionService.getPlanDisplayInfo(usageSummary.plan).name)}</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span>Nuevo plan:</span> <span>Nuevo plan:</span>
<span>{availablePlans.plans[selectedPlan].name}</span> <span>{t(`subscription:plans.${selectedPlan}.name`, availablePlans.plans[selectedPlan].name)}</span>
</div> </div>
<div className="flex justify-between font-medium"> <div className="flex justify-between font-medium">
<span>Nuevo precio:</span> <span>Nuevo precio:</span>

View File

@@ -16,11 +16,13 @@ import {
trackUpgradeCTAClicked, trackUpgradeCTAClicked,
trackUsageMetricViewed trackUsageMetricViewed
} from '../../../../utils/subscriptionAnalytics'; } from '../../../../utils/subscriptionAnalytics';
import { useTranslation } from 'react-i18next';
const SubscriptionPage: React.FC = () => { const SubscriptionPage: React.FC = () => {
const user = useAuthUser(); const user = useAuthUser();
const currentTenant = useCurrentTenant(); const currentTenant = useCurrentTenant();
const { notifySubscriptionChanged } = useSubscriptionEvents(); const { notifySubscriptionChanged } = useSubscriptionEvents();
const { t } = useTranslation('subscription');
const [usageSummary, setUsageSummary] = useState<UsageSummary | null>(null); const [usageSummary, setUsageSummary] = useState<UsageSummary | null>(null);
const [availablePlans, setAvailablePlans] = useState<AvailablePlans | null>(null); const [availablePlans, setAvailablePlans] = useState<AvailablePlans | null>(null);
@@ -942,11 +944,11 @@ const SubscriptionPage: React.FC = () => {
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg space-y-2"> <div className="p-4 bg-[var(--bg-secondary)] rounded-lg space-y-2">
<div className="flex justify-between"> <div className="flex justify-between">
<span>Plan actual:</span> <span>Plan actual:</span>
<span>{usageSummary.plan}</span> <span>{t(`plans.${usageSummary.plan}.name`, usageSummary.plan)}</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span>Nuevo plan:</span> <span>Nuevo plan:</span>
<span>{availablePlans.plans[selectedPlan as keyof typeof availablePlans.plans].name}</span> <span>{t(`plans.${selectedPlan}.name`, availablePlans.plans[selectedPlan as keyof typeof availablePlans.plans].name)}</span>
</div> </div>
<div className="flex justify-between font-medium"> <div className="flex justify-between font-medium">
<span>Nuevo precio:</span> <span>Nuevo precio:</span>