// src/types/apiTypes.ts


export interface ApiErrorDetail
{
	[field: string]: string | string[];// field keys map to string or array of error messages
}

export interface GenericResponse<Type = any>
{
	success: boolean;
	message?: string | null;
	errors?: ApiErrorDetail | null;// optional field validation errors or other errors
	data?: Type | null;// optional additional response data e.g., userId, signup_phase
}

// src/utils/apiValidationHandlerUtils.ts
import { Dispatch, SetStateAction } from 'react';
import { ApiErrorDetail } from '../types/apiTypes';
import { emitEvent } from './eventBusUtils';
import logger from './loggerUtils';


export function setYupErrors
(
	validationError: any,
	setFormValidationErrors: Dispatch<SetStateAction<ApiErrorDetail>>
): void 
{
	if (validationError?.inner && Array.isArray(validationError.inner))
	{
		const yupErrors: ApiErrorDetail = {};
		
		validationError.inner.forEach((errorItem: any) =>
		{
			if (errorItem.path && !(errorItem.path in yupErrors))
			{
				yupErrors[errorItem.path] = errorItem.message;
			}
		});
		
		logger.log('Yup validation errors:', yupErrors);
		
		setFormValidationErrors(yupErrors);
	}
	else
	{
		logger.warn('Yup ValidationError missing inner array or invalid format:', validationError);
		
		setFormValidationErrors({});
	}
}

export function normalizeApiErrors(errors: any): ApiErrorDetail
{
	if (!errors || typeof errors !== 'object')
	{
		return {};
	}
	
	const normalized: ApiErrorDetail = {};
	
	Object.entries(errors).forEach(([field, value]) =>
	{
		if (Array.isArray(value))
		{
			const filtered = value.filter((msg) => typeof msg === 'string' && msg.trim() !== '');
			
			normalized[field] = filtered.length === 1 ? filtered[0] : filtered;
		}
		else if (typeof value === 'string' && value.trim() !== '')
		{
			normalized[field] = value;
		}
		
		else 
		{
			// If value is something else (object etc.), ignore or transform as needed
			// For now, ignore silently
			console.log('Normalize Api Error - Unexpected value:', value);
		}
	});
	
	return normalized;
}

export function handleValidationError
(
	error: any,
	setErrorMessage: Dispatch<SetStateAction<string | null>>,
	setFormValidationErrors: Dispatch<SetStateAction<ApiErrorDetail>>,
	emitGlobalAlert: boolean = false// default param to allow skipping alert if needed
): void
{
	// Yup validation error path
	if (error?.name === 'ValidationError' && Array.isArray(error.inner))
	{
		
		const validationErrors: ApiErrorDetail = {};
		
		error.inner.forEach((errorItem: any) =>
		{
			if (errorItem.path && !(errorItem.path in validationErrors))
			{
				validationErrors[errorItem.path] = errorItem.message;
			}
		});
		
		setFormValidationErrors(validationErrors);
		
		const firstErrorMessage = error.inner.length > 0 ? error.inner[0].message : error.message;
		
		setErrorMessage(firstErrorMessage);
		
		if (emitGlobalAlert)
		{
			emitEvent('alert', { type: 'danger', message: firstErrorMessage });
		}
		
		//console.warn('Yup validation error:', error);// <!-- TODO DEBUG
		logger.warn('Yup validation error:', error);
		
	}
	
	// API validation error response path
	else if (error?.errors && typeof error.errors === 'object')
	{
		// Backend or API validation errors (expected to be ApiErrorDetail shape or close)
		const normalizedErrors = normalizeApiErrors(error.errors);
		
		setFormValidationErrors(normalizedErrors);
		
		// Derive general message from API response message or first field error
		const generalErrorMessage = typeof error.message === 'string' && error.message.trim() !== '' 
			? error.message : Object.values(normalizedErrors)[0] ?? 'Validation failed';
			
		setErrorMessage(generalErrorMessage);
		
		if (emitGlobalAlert)
		{
			emitEvent('alert', { type: 'danger', message: generalErrorMessage });
		}
		
		logger.warn('API validation error:', error);
	}
	
	// Unknown/error fallback
	else
	{
		// Other kinds of errors (network, unknown, etc.)
		const fallbackErrorMessage = error?.message || 'An unknown error occurred.';
		
		setErrorMessage(fallbackErrorMessage);
		setFormValidationErrors({});
		
		if (emitGlobalAlert)
		{
			emitEvent('alert', { type: 'danger', message: fallbackErrorMessage });
		}
		
		logger.error('Unhandled error:', error);
	}
}

