import License from './License'; import Checkbox from './Checkbox'; import CheckboxCard from './CheckboxCard'; import TrackingTest from './TrackingTest'; import Email from './Email'; import Password from './Password'; import Plugins from './Plugins'; import QrCode from './QrCode'; import TwoFaValidation from './TwoFaValidation'; import BackupCodes from './BackupCodes'; import Dropdown from './Dropdown'; import NumberInputWithControls from './NumberInputWithControls'; import MultiSelectDropdown from './MultiSelectDropdown'; import Text from './Text'; import Textarea from './Textarea'; import Pricing from './Pricing'; import Status from './Status'; import { ErrorBoundary } from '../ErrorBoundary'; // @ts-ignore import useOnboardingStore from "@/store/useOnboardingStore"; // @ts-ignore import useAlertStore from "@/store/useAlertStore"; import ButtonInput from '../Inputs/ButtonInput'; import Alert from '../Alert'; import { normalizeValue } from '../../utils/normalizeValue'; import { useEffect } from '@wordpress/element'; type FieldsProps = { fields: any[]; onChange: (id: string, value: any) => void; fieldStatus?: (id: string, success: boolean) => void; }; /** * Fields component that renders different field types based on the field configuration * @param {Object} props Component props * @param {Array} props.fields Array of field configurations * @param {Function} props.onChange Callback function when field values change * @param {Function} props.fieldStatus Callback function when success status of field changes * @returns {JSX.Element|null} The rendered fields or null if no fields */ const Fields = ({ fields, onChange, fieldStatus = () => {} }:FieldsProps ) => { const { getValue, setValue, isEdited, settings, } = useOnboardingStore(); const { getAlertState, setAlertState } = useAlertStore(); // Effect to set default values for fields that haven't been edited. // This runs after the component renders, avoiding the "cannot update during render" warning. useEffect(() => { fields.forEach((field) => { const isEditedField = isEdited(field.id); const value = getValue(field.id); if (!isEditedField && (value === undefined || value === null) && field.default !== undefined) { const disabled = field.is_lock === true; if (disabled) { setValue(field.id, false); } else { setValue(field.id, field.default); } } }); }, [fields, settings, isEdited, setValue, getValue]); // Re-run only when necessary if (!fields) return null; const fieldComponents: Record = { two_fa_validation: TwoFaValidation, qr_code: QrCode, backup_codes: BackupCodes, license: License, checkbox: Checkbox, tracking_test: TrackingTest, email: Email, plugins: Plugins, password: Password, dropdown: Dropdown, number: NumberInputWithControls, multi_select: MultiSelectDropdown, button: ButtonInput, text: Text, textarea: Textarea, pricing: Pricing, status: Status, }; //the settings contain the values. return ( {fields.map((field) => { // 1) Evaluate visible_if (if present) if (field.visible_if && field.visible_if.field) { const cond = field.visible_if; // Try to get the current value from the store let currentValue = getValue(cond.field); const normalizedCurrent = normalizeValue(currentValue); const normalizedEquals = normalizeValue(cond.equals); if (normalizedCurrent !== normalizedEquals) { // Condition not met: skip rendering this field return null; } } // 2) Get the value from the store. Default values are now handled in the useEffect hook. const value = getValue(field.id); // Opt-in card layout for checkbox/switch fields. Any field that does not // set `layout: 'card'` keeps its existing component, so legacy consumers // are unaffected. const isCheckboxCard = field.type === 'checkbox' && field.layout === 'card'; const Component = isCheckboxCard ? CheckboxCard : (fieldComponents[field.type] || null); // 3) Special handling for "button" field type (externalAction) if (field.type === 'button') { const groupId = field.group_id; const alertState = getAlertState(groupId); const handleButtonClick = () => { const externalActionName = field.externalAction; const externalAction = (window as any).pluginOnboardingActions?.[externalActionName]; if (typeof externalAction === 'function') { externalAction( field, settings, (id: string, newState: any) => setAlertState(id, newState), setValue ); } else { console.warn(`External action '${externalActionName}' not found.`); setAlertState(groupId, { responseMessage: `Action '${externalActionName}' is not implemented.`, responseSuccess: false, responseCode: 'danger', isUpdating: false, }); fieldStatus(field.id, false); } }; // Determine variant and message for Alert const alertVariant = alertState.responseCode === 'loading' ? 'loading' : (alertState.responseCode === 'success' ? 'success' : 'danger'); const alertMessage = alertState.responseMessage; return (
{field.actionType === 'connection_test' && alertState.responseMessage && ( )} {field.label}
); } // 4) Common props for non-button fields const commonProps = { key: field.id, field: field, onChange: (val: any) => onChange(field.id, val), fieldStatus: (success: boolean) => fieldStatus(field.id, success), value: value, }; return Component ? : null; })}
); }; export default Fields;