import React, { useEffect, useState } from 'react'; import { Form, Field, useFormState, FormApi } from 'react-final-form'; import { OnChange } from 'react-final-form-listeners'; import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { CheckboxField, InputField, KnownHosts } from '@app/components'; import { LoadingState, useKnownHosts, useSettings } from '@app/hooks'; import './LoginForm.css'; interface LoginFormProps { onSubmit: (values: any) => void; disableSubmitButton: boolean; onResetPassword: () => void; } interface LoginFormBodyProps extends LoginFormProps { form: FormApi; handleSubmit: (event?: React.SyntheticEvent) => void; } const LoginFormBody = ({ form, handleSubmit, disableSubmitButton, onResetPassword, }: LoginFormBodyProps) => { const { t } = useTranslation(); const PASSWORD_LABEL = t('Common.label.password'); const STORED_PASSWORD_LABEL = `* ${t('LoginForm.label.savedPassword')} *`; const settings = useSettings(); const hosts = useKnownHosts(); const { values } = useFormState(); const selectedHost = hosts.status === LoadingState.READY ? hosts.value?.selectedHost : undefined; const [useStoredPasswordLabel, setUseStoredPasswordLabel] = useState(false); const [storedHashInvalidated, setStoredHashInvalidated] = useState(false); const canUseStoredPassword = (remember: boolean, password: string | undefined) => Boolean(remember && selectedHost?.hashedPassword && !password && !storedHashInvalidated); const togglePasswordLabel = (on: boolean) => setUseStoredPasswordLabel(on); // @critical Host-sync must not touch autoConnect — app-level setting, not per-host. useEffect(() => { if (!selectedHost) { return; } form.change('userName', selectedHost.userName); form.change('password', ''); form.change('remember', Boolean(selectedHost.remember)); setStoredHashInvalidated(false); togglePasswordLabel( Boolean(selectedHost.remember && selectedHost.hashedPassword) ); }, [selectedHost, form]); useEffect(() => { if (settings.status !== LoadingState.READY) { return; } form.change('autoConnect', settings.value?.autoConnect); }, [settings, form]); const onUserNameChange = (userName: string | undefined) => { const fieldChanged = selectedHost?.userName?.toLowerCase() !== userName?.toLowerCase(); if (canUseStoredPassword(values.remember, values.password) && fieldChanged) { setStoredHashInvalidated(true); } }; const onRememberChange = (checked: boolean) => { // @critical Writes form-only, never to persisted setting — "remember" toggle isn't a preference edit. if (!checked && values.autoConnect) { form.change('autoConnect', false); } togglePasswordLabel(canUseStoredPassword(checked, values.password)); }; // @critical Only persist-path for autoConnect; wired to native onChange, not , // to avoid leaking form.change() writes into Dexie. const onUserToggleAutoConnect = (checked: boolean, fieldOnChange: (v: boolean) => void) => { fieldOnChange(checked); if (settings.status === LoadingState.READY) { void settings.update({ autoConnect: checked }); } if (checked && !values.remember) { form.change('remember', true); } }; return (
{onUserNameChange}
setUseStoredPasswordLabel(false)} onBlur={() => togglePasswordLabel(canUseStoredPassword(values.remember, values.password)) } name="password" type="password" component={InputField} autoComplete="new-password" />
{onRememberChange}
{({ input }) => ( onUserToggleAutoConnect(checked, input.onChange)} color="primary" /> } /> )}
); }; const LoginForm = (props: LoginFormProps) => { const { t } = useTranslation(); const knownHosts = useKnownHosts(); const settings = useSettings(); const validate = (values: any) => { const errors: any = {}; if (!values.userName) { errors.userName = t('Common.validation.required'); } if (!values.selectedHost) { errors.selectedHost = t('Common.validation.required'); } return errors; }; const handleOnSubmit = ({ userName, ...values }: any) => { userName = userName?.trim(); props.onSubmit({ userName, ...values }); }; if (knownHosts.status !== LoadingState.READY || settings.status !== LoadingState.READY) { return null; } const selectedHost = knownHosts.value?.selectedHost; const initialValues = { selectedHost, userName: selectedHost?.userName ?? '', remember: Boolean(selectedHost?.remember), autoConnect: Boolean(settings.value?.autoConnect), password: '', }; return (
{({ handleSubmit, form }) => ( )} ); }; export default LoginForm;