diff --git a/striker-ui/components/GeneralInitForm.tsx b/striker-ui/components/GeneralInitForm.tsx index 190dbeea..62b3a277 100644 --- a/striker-ui/components/GeneralInitForm.tsx +++ b/striker-ui/components/GeneralInitForm.tsx @@ -1,9 +1,17 @@ -import { Box as MUIBox } from '@mui/material'; -import { forwardRef, useImperativeHandle, useRef, useState } from 'react'; +import { Grid as MUIGrid } from '@mui/material'; +import { + forwardRef, + ReactNode, + useCallback, + useImperativeHandle, + useRef, + useState, +} from 'react'; import FlexBox from './FlexBox'; import InputWithRef, { InputForwardedRefContent } from './InputWithRef'; import isEmpty from '../lib/isEmpty'; +import MessageBox from './MessageBox'; import OutlinedInputWithLabel, { OutlinedInputWithLabelProps, } from './OutlinedInputWithLabel'; @@ -12,6 +20,7 @@ import SuggestButton from './SuggestButton'; type GeneralInitFormForwardRefContent = { get?: () => { + adminPassword?: string; organizationName?: string; organizationPrefix?: string; domainName?: string; @@ -66,6 +75,7 @@ const buildHostName = ({ const GeneralInitForm = forwardRef( (generalInitFormProps, ref) => { + const [helpMessage, setHelpText] = useState(); const [ isShowOrganizationPrefixSuggest, setIsShowOrganizationPrefixSuggest, @@ -73,6 +83,12 @@ const GeneralInitForm = forwardRef( const [isShowHostNameSuggest, setIsShowHostNameSuggest] = useState(false); + const adminPasswordInputRef = useRef>( + {}, + ); + const confirmAdminPasswordInputRef = useRef< + InputForwardedRefContent<'string'> + >({}); const organizationNameInputRef = useRef>( {}, ); @@ -83,52 +99,66 @@ const GeneralInitForm = forwardRef( const hostNumberInputRef = useRef>({}); const hostNameInputRef = useRef>({}); - const populateOrganizationPrefixInput = ({ - organizationName = organizationNameInputRef.current.getValue?.call(null), - } = {}) => { - const organizationPrefix = buildOrganizationPrefix(organizationName); + 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 = ({ - 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 = () => - isEmpty([organizationNameInputRef.current.getValue?.call(null)], { - not: true, - }); - const isHostNamePrereqFilled = () => - isEmpty( - [ - organizationPrefixInputRef.current.getValue?.call(null), - hostNumberInputRef.current.getValue?.call(null), - domainNameInputRef.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 populateOrganizationPrefixInputOnBlur: OutlinedInputWithLabelOnBlur = - () => { + useCallback(() => { if (organizationPrefixInputRef.current.getIsChangedByUser?.call(null)) { setIsShowOrganizationPrefixSuggest( isOrganizationPrefixPrereqFilled(), @@ -136,27 +166,34 @@ const GeneralInitForm = forwardRef( } else { populateOrganizationPrefixInput(); } - }; - const populateHostNameInputOnBlur: OutlinedInputWithLabelOnBlur = () => { - if (hostNameInputRef.current.getIsChangedByUser?.call(null)) { - setIsShowHostNameSuggest(isHostNamePrereqFilled()); - } else { - populateHostNameInput(); - } - }; - const handleOrganizationPrefixSuggest = () => { + }, [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 handlerHostNameSuggest = () => { + }, [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), @@ -167,147 +204,210 @@ const GeneralInitForm = forwardRef( })); return ( - *': { - flexBasis: '50%', - }, - - '& > :not(:first-child)': { - marginLeft: { xs: 0, sm: '1em' }, - marginTop: { xs: '1em', sm: 0 }, - }, - }} - > - - + + + + { + setHelpText( + buildHelpMessage( + 'Name of the organization that maintains this Anvil! system. You can enter anything that makes sense to you.', + ), + ); + }} + /> + } + ref={organizationNameInputRef} /> - } - ref={organizationNameInputRef} - /> - - - ), - inputProps: { - maxLength: MAX_ORGANIZATION_PREFIX_LENGTH, - style: { width: '2.5em' }, - }, - onBlur: populateHostNameInputOnBlur, - sx: { - minWidth: 'min-content', - width: 'fit-content', - }, - }} - label="Prefix" - onChange={() => { - setIsShowOrganizationPrefixSuggest( - isOrganizationPrefixPrereqFilled(), - ); - }} - /> - } - ref={organizationPrefixInputRef} - /> - - } - ref={hostNumberInputRef} - valueType="number" - /> - - - - *': { + flexBasis: '50%', }, }} - label="Domain name" + > + + ), + inputProps: { + maxLength: MAX_ORGANIZATION_PREFIX_LENGTH, + }, + onBlur: populateHostNameInputOnBlur, + }} + label="Prefix" + onChange={() => { + setIsShowOrganizationPrefixSuggest( + isOrganizationPrefixPrereqFilled(), + ); + }} + onHelp={() => { + setHelpText( + buildHelpMessage( + "Alphanumberic short-form of the organization name. It's used as the prefix for host names.", + ), + ); + }} + /> + } + ref={organizationPrefixInputRef} + /> + { + setHelpText( + 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" + /> + + + + + + { + setHelpText( + buildHelpMessage( + "Domain name for this striker. It's also the default domain used when creating new install manifests.", + ), + ); + }} + /> + } + ref={domainNameInputRef} /> - } - ref={domainNameInputRef} - /> - - ), - inputProps: { - style: { - minWidth: '4em', - }, - }, - sx: { - minWidth: 'min-content', - }, - }} - label="Host name" - onChange={() => { - setIsShowHostNameSuggest(isHostNamePrereqFilled()); - }} + + ), + }} + label="Host name" + onChange={() => { + setIsShowHostNameSuggest(isHostNamePrereqFilled()); + }} + onHelp={() => { + setHelpText( + buildHelpMessage( + "Host name for this striker. It's usually a good idea to use the auto-generated value.", + ), + ); + }} + /> + } + ref={hostNameInputRef} /> - } - ref={hostNameInputRef} - /> - - + + + + * > *': { + width: '100%', + }, + }} + > + + { + setHelpText( + buildHelpMessage( + "Password use to login to this Striker and connect to its database. Don't reuse an existing password here because it'll be stored as plaintext.", + ), + ); + }} + /> + } + ref={adminPasswordInputRef} + /> + + + + } + ref={confirmAdminPasswordInputRef} + /> + + + + + {helpMessage && ( + { + setHelpText(undefined); + }} + > + {helpMessage} + + )} + ); }, );