diff --git a/web/package.json b/web/package.json index bdc9aaa..233cce4 100644 --- a/web/package.json +++ b/web/package.json @@ -27,6 +27,7 @@ "prettier": "^2.7.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-form": "^7.41.3", "react-hot-toast": "^2.4.0", "react-markdown": "^8.0.1", "typescript": "^4.6.3", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index be37567..3f63d7b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -38,6 +38,7 @@ specifiers: prop-types: ^15.8.1 react: 18.2.0 react-dom: 18.2.0 + react-hook-form: ^7.41.3 react-hot-toast: ^2.4.0 react-markdown: ^8.0.1 rimraf: ^3.0.2 @@ -69,6 +70,7 @@ dependencies: prettier: 2.7.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + react-hook-form: 7.41.3_react@18.2.0 react-hot-toast: 2.4.0_6dztgcaza2bodipt2i5a4is4mu_npw22p3c4ehm7n7vxn2gqac44u react-markdown: 8.0.1_bbvjflvjoibwhtpmedigb26h6y typescript: 4.6.3 @@ -2346,6 +2348,15 @@ packages: scheduler: 0.23.0 dev: false + /react-hook-form/7.41.3_react@18.2.0: + resolution: {integrity: sha512-5QNTmqJtDb88WV5n41b6+AmcDMVyaJ3tccPgHAgS215w3jZ3bmJhDO27kNTr8u4YHNYXmS7p1/4/KachBAlUtw==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /react-hot-toast/2.4.0_6dztgcaza2bodipt2i5a4is4mu_npw22p3c4ehm7n7vxn2gqac44u: resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==} engines: {node: '>=10'} diff --git a/web/src/features/dialog/InputDialog.tsx b/web/src/features/dialog/InputDialog.tsx index 100e8a9..7232352 100644 --- a/web/src/features/dialog/InputDialog.tsx +++ b/web/src/features/dialog/InputDialog.tsx @@ -1,5 +1,5 @@ import { Group, Modal, Button, Stack } from '@mantine/core'; -import React from 'react'; +import React, { FormEvent, useRef } from 'react'; import { useNuiEvent } from '../../hooks/useNuiEvent'; import { useLocales } from '../../providers/LocaleProvider'; import { fetchNui } from '../../utils/fetchNui'; @@ -9,12 +9,19 @@ import CheckboxField from './components/fields/checkbox'; import SelectField from './components/fields/select'; import NumberField from './components/fields/number'; import SliderField from './components/fields/slider'; +import { useFieldArray, useForm } from 'react-hook-form'; export interface InputProps { heading: string; rows: Array; } +export type FormValues = { + test: { + value: any; + }[]; +}; + const InputDialog: React.FC = () => { const [fields, setFields] = React.useState({ heading: '', @@ -25,6 +32,12 @@ const InputDialog: React.FC = () => { const [visible, setVisible] = React.useState(false); const { locale } = useLocales(); + const form = useForm<{ test: { value: any }[] }>({}); + const fieldForm = useFieldArray({ + control: form.control, + name: 'test', + }); + const handlePasswordStates = (index: number) => { setPasswordStates({ ...passwordStates, @@ -37,6 +50,9 @@ const InputDialog: React.FC = () => { setFields(data); setInputData([]); setVisible(true); + data.rows.forEach((row, index) => { + fieldForm.insert(index, { value: row.type !== 'checkbox' ? row.default : row.checked } || { value: null }); + }); }); useNuiEvent('closeInputDialog', () => { @@ -60,6 +76,15 @@ const InputDialog: React.FC = () => { fetchNui('inputData', inputData); }; + const onSubmit = form.handleSubmit(async (data) => { + const values: any[] = []; + Object.values(data.test).forEach((obj: { value: any }) => values.push(obj.value)); + console.log(values); + await new Promise((resolve) => setTimeout(resolve, 200)); + form.reset(); + fieldForm.remove(); + }); + return ( <> { transition="fade" exitTransitionDuration={150} > - - {fields.rows.map((row: IInput | ICheckbox | ISelect | INumber | ISlider, index) => ( - - {row.type === 'input' && ( - - )} - {row.type === 'checkbox' && } - {row.type === 'select' && } - {row.type === 'number' && } - {row.type === 'slider' && } - - ))} - - - - - +
+ + {fieldForm.fields.map((item, index) => { + const row = fields.rows[index]; + return ( + + {row.type === 'input' && ( + + )} + {row.type === 'checkbox' && ( + + )} + {row.type === 'select' && } + {row.type === 'number' && } + {row.type === 'slider' && } + + ); + })} + + + + + +
); diff --git a/web/src/features/dialog/components/fields/checkbox.tsx b/web/src/features/dialog/components/fields/checkbox.tsx index bab6483..b2333cf 100644 --- a/web/src/features/dialog/components/fields/checkbox.tsx +++ b/web/src/features/dialog/components/fields/checkbox.tsx @@ -1,25 +1,17 @@ import { Checkbox } from '@mantine/core'; import { useEffect } from 'react'; import { ICheckbox } from '../../../../interfaces/dialog'; +import { UseFormRegisterReturn } from 'react-hook-form'; interface Props { row: ICheckbox; index: number; - handleChange: (value: boolean, index: number) => void; + register: UseFormRegisterReturn; } const CheckboxField: React.FC = (props) => { - useEffect(() => { - if (props.row.checked) props.handleChange(props.row.checked, props.index); - }, []); - return ( - ) => props.handleChange(e.target.checked, props.index)} - defaultChecked={props.row.checked} - /> + ); }; diff --git a/web/src/features/dialog/components/fields/input.tsx b/web/src/features/dialog/components/fields/input.tsx index 5347d91..634f889 100644 --- a/web/src/features/dialog/components/fields/input.tsx +++ b/web/src/features/dialog/components/fields/input.tsx @@ -1,28 +1,26 @@ import { TextInput } from '@mantine/core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { IInput } from '../../../../interfaces/dialog'; +import { UseFormRegisterReturn } from 'react-hook-form'; interface Props { + register: UseFormRegisterReturn; row: IInput; index: number; - handleChange: (value: string, index: number) => void; passwordStates: boolean[]; handlePasswordStates: (index: number) => void; } const InputField: React.FC = (props) => { - useEffect(() => { - if (props.row.default) props.handleChange(props.row.default, props.index); - }, []); - return ( } placeholder={props.row.placeholder} - defaultValue={props.row.default} disabled={props.row.disabled} type={!props.row.password || props.passwordStates[props.index] ? 'text' : 'password'} rightSection={ diff --git a/web/src/features/dialog/components/fields/number.tsx b/web/src/features/dialog/components/fields/number.tsx index f3c3049..79bc978 100644 --- a/web/src/features/dialog/components/fields/number.tsx +++ b/web/src/features/dialog/components/fields/number.tsx @@ -2,27 +2,35 @@ import { NumberInput } from '@mantine/core'; import { useEffect } from 'react'; import { INumber } from '../../../../interfaces/dialog'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Control, useController, UseFormRegisterReturn } from 'react-hook-form'; +import { FormValues } from '../../InputDialog'; interface Props { row: INumber; index: number; - handleChange: (value: number, index: number) => void; + control: Control; } const NumberField: React.FC = (props) => { - useEffect(() => { - if (props.row.default) props.handleChange(props.row.default, props.index); - }, []); + const controller = useController({ + name: `test.${props.index}.value`, + control: props.control, + defaultValue: props.row.default, + }); return ( props.handleChange(value as number, props.index)} icon={props.row.icon && } /> ); diff --git a/web/src/features/dialog/components/fields/select.tsx b/web/src/features/dialog/components/fields/select.tsx index 7626667..ffdcc7c 100644 --- a/web/src/features/dialog/components/fields/select.tsx +++ b/web/src/features/dialog/components/fields/select.tsx @@ -1,31 +1,31 @@ import { Select } from '@mantine/core'; -import { useEffect } from 'react'; import { ISelect } from '../../../../interfaces/dialog'; +import { Control, FieldValues, useController, UseFormRegisterReturn, UseFormSetValue } from 'react-hook-form'; +import { FormValues } from '../../InputDialog'; interface Props { row: ISelect; index: number; - handleChange: (value: string, index: number) => void; + control: Control; } // TODO: set label to value of value when there is no label provided const SelectField: React.FC = (props) => { - useEffect(() => { - if (props.row.default) { - props.row.options?.map((option) => { - if (props.row.default === option.value) { - props.handleChange(option.value, props.index); - } - }); - } - }, []); + const controller = useController({ + name: `test.${props.index}.value`, + control: props.control, + defaultValue: props.row.default, + }); return (