// src/utils/apiResponseHandlerUtils.ts
import type { Dispatch, SetStateAction } from 'react';
import type { GenericResponse, ApiErrorDetail } from '../types/apiTypes';
import { emitEvent } from './eventBusUtils';
import logger from '../utils/loggerUtils';


export function handleApiResponse<Type = any>
(
	response: GenericResponse<Type>,
	setErrorMessage: Dispatch<SetStateAction<string | null>>,
	setSuccessMessage: Dispatch<SetStateAction<string | null>>,
	setFormValidationErrors: Dispatch<SetStateAction<ApiErrorDetail>>,
	emitGlobalAlert: boolean = false// default param to allow skipping alert if needed
): boolean
{
	if (response.success)
	{
		setErrorMessage(null);
		setSuccessMessage(response.message || null);
		setFormValidationErrors({});
		
		if (emitGlobalAlert && response.message)
		{
			emitEvent('alert', { type: 'success', message: response.message });
		}
		
		return true;
	}
	else
	{
		const errorMsg = response.message || 'An unknown error occurred.';
		
		setErrorMessage(errorMsg);
		
		if (response.errors && typeof response.errors === 'object')
		{
			setFormValidationErrors(response.errors);
		}
		else
		{
			setFormValidationErrors({});
		}
		
		if (emitGlobalAlert)
		{
			emitEvent('alert', { type: 'danger', message: errorMsg });
		}
		
		// Log error for diagnostics
		logger.error('API Response Error:', errorMsg, response.errors);
		
		return false;
	}
}

// src/components/site/signup/utils.ts
import type { Dispatch, SetStateAction } from 'react';
import type { GenericResponse, ApiErrorDetail } from '../../../types/apiTypes';
import { handleApiResponse } from '../../../utils/apiResponseHandlerUtils';
import logger from '../../../utils/loggerUtils';


export function handleApiResponseWithState<Type = any>(
	response: GenericResponse<Type>,
	setErrorMessage: Dispatch<SetStateAction<string | null>>,
	setSuccessMessage: Dispatch<SetStateAction<string | null>>,
	setFormValidationErrors: Dispatch<SetStateAction<ApiErrorDetail>>,
	setUserId: Dispatch<SetStateAction<number | null>>,
	setUserToken: Dispatch<SetStateAction<string | null>>,
	setStep: Dispatch<SetStateAction<number>>,
	// "nextStepMap" - Used as map for setting steps calls from backend data array next_step
	nextStepMap: Record<string, number> =
	{
		email_verification: 1,
		password_setup: 2,
		business_category: 3,
		// add other mappings if needed
	}
): boolean
{
	// Reuse existing, basic success/error handling:
	const success = handleApiResponse(
		response,
		setErrorMessage,
		setSuccessMessage,
		setFormValidationErrors,
		// using default true for emitGlobalAlert parameter, so omit here for brevity
	);
	
	if (success && response.data)
	{
		// Confirm passing setStep to handleApiResponseWithState
		if (typeof setStep === 'function')
		{
			if (typeof response.data.userId === 'number')
			{
				setUserId(response.data.userId);
			}
			if (typeof response.data.signup_phase === 'number')
			{
				setStep(response.data.signup_phase);
			}
			else if (
				typeof response.data.next_step === 'string' &&
				nextStepMap[response.data.next_step] !== undefined
			)
			{
				setStep(nextStepMap[response.data.next_step]);
			}
			else
			{
				setStep(0);
			}
		}
		
		if (
			'token' in response.data &&
			typeof response.data.token === 'string'
		)
		{
			setUserToken(response.data.token);
			localStorage.setItem('jwtToken', response.data.token);
		}
	}
		
	return success;
}


// src/components/site/signup/useSignup.ts
import { useState, useEffect, useCallback, ChangeEvent } from 'react';
import * as signupServices from '../../../services/signupServices';
import { handleValidationError } from '../../../utils/apiValidationHandlerUtils';
import { handleApiResponseWithState } from './utils';
import { stageSchemas } from './validationSchemas';


// Setup Total Steps for the signup progress bar
export const TOTAL_STEPS = Object.keys(SIGNUP_PHASES).length;

