diff --git a/client/package-lock.json b/client/package-lock.json
index a7046b0d..6cd52325 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -25,6 +25,7 @@
"react": "^16.13.1",
"react-click-outside": "^3.0.1",
"react-dom": "^16.13.1",
+ "react-hook-form": "^7.54.0",
"react-i18next": "^11.7.2",
"react-modal": "^3.11.2",
"react-popper-tooltip": "^2.11.1",
@@ -15570,6 +15571,21 @@
"react": "^16.13.1"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.54.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.0.tgz",
+ "integrity": "sha512-PS05+UQy/IdSbJNojBypxAo9wllhHgGmyr8/dyGQcPoiMf3e7Dfb9PWYVRco55bLbxH9S+1yDDJeTdlYCSxO3A==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-hot-loader": {
"version": "4.13.1",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.1.tgz",
@@ -31540,6 +31556,12 @@
"scheduler": "^0.19.1"
}
},
+ "react-hook-form": {
+ "version": "7.54.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.0.tgz",
+ "integrity": "sha512-PS05+UQy/IdSbJNojBypxAo9wllhHgGmyr8/dyGQcPoiMf3e7Dfb9PWYVRco55bLbxH9S+1yDDJeTdlYCSxO3A==",
+ "requires": {}
+ },
"react-hot-loader": {
"version": "4.13.1",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.1.tgz",
diff --git a/client/package.json b/client/package.json
index 62c26e19..7d2b4391 100644
--- a/client/package.json
+++ b/client/package.json
@@ -38,6 +38,7 @@
"react": "^16.13.1",
"react-click-outside": "^3.0.1",
"react-dom": "^16.13.1",
+ "react-hook-form": "^7.54.0",
"react-i18next": "^11.7.2",
"react-modal": "^3.11.2",
"react-popper-tooltip": "^2.11.1",
diff --git a/client/src/actions/install.ts b/client/src/actions/install.ts
index a52b268f..55114d2c 100644
--- a/client/src/actions/install.ts
+++ b/client/src/actions/install.ts
@@ -27,7 +27,8 @@ export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
export const setAllSettings = (values: any) => async (dispatch: any) => {
dispatch(setAllSettingsRequest());
try {
- const { confirm_password, ...config } = values;
+ const config = { ...values };
+ delete config.confirm_password;
await apiClient.setAllSettings(config);
dispatch(setAllSettingsSuccess());
@@ -48,7 +49,10 @@ export const checkConfig = (values: any) => async (dispatch: any) => {
dispatch(checkConfigRequest());
try {
const check = await apiClient.checkConfig(values);
- dispatch(checkConfigSuccess(check));
+ dispatch(checkConfigSuccess({
+ ...values,
+ check,
+ }));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(checkConfigFailure());
diff --git a/client/src/install/Setup/Auth.tsx b/client/src/install/Setup/Auth.tsx
index b9072b4e..ec9933af 100644
--- a/client/src/install/Setup/Auth.tsx
+++ b/client/src/install/Setup/Auth.tsx
@@ -1,47 +1,46 @@
import React from 'react';
-
-import { Field, reduxForm } from 'redux-form';
+import { useForm } from 'react-hook-form';
import { withTranslation, Trans } from 'react-i18next';
import flow from 'lodash/flow';
-
+import cn from 'classnames';
import i18n from '../../i18n';
-
import Controls from './Controls';
-
-import { renderInputField } from '../../helpers/form';
-import { FORM_NAME } from '../../helpers/constants';
import { validatePasswordLength } from '../../helpers/validators';
-const required = (value: any) => {
- if (value || value === 0) {
- return false;
- }
-
- return form_error_required;
-};
-
-const validate = (values: any) => {
- const errors: { confirm_password?: string } = {};
-
- if (values.confirm_password !== values.password) {
- errors.confirm_password = i18n.t('form_error_password');
- }
-
- return errors;
-};
-
-interface AuthProps {
- handleSubmit: (...args: unknown[]) => string;
+type Props = {
+ onAuthSubmit: (...args: unknown[]) => string;
pristine: boolean;
invalid: boolean;
t: (...args: unknown[]) => string;
}
-const Auth = (props: AuthProps) => {
- const { handleSubmit, pristine, invalid, t } = props;
+const Auth = (props: Props) => {
+ const { t, onAuthSubmit } = props;
+ const {
+ register,
+ handleSubmit,
+ watch,
+ formState: { errors, isDirty, isValid },
+ } = useForm({
+ mode: 'onChange',
+ defaultValues: {
+ username: '',
+ password: '',
+ confirm_password: '',
+ },
+ });
+
+ const password = watch('password');
+
+ const validateConfirmPassword = (value: string) => {
+ if (value !== password) {
+ return i18n.t('form_error_password');
+ }
+ return undefined;
+ };
return (
-
);
};
export default flow([
withTranslation(),
- reduxForm({
- form: FORM_NAME.INSTALL,
- destroyOnUnmount: false,
- forceUnregisterOnUnmount: true,
- validate,
- }),
])(Auth);
diff --git a/client/src/install/Setup/Settings.tsx b/client/src/install/Setup/Settings.tsx
index c884cc89..537f7e63 100644
--- a/client/src/install/Setup/Settings.tsx
+++ b/client/src/install/Setup/Settings.tsx
@@ -1,30 +1,65 @@
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-
-import { Field, reduxForm, formValueSelector } from 'redux-form';
-import { Trans, withTranslation } from 'react-i18next';
-import flow from 'lodash/flow';
-import i18n, { TFunction } from 'i18next';
+import React, { useEffect, useCallback } from 'react';
+import { useForm, Controller } from 'react-hook-form';
+import { Trans, useTranslation } from 'react-i18next';
+import i18n from 'i18next';
+import i18next from 'i18next';
import Controls from './Controls';
-
import AddressList from './AddressList';
import { getInterfaceIp } from '../../helpers/helpers';
import {
ALL_INTERFACES_IP,
- FORM_NAME,
ADDRESS_IN_USE_TEXT,
PORT_53_FAQ_LINK,
STATUS_RESPONSE,
STANDARD_DNS_PORT,
STANDARD_WEB_PORT,
+ MAX_PORT,
} from '../../helpers/constants';
-import { renderInputField, toNumber } from '../../helpers/form';
-import { validateRequiredValue, validateInstallPort } from '../../helpers/validators';
+import { toNumber } from '../../helpers/form';
+import { validateRequiredValue } from '../../helpers/validators';
import { DhcpInterface } from '../../initialState';
+const validateInstallPort = (value: any) => {
+ if (value < 1 || value > MAX_PORT) {
+ return i18next.t('form_error_port');
+ }
+ return undefined;
+};
+
+type StaticIpType = {
+ ip: string;
+ static: string;
+};
+
+type ConfigType = {
+ web: {
+ ip: string;
+ port?: number;
+ status: string;
+ can_autofix: boolean;
+ };
+ dns: {
+ ip: string;
+ port?: number;
+ status: string;
+ can_autofix: boolean;
+ };
+ staticIp: StaticIpType;
+};
+
+type Props = {
+ handleSubmit: (data: any) => void;
+ handleChange?: (...args: unknown[]) => unknown;
+ handleFix: (web: any, dns: any, set_static_ip: boolean) => void;
+ validateForm: (data: any) => void;
+ config: ConfigType;
+ interfaces: DhcpInterface[];
+ initialValues?: object;
+};
+
const renderInterfaces = (interfaces: DhcpInterface[]) =>
Object.values(interfaces).map((option: DhcpInterface) => {
const { name, ip_addresses, flags } = option;
@@ -43,113 +78,69 @@ const renderInterfaces = (interfaces: DhcpInterface[]) =>
return null;
});
-type Props = {
- handleSubmit: (...args: unknown[]) => string;
- handleChange?: (...args: unknown[]) => unknown;
- handleFix: (...args: unknown[]) => unknown;
- validateForm?: (...args: unknown[]) => unknown;
- webIp: string;
- dnsIp: string;
- config: {
+const Settings: React.FC = ({
+ handleSubmit,
+ handleFix,
+ validateForm,
+ config,
+ interfaces,
+}) => {
+ const { t } = useTranslation();
+
+ const defaultValues = {
web: {
- status: string;
- can_autofix: boolean;
- };
+ ip: config.web.ip || ALL_INTERFACES_IP,
+ port: config.web.port || STANDARD_WEB_PORT,
+ },
dns: {
- status: string;
- can_autofix: boolean;
- };
- staticIp: {
- ip: string;
- static: string;
- };
+ ip: config.dns.ip || ALL_INTERFACES_IP,
+ port: config.dns.port || STANDARD_DNS_PORT,
+ },
};
- webPort?: number;
- dnsPort?: number;
- interfaces: DhcpInterface[];
- invalid: boolean;
- initialValues?: object;
- t: TFunction;
-};
-class Settings extends Component {
- componentDidMount() {
- const { webIp, webPort, dnsIp, dnsPort } = this.props;
+ const {
+ control,
+ watch,
+ handleSubmit: reactHookFormSubmit,
+ formState: { isValid, errors },
+ } = useForm({
+ defaultValues,
+ mode: 'onChange',
+ });
- this.props.validateForm({
+ const watchFields = watch();
+
+ const { status: webStatus, can_autofix: isWebFixAvailable } = config.web;
+ const { status: dnsStatus, can_autofix: isDnsFixAvailable } = config.dns;
+ const { staticIp } = config;
+
+ const webIpVal = watch("web.ip");
+ const webPortVal = watch("web.port");
+ const dnsIpVal = watch("dns.ip");
+ const dnsPortVal = watch("dns.port");
+
+ useEffect(() => {
+ validateForm({
web: {
- ip: webIp,
- port: webPort,
+ ip: webIpVal,
+ port: webPortVal,
},
dns: {
- ip: dnsIp,
- port: dnsPort,
+ ip: dnsIpVal,
+ port: dnsPortVal,
},
});
- }
-
- getStaticIpMessage = (staticIp: { ip: string; static: string }) => {
- const { static: status, ip } = staticIp;
-
- switch (status) {
- case STATUS_RESPONSE.NO: {
- return (
- <>
-
- text]}>
- install_static_configure
-
-
-
-
- >
- );
- }
- case STATUS_RESPONSE.ERROR: {
- return (
-
- install_static_error
-
- );
- }
- case STATUS_RESPONSE.YES: {
- return (
-
- install_static_ok
-
- );
- }
- default:
- return null;
- }
- };
-
- handleAutofix = (type: any) => {
- const {
- webIp,
-
- webPort,
-
- dnsIp,
-
- dnsPort,
-
- handleFix,
- } = this.props;
+ }, [webIpVal, webPortVal, dnsIpVal, dnsPortVal]);
+ const handleAutofix = (type: string) => {
const web = {
- ip: webIp,
- port: webPort,
+ ip: watchFields.web?.ip,
+ port: watchFields.web?.port,
autofix: false,
};
const dns = {
- ip: dnsIp,
- port: dnsPort,
+ ip: watchFields.dns?.ip,
+ port: watchFields.dns?.port,
autofix: false,
};
const set_static_ip = false;
@@ -163,276 +154,315 @@ class Settings extends Component {
handleFix(web, dns, set_static_ip);
};
- handleStaticIp = (ip: any) => {
- const {
- webIp,
-
- webPort,
-
- dnsIp,
-
- dnsPort,
-
- handleFix,
- } = this.props;
-
+ const handleStaticIp = (ip: string) => {
const web = {
- ip: webIp,
- port: webPort,
+ ip: watchFields.web?.ip,
+ port: watchFields.web?.port,
autofix: false,
};
const dns = {
- ip: dnsIp,
- port: dnsPort,
+ ip: watchFields.dns?.ip,
+ port: watchFields.dns?.port,
autofix: false,
};
const set_static_ip = true;
- if (window.confirm(this.props.t('confirm_static_ip', { ip }))) {
+ if (window.confirm(t('confirm_static_ip', { ip }))) {
handleFix(web, dns, set_static_ip);
}
};
- render() {
- const {
- handleSubmit,
+ const getStaticIpMessage = useCallback((staticIp: StaticIpType) => {
+ const { static: status, ip } = staticIp;
- handleChange,
+ switch (status) {
+ case STATUS_RESPONSE.NO:
+ return (
+ <>
+
+ text]}>
+ install_static_configure
+
+
- webIp,
+
+ >
+ );
+ case STATUS_RESPONSE.ERROR:
+ return (
+
+ install_static_error
+
+ );
+ case STATUS_RESPONSE.YES:
+ return (
+
+ install_static_ok
+
+ );
+ default:
+ return null;
+ }
+ }, [handleStaticIp]);
- webPort,
+ const onSubmit = (data: any) => {
+ validateForm(data);
+ handleSubmit(data);
+ };
- dnsIp,
+ return (
+