| import * as redux from "react-redux"; | |||||
| import store from "../../store"; | |||||
| import { Router } from "react-router-dom"; | |||||
| import { mockState } from "../../mockState"; | |||||
| import { fireEvent, render, screen, waitFor } from "@testing-library/react"; | |||||
| import history from "../../store/utils/history"; | |||||
| import LoginPage from "../../pages/LoginPage/LoginPageMUI"; | |||||
| // class LocalStorageMock { | |||||
| // constructor() { | |||||
| // this.store = {}; | |||||
| // } | |||||
| // clear() { | |||||
| // this.store = {}; | |||||
| // } | |||||
| // getItem(key) { | |||||
| // return undefined; | |||||
| // } | |||||
| // setItem(key, value) { | |||||
| // this.store[key] = String(value); | |||||
| // } | |||||
| // removeItem(key) { | |||||
| // delete this.store[key]; | |||||
| // } | |||||
| // } | |||||
| // global.localStorage = new LocalStorageMock; | |||||
| describe("LoginPage render tests", () => { | |||||
| var props = { | |||||
| history: { | |||||
| replace: jest.fn(), | |||||
| push: jest.fn(), | |||||
| location: { | |||||
| pathname: "/", | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| const cont = ( | |||||
| <redux.Provider store={store}> | |||||
| <Router history={history}> | |||||
| <LoginPage {...props} /> | |||||
| </Router> | |||||
| </redux.Provider> | |||||
| ); | |||||
| let spyOnUseSelector; | |||||
| let spyOnUseDispatch; | |||||
| let mockDispatch; | |||||
| beforeEach(() => { | |||||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||||
| spyOnUseSelector.mockReturnValue("some error"); | |||||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||||
| mockDispatch = jest.fn(); | |||||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||||
| }); | |||||
| afterEach(() => { | |||||
| jest.restoreAllMocks(); | |||||
| }); | |||||
| it("Should render", () => { | |||||
| const { container } = render(cont); | |||||
| expect( | |||||
| container.getElementsByClassName("c-login-container")[0] | |||||
| ).toBeDefined(); | |||||
| }); | |||||
| it("Should render login-logo", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("login-logo")[0]).toBeDefined(); | |||||
| }); | |||||
| it("Should render welcome message", () => { | |||||
| const { container } = render(cont); | |||||
| expect( | |||||
| container | |||||
| .getElementsByClassName("c-login-container")[0] | |||||
| .querySelector("h") | |||||
| ).toBeDefined(); | |||||
| }); | |||||
| it("Should render error message because we mocked error message", () => { | |||||
| render(cont); | |||||
| expect(screen.getByTestId("error-message")).toBeDefined(); | |||||
| }); | |||||
| it("Should render username input", () => { | |||||
| render(cont); | |||||
| expect(screen.getByTestId("username-input")).toBeDefined(); | |||||
| }); | |||||
| it("Should render password input", () => { | |||||
| render(cont); | |||||
| expect(screen.getByTestId("password-input")).toBeDefined(); | |||||
| }); | |||||
| it("Should render forgot paswword link", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("text-end")[0]).toBeDefined(); | |||||
| }); | |||||
| it("Should render submit button", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("c-btn")[0]).toBeDefined(); | |||||
| }); | |||||
| it("Should render separator container", () => { | |||||
| render(cont); | |||||
| expect(screen.getByTestId("separator")).toBeDefined(); | |||||
| }); | |||||
| it("Should dilig logo", () => { | |||||
| render(cont); | |||||
| expect(screen.getByTestId("dilig-logo")).toBeDefined(); | |||||
| }); | |||||
| it("Should not dispatch functions after clicking submit button because username and password inputs are emtpy", async () => { | |||||
| const { container } = render(cont); | |||||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(0)); | |||||
| }); | |||||
| it("Should not dispatch functions after clicking submit button because password input is emtpy", async () => { | |||||
| const { container } = render(cont); | |||||
| fireEvent.change(screen.getByTestId("username-input"), { | |||||
| target: { value: "some username" }, | |||||
| }); | |||||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(0)); | |||||
| }); | |||||
| it("Should not dispatch functions after clicking submit button because username input is emtpy", async () => { | |||||
| const { container } = render(cont); | |||||
| fireEvent.change(screen.getByTestId("password-input"), { | |||||
| target: { value: "some password" }, | |||||
| }); | |||||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(0)); | |||||
| }); | |||||
| it("Should dispatch two functions after clicking submit button", async () => { | |||||
| const { container } = render(cont); | |||||
| fireEvent.change(screen.getByTestId("username-input"), { | |||||
| target: { value: "some username" }, | |||||
| }); | |||||
| fireEvent.change(screen.getByTestId("password-input"), { | |||||
| target: { value: "some password" }, | |||||
| }); | |||||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(2)); | |||||
| }); | |||||
| // it("After clicking submit button we should go to ads page", async () => { | |||||
| // const { container } = render(cont); | |||||
| // fireEvent.change(screen.getByTestId("username-input"), { | |||||
| // target: { value: "some username" }, | |||||
| // }); | |||||
| // fireEvent.change(screen.getByTestId("password-input"), { | |||||
| // target: { value: "some password" }, | |||||
| // }); | |||||
| // fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||||
| // const arg = { pathname: "/ads" }; | |||||
| // expect(props.history.push).toHaveBeenCalledWith(arg); | |||||
| // }); | |||||
| }); |
| import history from "../../store/utils/history"; | import history from "../../store/utils/history"; | ||||
| import TableViewPage from "../../pages/CandidatesPage/TableViewPage"; | import TableViewPage from "../../pages/CandidatesPage/TableViewPage"; | ||||
| import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants"; | import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants"; | ||||
| import * as requests from "../../request/candidatesRequest"; | |||||
| describe("TableViewPage render tests", () => { | describe("TableViewPage render tests", () => { | ||||
| var props = { | var props = { | ||||
| pathname: "/candidates", | pathname: "/candidates", | ||||
| }, | }, | ||||
| }, | }, | ||||
| setPage: jest.fn(), | |||||
| }; | }; | ||||
| const cont = ( | const cont = ( | ||||
| ); | ); | ||||
| let spyOnUseSelector; | let spyOnUseSelector; | ||||
| let spyOnUseDispatch; | |||||
| let mockDispatch; | |||||
| beforeEach(() => { | beforeEach(() => { | ||||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | spyOnUseSelector = jest.spyOn(redux, "useSelector"); | ||||
| spyOnUseSelector | spyOnUseSelector | ||||
| .mockReturnValueOnce(mockState.candidates.candidates) | .mockReturnValueOnce(mockState.candidates.candidates) | ||||
| .mockReturnValueOnce(mockState.candidates.pagination); | .mockReturnValueOnce(mockState.candidates.pagination); | ||||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||||
| mockDispatch = jest.fn(); | |||||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||||
| }); | }); | ||||
| afterEach(() => { | afterEach(() => { | ||||
| ).toBeDefined(); | ).toBeDefined(); | ||||
| }); | }); | ||||
| it("Should dispatch function when component is rendered", async () => { | |||||
| render(cont); | |||||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(1)); | |||||
| }); | |||||
| it("Should render table", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("usersTable")[0]).toBeDefined(); | |||||
| }); | |||||
| it("Should render pagination component", () => { | it("Should render pagination component", () => { | ||||
| const { container } = render(cont); | const { container } = render(cont); | ||||
| expect( | expect( | ||||
| ).toBe("0"); | ).toBe("0"); | ||||
| }); | }); | ||||
| it("When user change table page function for fetching users should be called", async () => { | |||||
| const { container } = render(cont); | |||||
| const pag = container | |||||
| .getElementsByClassName("MuiPagination-ul")[0] | |||||
| .getElementsByTagName("li")[1] | |||||
| .querySelector("button"); | |||||
| fireEvent.click(pag); | |||||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(2)); | |||||
| }); | |||||
| // How to mock getCV() function ? | // How to mock getCV() function ? | ||||
| // it("Should render CV of candidate after clicking on CV name", async () => { | // it("Should render CV of candidate after clicking on CV name", async () => { | ||||
| // const mockedCall = { data: mockState.candidates.adsCandidates }; | // const mockedCall = { data: mockState.candidates.adsCandidates }; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import SectionLoader from '../Loader/SectionLoader'; | |||||
| const AuthCard = ({ children, title, subtitle, isLoading }) => { | |||||
| return ( | |||||
| <div className="c-auth-card"> | |||||
| <SectionLoader isLoading={isLoading}> | |||||
| <h1 className="c-auth-card__title">{title}</h1> | |||||
| <h2 className="c-auth-card__subtitle">{subtitle}</h2> | |||||
| {children} | |||||
| </SectionLoader> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| AuthCard.propTypes = { | |||||
| children: PropTypes.node, | |||||
| title: PropTypes.string, | |||||
| subtitle: PropTypes.string, | |||||
| isLoading: PropTypes.bool, | |||||
| }; | |||||
| export default AuthCard; |
| import React, { useEffect, useState, useRef } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ErrorMessage } from 'formik'; | |||||
| import IconButton from '../IconButton/IconButton'; | |||||
| import { ReactComponent as Search } from '../../assets/images/svg/search.svg'; | |||||
| import { ReactComponent as EyeOn } from '../../assets/images/svg/eye-on.svg'; | |||||
| import { ReactComponent as EyeOff } from '../../assets/images/svg/eye-off.svg'; | |||||
| import { ReactComponent as CapsLock } from '../../assets/images/svg/caps-lock.svg'; | |||||
| const BaseInputField = ({ | |||||
| type, | |||||
| label, | |||||
| field, | |||||
| form, | |||||
| placeholder, | |||||
| clearPlaceholderOnFocus = true, | |||||
| isSearch, | |||||
| className, | |||||
| disabled, | |||||
| centerText, | |||||
| link, | |||||
| errorMessage, | |||||
| autoFocus, | |||||
| isCapsLockOn, | |||||
| ...props | |||||
| }) => { | |||||
| const [inputPlaceholder, setPlaceholder] = useState(placeholder); | |||||
| const inputField = useRef(null); | |||||
| useEffect(() => { | |||||
| if (autoFocus) { | |||||
| inputField.current.focus(); | |||||
| } | |||||
| }, [autoFocus, inputField]); | |||||
| useEffect(() => { | |||||
| if (errorMessage) { | |||||
| form.setFieldError(field.name, errorMessage); | |||||
| } | |||||
| }, [errorMessage]); // eslint-disable-line | |||||
| useEffect(() => { | |||||
| setPlaceholder(placeholder); | |||||
| }, [placeholder]); | |||||
| const [inputType, setInputType] = useState('password'); | |||||
| const passwordInput = type === 'password' ? ' c-input--password' : ''; | |||||
| const showPassword = () => { | |||||
| if (inputType === 'password') { | |||||
| setInputType('text'); | |||||
| } else { | |||||
| setInputType('password'); | |||||
| } | |||||
| }; | |||||
| // Nester Formik Field Names get bugged because of Undefined values, so i had to fix it like this | |||||
| // If you ask why 0 and 1? I dont see a need for forms to be nested more then 2 levels? | |||||
| const fieldName = field.name.split('.'); | |||||
| const formError = | |||||
| fieldName[0] && fieldName[1] | |||||
| ? form.errors[fieldName[0]] && form.errors[fieldName[0]][fieldName[1]] | |||||
| : form.errors[fieldName[0]]; | |||||
| const formTouched = | |||||
| fieldName[0] && fieldName[1] | |||||
| ? form.touched[fieldName[0]] && form.touched[fieldName[0]][fieldName[1]] | |||||
| : form.touched[fieldName[0]]; | |||||
| function styles() { | |||||
| let style = 'c-input'; | |||||
| if (formError && formTouched) { | |||||
| style += ` c-input--error`; | |||||
| } | |||||
| if (type === 'password') { | |||||
| style += ` c-input--password`; | |||||
| } | |||||
| if (isSearch) { | |||||
| style += ` c-input--search`; | |||||
| } | |||||
| if (centerText) { | |||||
| style += ` c-input--center-text`; | |||||
| } | |||||
| if (type === 'number') { | |||||
| style += ` c-input--demi-bold`; | |||||
| } | |||||
| if (className) { | |||||
| style += ` ${className}`; | |||||
| } | |||||
| return style; | |||||
| } | |||||
| const additionalActions = () => { | |||||
| if (!clearPlaceholderOnFocus) { | |||||
| return null; | |||||
| } | |||||
| return { | |||||
| onFocus: () => { | |||||
| setPlaceholder(''); | |||||
| }, | |||||
| onBlur: (e) => { | |||||
| setPlaceholder(placeholder); | |||||
| field.onBlur(e); | |||||
| }, | |||||
| }; | |||||
| }; | |||||
| return ( | |||||
| <div className={styles()}> | |||||
| {!!label && ( | |||||
| <label className="c-input__label" htmlFor={field.name}> | |||||
| {label} | |||||
| </label> | |||||
| )} | |||||
| {link && <div className="c-input__link">{link}</div>} | |||||
| <div className="c-input__field-wrap"> | |||||
| <input | |||||
| ref={inputField} | |||||
| type={type === 'password' ? inputType : type} | |||||
| placeholder={inputPlaceholder} | |||||
| disabled={disabled} | |||||
| {...field} | |||||
| {...props} | |||||
| {...additionalActions()} | |||||
| className="c-input__field" | |||||
| /> | |||||
| {!!isSearch && <Search className="c-input__icon" />} | |||||
| {!!passwordInput && ( | |||||
| <> | |||||
| {isCapsLockOn && <CapsLock className="c-input__caps-lock" />} | |||||
| <IconButton | |||||
| onClick={() => { | |||||
| showPassword(); | |||||
| }} | |||||
| className="c-input__icon" | |||||
| > | |||||
| {inputType === 'password' ? <EyeOff /> : <EyeOn />} | |||||
| </IconButton> | |||||
| </> | |||||
| )} | |||||
| </div> | |||||
| <ErrorMessage name={field.name}> | |||||
| {(errorMessage) => ( | |||||
| <span className="c-input__error">{errorMessage}</span> | |||||
| )} | |||||
| </ErrorMessage> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| BaseInputField.propTypes = { | |||||
| type: PropTypes.string, | |||||
| field: PropTypes.shape({ | |||||
| name: PropTypes.string, | |||||
| onFocus: PropTypes.func, | |||||
| onBlur: PropTypes.func, | |||||
| }), | |||||
| form: PropTypes.shape({ | |||||
| errors: PropTypes.shape({}), | |||||
| setFieldError: PropTypes.func, | |||||
| touched: PropTypes.shape({}), | |||||
| }), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||||
| disabled: PropTypes.bool, | |||||
| isSearch: PropTypes.bool, | |||||
| className: PropTypes.string, | |||||
| link: PropTypes.node, | |||||
| errorMessage: PropTypes.string, | |||||
| centerText: PropTypes.bool, | |||||
| clearPlaceholderOnFocus: PropTypes.bool, | |||||
| demiBold: PropTypes.bool, | |||||
| touched: PropTypes.bool, | |||||
| autoFocus: PropTypes.bool, | |||||
| isCapsLockOn: PropTypes.bool, | |||||
| }; | |||||
| export default BaseInputField; |
| import React, { useState } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| import PasswordStrength from './PasswordStrength'; | |||||
| const PasswordField = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| shouldTestPasswordStrength, | |||||
| autoFocus, | |||||
| ...props | |||||
| }) => { | |||||
| const [passwordValue, setPasswordValue] = useState(''); | |||||
| const [isCapsLockOn, setIsCapsLockOn] = useState(false); | |||||
| const onChange = (e) => { | |||||
| if (shouldTestPasswordStrength) { | |||||
| const { value } = e.target; | |||||
| setPasswordValue(value); | |||||
| } | |||||
| field.onChange(e); | |||||
| }; | |||||
| const onKeyDown = (keyEvent) => { | |||||
| if (keyEvent.getModifierState('CapsLock')) { | |||||
| setIsCapsLockOn(true); | |||||
| } else { | |||||
| setIsCapsLockOn(false); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className="c-password"> | |||||
| <BaseInputField | |||||
| type="password" | |||||
| label={label} | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| {...props} | |||||
| onChange={onChange} | |||||
| autoFocus={autoFocus} | |||||
| onKeyDown={onKeyDown} | |||||
| isCapsLockOn={isCapsLockOn} | |||||
| /> | |||||
| {shouldTestPasswordStrength && ( | |||||
| <PasswordStrength | |||||
| passwordValue={passwordValue} | |||||
| shouldTestPasswordStrength | |||||
| /> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| PasswordField.propTypes = { | |||||
| field: PropTypes.shape({ | |||||
| onChange: PropTypes.func, | |||||
| }), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.string, | |||||
| disabled: PropTypes.bool, | |||||
| shouldTestPasswordStrength: PropTypes.bool, | |||||
| autoFocus: PropTypes.bool, | |||||
| }; | |||||
| export default PasswordField; |
| import React, { useEffect, useRef, useState } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import owasp from 'owasp-password-strength-test'; | |||||
| import i18next from 'i18next'; | |||||
| owasp.config({ | |||||
| minOptionalTestsToPass: 3, | |||||
| }); | |||||
| const passwordStrengthOptions = [ | |||||
| { | |||||
| strength: 'weak', | |||||
| color: '#FF5028', | |||||
| }, | |||||
| { | |||||
| strength: 'average', | |||||
| color: '#FDB942', | |||||
| }, | |||||
| { | |||||
| strength: 'good', | |||||
| color: '#06BEE7', | |||||
| }, | |||||
| { | |||||
| strength: 'strong', | |||||
| color: '#00876A', | |||||
| }, | |||||
| ]; | |||||
| /** | |||||
| * User must pass a required test and at least 3 optional. | |||||
| * @param result - owasp result | |||||
| * @returns {number} - index of password strength 0-3 | |||||
| */ | |||||
| function getPasswordStrengthIndex(result) { | |||||
| // requirement for strong password is required test passed and at least 3 optional tests | |||||
| if (result.strong) { | |||||
| return 3; | |||||
| } | |||||
| if (!result.strong && result.optionalTestsPassed >= 3) { | |||||
| return 2; | |||||
| } | |||||
| if (result.optionalTestsPassed <= 0) { | |||||
| return 0; | |||||
| } | |||||
| return result.optionalTestsPassed - 1; | |||||
| } | |||||
| const PasswordStrength = ({ | |||||
| shouldTestPasswordStrength, | |||||
| passwordValue, | |||||
| passwordStrengthTestsRequired, | |||||
| }) => { | |||||
| const strengthContainer = useRef(null); | |||||
| const [passwordStrength, setPasswordStrength] = useState({ | |||||
| width: 0, | |||||
| color: 'red', | |||||
| }); | |||||
| const [error, setError] = useState(''); | |||||
| useEffect(() => { | |||||
| if (shouldTestPasswordStrength && passwordValue) { | |||||
| const bBox = strengthContainer.current.getBoundingClientRect(); | |||||
| const result = owasp.test(passwordValue); | |||||
| const passwordStrengthIndex = getPasswordStrengthIndex(result); | |||||
| const passwordOption = passwordStrengthOptions[passwordStrengthIndex]; | |||||
| const width = !passwordValue | |||||
| ? 0 | |||||
| : (bBox.width * (passwordStrengthIndex + 1)) / | |||||
| passwordStrengthTestsRequired; | |||||
| setPasswordStrength({ width, color: passwordOption.color }); | |||||
| const strength = i18next.t(`password.${passwordOption.strength}`); | |||||
| setError(i18next.t('login.passwordStrength', { strength })); | |||||
| } | |||||
| }, [ | |||||
| passwordValue, | |||||
| shouldTestPasswordStrength, | |||||
| passwordStrengthTestsRequired, | |||||
| ]); | |||||
| if (!shouldTestPasswordStrength || !passwordValue) { | |||||
| return null; | |||||
| } | |||||
| const renderError = () => { | |||||
| if (!error) { | |||||
| return null; | |||||
| } | |||||
| return ( | |||||
| <div | |||||
| className="c-input--error" | |||||
| style={{ | |||||
| color: passwordStrength.color, | |||||
| }} | |||||
| > | |||||
| {error} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <div ref={strengthContainer} className="c-password-strength__container"> | |||||
| <div className="c-password-strength__line--wrapper"> | |||||
| <div | |||||
| className="c-password-strength__line" | |||||
| style={{ | |||||
| backgroundColor: passwordStrength.color, | |||||
| width: passwordStrength.width, | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| {renderError()} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| PasswordStrength.propTypes = { | |||||
| shouldTestPasswordStrength: PropTypes.bool, | |||||
| passwordValue: PropTypes.string, | |||||
| passwordStrengthTestsRequired: PropTypes.number, | |||||
| }; | |||||
| PasswordStrength.defaultProps = { | |||||
| passwordStrengthTestsRequired: 4, | |||||
| }; | |||||
| export default PasswordStrength; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| import { | |||||
| BACKSPACE_KEYCODE, | |||||
| TAB_KEYCODE, | |||||
| RIGHT_ARROW_KEYCODE, | |||||
| LEFT_ARROW_KEYCODE, | |||||
| } from '../../constants/keyCodeConstants'; | |||||
| const TextField = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| centerText, | |||||
| autoFocus, | |||||
| preventAllExceptNumbers, | |||||
| ...props | |||||
| }) => { | |||||
| const onKeydownHandler = (event) => { | |||||
| if (preventAllExceptNumbers) { | |||||
| if ( | |||||
| event.keyCode === BACKSPACE_KEYCODE || | |||||
| event.keyCode === TAB_KEYCODE || | |||||
| event.keyCode === RIGHT_ARROW_KEYCODE || | |||||
| event.keyCode === LEFT_ARROW_KEYCODE | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| if ( | |||||
| (event.keyCode < 58 && event.keyCode > 47) || | |||||
| (event.keyCode < 106 && event.keyCode > 95) | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| event.preventDefault(); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <BaseInputField | |||||
| autoFocus={autoFocus} | |||||
| type="text" | |||||
| label={label} | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| centerText={centerText} | |||||
| {...props} | |||||
| onKeyDown={(event) => onKeydownHandler(event)} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| TextField.propTypes = { | |||||
| field: PropTypes.shape({}), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.string, | |||||
| placeholder: PropTypes.string, | |||||
| disabled: PropTypes.bool, | |||||
| centerText: PropTypes.bool, | |||||
| autoFocus: PropTypes.bool, | |||||
| preventAllExceptNumbers: PropTypes.bool, | |||||
| }; | |||||
| export default TextField; |
| import { Typography } from '@mui/material'; | import { Typography } from '@mui/material'; | ||||
| const ErrorMessageComponent = ({ error }) => ( | const ErrorMessageComponent = ({ error }) => ( | ||||
| <Typography variant="body1" color="error" my={2}> | |||||
| <Typography variant="body1" color="error" my={2} data-testid="error-message"> | |||||
| {error} | {error} | ||||
| </Typography> | </Typography> | ||||
| ); | ); |
| setPage, | setPage, | ||||
| page, | page, | ||||
| search, | search, | ||||
| // setIsCVDisplayed, | |||||
| // isCVDisplayed, | |||||
| // setLinkToCV, | |||||
| }) => { | }) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const candidates = useSelector(selectCandidates); | const candidates = useSelector(selectCandidates); | ||||
| setPage: PropTypes.func, | setPage: PropTypes.func, | ||||
| page: PropTypes.number, | page: PropTypes.number, | ||||
| search: PropTypes.string, | search: PropTypes.string, | ||||
| // setIsCVDisplayed: PropTypes.func, | |||||
| // isCVDisplayed: PropTypes.bool, | |||||
| // setLinkToCV: PropTypes.func, | |||||
| }; | }; | ||||
| export default TableViewPage; | export default TableViewPage; |
| import Backdrop from "../../components/MUI/BackdropComponent"; | import Backdrop from "../../components/MUI/BackdropComponent"; | ||||
| import ErrorMessage from "../../components/MUI/ErrorMessageComponent"; | import ErrorMessage from "../../components/MUI/ErrorMessageComponent"; | ||||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | ||||
| import { LOGIN_USER_LOADING, LOGIN_GOOGLE_USER_LOADING } from "../../store/actions/login/loginActionConstants"; | |||||
| import { | |||||
| LOGIN_USER_LOADING, | |||||
| LOGIN_GOOGLE_USER_LOADING, | |||||
| } from "../../store/actions/login/loginActionConstants"; | |||||
| import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers"; | import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers"; | ||||
| import { JWT_TOKEN } from "../../constants/localStorage"; | import { JWT_TOKEN } from "../../constants/localStorage"; | ||||
| const LoginPage = ({ history }) => { | const LoginPage = ({ history }) => { | ||||
| useEffect(() => { | useEffect(() => { | ||||
| function redirectClient() { | function redirectClient() { | ||||
| let token = authScopeStringGetHelper(JWT_TOKEN); | |||||
| let token = authScopeStringGetHelper(JWT_TOKEN); | |||||
| if (!token) { | if (!token) { | ||||
| return; | return; | ||||
| } | } | ||||
| const isLoading = useSelector( | const isLoading = useSelector( | ||||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | selectIsLoadingByActionType(LOGIN_USER_LOADING) | ||||
| ); | ); | ||||
| const isGoogleLoading = useSelector( | const isGoogleLoading = useSelector( | ||||
| selectIsLoadingByActionType(LOGIN_GOOGLE_USER_LOADING) | selectIsLoadingByActionType(LOGIN_GOOGLE_USER_LOADING) | ||||
| ); | ); | ||||
| setIsInit(false); | setIsInit(false); | ||||
| return; | return; | ||||
| } | } | ||||
| try{ | |||||
| google.accounts.id.initialize({ | |||||
| client_id: | |||||
| "734219382849-nvnulsu7ibfl4bk3n164bgb7c1h5dgca.apps.googleusercontent.com", | |||||
| callback: handleCallbackResponse, | |||||
| }); | |||||
| google.accounts.id.renderButton(document.getElementById("signInDiv"), { | |||||
| theme: "outline", | |||||
| size: "large", | |||||
| width: "250", | |||||
| }); | |||||
| } | |||||
| catch{ | |||||
| console.log('Google login is not initialized'); | |||||
| } | |||||
| try { | |||||
| google.accounts.id.initialize({ | |||||
| client_id: | |||||
| "734219382849-nvnulsu7ibfl4bk3n164bgb7c1h5dgca.apps.googleusercontent.com", | |||||
| callback: handleCallbackResponse, | |||||
| }); | |||||
| google.accounts.id.renderButton(document.getElementById("signInDiv"), { | |||||
| theme: "outline", | |||||
| size: "large", | |||||
| width: "250", | |||||
| }); | |||||
| } catch { | |||||
| console.log("Google login is not initialized"); | |||||
| } | |||||
| }, [isInit]); | }, [isInit]); | ||||
| // const handleGoogleSubmit = (values) => { | // const handleGoogleSubmit = (values) => { | ||||
| onSubmit={formik.handleSubmit} | onSubmit={formik.handleSubmit} | ||||
| sx={{ position: "relative" }} | sx={{ position: "relative" }} | ||||
| > | > | ||||
| <Backdrop position="absolute" isLoading={isLoading || isGoogleLoading} /> | |||||
| <Backdrop | |||||
| position="absolute" | |||||
| isLoading={isLoading || isGoogleLoading} | |||||
| /> | |||||
| <TextField | <TextField | ||||
| name="username" | name="username" | ||||
| label={t("common.labelUsername")} | label={t("common.labelUsername")} | ||||
| helperText={formik.touched.username && formik.errors.username} | helperText={formik.touched.username && formik.errors.username} | ||||
| autoFocus | autoFocus | ||||
| fullWidth | fullWidth | ||||
| inputProps={{ "data-testid": "username-input" }} | |||||
| /> | /> | ||||
| <TextField | <TextField | ||||
| className="rounded-input" | className="rounded-input" | ||||
| error={formik.touched.password && Boolean(formik.errors.password)} | error={formik.touched.password && Boolean(formik.errors.password)} | ||||
| helperText={formik.touched.password && formik.errors.password} | helperText={formik.touched.password && formik.errors.password} | ||||
| fullWidth | fullWidth | ||||
| inputProps={{"data-testid":"password-input"}} | |||||
| InputProps={{ | InputProps={{ | ||||
| endAdornment: ( | endAdornment: ( | ||||
| <InputAdornment position="end"> | <InputAdornment position="end"> | ||||
| > | > | ||||
| {t("login.logIn")} | {t("login.logIn")} | ||||
| </Button> | </Button> | ||||
| <div className="flex-center"> | |||||
| <div className="flex-center" data-testid="separator"> | |||||
| <div className="hr hr-s"></div> | <div className="hr hr-s"></div> | ||||
| <div className="hr-mid">{t("common.or")}</div> | <div className="hr-mid">{t("common.or")}</div> | ||||
| <div className="hr hr-e"></div> | <div className="hr hr-e"></div> | ||||
| </Button> */} | </Button> */} | ||||
| <div id="signInDiv"></div> | <div id="signInDiv"></div> | ||||
| </div> | </div> | ||||
| <div className="flex-center"> | |||||
| <div className="flex-center" data-testid="dilig-logo"> | |||||
| <img src={DiligLogo} style={{ margin: "70px auto 0px auto" }} /> | <img src={DiligLogo} style={{ margin: "70px auto 0px auto" }} /> | ||||
| </div> | </div> | ||||
| </Box> | </Box> |
| export const LOAD_DATA = 'LOAD_DATA'; | |||||
| export const UPDATE_PAGE = 'UPDATE_PAGE'; | |||||
| export const UPDATE_ITEMS_PER_PAGE = 'UPDATE_ITEMS_PER_PAGE'; | |||||
| export const UPDATE_FILTER = 'UPDATE_FILTER'; | |||||
| export const UPDATE_SORT = 'UPDATE_SORT'; |
| import { | |||||
| LOAD_DATA, | |||||
| UPDATE_PAGE, | |||||
| UPDATE_ITEMS_PER_PAGE, | |||||
| UPDATE_FILTER, | |||||
| UPDATE_SORT | |||||
| } from './randomDataActionConstants'; | |||||
| export const loadData = (payload) => ({ | |||||
| type: LOAD_DATA, | |||||
| payload, | |||||
| }); | |||||
| export const updatePage = (payload) => ({ | |||||
| type: UPDATE_PAGE, | |||||
| payload, | |||||
| }); | |||||
| export const updateItemsPerPage = (payload) => ({ | |||||
| type: UPDATE_ITEMS_PER_PAGE, | |||||
| payload, | |||||
| }); | |||||
| export const updateFilter = (payload) => ({ | |||||
| type: UPDATE_FILTER, | |||||
| payload, | |||||
| }) | |||||
| export const updateSort = (payload) => ({ | |||||
| type: UPDATE_SORT, | |||||
| payload, | |||||
| }) |
| import loginReducer from "./login/loginReducer"; | import loginReducer from "./login/loginReducer"; | ||||
| import loadingReducer from "./loading/loadingReducer"; | import loadingReducer from "./loading/loadingReducer"; | ||||
| import userReducer from "./user/userReducer"; | import userReducer from "./user/userReducer"; | ||||
| import randomDataReducer from "./randomData/randomDataReducer"; | |||||
| import usersReducer from "./user/usersReducer"; | import usersReducer from "./user/usersReducer"; | ||||
| import candidateReducer from "./candidate/candidateReducer"; | import candidateReducer from "./candidate/candidateReducer"; | ||||
| import adsReducer from "./ad/adsReducer"; | import adsReducer from "./ad/adsReducer"; | ||||
| login: loginReducer, | login: loginReducer, | ||||
| user: userReducer, | user: userReducer, | ||||
| loading: loadingReducer, | loading: loadingReducer, | ||||
| randomData: randomDataReducer, | |||||
| users: usersReducer, | users: usersReducer, | ||||
| candidate: candidateReducer, | candidate: candidateReducer, | ||||
| ads: adsReducer, | ads: adsReducer, |
| import createReducer from '../../utils/createReducer'; | |||||
| import { | |||||
| LOAD_DATA, | |||||
| UPDATE_PAGE, | |||||
| UPDATE_ITEMS_PER_PAGE, | |||||
| UPDATE_FILTER, | |||||
| UPDATE_SORT, | |||||
| } from '../../actions/randomData/randomDataActionConstants.js'; | |||||
| import generate from '../../../util/helpers/randomData'; | |||||
| const initialState = { | |||||
| items: [], | |||||
| filteredItems: [], | |||||
| count: 0, | |||||
| page: 0, | |||||
| itemsPerPage: 12, | |||||
| filter: '', | |||||
| sort: '', | |||||
| }; | |||||
| export default createReducer( | |||||
| { | |||||
| [LOAD_DATA]: loadRandomData, | |||||
| [UPDATE_PAGE]: updatePage, | |||||
| [UPDATE_ITEMS_PER_PAGE]: updateItemsPerPage, | |||||
| [UPDATE_FILTER]: updateFilter, | |||||
| [UPDATE_SORT]: updateSort, | |||||
| }, | |||||
| initialState | |||||
| ); | |||||
| function loadRandomData(state, action) { | |||||
| const count = action.payload; | |||||
| const items = generate(count); | |||||
| return { | |||||
| ...state, | |||||
| items, | |||||
| filteredItems: items, | |||||
| count: items.length, | |||||
| }; | |||||
| } | |||||
| function updatePage(state, action) { | |||||
| const page = action.payload; | |||||
| return { | |||||
| ...state, | |||||
| page, | |||||
| }; | |||||
| } | |||||
| function updateItemsPerPage(state, action) { | |||||
| const itemsPerPage = action.payload; | |||||
| return { | |||||
| ...state, | |||||
| itemsPerPage, | |||||
| }; | |||||
| } | |||||
| function updateFilter(state, action) { | |||||
| const filter = action.payload; | |||||
| const filteredItems = filter | |||||
| ? state.items.filter((item) => item.name.toLowerCase().includes(filter.toLowerCase())) : state.items; | |||||
| return { | |||||
| ...state, | |||||
| filter, | |||||
| filteredItems, | |||||
| count: filteredItems.length, | |||||
| }; | |||||
| } | |||||
| function updateSort(state, action) { | |||||
| const sort = action.payload; | |||||
| const [field, direction] = sort.split('-'); | |||||
| const sortDirection = direction === 'asc' ? 1 : -1; | |||||
| const dataItems = state.filteredItems.length | |||||
| ? state.filteredItems | |||||
| : state.items; | |||||
| const sorted = dataItems.sort((a, b) => { | |||||
| if (a[field] > b[field]) { | |||||
| return sortDirection; | |||||
| } | |||||
| if (b[field] > a[field]) { | |||||
| return sortDirection * -1; | |||||
| } | |||||
| return 0; | |||||
| }); | |||||
| const filteredItems = state.filteredItems.length | |||||
| ? sorted | |||||
| : state.filteredItems; | |||||
| return { | |||||
| ...state, | |||||
| sort, | |||||
| filteredItems, | |||||
| }; | |||||
| } |