export function useSignup()
{
	const [errorMessage, setErrorMessage] = useState<string | null>(null);
	const [successMessage, setSuccessMessage] = useState<string | null>(null);
	const [formValidationErrors, setFormValidationErrors] = useState<Record<string, string[] | string>>({});

	const submitStage = useCallback(async () =>
	{
		// Clear errors and success alerts on each new step
		setErrorMessage(null);
		setFormValidationErrors({});
		setSuccessMessage(null);
		
		if (!csrf)
		{
			setErrorMessage('CSRF token missing, please refresh.');
			
			return;
		}
		
		setLoading(true);
		
		try
		{
			let response: GenericResponse<any> | undefined;
			
			switch (step)
			{
				//  Stage Zero - Submit email
				case 0 : {
				
					await stageSchemas[0].validate(
					{
						email: signupData.email
					}, {
						abortEarly: false
					});
					
					try
					{
						response = await signupServices.submitSignupStageZeroEmail(
						{
							email: signupData.email
						},
							csrf
						);
						
						// Handle response + update signup state (userId, token, step, etc)
						const success = handleApiResponseWithState(
							response,
							setErrorMessage,
							setSuccessMessage,
							setFormValidationErrors,
							setUserId,
							setUserToken,
							setStep
						);
						
						if (success && response.message)
						{
							logger.log('Email submitted! Checking your inbox for a verification token.');
							
						}
					}
					catch (error: any)
					{
						logger.log('Yup ValidationError:', error);
						
						handleValidationError(errorMessage, setErrorMessage, setFormValidationErrors);
					}
					
					break;
				}
				
				// rest of code here...
				

// src/components/site/Signup.tsx
import React, { FormEvent } from 'react';
import { useSignup } from './signup/useSignup';
import StageZeroEmail from './signup/stages/StageZeroEmail';


function Signup()
{
	// Passing data to useSignup()
	const {
		step,
		//setStep,// Assigned but not used
		//userId,// Assigned but not used
		//setUserId,// Assigned but not used
		//categories,// Assigned but not used
		//setCategories,// Not passed from useSignup.ts to here
		signupData,
		//setSignupData,// Assigned but not used
		csrf,
		//setCsrf,// Not passed from useSignup.ts to here
		loading,
		//setLoading,// Not passed from useSignup.ts to here
		errorMessage,
		//setErrorMessage,// Not passed from useSignup.ts to here
		successMessage,
		//setSuccessMessage,// Not passed from useSignup.ts to here
		formValidationErrors,
		//setFormValidationErrors,// Not passed from useSignup.ts to here
		updateField,// function
		//handleFileChange,// function // Assigned but not used
		submitStage,// function
		handleBack,// function
		SIGNUP_PHASES,
		TOTAL_STEPS,
	} = useSignup();
	
	const renderStepFields = () =>
	{
		switch (step)
		{
			case 0 :
				return (
					<StageZeroEmail
						csrf={csrf}
						signupData={signupData}
						formErrors={formValidationErrors}
						loading={loading}
						updateField={updateField}
					/>
				);
			
			// rest of code here...
	
	
// src/components/site/signup/stages/StageZeroEmail.ts


interface StageZeroEmailProps
{
	csrf: CsrfData | null;
	signupData: SignupData;
	formErrors: Record<string, string[] | string>;
	loading: boolean;
	updateField: <Key extends keyof SignupData>(field: Key, value: SignupData[Key]) => void;
}

function StageZeroEmail({ csrf, signupData, formErrors, loading, updateField }: StageZeroEmailProps)
{
	return (
		<div className="mb-3">
		
			{csrf && (<input type="hidden" name={csrf?.csrfParam} value={csrf?.csrfToken} />)}
			
			<label htmlFor="email" className="form-label">Email</label>
			<input
				id="email"
				type="email"
				className={`form-control ${formErrors.email ? 'is-invalid' : ''}`}
				value={signupData.email}
				onChange={e => updateField('email', e.target.value)}
				disabled={loading}
				required
			/>
			
			{formErrors.email && (
				<div className="invalid-feedback">
					{formErrors.email && 
						(Array.isArray(formErrors.email) ? formErrors.email[0] : formErrors.email)
					}
				</div>
			)}
			
		</div>
	);
}

Above is the code structure I believe you need to see in order to help me understand why yup form field error messages are not displaying.