diff --git a/react-ui/src/components/login/view/login.view.tsx b/react-ui/src/components/login/view/login.view.tsx index 6b782e8779abdd754158d9b565fd78fdec5c4344..5b7b40a86ab5b91d329a42b4a8edf42e03bb073e 100755 --- a/react-ui/src/components/login/view/login.view.tsx +++ b/react-ui/src/components/login/view/login.view.tsx @@ -1,86 +1,104 @@ -import { BasicProp } from '@shared/types/interfaces.type' -import React, { useRef } from 'react' -import { Alert, Button, Col, Container, Form, Image, Row, Spinner } from 'react-bootstrap' -import { useTranslation } from 'react-i18next' -import useLoginViewModel from '../viewmodel/login.viewmodel' -import './login.scss' -import logo from '/public/logo.svg' +// login.view.tsx +import { BasicProp } from "@shared/types/interfaces.type"; +import React from "react"; +import { + Alert, + Button, + Col, + Container, + Form, + Image, + Row, + Spinner, +} from "react-bootstrap"; +import { useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import useLoginViewModel from "../viewmodel/login.viewmodel"; +import "./login.scss"; +import { LoginFormInputs } from "./login.types"; +import logo from "/public/logo.svg"; -const LoginView: React.FC<BasicProp> = () => { - const { t } = useTranslation('common') - const { login, handleErrorMessageRendering, displayFormFieldChecks, loginLoading } = useLoginViewModel(); +export const LoginView: React.FC<BasicProp> = () => { + const { t } = useTranslation("common"); + const { handleLogin, getErrorMessage, loginLoading } = useLoginViewModel(); - const usernameRef = useRef<HTMLInputElement>(null) - const passwordRef = useRef<HTMLInputElement>(null) + const { + register, + handleSubmit, + formState: { errors }, + } = useForm<LoginFormInputs>({ + mode: "onSubmit", + defaultValues: { + username: "", + password: "", + }, + }); - const triggerLogin = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - const username = usernameRef.current!.value; - const password = passwordRef.current!.value; + const invalidForm = ( + <Alert variant="warning">{t("login.form.invalid")}</Alert> + ); + const invalidCredentials = ( + <Alert variant="danger">{t("login.form.failed")}</Alert> + ); - login(username, password); - } + return ( + <Container className="vh-100 d-flex flex-column justify-content-center login-container"> + <Row className="align-items-center"> + <Image src={logo} alt="logo" height={150} /> + </Row> + <Row className="mt-2 justify-content-center"> + <Col md={6} sm={10} className="c-box p-4 bg-white"> + <h1 className="text-center h2">goSDN - Web</h1> - const invalidForm = (<Alert variant="warning">{t('login.form.invalid')}</Alert>) - const invalidCredentials = (<Alert variant="danger">{t('login.form.failed')}</Alert>) + {getErrorMessage(invalidForm, invalidCredentials)} + <Form className="mt-4" onSubmit={handleSubmit(handleLogin)}> + <Form.Group className="mb-3" controlId="loginForm.username"> + <Form.Label>{t("login.form.username.label")}</Form.Label> + <Form.Control + type="text" + isInvalid={!!errors.username} + {...register("username", { + required: t("global.form.empty_field"), + })} + autoComplete="on" + autoFocus={true} + /> + <Form.Control.Feedback type="invalid"> + {errors.username?.message} + </Form.Control.Feedback> + </Form.Group> + <Form.Group className="mb-3" controlId="loginForm.password"> + <Form.Label>{t("login.form.password.label")}</Form.Label> + <Form.Control + type="password" + isInvalid={!!errors.password} + {...register("password", { + required: t("global.form.empty_field"), + })} + /> + <Form.Control.Feedback type="invalid"> + {errors.password?.message} + </Form.Control.Feedback> + </Form.Group> + <Button + variant="primary" + type="submit" + className="w-100 mt-3" + disabled={loginLoading} + > + {t("global.form.submit")} + {loginLoading && ( + <Spinner animation="border" size="sm" role="status"> + <span className="visually-hidden">Loading...</span> + </Spinner> + )} + </Button> + </Form> + </Col> + </Row> + </Container> + ); +}; - return ( - <Container className="vh-100 d-flex flex-column justify-content-center login-container"> - <Row className="align-items-center"> - <Image src={logo} alt="logo" height={150} /> - </Row> - <Row className="mt-2 justify-content-center"> - <Col md={6} sm={10} className="c-box p-4 bg-white"> - <h1 className="text-center h2">goSDN - Web</h1> - - {handleErrorMessageRendering(invalidForm, invalidCredentials)} - - <Form className="mt-4" noValidate validated={displayFormFieldChecks()} onSubmit={triggerLogin}> - <Form.Group - className="mb-3" - controlId="loginForm.username" - > - <Form.Label>{t('login.form.username.label')}</Form.Label> - <Form.Control - type="text" - ref={usernameRef} - required - autoComplete='on' - autoFocus={true} - /> - <Form.Control.Feedback type="invalid"> - {t('global.form.empty_field')} - </Form.Control.Feedback> - </Form.Group> - <Form.Group - className="mb-3" - controlId="loginForm.pasword" - > - <Form.Label>{t('login.form.password.label')}</Form.Label> - <Form.Control type="password" ref={passwordRef} required /> - <Form.Control.Feedback type="invalid"> - {t('global.form.empty_field')} - </Form.Control.Feedback> - </Form.Group> - <Button - variant="primary" - type="submit" - className="w-100 mt-3" - disabled={loginLoading} - > - {t('global.form.submit')} - {loginLoading && - <Spinner animation="border" size="sm" role="status"> - <span className="visually-hidden">Loading...</span> - </Spinner> - } - </Button> - </Form> - </Col> - </Row> - </Container> - ) -} - -export default LoginView +export default LoginView; diff --git a/react-ui/src/components/login/viewmodel/login.viewmodel.ts b/react-ui/src/components/login/viewmodel/login.viewmodel.ts index fabb0a8613cfee92f864beef15a998dda159238f..9617a117ca8db86e127f8065f2f93db57c5f8955 100755 --- a/react-ui/src/components/login/viewmodel/login.viewmodel.ts +++ b/react-ui/src/components/login/viewmodel/login.viewmodel.ts @@ -1,65 +1,31 @@ -import { useAuth } from "@provider/auth.provider"; -import { useState } from "react"; - -export interface PageLoginState { - submitted: boolean, - valid: boolean, +export interface LoginFormInputs { + username: string; + password: string; } +// login.viewmodel.ts +import { useAuth } from "@provider/auth.provider"; + export default function useLoginViewModel() { const { login, loginProperties } = useAuth(); const { isLoading: loginLoading, error: loginError, reset: resetLogin } = loginProperties; - const [localFormState, updateLocalFormState] = useState({ - submitted: false, - valid: false, - }); + const handleLogin = async (data: LoginFormInputs) => { + resetLogin(); + await login(data.username, data.password); + }; - const handleErrorMessageRendering = (formInvalidError: JSX.Element, backendResponseError: JSX.Element): JSX.Element | null => { - // backend response check + const getErrorMessage = (formInvalidError: JSX.Element, backendResponseError: JSX.Element): JSX.Element | null => { if (loginError) { return backendResponseError; } - - // form invalid check - if (localFormState.submitted && !localFormState.valid) { - return formInvalidError; - } - return null; - } - - const isFormValid = (username: string | undefined, password: string | undefined): boolean => { - return !!username && !!password; - } - - /** - * Tries to `/login` by using the input fields. - * - * @description The fields are getting validated against null values - * @param event Submit event - */ - const loginHandler = (username: string | undefined, password: string | undefined) => { - resetLogin(); - const valid = isFormValid(username, password); - - updateLocalFormState({ ...localFormState, valid, submitted: true }) - - if (valid) { - //executeLogin(username!, password!); - login(username!, password!); - } - } - - const displayFormFieldChecks = (): boolean => { - return localFormState.submitted && !loginError; - } - + }; return { - displayFormFieldChecks, - login: loginHandler, - handleErrorMessageRendering: handleErrorMessageRendering, + handleLogin, + getErrorMessage, loginLoading, - } -} + loginError + }; +} \ No newline at end of file diff --git a/react-ui/src/shared/components/box/gridBox.view.tsx b/react-ui/src/shared/components/box/gridBox.view.tsx index cc887610090525e4f714288173bce44ecd66cb5f..40da8ada34e210fff9da3e4a10b29d9c6ae9eede 100644 --- a/react-ui/src/shared/components/box/gridBox.view.tsx +++ b/react-ui/src/shared/components/box/gridBox.view.tsx @@ -1,39 +1,52 @@ -import { faGripVertical, IconDefinition } from "@fortawesome/free-solid-svg-icons" -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import UpdateIndicator from "@layout/grid.layout/update-inidicator.layout/update-indicator.layout" -import { Category, CategoryType } from "@shared/types/category.type" -import { Col, Container, Row } from "react-bootstrap" -import './gridBox.view.scss' +import { + faGripVertical, + IconDefinition, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import UpdateIndicator from "@layout/grid.layout/update-inidicator.layout/update-indicator.layout"; +import { Category, CategoryType } from "@shared/types/category.type"; +import { Col, Container, Row } from "react-bootstrap"; interface GridBoxProps { - title: string, - title_icon: IconDefinition, - children: React.ReactNode, - className?: string, + title: string; + title_icon: IconDefinition; + children: React.ReactNode; + className?: string; } - -export const GridBox: React.FC<GridBoxProps> = ({ children, title, title_icon, className = "" }) => { - return ( - <div className="grid-box h-100"> - <Container fluid className={`c-box d-flex flex-column h-100 ${className}`}> - <div> - <UpdateIndicator - category={Category.DEVICE as CategoryType} - updateInterval={15000} - /> - <FontAwesomeIcon icon={faGripVertical} className="drag-handle" /> - <Row className="mb-0"> - <Col xs={12}> - - <h4 className='c-box-title'><FontAwesomeIcon icon={title_icon} size="1x" className="me-2 text-primary" />{title}</h4> - </Col> - </Row> - </div> - <div className="flex-grow-1 content"> - {children} - </div> - </Container> +export const GridBox: React.FC<GridBoxProps> = ({ + children, + title, + title_icon, + className = "", +}) => { + return ( + <div className="grid-box h-100"> + <Container + fluid + className={`c-box d-flex flex-column h-100 ${className}`} + > + <div> + <UpdateIndicator + category={Category.DEVICE as CategoryType} + updateInterval={15000} + /> + <FontAwesomeIcon icon={faGripVertical} className="drag-handle" /> + <Row className="mb-0"> + <Col xs={12}> + <h4 className="c-box-title"> + <FontAwesomeIcon + icon={title_icon} + size="1x" + className="me-2 text-primary" + /> + {title} + </h4> + </Col> + </Row> </div> - ) -} \ No newline at end of file + <div className="flex-grow-1 content">{children}</div> + </Container> + </div> + ); +}; diff --git a/react-ui/src/shared/style/index.scss b/react-ui/src/shared/style/index.scss index b70f6c35e87e35090674a0ac0041e9118a329d3a..22d919a9ae30a4f93a78661174ef4e08af2fd012 100755 --- a/react-ui/src/shared/style/index.scss +++ b/react-ui/src/shared/style/index.scss @@ -3,5 +3,6 @@ @import "./utils.scss"; @import "./toast.scss"; @import "./skeleton.scss"; +@import "../components/box/gridBox.view.scss"; @import "/node_modules/react-loading-skeleton/dist/skeleton.css";