diff --git a/package-lock.json b/package-lock.json
index e8bcad30..ac85919e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-highlight-words": "^0.21.0",
+ "react-hook-form": "^7.68.0",
"react-redux": "^9.2.0",
"react-router": "^7.5.2",
"react-transition-group": "^4.4.5",
@@ -6454,6 +6455,22 @@
"react": "^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.68.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
+ "integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
+ "license": "MIT",
+ "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-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/package.json b/package.json
index 4d722831..43a7f3bd 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-highlight-words": "^0.21.0",
+ "react-hook-form": "^7.68.0",
"react-redux": "^9.2.0",
"react-router": "^7.5.2",
"react-transition-group": "^4.4.5",
diff --git a/src/components/generics/form/Fields.jsx b/src/components/generics/form/Fields.jsx
new file mode 100644
index 00000000..d556cde9
--- /dev/null
+++ b/src/components/generics/form/Fields.jsx
@@ -0,0 +1,99 @@
+import { InlineFormContext } from 'contexts'
+import PropTypes from 'prop-types'
+import { useContext } from 'react'
+
+function Field({ children, ...rest }) {
+ const inline = useContext(InlineFormContext)
+
+ if (inline) {
+ return {children}
+ }
+
+ return {children}
+}
+
+Field.propTypes = {
+ children: PropTypes.node.isRequired,
+}
+
+function FieldInline({ children }) {
+ return
{children}
+}
+
+FieldInline.propTypes = {
+ children: PropTypes.node.isRequired,
+}
+
+function FieldBlock({ id, label, errors, children }) {
+ let errorNotification
+ const error = errors?.[id]
+ if (error) {
+ if (error.type === 'required') {
+ errorNotification = This field is requiered
+ } else {
+ errorNotification = {error.message}
+ }
+ }
+
+ return (
+
+
+
+ {children}
+ {errorNotification}
+
+
+ )
+}
+
+FieldBlock.propTypes = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
+ errors: PropTypes.object,
+ children: PropTypes.node.isRequired,
+}
+
+export function InputField({ id, label, type, register, errors, validation }) {
+ return (
+
+
+
+
+
+ )
+}
+
+InputField.propTypes = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
+ type: PropTypes.string,
+ register: PropTypes.func,
+ errors: PropTypes.object,
+ validation: PropTypes.object,
+}
+
+// export function SelectField({
+// id,
+// label,
+// options,
+// multiple,
+// registrer,
+// errors,
+// validation,
+// }) {
+// return (
+//
+//
+//
+//
+//
+// )
+// }
diff --git a/src/components/generics/form/Form.jsx b/src/components/generics/form/Form.jsx
new file mode 100644
index 00000000..6d75f976
--- /dev/null
+++ b/src/components/generics/form/Form.jsx
@@ -0,0 +1,63 @@
+import classNames from 'classnames'
+import PropTypes from 'prop-types'
+import { Form as FormOrigin } from 'react-hook-form'
+
+export function Form({ inline, children, ...rest }) {
+ if (inline) {
+ // wrap by InlineFormContext
+ return null
+ }
+
+ return {children}
+}
+
+Form.propTypes = {
+ inline: PropTypes.bool,
+ children: PropTypes.node,
+}
+
+export function FormBlock({
+ title,
+ action,
+ control,
+ noSubmit,
+ submitClass = 'primary',
+ submitText = 'Submit',
+ extraControls,
+ children,
+}) {
+ return (
+
+ {title && (
+
+
{title}
+
+ )}
+ {children}
+
+ {!noSubmit && (
+
+ )}
+ {extraControls}
+
+
+ )
+}
+
+FormBlock.propTypes = {
+ title: PropTypes.string,
+ action: PropTypes.string.isRequired,
+ control: PropTypes.object.isRequired,
+ noSubmit: PropTypes.bool,
+ submitClass: PropTypes.string,
+ submitText: PropTypes.string,
+ extraControls: PropTypes.element,
+ children: PropTypes.node,
+}
diff --git a/src/components/registration/Login.jsx b/src/components/registration/Login.jsx
index 293e145c..282fe7bf 100644
--- a/src/components/registration/Login.jsx
+++ b/src/components/registration/Login.jsx
@@ -1,7 +1,18 @@
+import { useForm } from 'react-hook-form'
import { useSelector } from 'react-redux'
import { Navigate, NavLink, useSearchParams } from 'react-router'
-import { FormBlock, InputField } from 'components/generics/Form'
+import { InputField } from 'components/generics/form/Fields'
+import { Form } from 'components/generics/form/Form'
+
+function ForgottenPasswordLink() {
+ return (
+
+ Or reset your password{' '}
+ if you have forgotten it.
+
+ )
+}
export default function Login() {
const isLoggedIn = useSelector((state) => !!state.token)
@@ -9,36 +20,23 @@ export default function Login() {
const [searchParams, _] = useSearchParams()
+ const {
+ register,
+ formState: { errors },
+ control,
+ } = useForm()
+
if (isLoggedIn) {
return
}
- let forgottenPasswordLink
- if (serverSettings?.email_enabled) {
- forgottenPasswordLink = (
-
- {' '}
- Or
- reset your password
- {' '}
- if you have forgotten it.
-
- )
- }
-
return (
Login
-
+
+
New here? Create a new account.
- {forgottenPasswordLink}
+ {serverSettings?.email_enabled && (
+ <>
+ {' '}
+
+ >
+ )}
diff --git a/src/contexts/index.js b/src/contexts/index.js
new file mode 100644
index 00000000..77414d28
--- /dev/null
+++ b/src/contexts/index.js
@@ -0,0 +1,3 @@
+import { createContext } from 'react'
+
+export const InlineFormContext = createContext(false)