diff --git a/striker-ui/components/GeneralInitForm.tsx b/striker-ui/components/GeneralInitForm.tsx index 789d5d74..d99fb9e9 100644 --- a/striker-ui/components/GeneralInitForm.tsx +++ b/striker-ui/components/GeneralInitForm.tsx @@ -23,11 +23,7 @@ import OutlinedInputWithLabel, { } from './OutlinedInputWithLabel'; import pad from '../lib/pad'; import SuggestButton from './SuggestButton'; -import { - createTestInputFunction, - testLength, - testNotBlank, -} from '../lib/test_input'; +import { createTestInputFunction, testNotBlank } from '../lib/test_input'; import { InputTestBatches } from '../types/TestInputFunction'; import { BodyText } from './Text'; @@ -51,6 +47,15 @@ const MAX_ORGANIZATION_PREFIX_LENGTH = 5; const MIN_ORGANIZATION_PREFIX_LENGTH = 1; const MAX_HOST_NUMBER_LENGTH = 2; const INPUT_COUNT = 7; +const INPUT_TEST_IDS = { + adminPassword: 'adminPassword', + confirmAdminPassword: 'confirmAdminPassword', + domainName: 'domainName', + hostName: 'hostName', + hostNumber: 'hostNumber', + organizationName: 'organizationName', + organizationPrefix: 'organizationPrefix', +}; const MAP_TO_ORGANIZATION_PREFIX_BUILDER: Record< number, @@ -104,585 +109,625 @@ const MessageChildren: FC = ({ ...flexBoxProps }) => ( /> ); -const GeneralInitForm = forwardRef( - (generalInitFormProps, ref) => { - const [helpMessage, setHelpMessage] = useState(); - const [ - isShowOrganizationPrefixSuggest, - setIsShowOrganizationPrefixSuggest, - ] = useState(false); - const [isShowHostNameSuggest, setIsShowHostNameSuggest] = - useState(false); - const [isConfirmAdminPassword, setIsConfirmAdminPassword] = - useState(true); +const GeneralInitForm = forwardRef< + GeneralInitFormForwardRefContent, + { toggleSubmitDisabled?: ToggleSubmitDisabledFunction } +>(({ toggleSubmitDisabled }, ref) => { + const [helpMessage, setHelpMessage] = useState(); + const [isShowOrganizationPrefixSuggest, setIsShowOrganizationPrefixSuggest] = + useState(false); + const [isShowHostNameSuggest, setIsShowHostNameSuggest] = + useState(false); + const [isConfirmAdminPassword, setIsConfirmAdminPassword] = + useState(true); - const adminPasswordInputRef = useRef>( - {}, - ); - const confirmAdminPasswordInputRef = useRef< - InputForwardedRefContent<'string'> - >({}); - const organizationNameInputRef = useRef>( - {}, - ); - const organizationPrefixInputRef = useRef< - InputForwardedRefContent<'string'> - >({}); - const domainNameInputRef = useRef>({}); - const hostNumberInputRef = useRef>({}); - const hostNameInputRef = useRef>({}); - const messageGroupRef = useRef({}); + const adminPasswordInputRef = useRef>({}); + const confirmAdminPasswordInputRef = useRef< + InputForwardedRefContent<'string'> + >({}); + const organizationNameInputRef = useRef>( + {}, + ); + const organizationPrefixInputRef = useRef>( + {}, + ); + const domainNameInputRef = useRef>({}); + const hostNumberInputRef = useRef>({}); + const hostNameInputRef = useRef>({}); + const messageGroupRef = useRef({}); - const setOrganizationPrefixInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 1, message), - [], - ); - const setHostNumberInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 2, message), - [], - ); - const setDomainNameInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 3, message), - [], - ); - const setHostNameInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 4, message), - [], - ); - const setAdminPasswordInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 5, message), - [], - ); - const setConfirmAdminPasswordInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 6, message), - [], - ); + const setOrganizationPrefixInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 1, message), + [], + ); + const setHostNumberInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 2, message), + [], + ); + const setDomainNameInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 3, message), + [], + ); + const setHostNameInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 4, message), + [], + ); + const setAdminPasswordInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 5, message), + [], + ); + const setConfirmAdminPasswordInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 6, message), + [], + ); - const inputTests: InputTestBatches = useMemo( - () => ({ - adminPassword: { - defaults: { - onSuccess: () => { - setAdminPasswordInputMessage(undefined); - }, + const inputTests: InputTestBatches = useMemo( + () => ({ + [INPUT_TEST_IDS.adminPassword]: { + defaults: { + getValue: () => adminPasswordInputRef.current.getValue?.call(null), + onSuccess: () => { + setAdminPasswordInputMessage(undefined); }, - tests: [ - { - onFailure: () => { - setAdminPasswordInputMessage({ - children: ( - - Admin password cannot contain single-quote ({ms("'")}), - double-quote ({ms('"')}), slash ({ms('/')}), backslash ( - {ms('\\')}), angle brackets ({ms('<>')}), curly brackets ( - {ms('{}')}). - - ), - }); - }, - test: ({ value }) => !/['"/\\><}{]/g.test(value as string), - }, - { test: testNotBlank }, - ], }, - confirmAdminPassword: { - defaults: { - onSuccess: () => { - setConfirmAdminPasswordInputMessage(undefined); + tests: [ + { + onFailure: () => { + setAdminPasswordInputMessage({ + children: ( + + Admin password cannot contain single-quote ({ms("'")}), + double-quote ({ms('"')}), slash ({ms('/')}), backslash ( + {ms('\\')}), angle brackets ({ms('<>')}), curly brackets ( + {ms('{}')}). + + ), + }); }, + test: ({ value }) => !/['"/\\><}{]/g.test(value as string), + }, + { test: testNotBlank }, + ], + }, + [INPUT_TEST_IDS.confirmAdminPassword]: { + defaults: { + getValue: () => + confirmAdminPasswordInputRef.current.getValue?.call(null), + onSuccess: () => { + setConfirmAdminPasswordInputMessage(undefined); }, - tests: [ - { - onFailure: () => { - setConfirmAdminPasswordInputMessage({ - children: 'Admin password confirmation failed.', - }); - }, - test: ({ value }) => - value === adminPasswordInputRef.current.getValue?.call(null), - }, - { test: testNotBlank }, - ], }, - domainName: { - defaults: { - onSuccess: () => { - setDomainNameInputMessage(undefined); + tests: [ + { + onFailure: () => { + setConfirmAdminPasswordInputMessage({ + children: 'Admin password confirmation failed.', + }); }, + test: ({ value }) => + value === adminPasswordInputRef.current.getValue?.call(null), + }, + { test: testNotBlank }, + ], + }, + [INPUT_TEST_IDS.domainName]: { + defaults: { + getValue: () => domainNameInputRef.current.getValue?.call(null), + onSuccess: () => { + setDomainNameInputMessage(undefined); }, - tests: [ - { - onFailure: () => { - setDomainNameInputMessage({ - children: ( - - Domain name can only contain lowercase alphanumeric, - hyphen ({ms('-')}), and dot ({ms('.')}) characters. - - ), - }); - }, - test: ({ value }) => REP_DOMAIN.test(value as string), - }, - { test: testNotBlank }, - ], }, - hostName: { - defaults: { - onSuccess: () => { - setHostNameInputMessage(undefined); + tests: [ + { + onFailure: () => { + setDomainNameInputMessage({ + children: ( + + Domain name can only contain lowercase alphanumeric, hyphen + ({ms('-')}), and dot ({ms('.')}) characters. + + ), + }); }, + test: ({ value }) => REP_DOMAIN.test(value as string), + }, + { test: testNotBlank }, + ], + }, + [INPUT_TEST_IDS.hostName]: { + defaults: { + getValue: () => hostNameInputRef.current.getValue?.call(null), + onSuccess: () => { + setHostNameInputMessage(undefined); }, - tests: [ - { - onFailure: () => { - setHostNameInputMessage({ - children: ( - - Host name can only contain lowercase alphanumeric, hyphen - ({ms('-')}), and dot ({ms('.')}) characters. - - ), - }); - }, - test: ({ value }) => REP_DOMAIN.test(value as string), - }, - { test: testNotBlank }, - ], }, - hostNumber: { - defaults: { - onSuccess: () => { - setHostNumberInputMessage(undefined); + tests: [ + { + onFailure: () => { + setHostNameInputMessage({ + children: ( + + Host name can only contain lowercase alphanumeric, hyphen ( + {ms('-')}), and dot ({ms('.')}) characters. + + ), + }); }, + test: ({ value }) => REP_DOMAIN.test(value as string), }, - tests: [ - { - onFailure: () => { - setHostNumberInputMessage({ - children: 'Host number can only contain digits.', - }); - }, - test: ({ value }) => /^\d+$/.test(value as string), + { test: testNotBlank }, + ], + }, + [INPUT_TEST_IDS.hostNumber]: { + defaults: { + getValue: () => hostNumberInputRef.current.getValue?.call(null), + onSuccess: () => { + setHostNumberInputMessage(undefined); + }, + }, + tests: [ + { + onFailure: () => { + setHostNumberInputMessage({ + children: 'Host number can only contain digits.', + }); }, - { test: testNotBlank }, - ], + test: ({ value }) => /^\d+$/.test(value as string), + }, + { test: testNotBlank }, + ], + }, + [INPUT_TEST_IDS.organizationName]: { + defaults: { + getValue: () => organizationNameInputRef.current.getValue?.call(null), }, - organizationName: { - tests: [{ test: testNotBlank }], + tests: [{ test: testNotBlank }], + }, + [INPUT_TEST_IDS.organizationPrefix]: { + defaults: { + getValue: () => + organizationPrefixInputRef.current.getValue?.call(null), + max: MAX_ORGANIZATION_PREFIX_LENGTH, + min: MIN_ORGANIZATION_PREFIX_LENGTH, + onSuccess: () => { + setOrganizationPrefixInputMessage(undefined); + }, }, - organizationPrefix: { - defaults: { - onSuccess: () => { - setOrganizationPrefixInputMessage(undefined); + tests: [ + { + onFailure: ({ max, min }) => { + setOrganizationPrefixInputMessage({ + children: `Organization prefix must be ${min} to ${max} lowercase alphanumeric characters.`, + }); }, + test: ({ max, min, value }) => + RegExp(`^[a-z0-9]{${min},${max}}$`).test(value as string), }, - tests: [ - { - onFailure: ({ max, min }) => { - setOrganizationPrefixInputMessage({ - children: `Organization prefix must be ${min} to ${max} characters.`, - }); - }, - test: testLength, - }, - { - onFailure: () => { - setOrganizationPrefixInputMessage({ - children: - 'Organization prefix can only contain lowercase alphanumeric characters.', - }); - }, - test: ({ value }) => /^[a-z0-9]+$/.test(value as string), - }, - { test: testNotBlank }, - ], - }, - }), - [ - setAdminPasswordInputMessage, - setConfirmAdminPasswordInputMessage, - setDomainNameInputMessage, - setHostNameInputMessage, - setHostNumberInputMessage, - setOrganizationPrefixInputMessage, - ], - ); + ], + }, + }), + [ + setAdminPasswordInputMessage, + setConfirmAdminPasswordInputMessage, + setDomainNameInputMessage, + setHostNameInputMessage, + setHostNumberInputMessage, + setOrganizationPrefixInputMessage, + ], + ); - const testInput = useMemo( - () => createTestInputFunction(inputTests), - [inputTests], - ); - const populateOrganizationPrefixInput = useCallback( - ({ - organizationName = organizationNameInputRef.current.getValue?.call( - null, - ), - } = {}) => { - const organizationPrefix = buildOrganizationPrefix(organizationName); + const testInput = useMemo( + () => createTestInputFunction(inputTests), + [inputTests], + ); + const testAllInputs = useCallback( + (...excludeTestIds: string[]) => + testInput({ excludeTestIds, isIgnoreOnCallbacks: true }), + [testInput], + ); + const populateOrganizationPrefixInput = useCallback( + ({ + organizationName = organizationNameInputRef.current.getValue?.call(null), + } = {}) => { + const organizationPrefix = buildOrganizationPrefix(organizationName); - organizationPrefixInputRef.current.setValue?.call( - null, - organizationPrefix, - ); + organizationPrefixInputRef.current.setValue?.call( + null, + organizationPrefix, + ); - return organizationPrefix; - }, - [], - ); - const populateHostNameInput = useCallback( - ({ - organizationPrefix = organizationPrefixInputRef.current.getValue?.call( - null, - ), - hostNumber = hostNumberInputRef.current.getValue?.call(null), - domainName = domainNameInputRef.current.getValue?.call(null), - } = {}) => { - const hostName = buildHostName({ - organizationPrefix, - hostNumber, - domainName, - }); + return organizationPrefix; + }, + [], + ); + const populateHostNameInput = useCallback( + ({ + organizationPrefix = organizationPrefixInputRef.current.getValue?.call( + null, + ), + hostNumber = hostNumberInputRef.current.getValue?.call(null), + domainName = domainNameInputRef.current.getValue?.call(null), + } = {}) => { + const hostName = buildHostName({ + organizationPrefix, + hostNumber, + domainName, + }); - hostNameInputRef.current.setValue?.call(null, hostName); + hostNameInputRef.current.setValue?.call(null, hostName); - return hostName; - }, - [], - ); - const isOrganizationPrefixPrereqFilled = useCallback( - () => - isEmpty([organizationNameInputRef.current.getValue?.call(null)], { + return hostName; + }, + [], + ); + const isOrganizationPrefixPrereqFilled = useCallback( + () => + isEmpty([organizationNameInputRef.current.getValue?.call(null)], { + not: true, + }), + [], + ); + const isHostNamePrereqFilled = useCallback( + () => + isEmpty( + [ + organizationPrefixInputRef.current.getValue?.call(null), + hostNumberInputRef.current.getValue?.call(null), + domainNameInputRef.current.getValue?.call(null), + ], + { not: true, - }), - [], - ); - const isHostNamePrereqFilled = useCallback( - () => - isEmpty( - [ - organizationPrefixInputRef.current.getValue?.call(null), - hostNumberInputRef.current.getValue?.call(null), - domainNameInputRef.current.getValue?.call(null), - ], - { - not: true, - }, - ), - [], - ); - const populateOrganizationPrefixInputOnBlur: OutlinedInputWithLabelOnBlur = - useCallback(() => { - if (organizationPrefixInputRef.current.getIsChangedByUser?.call(null)) { - setIsShowOrganizationPrefixSuggest( - isOrganizationPrefixPrereqFilled(), - ); - } else { - populateOrganizationPrefixInput(); - } - }, [isOrganizationPrefixPrereqFilled, populateOrganizationPrefixInput]); - const populateHostNameInputOnBlur: OutlinedInputWithLabelOnBlur = - useCallback(() => { - if (hostNameInputRef.current.getIsChangedByUser?.call(null)) { - setIsShowHostNameSuggest(isHostNamePrereqFilled()); - } else { - populateHostNameInput(); - } - }, [isHostNamePrereqFilled, populateHostNameInput]); - const handleOrganizationPrefixSuggest = useCallback(() => { - const organizationPrefix = populateOrganizationPrefixInput(); - - if (!hostNameInputRef.current.getIsChangedByUser?.call(null)) { - populateHostNameInput({ organizationPrefix }); + }, + ), + [], + ); + const populateOrganizationPrefixInputOnBlur: OutlinedInputWithLabelOnBlur = + useCallback(() => { + if (organizationPrefixInputRef.current.getIsChangedByUser?.call(null)) { + setIsShowOrganizationPrefixSuggest(isOrganizationPrefixPrereqFilled()); + } else { + populateOrganizationPrefixInput(); + } + }, [isOrganizationPrefixPrereqFilled, populateOrganizationPrefixInput]); + const populateHostNameInputOnBlur: OutlinedInputWithLabelOnBlur = + useCallback(() => { + if (hostNameInputRef.current.getIsChangedByUser?.call(null)) { + setIsShowHostNameSuggest(isHostNamePrereqFilled()); + } else { + populateHostNameInput(); } - }, [populateHostNameInput, populateOrganizationPrefixInput]); - const handlerHostNameSuggest = useCallback(() => { - populateHostNameInput(); - }, [populateHostNameInput]); - const buildHelpMessage = useCallback( - (text: string) => (previous?: string) => - previous === text ? undefined : text, - [], - ); + }, [isHostNamePrereqFilled, populateHostNameInput]); + const handleOrganizationPrefixSuggest = useCallback(() => { + const organizationPrefix = populateOrganizationPrefixInput(); - useImperativeHandle(ref, () => ({ - get: () => ({ - adminPassword: adminPasswordInputRef.current.getValue?.call(null), - organizationName: organizationNameInputRef.current.getValue?.call(null), - organizationPrefix: - organizationPrefixInputRef.current.getValue?.call(null), - domainName: domainNameInputRef.current.getValue?.call(null), - hostNumber: hostNumberInputRef.current.getValue?.call(null), - hostName: hostNameInputRef.current.getValue?.call(null), - }), - })); + if (!hostNameInputRef.current.getIsChangedByUser?.call(null)) { + populateHostNameInput({ organizationPrefix }); + } + }, [populateHostNameInput, populateOrganizationPrefixInput]); + const handlerHostNameSuggest = useCallback(() => { + populateHostNameInput(); + }, [populateHostNameInput]); + const buildHelpMessage = useCallback( + (text: string) => (previous?: string) => + previous === text ? undefined : text, + [], + ); + + useImperativeHandle(ref, () => ({ + get: () => ({ + adminPassword: adminPasswordInputRef.current.getValue?.call(null), + organizationName: organizationNameInputRef.current.getValue?.call(null), + organizationPrefix: + organizationPrefixInputRef.current.getValue?.call(null), + domainName: domainNameInputRef.current.getValue?.call(null), + hostNumber: hostNumberInputRef.current.getValue?.call(null), + hostName: hostNameInputRef.current.getValue?.call(null), + }), + })); - return ( - - - - + return ( + + + + + { + toggleSubmitDisabled?.call(null, testAllInputs()); + }} + onHelp={() => { + setHelpMessage( + buildHelpMessage( + 'Name of the organization that maintains this Anvil! system. You can enter anything that makes sense to you.', + ), + ); + }} + /> + } + ref={organizationNameInputRef} + /> + :first-child': { + flexGrow: 1, + }, + }} + > + ), + inputProps: { + maxLength: MAX_ORGANIZATION_PREFIX_LENGTH, + sx: { + minWidth: '2.5em', + }, + }, + onBlur: populateHostNameInputOnBlur, }} inputLabelProps={{ isNotifyRequired: true }} - label="Organization name" + label="Prefix" + onChange={({ target: { value } }) => { + testInput({ + inputs: { + [INPUT_TEST_IDS.organizationPrefix]: { + max: MAX_ORGANIZATION_PREFIX_LENGTH, + min: MIN_ORGANIZATION_PREFIX_LENGTH, + value, + }, + }, + }); + toggleSubmitDisabled?.call( + null, + testAllInputs(INPUT_TEST_IDS.organizationPrefix), + ); + setIsShowOrganizationPrefixSuggest( + isOrganizationPrefixPrereqFilled(), + ); + }} onHelp={() => { setHelpMessage( buildHelpMessage( - 'Name of the organization that maintains this Anvil! system. You can enter anything that makes sense to you.', + "Alphanumberic short-form of the organization name. It's used as the prefix for host names.", ), ); }} /> } - ref={organizationNameInputRef} + ref={organizationPrefixInputRef} /> - :first-child': { - flexGrow: 1, - }, - }} - > - - ), - inputProps: { - maxLength: MAX_ORGANIZATION_PREFIX_LENGTH, - sx: { - minWidth: '2.5em', - }, - }, - onBlur: populateHostNameInputOnBlur, - }} - inputLabelProps={{ isNotifyRequired: true }} - label="Prefix" - onChange={({ target: { value } }) => { - testInput({ - inputs: { - organizationPrefix: { - max: MAX_ORGANIZATION_PREFIX_LENGTH, - min: MIN_ORGANIZATION_PREFIX_LENGTH, - value, - }, - }, - }); - setIsShowOrganizationPrefixSuggest( - isOrganizationPrefixPrereqFilled(), - ); - }} - onHelp={() => { - setHelpMessage( - buildHelpMessage( - "Alphanumberic short-form of the organization name. It's used as the prefix for host names.", - ), - ); - }} - /> - } - ref={organizationPrefixInputRef} - /> - { - testInput({ inputs: { hostNumber: { value } } }); - }} - onHelp={() => { - setHelpMessage( - buildHelpMessage( - "Number or count of this striker; this should be '1' for the first striker, '2' for the second striker, and such.", - ), - ); - }} - /> - } - ref={hostNumberInputRef} - valueType="number" - /> - - - - - { - testInput({ inputs: { domainName: { value } } }); + testInput({ + inputs: { [INPUT_TEST_IDS.hostNumber]: { value } }, + }); + toggleSubmitDisabled?.call( + null, + testAllInputs(INPUT_TEST_IDS.hostNumber), + ); }} onHelp={() => { setHelpMessage( buildHelpMessage( - "Domain name for this striker. It's also the default domain used when creating new install manifests.", + "Number or count of this striker; this should be '1' for the first striker, '2' for the second striker, and such.", ), ); }} /> } - ref={domainNameInputRef} + ref={hostNumberInputRef} + valueType="number" /> + + + + + + { + testInput({ + inputs: { [INPUT_TEST_IDS.domainName]: { value } }, + }); + toggleSubmitDisabled?.call( + null, + testAllInputs(INPUT_TEST_IDS.domainName), + ); + }} + onHelp={() => { + setHelpMessage( + buildHelpMessage( + "Domain name for this striker. It's also the default domain used when creating new install manifests.", + ), + ); + }} + /> + } + ref={domainNameInputRef} + /> + + ), + }} + inputLabelProps={{ isNotifyRequired: true }} + label="Host name" + onChange={({ target: { value } }) => { + testInput({ + inputs: { [INPUT_TEST_IDS.hostName]: { value } }, + }); + toggleSubmitDisabled?.call( + null, + testAllInputs(INPUT_TEST_IDS.hostName), + ); + setIsShowHostNameSuggest(isHostNamePrereqFilled()); + }} + onHelp={() => { + setHelpMessage( + buildHelpMessage( + "Host name for this striker. It's usually a good idea to use the auto-generated value.", + ), + ); + }} + /> + } + ref={hostNameInputRef} + /> + + + + * > *': { + width: '100%', + }, + }} + > + - ), + inputProps: { + type: INPUT_TYPES.password, + }, + onPasswordVisibilityAppend: (inputType) => { + setIsConfirmAdminPassword( + inputType === INPUT_TYPES.password, + ); + }, }} inputLabelProps={{ isNotifyRequired: true }} - label="Host name" + label="Admin password" onChange={({ target: { value } }) => { - testInput({ inputs: { hostName: { value } } }); - setIsShowHostNameSuggest(isHostNamePrereqFilled()); + testInput({ + inputs: { [INPUT_TEST_IDS.adminPassword]: { value } }, + }); + toggleSubmitDisabled?.call( + null, + testAllInputs(INPUT_TEST_IDS.adminPassword), + ); }} onHelp={() => { setHelpMessage( buildHelpMessage( - "Host name for this striker. It's usually a good idea to use the auto-generated value.", + "Password use to login to this Striker and connect to its database. Don't provide an used password here because it'll be stored as plaintext.", ), ); }} /> } - ref={hostNameInputRef} + ref={adminPasswordInputRef} /> - - - - * > *': { - width: '100%', - }, - }} - > + + {isConfirmAdminPassword && ( { - setIsConfirmAdminPassword( - inputType === INPUT_TYPES.password, - ); - }, }} - inputLabelProps={{ isNotifyRequired: true }} - label="Admin password" - onChange={({ target: { value } }) => { - testInput({ inputs: { adminPassword: { value } } }); + inputLabelProps={{ + isNotifyRequired: isConfirmAdminPassword, }} - onHelp={() => { - setHelpMessage( - buildHelpMessage( - "Password use to login to this Striker and connect to its database. Don't provide an used password here because it'll be stored as plaintext.", - ), + label="Confirm password" + onChange={({ target: { value } }) => { + testInput({ + inputs: { + [INPUT_TEST_IDS.confirmAdminPassword]: { value }, + }, + }); + toggleSubmitDisabled?.call( + null, + testAllInputs(INPUT_TEST_IDS.confirmAdminPassword), ); }} /> } - ref={adminPasswordInputRef} + ref={confirmAdminPasswordInputRef} /> - {isConfirmAdminPassword && ( - - { - testInput({ - inputs: { confirmAdminPassword: { value } }, - }); - }} - /> - } - ref={confirmAdminPasswordInputRef} - /> - - )} - + )} - - {helpMessage && ( - { - setHelpMessage(undefined); - }} - > - {helpMessage} - - )} - - ); - }, -); + + + {helpMessage && ( + { + setHelpMessage(undefined); + }} + > + {helpMessage} + + )} + + ); +}); +GeneralInitForm.defaultProps = { toggleSubmitDisabled: undefined }; GeneralInitForm.displayName = 'GeneralInitForm'; export type { GeneralInitFormForwardRefContent }; diff --git a/striker-ui/components/StrikerInitForm.tsx b/striker-ui/components/StrikerInitForm.tsx index 158ae437..2715d421 100644 --- a/striker-ui/components/StrikerInitForm.tsx +++ b/striker-ui/components/StrikerInitForm.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useRef, useState } from 'react'; +import { FC, useCallback, useMemo, useRef, useState } from 'react'; import ContainedButton from './ContainedButton'; import FlexBox from './FlexBox'; @@ -16,6 +16,11 @@ import { HeaderText } from './Text'; const StrikerInitForm: FC = () => { const [submitMessage, setSubmitMessage] = useState(); + const [isDisableSubmit, setIsDisableSubmit] = useState(true); + const [isGeneralInitFormValid, setIsGeneralInitFormValid] = + useState(false); + const [isNetworkInitFormValid, setIsNetworkInitFormValid] = + useState(false); const [isSubmittingForm, setIsSubmittingForm] = useState(false); const generalInitFormRef = useRef({}); @@ -28,6 +33,7 @@ const StrikerInitForm: FC = () => { ) : ( { setIsSubmittingForm(true); @@ -57,17 +63,37 @@ const StrikerInitForm: FC = () => { ), - [isSubmittingForm], + [isDisableSubmit, isSubmittingForm], ); + const toggleSubmitDisabled = useCallback((...testResults: boolean[]) => { + setIsDisableSubmit(!testResults.every((testResult) => testResult)); + }, []); + return ( - - + { + if (testResult !== isGeneralInitFormValid) { + setIsGeneralInitFormValid(testResult); + toggleSubmitDisabled(testResult, isNetworkInitFormValid); + } + }} + /> + { + if (testResult !== isNetworkInitFormValid) { + setIsNetworkInitFormValid(testResult); + toggleSubmitDisabled(isGeneralInitFormValid, testResult); + } + }} + /> {submitMessage && (