Architect navigation buttons correctly: move from wizard-level to step-level
Fixed the navigation architecture to follow proper onboarding patterns: **ARCHITECTURE CHANGE:** - REMOVED: External navigation footer from UnifiedOnboardingWizard (Back + Continue buttons at wizard level) - ADDED: Internal Continue buttons inside each setup wizard step component **WHY THIS MATTERS:** 1. Onboarding should NEVER show Back buttons (users cannot go back) 2. Each step should be self-contained with its own Continue button 3. Setup wizard steps are reused in both contexts: - SetupWizard (/app/setup): Uses external StepNavigation component - UnifiedOnboardingWizard: Steps now render their own buttons **CHANGES MADE:** 1. UnifiedOnboardingWizard.tsx: - Removed navigation footer (lines 548-588) - Now passes canContinue prop to steps - Steps are responsible for their own navigation 2. All setup wizard steps updated: - QualitySetupStep: Added onComplete, canContinue props + Continue button - SuppliersSetupStep: Modified existing button to call onComplete - InventorySetupStep: Added onComplete, canContinue props + Continue button - RecipesSetupStep: Added canContinue prop + Continue button - TeamSetupStep: Added onComplete, canContinue props + Continue button - ReviewSetupStep: Added onComplete, canContinue props + Continue button 3. Continue button pattern: - Only renders when onComplete prop exists (onboarding context) - Disabled based on canContinue prop from parent - Styled consistently across all steps - Positioned at bottom with border-top separator **RESULT:** - Clean separation: onboarding steps have internal buttons, no external navigation - No Back button in onboarding (as required) - Setup wizard still works with external StepNavigation - Consistent UX across all steps
This commit is contained in:
@@ -542,50 +542,9 @@ const OnboardingWizardContent: React.FC = () => {
|
|||||||
onUpdate={handleStepUpdate}
|
onUpdate={handleStepUpdate}
|
||||||
isFirstStep={currentStepIndex === 0}
|
isFirstStep={currentStepIndex === 0}
|
||||||
isLastStep={currentStepIndex === VISIBLE_STEPS.length - 1}
|
isLastStep={currentStepIndex === VISIBLE_STEPS.length - 1}
|
||||||
|
canContinue={canContinue}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
||||||
{/* Navigation Footer - Only for setup wizard steps that don't render their own buttons */}
|
|
||||||
{['suppliers-setup', 'inventory-setup', 'recipes-setup', 'quality-setup', 'team-setup', 'setup-review'].includes(currentStep.id) && (
|
|
||||||
<div className="border-t border-[var(--border-primary)] px-6 py-4 bg-[var(--bg-secondary)]/30">
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-3">
|
|
||||||
{/* Left side - Back button */}
|
|
||||||
<div className="w-full sm:w-auto">
|
|
||||||
{currentStepIndex > 0 && (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => setCurrentStepIndex(currentStepIndex - 1)}
|
|
||||||
disabled={markStepCompleted.isPending}
|
|
||||||
className="w-full sm:w-auto"
|
|
||||||
>
|
|
||||||
← {t('onboarding:wizard.navigation.back', 'Atrás')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right side - Continue button */}
|
|
||||||
<div className="w-full sm:w-auto">
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={handleStepComplete}
|
|
||||||
disabled={!canContinue || markStepCompleted.isPending}
|
|
||||||
className="w-full sm:w-auto min-w-[200px]"
|
|
||||||
>
|
|
||||||
{markStepCompleted.isPending ? (
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
||||||
{t('common:saving', 'Guardando...')}
|
|
||||||
</span>
|
|
||||||
) : currentStepIndex === VISIBLE_STEPS.length - 1 ? (
|
|
||||||
t('onboarding:wizard.navigation.finish', 'Finalizar →')
|
|
||||||
) : (
|
|
||||||
t('onboarding:wizard.navigation.next', 'Continuar →')
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { UnitOfMeasure, IngredientCategory } from '../../../../api/types/invento
|
|||||||
import type { IngredientCreate, IngredientUpdate } from '../../../../api/types/inventory';
|
import type { IngredientCreate, IngredientUpdate } from '../../../../api/types/inventory';
|
||||||
import { ESSENTIAL_INGREDIENTS, COMMON_INGREDIENTS, PACKAGING_ITEMS, type IngredientTemplate, templateToIngredientCreate } from '../data/ingredientTemplates';
|
import { ESSENTIAL_INGREDIENTS, COMMON_INGREDIENTS, PACKAGING_ITEMS, type IngredientTemplate, templateToIngredientCreate } from '../data/ingredientTemplates';
|
||||||
|
|
||||||
export const InventorySetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
export const InventorySetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, canContinue }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Get tenant ID
|
// Get tenant ID
|
||||||
@@ -669,6 +669,19 @@ export const InventorySetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Continue button - only shown when used in onboarding context */}
|
||||||
|
{onComplete && (
|
||||||
|
<div className="flex justify-end mt-6 pt-6 border-t border-[var(--border-secondary)]">
|
||||||
|
<button
|
||||||
|
onClick={onComplete}
|
||||||
|
disabled={canContinue === false}
|
||||||
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{t('setup_wizard:navigation.continue', 'Continue →')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useAuthUser } from '../../../../stores/auth.store';
|
|||||||
import { QualityCheckType, ProcessStage } from '../../../../api/types/qualityTemplates';
|
import { QualityCheckType, ProcessStage } from '../../../../api/types/qualityTemplates';
|
||||||
import type { QualityCheckTemplateCreate } from '../../../../api/types/qualityTemplates';
|
import type { QualityCheckTemplateCreate } from '../../../../api/types/qualityTemplates';
|
||||||
|
|
||||||
export const QualitySetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
export const QualitySetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, canContinue }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Get tenant ID and user
|
// Get tenant ID and user
|
||||||
@@ -416,6 +416,19 @@ export const QualitySetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Continue button - only shown when used in onboarding context */}
|
||||||
|
{onComplete && (
|
||||||
|
<div className="flex justify-end mt-6 pt-6 border-t border-[var(--border-secondary)]">
|
||||||
|
<button
|
||||||
|
onClick={onComplete}
|
||||||
|
disabled={canContinue === false}
|
||||||
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{t('setup_wizard:navigation.continue', 'Continue →')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface RecipeIngredientForm {
|
|||||||
ingredient_order: number;
|
ingredient_order: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete }) => {
|
export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, canContinue }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Get tenant ID
|
// Get tenant ID
|
||||||
@@ -793,6 +793,19 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
|||||||
tenantId={tenantId}
|
tenantId={tenantId}
|
||||||
context="recipe"
|
context="recipe"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Continue button - only shown when used in onboarding context */}
|
||||||
|
{onComplete && (
|
||||||
|
<div className="flex justify-end mt-6 pt-6 border-[var(--border-secondary)]">
|
||||||
|
<button
|
||||||
|
onClick={onComplete}
|
||||||
|
disabled={canContinue === false}
|
||||||
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{t('setup_wizard:navigation.continue', 'Continue →')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useQualityTemplates } from '../../../../api/hooks/qualityTemplates';
|
|||||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||||
import { useAuthUser } from '../../../../stores/auth.store';
|
import { useAuthUser } from '../../../../stores/auth.store';
|
||||||
|
|
||||||
export const ReviewSetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
export const ReviewSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, canContinue }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Get tenant ID
|
// Get tenant ID
|
||||||
@@ -306,6 +306,19 @@ export const ReviewSetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Continue button - only shown when used in onboarding context */}
|
||||||
|
{onComplete && (
|
||||||
|
<div className="flex justify-end mt-6 pt-6 border-t border-[var(--border-secondary)]">
|
||||||
|
<button
|
||||||
|
onClick={onComplete}
|
||||||
|
disabled={canContinue === false}
|
||||||
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{t('setup_wizard:navigation.continue', 'Continue →')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const SuppliersSetupStep: React.FC<SetupStepProps> = ({
|
|||||||
onComplete,
|
onComplete,
|
||||||
onSkip,
|
onSkip,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
canContinue,
|
||||||
isFirstStep,
|
isFirstStep,
|
||||||
isLastStep
|
isLastStep
|
||||||
}) => {
|
}) => {
|
||||||
@@ -471,12 +472,11 @@ export const SuppliersSetupStep: React.FC<SetupStepProps> = ({
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={isLastStep ? () => onComplete?.() : onNext}
|
onClick={() => onComplete?.()}
|
||||||
disabled={!canContinue}
|
disabled={!canContinue}
|
||||||
className="px-6 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium flex items-center gap-2"
|
className="px-6 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{isLastStep ? t('common:complete', 'Complete') : t('common:next', 'Next')}
|
{t('setup_wizard:navigation.continue', 'Continue →')}
|
||||||
→
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface TeamMember {
|
|||||||
role: string;
|
role: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, canContinue }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Local state for team members (will be sent to backend when API is available)
|
// Local state for team members (will be sent to backend when API is available)
|
||||||
@@ -310,6 +310,19 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Continue button - only shown when used in onboarding context */}
|
||||||
|
{onComplete && (
|
||||||
|
<div className="flex justify-end mt-6 pt-6 border-t border-[var(--border-secondary)]">
|
||||||
|
<button
|
||||||
|
onClick={onComplete}
|
||||||
|
disabled={canContinue === false}
|
||||||
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{t('setup_wizard:navigation.continue', 'Continue →')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user