瀏覽代碼

Finished authentication

pull/2/head
Djordje Mitrovic 3 年之前
父節點
當前提交
280a65f2c4
共有 58 個文件被更改,包括 902 次插入812 次删除
  1. 12
    10
      src/App.js
  2. 2
    2
      src/AppRoutes.js
  3. 11
    3
      src/components/Cards/CreateOfferCard/CreateOffer.js
  4. 1
    1
      src/components/Cards/CreateOfferCard/CreateOffer.styled.js
  5. 16
    11
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js
  6. 12
    1
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js
  7. 14
    0
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  8. 8
    0
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled.js
  9. 3
    1
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  10. 3
    1
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  11. 11
    7
      src/components/Dropdown/DropdownList/DropdownList.js
  12. 6
    0
      src/components/Dropdown/DropdownList/DropdownList.styled.js
  13. 29
    6
      src/components/MarketPlace/Header/Header.js
  14. 7
    4
      src/components/MarketPlace/Header/Header.styled.js
  15. 2
    1
      src/components/Select/Select.js
  16. 1
    1
      src/components/TextFields/TextField/TextField.js
  17. 1
    1
      src/components/TextFields/TextField/TextField.styled.js
  18. 1
    1
      src/constants/pages.js
  19. 7
    2
      src/i18n/resources/rs.js
  20. 8
    1
      src/pages/ForgotPasswordPage/ForgotPassword.styled.js
  21. 15
    18
      src/pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent.js
  22. 105
    51
      src/pages/ForgotPasswordPage/ForgotPasswordPage.js
  23. 0
    101
      src/pages/ForgotPasswordPage/ForgotPasswordPageMUI.js
  24. 20
    34
      src/pages/HomePage/HomePageMUI.js
  25. 10
    2
      src/pages/LoginPage/Login.styled.js
  26. 183
    105
      src/pages/LoginPage/LoginPage.js
  27. 0
    201
      src/pages/LoginPage/LoginPageMUI.js
  28. 25
    16
      src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.js
  29. 3
    0
      src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.styled.js
  30. 30
    21
      src/pages/RegisterPages/Register/Register.js
  31. 2
    2
      src/pages/RegisterPages/Register/Register.styled.js
  32. 14
    7
      src/pages/RegisterPages/Register/SecondPart/SecondPartOfRegistration.js
  33. 3
    0
      src/pages/RegisterPages/Register/SecondPart/SecondPartOfRegistration.styled.js
  34. 8
    16
      src/pages/RegisterPages/Register/ThirdPart/ThirdPartOfRegistration.js
  35. 1
    1
      src/pages/RegisterPages/Register/ThirdPart/ThirdPartOfRegistration.styled.js
  36. 34
    14
      src/pages/ResetPasswordPage/ResetPasswordPage.js
  37. 7
    4
      src/request/apiEndpoints.js
  38. 9
    4
      src/request/forgotPasswordRequest.js
  39. 63
    11
      src/request/index.js
  40. 2
    14
      src/request/loginRequest.js
  41. 4
    1
      src/request/offersRequest.js
  42. 2
    1
      src/store/actions/login/loginActions.js
  43. 2
    1
      src/store/actions/offers/offersActionConstants.js
  44. 5
    1
      src/store/actions/offers/offersActions.js
  45. 3
    1
      src/store/actions/user/userActionConstants.js
  46. 10
    0
      src/store/actions/user/userActions.js
  47. 2
    0
      src/store/index.js
  48. 50
    0
      src/store/middleware/accessTokensMiddleware.js
  49. 4
    1
      src/store/middleware/internalServerErrorMiddleware.js
  50. 34
    30
      src/store/middleware/loadingMiddleware.js
  51. 4
    1
      src/store/middleware/requestStatusMiddleware.js
  52. 1
    0
      src/store/reducers/login/loginReducer.js
  53. 9
    0
      src/store/reducers/offers/offersReducer.js
  54. 20
    2
      src/store/reducers/user/userReducer.js
  55. 3
    0
      src/store/saga/filtersSaga.js
  56. 5
    1
      src/store/saga/forgotPasswordSaga.js
  57. 40
    93
      src/store/saga/loginSaga.js
  58. 15
    3
      src/store/saga/offersSaga.js

+ 12
- 10
src/App.js 查看文件

@@ -9,19 +9,21 @@ import { StyledEngineProvider } from "@mui/material";

const App = () => {
return (
<>
<Router history={history}>
<Helmet>
<title>{i18next.t("app.title")}</title>
</Helmet>
{/* <main className="l-page"> */}
<>
<Router history={history}>
<Helmet>
<title>{i18next.t("app.title")}</title>
</Helmet>
{/* <main className="l-page"> */}

<StyledEngineProvider injectFirst>
<GlobalStyle />
<AppRoutes />
</StyledEngineProvider>
{/* </main> */}
</Router>
</>
)};
{/* </main> */}
</Router>
</>
);
};

export default App;

+ 2
- 2
src/AppRoutes.js 查看文件

@@ -14,11 +14,11 @@ import {
RESET_PASSWORD_PAGE,
CREATE_OFFER_PAGE,
} from './constants/pages';
import LoginPage from './pages/LoginPage/LoginPageMUI';
import LoginPage from './pages/LoginPage/LoginPage';
import HomePage from './pages/HomePage/HomePageMUI';
import NotFoundPage from './pages/ErrorPages/NotFoundPage';
import ErrorPage from './pages/ErrorPages/ErrorPage';
import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPageMUI';
import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPage';
import PrivateRoute from './components/Router/PrivateRoute';
import MailSent from './pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent';
import Register from './pages/RegisterPages/Register/Register';

+ 11
- 3
src/components/Cards/CreateOfferCard/CreateOffer.js 查看文件

@@ -29,12 +29,14 @@ import selectedTheme from "../../../themes";
import StepProgress from "../../StepProgress/StepProgress";
import { Label } from "../../CheckBox/Label";
import FirstPartCreateOffer from "./FirstPart/FirstPartCreateOffer";
import SecondPartCreateOffer from "./SecondPart/SecondPartCreateOffer";

const CreateOffer = ({ history }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const [informations, setInformations] = useState({});
const [showPassword, setShowPassword] = useState(false);
const [currentStep, setCurrentStep] = useState(1);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);

@@ -73,14 +75,20 @@ const CreateOffer = ({ history }) => {
);
};

const handleNext = (values) => {
setInformations({...values});
setCurrentStep(prevState => prevState+1)
}

return (
<CreateOfferContainer>
<CreateOfferTitle component="h1" variant="h5">
Nova Objava
</CreateOfferTitle>

<StepProgress current={1} numberOfSteps={3} />
<FirstPartCreateOffer />
<StepProgress current={currentStep} numberOfSteps={3} />
{currentStep === 1 && <FirstPartCreateOffer handleNext={handleNext}/>}
{currentStep === 2 && <SecondPartCreateOffer handleNext={handleNext} />}
</CreateOfferContainer>
);

+ 1
- 1
src/components/Cards/CreateOfferCard/CreateOffer.styled.js 查看文件

@@ -72,5 +72,5 @@ export const FieldLabel = styled(Label)`
export const SelectField = styled(Select)`
position: relative;
top: 15px;
margin-bottom: 30px;
margin-bottom: 18px;
`

+ 16
- 11
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js 查看文件

@@ -3,20 +3,24 @@ import PropTypes from "prop-types";
import { useFormik } from "formik";
import {
CreateOfferFormContainer,
DescriptionField,
FieldLabel,
NextButton,
TitleField,
} from "./FirstPartCreateOffer.styled";
import { TextField } from "../../../TextFields/TextField/TextField";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
// import { TextField } from "../../../TextFields/TextField/TextField";
// import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import * as Yup from "yup";
import selectedTheme from "../../../../themes";
import { useTranslation } from "react-i18next";
import Option from "../../../Select/Option/Option";
import { SelectField } from "../CreateOffer.styled";

const FirstPartCreateOffer = () => {
const FirstPartCreateOffer = (props) => {
const { t } = useTranslation();
const handleSubmit = (values) => {
console.log(values);
props.handleNext(values);
};
const formik = useFormik({
initialValues: {
@@ -36,7 +40,7 @@ const FirstPartCreateOffer = () => {
{/* <Backdrop position="absolute" isLoading={isLoading} /> */}

<FieldLabel leftText={"NASLOV"} />
<TextField
<TitleField
name="nameOfProduct"
placeholder={"Naziv proizvoda..."}
italicPlaceholder
@@ -50,7 +54,7 @@ const FirstPartCreateOffer = () => {
/>

<FieldLabel leftText={"OPIS PROIZVODA"} />
<TextField
<DescriptionField
name="description"
placeholder={"Opis..."}
margin="normal"
@@ -65,7 +69,7 @@ const FirstPartCreateOffer = () => {
height={"100px"}
/>

<FieldLabel leftText={"KATEGORIJA"} />
<FieldLabel leftText={"LOKACIJA"} />
<SelectField defaultValue={1}>
<Option value={1}>Opcija 1</Option>
<Option value={2}>Opcija 2</Option>
@@ -73,7 +77,7 @@ const FirstPartCreateOffer = () => {
<Option value={4}>Opcija 4</Option>
</SelectField>

<FieldLabel leftText={"PODKATEGORIJA"} />
<FieldLabel leftText={"KATEGORIJA"} />
<SelectField defaultValue={1}>
<Option value={1}>Opcija 1</Option>
<Option value={2}>Opcija 2</Option>
@@ -81,7 +85,7 @@ const FirstPartCreateOffer = () => {
<Option value={4}>Opcija 4</Option>
</SelectField>

<FieldLabel leftText={"LOKACIJA"} />
<FieldLabel leftText={"PODKATEGORIJA"} />
<SelectField defaultValue={1}>
<Option value={1}>Opcija 1</Option>
<Option value={2}>Opcija 2</Option>
@@ -89,11 +93,11 @@ const FirstPartCreateOffer = () => {
<Option value={4}>Opcija 4</Option>
</SelectField>

<PrimaryButton
<NextButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={
@@ -102,13 +106,14 @@ const FirstPartCreateOffer = () => {
// }
>
NASTAVI
</PrimaryButton>
</NextButton>
</CreateOfferFormContainer>
);
};

FirstPartCreateOffer.propTypes = {
children: PropTypes.any,
handleNext: PropTypes.func,
};

export default FirstPartCreateOffer;

+ 12
- 1
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js 查看文件

@@ -1,7 +1,9 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import { Label } from "../../../CheckBox/Label";
import { TextField } from "../../../TextFields/TextField/TextField";

export const CreateOfferTitle = styled(Typography)`
font-family: "Open Sans";
@@ -30,7 +32,7 @@ export const CreateOfferDescription = styled(Typography)`
align-items: center;
text-align: center;
color: ${selectedTheme.primaryGrayText};
margin-bottom: 20px;
margin-bottom: 18px;
`;
export const CreateOfferFormContainer = styled(Box)`
width: 335px;
@@ -48,4 +50,13 @@ export const FieldLabel = styled(Label)`
cursor: auto;
letter-spacing: 0.2px;
}
`
export const DescriptionField = styled(TextField)`
margin-bottom: 4px;
`
export const TitleField = styled(TextField)`

`
export const NextButton = styled(PrimaryButton)`
margin-top: 16px;
`

+ 14
- 0
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js 查看文件

@@ -0,0 +1,14 @@
import React from "react";
import PropTypes from "prop-types";
import { CreateOfferFormContainer } from "./SecondPartCreateOffer.styled";

const SecondPartCreateOffer = () => {
return <CreateOfferFormContainer>Aaaa</CreateOfferFormContainer>;
};

SecondPartCreateOffer.propTypes = {
children: PropTypes.node,
handleOffer: PropTypes.func,
};

export default SecondPartCreateOffer;

+ 8
- 0
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled.js 查看文件

@@ -0,0 +1,8 @@
import { Box } from "@mui/material";
import styled from "styled-components";

export const CreateOfferFormContainer = styled(Box)`
width: 335px;
height: 700px;
padding-top: 20px;
`;

+ 3
- 1
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js 查看文件

@@ -19,6 +19,7 @@ import { CheckBox } from "../../../../CheckBox/CheckBox";
const FilterCheckboxDropdown = (props) => {
const [toSearch, setToSearch] = useState("");
const [dataToShow, setDataToShow] = useState([]);
const [isOpened, setIsOpened] = useState(false);
const { data } = props;

useEffect(() => {
@@ -77,8 +78,9 @@ const FilterCheckboxDropdown = (props) => {
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
setIsOpened={setIsOpened}
toggleIconStyles={{
backgroundColor: selectedTheme.primaryIconBackgroundColor,
backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
<React.Fragment>

+ 3
- 1
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js 查看文件

@@ -14,6 +14,7 @@ import RadioGroup from "../../../../Radio/Group/RadioGroup";
const FilterRadioDropdown = (props) => {
const [toSearch, setToSearch] = useState("");
const [dataToShow, setDataToShow] = useState([]);
const [isOpened, setIsOpened] = useState(false);
const { data } = props;

useEffect(() => {
@@ -51,8 +52,9 @@ const FilterRadioDropdown = (props) => {
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
setIsOpened={setIsOpened}
toggleIconStyles={{
backgroundColor: selectedTheme.primaryIconBackgroundColor,
backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
<React.Fragment>

+ 11
- 7
src/components/Dropdown/DropdownList/DropdownList.js 查看文件

@@ -14,31 +14,34 @@ import PropTypes from "prop-types";

const DropdownList = (props) => {
const [listShown, setListShown] = useState(props.defaultOpen);
const handleShow = () => {
if (props.setIsOpened) {
props.setIsOpened(!listShown);
}
setListShown((prevState) => !prevState);
};
return (
<DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}>
<DropdownHeader>
{props.dropdownIcon && (
<DropdownIcon onClick={() => setListShown((prevState) => !prevState)}>
<DropdownIcon onClick={() => handleShow()}>
{props.dropdownIcon}
</DropdownIcon>
)}
<DropdownTitle
onClick={() => setListShown((prevState) => !prevState)}
textcolor={props.textcolor}
>
<DropdownTitle onClick={() => handleShow()} textcolor={props.textcolor}>
{props.title}
</DropdownTitle>
{listShown ? (
<ToggleIconOpened
style={props.toggleIconStyles}
onClick={() => setListShown((prevState) => !prevState)}
onClick={() => handleShow()}
>
{props.toggleIconOpened}
</ToggleIconOpened>
) : (
<ToggleIconClosed
style={props.toggleIconStyles}
onClick={() => setListShown((prevState) => !prevState)}
onClick={() => handleShow()}
>
{props.toggleIconClosed}
</ToggleIconClosed>
@@ -65,6 +68,7 @@ DropdownList.propTypes = {
toggleIconStyles: PropTypes.any,
headerOptions: PropTypes.node,
textcolor: PropTypes.string,
setIsOpened: PropTypes.func,
};

DropdownList.defaultProps = {

+ 6
- 0
src/components/Dropdown/DropdownList/DropdownList.styled.js 查看文件

@@ -26,11 +26,17 @@ export const DropdownTitle = styled(Typography)`
export const ToggleIconOpened = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.primaryPurple};
& span {
${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
}
`;

export const ToggleIconClosed = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.primaryPurple};
& span {
${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
}
`;

export const DropdownIcon = styled(IconButton)`

+ 29
- 6
src/components/MarketPlace/Header/Header.js 查看文件

@@ -12,11 +12,12 @@ import {
import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg";
import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-grid-line.svg";
import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg";
import { MenuItem } from "@mui/material";
import selectedTheme from "../../../themes";
import { useSelector } from "react-redux";
import { selectFilters } from "../../../store/selectors/filtersSelectors";
import Mockupdata from "../../Cards/FilterCard/Mockupdata";
import Option from "../../Select/Option/Option";
import { useHistory, useLocation, useRouteMatch } from "react-router-dom";

const DownArrow = (props) => (
<IconStyled {...props}>
@@ -31,11 +32,15 @@ const MockupdataForSelect = [
},
{
id: 1,
string: "Ceni rastuce",
string: "Najpopularnije",
},
{
id: 2,
string: "Ceni opadajuce",
string: "Najnovije",
},
{
id: 3,
string: "Najstarije",
},
];

@@ -43,6 +48,9 @@ const Header = (props) => {
const [categoryString, setCategoryString] = useState("");
const [filtersString, setFiltersString] = useState("");
const { category, cities } = useSelector(selectFilters);
const history = useHistory();
const location = useLocation();
const routeMatch = useRouteMatch();

useEffect(() => {
let categorystring = "";
@@ -67,6 +75,15 @@ const Header = (props) => {
setCategoryString(categorystring);
setFiltersString(filtersstring);
}, [category, cities]);

const handleChangeSelect = (value) => {
let chosenOption = MockupdataForSelect.find(item => item.id === value);
console.log(location);
console.log(history);
console.log(chosenOption);
console.log(routeMatch)
}

return (
<HeaderContainer>
<HeaderLocation>{categoryString + filtersString} </HeaderLocation>
@@ -93,15 +110,21 @@ const Header = (props) => {
<GridSquare />
</HeaderButton>
</HeaderButtons>
<HeaderSelect defaultValue={0} IconComponent={DownArrow}>
<HeaderSelect
defaultValue={0}
IconComponent={DownArrow}
width="209px"
height="34px"
onChange={handleChangeSelect}
>
{MockupdataForSelect.map((item) => (
<MenuItem
<Option
value={item.id}
key={item.id}
style={{ display: item.id === 0 ? "none" : "flex" }}
>
{item.string}
</MenuItem>
</Option>
))}
</HeaderSelect>
</HeaderOptions>

+ 7
- 4
src/components/MarketPlace/Header/Header.styled.js 查看文件

@@ -1,7 +1,8 @@
import { Box, MenuItem, Select } from "@mui/material";
import { Box, MenuItem } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import Select from "../../Select/Select";

export const HeaderContainer = styled(Box)`
margin-top: 20px;
@@ -34,9 +35,11 @@ export const HeaderSelect = styled(Select)`
height: 35px;
font-family: "Open Sans";
margin-top: 3px;
& div span {
position: relative;
top: -4px;
font-weight: 400;
position: relative;
left: -5px;
& div {
padding-left: 8px;
}
`
export const SelectItem = styled(MenuItem)`

+ 2
- 1
src/components/Select/Select.js 查看文件

@@ -6,7 +6,6 @@ import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg";
// import {Select as SelectMUI} from "@mui/material";

const Select = (props) => {
console.log(props);
return (
<SelectStyled
defaultValue={props.defaultValue}
@@ -14,6 +13,7 @@ const Select = (props) => {
width={props.width}
height={props.height}
className={props.className}
onChange={props.onChange}
IconComponent={(iconProps) => (
<SelectIcon {...iconProps}>
<Down />
@@ -32,6 +32,7 @@ Select.propTypes = {
fullwidth: PropTypes.bool,
defaultValue: PropTypes.number,
className: PropTypes.string,
onChange: PropTypes.func,
};
Select.defaultProps = {
fullwidth: true,

+ 1
- 1
src/components/TextFields/TextField/TextField.js 查看文件

@@ -43,7 +43,7 @@ export const TextField = (props) => {
label={props.showAnimation ? props.placeholder : ""}
italicplaceholder={props.italicPlaceholder && isFieldEmpty}
italicplaceholder={(props.italicPlaceholder && isFieldEmpty) ? "true" : "false"}
ref={textInputRef}
focused={props.focused}
>

+ 1
- 1
src/components/TextFields/TextField/TextField.styled.js 查看文件

@@ -14,7 +14,7 @@ export const TextFieldStyled = styled(TextField)`
background-color: ${selectedTheme.primaryBackgroundColor};
width: ${(props) => props.width};
font-style: ${(props) =>
props.italicplaceholder === true ? "italic" : "normal"};
props.italicplaceholder === "true" ? "italic" : "normal"};
padding-left: 0;
margin: 0;
padding: 0;

+ 1
- 1
src/constants/pages.js 查看文件

@@ -7,5 +7,5 @@ export const NOT_FOUND_PAGE = '/not-found';
export const FORGOT_PASSWORD_MAIL_SENT = '/forgot-password/mail-sent'
export const REGISTER_PAGE = "/register"
export const REGISTER_SUCCESSFUL_PAGE = "/register/success";
export const RESET_PASSWORD_PAGE = "/reset-password"
export const RESET_PASSWORD_PAGE = "/reset-password/:token"
export const CREATE_OFFER_PAGE = "/create-offer"

+ 7
- 2
src/i18n/resources/rs.js 查看文件

@@ -55,7 +55,7 @@ export default {
emailRequired: 'Email adresa je obavezna!',
noUsers: 'Ne postoji korisnik sa zadatom email adresom.',
passwordStrength: 'Your password is {{strength}}.',
passwordLength: 'Lozinka mora imati izmedju 8 i 50 karaktera!',
passwordLength: 'Lozinka mora imati najmanje 8 karaktera!',
signUpRecommendation: 'Registrujte se.',
email: 'Unesite email adresu kako biste se prijavili',
logInTitle: 'Uloguj se',
@@ -66,6 +66,7 @@ export default {
forgotYourPassword: 'Zaboravili ste lozinku?',
forgotPasswordEmail:'Email',
useDifferentEmail: 'Iskoristite drugačiju lozinku.',
wrongCredentials: 'Pogrešan mail ili lozinka!'
},
password: {
weak: 'slaba',
@@ -80,11 +81,14 @@ export default {
descriptionSecond: 'Ovaj korak nije obavezan za razgledanje artikla ali za proces trampe je obavezan. Uvek možete popuniti ova polja u podešavanjima naloga kasnije',
descriptionThird: 'Ovaj korak nije obavezan za razgledanje artikla ali za proces trampe je obavezan. Uvek možete popuniti ova polja u podešavanjima naloga kasnije',
loginText: "Već posedujete nalog?",
emailFormat: 'Nevalidan format email adrese!',
emailTaken: "E-mail je zauzet!",
login: "Ulogujte se.",
acceptTerms: `Pri klikom na dugme "Registruj se", prihvatate naše`,
terms: "Uslove Korišćenja",
success: 'Registracija Uspešna',
welcome: 'Dobro došli na trampu, želimo vam uspešno trampovanje!'
PIBTaken: "PIB je zauzet!",
welcome: 'Dobro došli na trampu, želimo vam uspešno trampovanje!',
},
forgotPassword: {
title: 'Povrati lozinku',
@@ -94,6 +98,7 @@ export default {
emailFormat: 'Nevalidan format email adrese!',
mailSent: "E-Mail poslat!",
mailSentDescription: "Poslat vam je email sa instrukcijama kako da resetujete lozinku",
mailNotFound: "Mejl nije povezan ni sa jednim nalogom!",
notRecievedMail: "Niste dobili email?",
checkSpam: "U slučaju da Vam ne stigne email, pogledajte <strong>Spam</strong> folder email provajdera",
forgotPassword: {

+ 8
- 1
src/pages/ForgotPasswordPage/ForgotPassword.styled.js 查看文件

@@ -44,4 +44,11 @@ export const ForgotPasswordDescription = styled(Typography)`
export const FormContainer = styled(Box)`
width: 335px;
height: 216px;
`;
`;
export const ErrorMessage = styled(Typography)`
color: red;
font-family: "Open Sans";
position: relative;
top: -7px;
font-size: 14px;
`

+ 15
- 18
src/pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent.js 查看文件

@@ -29,19 +29,18 @@ const MailSent = () => {

useEffect(() => {
setEmail(location.state.email);
}, [])
}, []);

const navigateLogin = () => {
history.replace(LOGIN_PAGE);
};

const handleResend = () => {
dispatch(forgotPassword({email: mail}))
}
dispatch(forgotPassword({ email: mail }));
};

return (
<MailSentContainer>

<MailSentImage />

<Title component="h1" variant="h5">
@@ -53,12 +52,11 @@ const MailSent = () => {
</Description>

<FormContainer>

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
onClick={navigateLogin}
@@ -67,27 +65,26 @@ const MailSent = () => {
</PrimaryButton>

<SendAgainTextContainer>

<StandardText>
{t("forgotPassword.notRecievedMail")}
</StandardText>

<Link to="#" component={NavLink} underline="hover" align="center" textsize="16px" onClick={handleResend}>
<StandardText>{t("forgotPassword.notRecievedMail")}</StandardText>

<Link
to="#"
component={NavLink}
underline="hover"
align="center"
textsize="16px"
onClick={handleResend}
>
{t("common.sendAgain")}
</Link>

</SendAgainTextContainer>

</FormContainer>

<Footer>

<FooterText>
<Trans i18nKey="forgotPassword.checkSpam" />
<Trans i18nKey="forgotPassword.checkSpam" />
</FooterText>

</Footer>
</MailSentContainer>
);
};

+ 105
- 51
src/pages/ForgotPasswordPage/ForgotPasswordPage.js 查看文件

@@ -1,62 +1,116 @@
import React from 'react';
import { Formik, Form, Field } from 'formik';
import { useTranslation } from 'react-i18next';
import * as Yup from 'yup';
import i18next from 'i18next';
import Auth from '../../components/Auth/Auth';
import AuthCard from '../../components/AuthCards/AuthCard';
import TextField from '../../components/InputFields/TextField';
import Button from '../../components/Buttons/Button';

import Section from '../../components/Section/Section';

const forgotPasswordValidationSchema = Yup.object().shape({
email: Yup.string().required(
i18next.t('login.securityQuestion.answerRequired'),
),
});
import React, { useState } from "react";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
// import i18next from "i18next";
import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg";
import {
ForgotPasswordPageContainer,
ForgotPasswordDescription,
ForgotPasswordTitle,
FormContainer,
ErrorMessage,
} from "./ForgotPassword.styled";
import { TextField } from "../../components/TextFields/TextField/TextField";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import { useHistory } from "react-router-dom";
import { FORGOT_PASSWORD_MAIL_SENT } from "../../constants/pages";
import selectedTheme from "../../themes";
import { useDispatch } from "react-redux";
import { forgotPassword } from "../../store/actions/user/userActions";

const ForgotPasswordPage = () => {
const history = useHistory();
const { t } = useTranslation();
const dispatch = useDispatch();
const [emailNotFoundStatus, setEmailNotFoundStatus] = useState(false);

const forgotPasswordValidationSchema = Yup.object().shape({
email: Yup.string()
.required(t("forgotPassword.emailRequired"))
.email(t("forgotPassword.emailFormat")),
});

const handleResponseSuccess = () => {
history.push({
pathname: FORGOT_PASSWORD_MAIL_SENT,
state: { email: formik.values.email },
});
};
const handleResponseError = () => {
setEmailNotFoundStatus(true);
};

const handleSubmit = (values) => {
console.log("Values",values)
dispatch(
forgotPassword({
email: values.email,
handleResponseSuccess,
handleResponseError,
})
);
};

const formik = useFormik({
initialValues: {
email: "",
},
validationSchema: forgotPasswordValidationSchema,
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

return (
<Auth>
<AuthCard
title={t('forgotPassword.title')}
>
<Section>
<div className="c-reset-security">
<div className="c-reset-security__form">
<Formik
onSubmit={handleSubmit}
initialValues={{ email: '' }}
validationSchema={forgotPasswordValidationSchema}
>
<Form>
<Field
label={t('login.forgotPasswordEmail')}
name="email"
component={TextField}
/>
<Button
className="c-reset-security__button"
authButton
variant="primary"
type="submit"
>
{t('forgotPassword.label')}
</Button>
</Form>
</Formik>
</div>
</div>
</Section>
</AuthCard>
</Auth>
<ForgotPasswordPageContainer>
<Logo />

<ForgotPasswordTitle component="h1" variant="h5">
{t("forgotPassword.title")}
</ForgotPasswordTitle>

<ForgotPasswordDescription component="h1" variant="h6">
{t("forgotPassword.description")}
</ForgotPasswordDescription>

<FormContainer component="form" onSubmit={formik.handleSubmit}>
{/* <Backdrop position="absolute" isLoading={isLoading} /> */}

<TextField
name="email"
placeholder={t("common.labelEmail")}
margin="normal"
value={formik.values.email}
error={
(formik.touched.email && Boolean(formik.errors.email)) ||
emailNotFoundStatus
}
helperText={formik.touched.email && formik.errors.email}
onChange={formik.handleChange}
autoFocus
fullWidth
/>

{formik.errors.email && formik.touched.email && (
<ErrorMessage>{formik.errors.email}</ErrorMessage>
)}
{emailNotFoundStatus && (
<ErrorMessage>{t("forgotPassword.mailNotFound")}</ErrorMessage>
)}

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={formik.values.email?.length === 0}
>
{t("common.send")}
</PrimaryButton>
</FormContainer>
</ForgotPasswordPageContainer>
);
};


+ 0
- 101
src/pages/ForgotPasswordPage/ForgotPasswordPageMUI.js 查看文件

@@ -1,101 +0,0 @@
import React, { useState } from "react";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
import i18next from "i18next";
import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg";
import {
ForgotPasswordPageContainer,
ForgotPasswordDescription,
ForgotPasswordTitle,
FormContainer,
} from "./ForgotPassword.styled";
import { TextField } from "../../components/TextFields/TextField/TextField";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import { useHistory } from "react-router-dom";
import { FORGOT_PASSWORD_MAIL_SENT } from "../../constants/pages";
import selectedTheme from "../../themes";
import { useDispatch } from "react-redux";
import { forgotPassword } from "../../store/actions/user/userActions";

const forgotPasswordValidationSchema = Yup.object().shape({
email: Yup.string()
.required(i18next.t("forgotPassword.emailRequired"))
.email(i18next.t("forgotPassword.emailFormat")),
});

const ForgotPasswordPage = () => {
const history = useHistory();
const { t } = useTranslation();
const dispatch = useDispatch();
const [emailNotFoundStatus, setEmailNotFoundStatus] = useState(false);

const handleResponseSuccess = () => {
history.push({
pathname: FORGOT_PASSWORD_MAIL_SENT,
state: {email: formik.values.email}
});
}
const handleResponseError = () => {
setEmailNotFoundStatus(true);
}

const handleSubmit = (values) => {
// validate email
dispatch(forgotPassword({email: values.email, handleResponseSuccess, handleResponseError}));
};

const formik = useFormik({
initialValues: {
email: "",
},
validationSchema: forgotPasswordValidationSchema,
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

return (
<ForgotPasswordPageContainer>
<Logo />

<ForgotPasswordTitle component="h1" variant="h5">
{t("forgotPassword.title")}
</ForgotPasswordTitle>

<ForgotPasswordDescription component="h1" variant="h6">
{t("forgotPassword.description")}
</ForgotPasswordDescription>

<FormContainer component="form" onSubmit={formik.handleSubmit}>
{/* <Backdrop position="absolute" isLoading={isLoading} /> */}

<TextField
name="email"
placeholder={t("common.labelEmail")}
margin="normal"
value={formik.values.email}
error={(formik.touched.email && Boolean(formik.errors.email)) || emailNotFoundStatus}
helperText={formik.touched.email && formik.errors.email}
onChange={formik.handleChange}
autoFocus
fullWidth
/>

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={formik.values.email?.length === 0}
>
{t("common.send")}
</PrimaryButton>
</FormContainer>
</ForgotPasswordPageContainer>
);
};

export default ForgotPasswordPage;

+ 20
- 34
src/pages/HomePage/HomePageMUI.js 查看文件

@@ -20,15 +20,20 @@ const HomePage = () => {

const history = useHistory();
useEffect(() => {
let category = null, subcategory = null, cities = [], _des_date = false, _des_popular = false, page, size;
const queryString = history.location.search.substring(1);
const queryObject = qs.parse(queryString);
let category = null;
if (queryObject.category) {
category = Mockupdata[1].find(
(item) => item.string === queryObject.category.toString()
).id;
}
let cities = [];
if (queryObject.subcategory) {
subcategory = Mockupdata[1].find(
(item) => item.string === queryObject.subcategory.toString()
).id;
}
if (queryObject.city) {
if (Array.isArray(queryObject.city)) {
queryObject.city.forEach((item) => {
@@ -40,44 +45,25 @@ const HomePage = () => {
);
}
}
let subcategory = null;
if (queryObject.subcategory) {
subcategory = Mockupdata[1].find(
(item) => item.string === queryObject.subcategory.toString()
).id;
if (queryObject.sortBy) {
if (queryObject.sortBy === "dateAsc") _des_date = false;
if (queryObject.sortBy === "dateDesc") _des_date = true;
if (queryObject.sortBy === "popular") _des_popular = true;
}
if (queryObject.page) {
page = queryObject.page;
}
if (queryObject.size) {
size = queryObject.size;
}
dispatch(setFilters({ category, subcategory, cities }));

dispatch(setFilters({ category, subcategory, cities, _des_date, _des_popular, page, size }));
}, [history.location.search]);
// const handleCl = () => {
// dispatch(logoutUser());
// };

return (
<HomePageContainer>
{/* <button onClick={handleCl}>Dugme</button> */}
<Navbar />
<MainLayout leftCard={<FilterCard />} content={<MarketPlace />} />

{/* <Box sx={{ mt: 4, mx: 4 }}>
<GridStyled container justifyContent="space-between">
<GridStyled item xs={12} md={3}>
asdasdasd
</GridStyled>
<GridStyled item xs={12} md={6}>
<GridStyled xs={12} md={12}>
<HomeListCard></HomeListCard>
</GridStyled>
</GridStyled> */}
{/* <GridStyled item xs={12} md={9}>
<PagingSortingFiltering />
</GridStyled>
<GridStyled item xs={12} md={9}>
{/* Move to higher components? */}
{/* <RandomDataProvider>
<PagingSortingFilteringServerSide />
</RandomDataProvider>
</GridStyled> */}
{/* </GridStyled>
</Box> */}
</HomePageContainer>
);
};

+ 10
- 2
src/pages/LoginPage/Login.styled.js 查看文件

@@ -51,10 +51,18 @@ export const RegisterAltText = styled(Typography)`
font-size: 14px;
padding-right: 6px;
line-height: 14px;
`
`;
export const RegisterTextContainer = styled(Box)`
display: flex;
flex-direction: row;
margin-top: 36px;
justify-content: center;
`
`;
export const ErrorMessage = styled(Typography)`
color: red;
font-family: "Open Sans";
position: relative;
top: -12px;
height: 20px;
font-size: 14px;
`;

+ 183
- 105
src/pages/LoginPage/LoginPage.js 查看文件

@@ -1,135 +1,213 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, Form, Formik } from 'formik';
import { useDispatch, useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom';
import * as Yup from 'yup';
import { useTranslation } from 'react-i18next';
import i18next from 'i18next';
import PasswordField from '../../components/InputFields/PasswordField';
import Button from '../../components/Buttons/Button';
import TextField from '../../components/InputFields/TextField';
import Auth from '../../components/Auth/Auth';
import AuthCard from '../../components/AuthCards/AuthCard';
/* eslint-disable */
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import * as Yup from "yup";
import { useTranslation } from "react-i18next";
import {
clearLoginErrors,
fetchUser,
} from '../../store/actions/login/loginActions';
} from "../../store/actions/login/loginActions";
import { selectLoginError } from "../../store/selectors/loginSelectors";
import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../constants/pages";
import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg";
import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg";
import Backdrop from "../../components/MUI/BackdropComponent";
import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors";
import { LOGIN_USER_LOADING } from "../../store/actions/login/loginActionConstants";
import { TextField } from "../../components/TextFields/TextField/TextField";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import { IconButton } from "../../components/Buttons/IconButton/IconButton";
import Link from "../../components/Link/Link";
import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg";
import {
selectLoginError,
} from '../../store/selectors/loginSelectors';
import {
FORGOT_PASSWORD_PAGE, HOME_PAGE,
} from '../../constants/pages';
import { selectIsLoadingByActionType } from '../../store/selectors/loadingSelectors';
import { LOGIN_USER_LOADING } from '../../store/actions/login/loginActionConstants';

const LoginValidationSchema = Yup.object().shape({
username: Yup.string().required(i18next.t('login.usernameRequired')),
password: Yup.string().required(i18next.t('login.passwordRequired')),
});
LoginPageContainer,
LoginTitle,
LoginDescription,
LoginFormContainer,
RegisterAltText,
RegisterTextContainer,
ErrorMessage,
} from "./Login.styled";
import selectedTheme from "../../themes";

const LoginPage = ({ history }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const error = useSelector(selectLoginError);

const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);

// When user refreshes page
// useEffect(() => {
// function redirectClient() {
// if (!tokens.RefreshToken && !tokens.JwtToken) {
// return
// }
// }
// function redirectClient() {
// if (!tokens.RefreshToken && !tokens.JwtToken) {
// return;
// }
// }

// redirectClient();
// redirectClient();
// }, [history, tokens]);

const isLoading = useSelector(
selectIsLoadingByActionType(LOGIN_USER_LOADING),
selectIsLoadingByActionType(LOGIN_USER_LOADING)
);
const handleApiResponseSuccess =()=>{

useEffect(() => {
dispatch(clearLoginErrors());
}, []);

const handleApiResponseSuccess = () => {
history.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
},
});
}
};

const handleSubmit = (values) => {
// destructure value as username.
const { username: Username } = values;
const { password: Password } = values;
dispatch(clearLoginErrors());
dispatch(
fetchUser({
Username,
Password,
handleApiResponseSuccess
},
),
);
const { email, password: password } = values;
dispatch(clearLoginErrors());
dispatch(
fetchUser({
email,
password,
handleApiResponseSuccess,
})
);
};

const formik = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: Yup.object().shape({
email: Yup.string().required(t("login.mailRequired")),
password: Yup.string()
.required(t("login.passwordRequired"))
.min(8, t("login.passwordLength")),
}),
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});
useEffect(() => {
if (error) {
if (formik.errors.email || formik.errors.password) {
dispatch(clearLoginErrors());
}
}
}, [formik.errors.email, formik.errors.password])

return (
<Auth>
<AuthCard
title="Log In"
isLoading={isLoading}
>
<div className="c-login c-login--user">
<div className="c-login__form">
<Formik
initialValues={{
username: '',
password: '',
}}
onSubmit={handleSubmit}
validationSchema={LoginValidationSchema}
validateOnBlur
enableReinitialize
>
{({ values }) => (
<Form>
<Field
label={t('common.labelUsername')}
value={values.username.value}
component={TextField}
name="username"
/>
<Field
label={
<div className="c-login--password__label">
{t('common.labelPassword')}
</div>
}
link={
<NavLink
to={FORGOT_PASSWORD_PAGE}
>
{t('login.forgotYourPassword')}
</NavLink>
}
name="password"
component={PasswordField}
errorMessage={error}
autoFocus
/>
<Button
className="c-login__button"
authButton
variant="primary"
type="submit"
>
{t('common.continue')}
</Button>
</Form>
)}
</Formik>
</div>
</div>
</AuthCard>
</Auth>
<LoginPageContainer>
<Logo />

<LoginTitle component="h1" variant="h5">
{t("login.logInTitle")}
</LoginTitle>

<LoginDescription component="h1" variant="h6">
{t("login.welcomeText")}
</LoginDescription>

<LoginFormContainer component="form" onSubmit={formik.handleSubmit}>
<Backdrop position="absolute" isLoading={isLoading} />

<TextField
name="email"
placeholder={t("common.labelEmail")}
margin="normal"
value={formik.values.email}
onChange={formik.handleChange}
error={
(formik.touched.email && formik.errors.email) || error.length > 0
}
helperText={formik.touched.email && formik.errors.email}
autoFocus
fullWidth
/>

<TextField
name="password"
placeholder={t("common.labelPassword")}
margin="normal"
type={showPassword ? "text" : "password"}
value={formik.values.password}
onChange={formik.handleChange}
error={
(formik.touched.password && formik.errors.password) ||
error.length > 0
}
helperText={formik.touched.password && formik.errors.password}
fullWidth
InputProps={{
endAdornment: (
<IconButton
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
>
{showPassword ? <VisibilityOn /> : <VisibilityOff />}
</IconButton>
),
}}
/>
{formik.errors.password && formik.touched.password && (
<ErrorMessage>{formik.errors.password}</ErrorMessage>
)}
{error.length > 0 && !formik.errors.password && <ErrorMessage>{error}</ErrorMessage>}

<Link
to={FORGOT_PASSWORD_PAGE}
textsize="12px"
component={NavLink}
underline="hover"
align="right"
style={{
marginTop: error.length > 0 ? "0" : "18px",
marginBottom: "18px",
}}
>
{t("login.forgotYourPassword")}
</Link>

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={
formik.values.email.length === 0 ||
formik.values.password.length === 0
}
>
{t("login.logIn")}
</PrimaryButton>

<RegisterTextContainer>
<RegisterAltText>
{t("login.dontHaveAccount").padEnd(2, " ")}
</RegisterAltText>

<Link
to="/register"
component={NavLink}
underline="hover"
align="center"
>
{t("login.signUp")}
</Link>
</RegisterTextContainer>
</LoginFormContainer>
</LoginPageContainer>
);
};


+ 0
- 201
src/pages/LoginPage/LoginPageMUI.js 查看文件

@@ -1,201 +0,0 @@
/* eslint-disable */
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import * as Yup from "yup";
import { useTranslation } from "react-i18next";
import {
clearLoginErrors,
fetchUser,
} from "../../store/actions/login/loginActions";
import { selectLoginError } from "../../store/selectors/loginSelectors";
import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../constants/pages";
import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg";
import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg";
import Backdrop from "../../components/MUI/BackdropComponent";
import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors";
import { LOGIN_USER_LOADING } from "../../store/actions/login/loginActionConstants";
import { TextField } from "../../components/TextFields/TextField/TextField";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import { IconButton } from "../../components/Buttons/IconButton/IconButton";
import Link from "../../components/Link/Link";
import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg";
import {
LoginPageContainer,
LoginTitle,
LoginDescription,
LoginFormContainer,
RegisterAltText,
RegisterTextContainer,
} from "./Login.styled";
import selectedTheme from "../../themes";

const LoginPage = ({ history }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const error = useSelector(selectLoginError);

const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);

// When user refreshes page
// useEffect(() => {
// function redirectClient() {
// if (!tokens.RefreshToken && !tokens.JwtToken) {
// return;
// }
// }

// redirectClient();
// }, [history, tokens]);

const isLoading = useSelector(
selectIsLoadingByActionType(LOGIN_USER_LOADING)
);

useEffect(() => {
dispatch(clearLoginErrors());
}, []);

const handleApiResponseSuccess = (status) => {
history.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
},
});
};

const handleSubmit = (values) => {
const { username: email, password: password } = values;
dispatch(clearLoginErrors());
dispatch(
fetchUser({
email,
password,
handleApiResponseSuccess,
})
);
};

const formik = useFormik({
initialValues: {
username: "",
password: "",
},
validationSchema: Yup.object().shape({
username: Yup.string().required(t("login.usernameRequired")),
password: Yup.string().required(t("login.passwordRequired")).min(8),
}),
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

return (
<LoginPageContainer>
<Logo />

<LoginTitle component="h1" variant="h5">
{t("login.logInTitle")}
</LoginTitle>

<LoginDescription component="h1" variant="h6">
{t("login.welcomeText")}
</LoginDescription>

<LoginFormContainer component="form" onSubmit={formik.handleSubmit}>
<Backdrop position="absolute" isLoading={isLoading} />

<TextField
name="username"
placeholder={t("common.labelEmail")}
margin="normal"
value={formik.values.username}
onChange={formik.handleChange}
error={(formik.touched.password && formik.errors.password) || error.length > 0}
helperText={formik.touched.username && formik.errors.username}
autoFocus
fullWidth
/>

<TextField
name="password"
placeholder={t("common.labelPassword")}
margin="normal"
type={showPassword ? "text" : "password"}
value={formik.values.password}
onChange={formik.handleChange}
error={(formik.touched.password && formik.errors.password) || error.length > 0}
helperText={formik.touched.password && formik.errors.password}
fullWidth={true}
InputProps={{
endAdornment: (
<IconButton
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
>
{showPassword ? <VisibilityOn /> : <VisibilityOff />}
</IconButton>
),
}}
/>

<Link
to={FORGOT_PASSWORD_PAGE}
textsize="12px"
component={NavLink}
underline="hover"
align="right"
style={{ marginTop: "18px", marginBottom: "18px" }}
>
{t("login.forgotYourPassword")}
</Link>

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={
formik.values.username.length === 0 ||
formik.values.password.length === 0
}
>
{t("login.logIn")}
</PrimaryButton>

<RegisterTextContainer>
<RegisterAltText>
{t("login.dontHaveAccount").padEnd(2, " ")}
</RegisterAltText>

<Link
to="/register"
component={NavLink}
underline="hover"
align="center"
>
{t("login.signUp")}
</Link>
</RegisterTextContainer>
</LoginFormContainer>
</LoginPageContainer>
);
};

LoginPage.propTypes = {
history: PropTypes.shape({
replace: PropTypes.func,
push: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string,
}),
}),
};
export default LoginPage;

+ 25
- 16
src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.js 查看文件

@@ -32,8 +32,12 @@ const FirstPartOfRegistration = (props) => {
password: "",
},
validationSchema: Yup.object().shape({
mail: Yup.string().email().required(t("login.usernameRequired")),
password: Yup.string().required(t("login.passwordRequired")).min(8),
mail: Yup.string()
.email(t("forgotPassword.emailFormat"))
.required(t("login.usernameRequired")),
password: Yup.string()
.required(t("login.passwordRequired"))
.min(8, t("login.passwordLength")),
}),
onSubmit: props.handleSubmit,
validateOnBlur: true,
@@ -54,15 +58,22 @@ const FirstPartOfRegistration = (props) => {
placeholder={t("common.labelEmail")}
margin="normal"
value={formik.values.mail}
onChange={(value) =>
formik.setFieldValue("mail", value.target.value)
onChange={(value) => formik.setFieldValue("mail", value.target.value)}
error={
(formik.touched.mail && Boolean(formik.errors.mail)) ||
emailTakenStatus
}
error={(formik.touched.mail && Boolean(formik.errors.mail)) || emailTakenStatus}
helperText={formik.touched.mail && formik.errors.mail}
autoFocus
fullWidth
/>

{formik.errors.mail && formik.touched.mail ? (
<ErrorMessage>{formik.errors.mail}</ErrorMessage>
) : (
<></>
)}

<TextField
name="password"
placeholder={t("common.labelPassword")}
@@ -72,14 +83,8 @@ const FirstPartOfRegistration = (props) => {
onChange={(value) =>
formik.setFieldValue("password", value.target.value)
}
error={
formik.touched.password &&
Boolean(formik.errors.password)
}
helperText={
formik.touched.password &&
formik.errors.password
}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
fullWidth
InputProps={{
endAdornment: (
@@ -89,14 +94,18 @@ const FirstPartOfRegistration = (props) => {
),
}}
/>

{props.error && (<ErrorMessage>{props.errorMessage}</ErrorMessage>)}
{formik.errors.password && formik.touched.password ? (
<ErrorMessage>{formik.errors.password}</ErrorMessage>
) : (
<></>
)}
{props.error && <ErrorMessage>{props.errorMessage}</ErrorMessage>}

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={

+ 3
- 0
src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.styled.js 查看文件

@@ -19,6 +19,9 @@ export const RegisterDescription = styled(Typography)`
margin-top: 31px;
margin-bottom: 2px;
letter-spacing: 0.02em;
@media (max-height: 800px) {
margin-top: 14px;
}
`;
export const ErrorMessage = styled(Typography)`

+ 30
- 21
src/pages/RegisterPages/Register/Register.js 查看文件

@@ -27,11 +27,11 @@ const Register = () => {
const history = useHistory();
const dispatch = useDispatch();
const [currentStep, setCurrentStep] = useState(1);
const [informations, setInformations] = useState({});
const [mailError, setMailError] = useState("");
const [mailErrorMessage, setMailErrorMessage] = useState("");
const [PIBErrorMessage, setPIBErrorMessage] = useState("");
const [PIBError, setPIBError] = useState("");
const [informations, setInformations] = useState({}); // Values of fields typed in all steps
const [mailError, setMailError] = useState(""); // Wrong mail typed
const [mailErrorMessage, setMailErrorMessage] = useState(""); // Error message caused by typing wrong mail
const [PIBError, setPIBError] = useState(""); // Wrong PIB typed
const [PIBErrorMessage, setPIBErrorMessage] = useState(""); // Error message caused by typing wrong PIB

const handleResponseSuccess = () => {
history.push(REGISTER_SUCCESSFUL_PAGE);
@@ -43,23 +43,26 @@ const Register = () => {
setInformations({});
setCurrentStep(1);
setMailError(mail);
if (error.error.response.data.toString() === 'User with email already exists') {
setMailErrorMessage("Vec postoji korisnik sa zadatim mail-om!");
if (
error.error.response.data.toString() ===
"User with email already exists"
) {
setMailErrorMessage(t("register.emailTaken"));
} else {
setMailErrorMessage("Nevalidan format mail-a!")
setMailErrorMessage(t("register.emailFormat"));
}
} else {
const { mail, password, PIB } = informations;
setInformations({ mail, password });
setCurrentStep(2);
setPIBError(PIB.toString());
setPIBErrorMessage("PIB broj je zauzet!");
setPIBErrorMessage(t("register.PIBTaken"));
}
};

const registerUser = (values) => {
dispatch(
fetchRegisterUser({values, handleResponseSuccess, handleResponseError})
fetchRegisterUser({ values, handleResponseSuccess, handleResponseError })
);
};

@@ -72,17 +75,19 @@ const Register = () => {
setInformations({ ...informations, ...values });
};

const goToFirst = () => {
setInformations({});
setCurrentStep(1);
}
const goToSecond = () => {
const {mail, password} = informations;
setInformations({mail, password});
setCurrentStep(2);
}
const goStepBack = (stepNumber) => {
setCurrentStep(stepNumber);
if (stepNumber === 1) {
setInformations({});
}
if (stepNumber === 2) {
const { mail, password } = informations;
setInformations({ mail, password });
}
};

return (
<RegisterPageContainer currentStep={currentStep}>
<RegisterPageContainer currentstep={currentStep}>
<Logo />

<RegisterTitle component="h1" variant="h5">
@@ -94,7 +99,11 @@ const Register = () => {
</RegisterDescription>

<ProgressContainer>
<StepProgress functions={[goToFirst, goToSecond]} current={currentStep} numberOfSteps={3} />
<StepProgress
functions={[() => goStepBack(1), () => goStepBack(2)]}
current={currentStep}
numberOfSteps={3}
/>
</ProgressContainer>

{currentStep === 1 && (

+ 2
- 2
src/pages/RegisterPages/Register/Register.styled.js 查看文件

@@ -18,8 +18,8 @@ export const RegisterPageContainer = styled(Container)`
margin-top: 30px;
flex: none;
height: 95vh;
${props => props.currentStep === 3 && `height: 105vh`};
${props => props.currentStep === 2 && `height: 100vh`};
${props => props.currentstep === 3 && `height: 105vh`};
${props => props.currentstep === 2 && `height: 100vh`};
}
`;
export const RegisterTitle = styled(Typography)`

+ 14
- 7
src/pages/RegisterPages/Register/SecondPart/SecondPartOfRegistration.js 查看文件

@@ -20,7 +20,7 @@ const SecondPartOfRegistration = (props) => {
if (props.error.length > 0) {
setPIBTakenStatus(true);
}
}, [props.error])
}, [props.error]);

const formik = useFormik({
initialValues: {
@@ -29,7 +29,10 @@ const SecondPartOfRegistration = (props) => {
},
validationSchema: Yup.object().shape({
nameOfFirm: Yup.string().required(t("login.usernameRequired")),
PIB: Yup.number().required(t("login.passwordRequired")).min(100000000).max(999999999),
PIB: Yup.number()
.required(t("login.passwordRequired"))
.min(100000000, "PIB mora imati 9 karaktera!")
.max(999999999, "PIB mora imati 9 karaktera!"),
}),
onSubmit: props.handleSubmit,
validateOnBlur: true,
@@ -60,18 +63,22 @@ const SecondPartOfRegistration = (props) => {
type="number"
value={formik.values.PIB}
onChange={formik.handleChange}
error={(formik.touched.PIB && Boolean(formik.errors.PIB)) || PIBTakenStatus}
error={
(formik.touched.PIB && Boolean(formik.errors.PIB)) || PIBTakenStatus
}
helperText={formik.touched.PIB && formik.errors.PIB}
fullWidth={true}
fullWidth
/>

{props.error && (<ErrorMessage>{props.errorMessage}</ErrorMessage>)}
{formik.errors.PIB && formik.touched.PIB && (
<ErrorMessage>{formik.errors.PIB}</ErrorMessage>
)}
{props.error && <ErrorMessage>{props.errorMessage}</ErrorMessage>}

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={

+ 3
- 0
src/pages/RegisterPages/Register/SecondPart/SecondPartOfRegistration.styled.js 查看文件

@@ -20,6 +20,9 @@ export const RegisterDescription = styled(Typography)`
margin-top: 31px;
margin-bottom: 2px;
letter-spacing: 0.02em;
@media (max-height: 800px) {
margin-top: 14px;
}
`;
export const ErrorMessage = styled(Typography)`
color: red;

+ 8
- 16
src/pages/RegisterPages/Register/ThirdPart/ThirdPartOfRegistration.js 查看文件

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React from "react";
import PropTypes from "prop-types";
import {
FormContainer,
@@ -13,16 +13,6 @@ import selectedTheme from "../../../../themes";

const ThirdPartOfRegistration = (props) => {
const { t } = useTranslation();
const [phoneNumberTakenStatus, setPhoneNumberTakenStatus] = useState(false);

const handleForm = (values) => {
// validate email
if (true) { // eslint-disable-line
props.handleSubmit(values)
} else {
setPhoneNumberTakenStatus(true);
}
}

const formik = useFormik({
initialValues: {
@@ -37,7 +27,7 @@ const ThirdPartOfRegistration = (props) => {
/^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$/gm
),
}),
onSubmit: handleForm,
onSubmit: props.handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});
@@ -55,7 +45,9 @@ const ThirdPartOfRegistration = (props) => {
type="number"
value={formik.values.phoneNumber}
onChange={formik.handleChange}
error={(formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)) || phoneNumberTakenStatus}
error={
(formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber))
}
helperText={formik.touched.phoneNumber && formik.errors.phoneNumber}
autoFocus
fullWidth
@@ -70,7 +62,7 @@ const ThirdPartOfRegistration = (props) => {
onChange={formik.handleChange}
error={formik.touched.location && Boolean(formik.errors.location)}
helperText={formik.touched.location && formik.errors.location}
fullWidth={true}
fullWidth
/>

<TextField
@@ -82,14 +74,14 @@ const ThirdPartOfRegistration = (props) => {
onChange={formik.handleChange}
error={formik.touched.website && Boolean(formik.errors.website)}
helperText={formik.touched.website && formik.errors.website}
fullWidth={true}
fullWidth
/>

<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth={true}
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={

+ 1
- 1
src/pages/RegisterPages/Register/ThirdPart/ThirdPartOfRegistration.styled.js 查看文件

@@ -20,6 +20,6 @@ export const RegisterDescription = styled(Typography)`
margin-bottom: 2px;
letter-spacing: 0.02em;
@media (max-height: 800px) {
margin-top: 21px;
margin-top: 14px;
}
`;

+ 34
- 14
src/pages/ResetPasswordPage/ResetPasswordPage.js 查看文件

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
@@ -13,16 +13,16 @@ import {
} from "./ResetPasswordPage.styled";
import { TextField } from "../../components/TextFields/TextField/TextField";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import { useHistory } from "react-router-dom";
import { LOGIN_PAGE } from "../../constants/pages";
import { useHistory, useRouteMatch } from "react-router-dom";
import { LOGIN_PAGE } from "../../constants/pages";
import selectedTheme from "../../themes";
import { useDispatch } from "react-redux";
import { resetPassword } from "../../store/actions/user/userActions";
import { resetPassword } from "../../store/actions/user/userActions";
import { Trans } from "react-i18next";
import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg";
import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg";
import { IconButton } from "../../components/Buttons/IconButton/IconButton";
import jwt from "jsonwebtoken";

const ResetPasswordPage = () => {
const history = useHistory();
@@ -30,6 +30,24 @@ const ResetPasswordPage = () => {
const dispatch = useDispatch();
const [showPassword, setShowPassword] = useState(false);
const [showPasswordConfirm, setShowPasswordConfirm] = useState(false);
const [error, setError] = useState(false);
const [token, setToken] = useState("");
const routeMatch = useRouteMatch();

useEffect(() => {
const tokenFromParams = routeMatch.params.token
setToken(tokenFromParams);
const data = jwt.decode(tokenFromParams);
if (!data || new Date() > new Date(data?.exp * 1000)) {
setError(true);
}
}, []);

useEffect(() => {
if (error) {
history.replace("/");
}
}, [error]);

const handleClickShowPassword = () => {
setShowPassword((prevState) => !prevState);
@@ -37,21 +55,18 @@ const ResetPasswordPage = () => {
const handleClickShowPasswordConfirm = () => {
setShowPasswordConfirm((prevState) => !prevState);
};

const handleResponseSuccess = () => {
history.push(LOGIN_PAGE);
}
const handleResponseError = () => {
console.log("error");
}
};

const handleSubmit = (values) => {
// validate email
dispatch(
resetPassword({
token: token,
password: values.password,
password2: values.passwordConfirm,
handleResponseSuccess,
handleResponseError
})
);
};
@@ -110,8 +125,13 @@ const ResetPasswordPage = () => {
type={showPasswordConfirm ? "text" : "password"}
value={formik.values.passwordConfirm}
onChange={formik.handleChange}
error={formik.touched.passwordConfirm && formik.errors.passwordConfirm?.length > 0}
helperText={formik.touched.passwordConfirm && formik.errors.passwordConfirm}
error={
formik.touched.passwordConfirm &&
formik.errors.passwordConfirm?.length > 0
}
helperText={
formik.touched.passwordConfirm && formik.errors.passwordConfirm
}
fullWidth
InputProps={{
endAdornment: (
@@ -126,7 +146,7 @@ const ResetPasswordPage = () => {
type="submit"
variant="contained"
height="48px"
fullWidth={true}
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={

+ 7
- 4
src/request/apiEndpoints.js 查看文件

@@ -2,7 +2,7 @@ export default {
accounts: {
get: 'accounts/{accountUid}',
forgotPassword: 'forgot-password',
resetPassword: 'resetPassword',
resetPassword: 'reset-password',
getCurrentUserPermissions:
'accounts/{currentAccountUid}/users/{currentUserUid}/permissions',
getAddresses: 'accounts/{accountUid}/addresses',
@@ -20,11 +20,13 @@ export default {
authentications: {
getUsernames: 'authenticate/usernames',
login: 'auth/token',
logout: 'auth/logout',
getUserSecurityQuestion: 'users/username/securityquestion',
confirmSecurityQuestion: 'authenticate/confirm',
confirmForgotPassword: 'users/passwords/reset_token',
resetPassword: 'users/passwords',
refreshToken: '/authenticate/refresh',
resetPassword: 'reset-password',
forgotPassword: 'forgot-password',
refreshToken: '/auth/refresh',
generateToken: '/authenticate/generate',
authenticate:
'/authenticate?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}&registrationFlowType={registrationFlowType}',
@@ -156,6 +158,7 @@ export default {
setFingerprint: '/affiliate/fingerprint',
},
offers: {
getOffers: 'offers'
getOffers: 'offers',
addOffer: 'offers',
}
};

+ 9
- 4
src/request/forgotPasswordRequest.js 查看文件

@@ -2,9 +2,14 @@ import { postRequest, getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const forgotPasswordRequest = (payload) => {
const encodedString = encodeURIComponent(payload);
return getRequest(`${apiEndpoints.accounts.forgotPassword}` + '/' + `${encodedString}`);
}
const encodedMail = encodeURIComponent(payload);
return getRequest(
`${apiEndpoints.authentications.forgotPassword}/${encodedMail}`
);
};

export const resetPasswordRequest = (payload) =>
postRequest(apiEndpoints.accounts.resetPassword, payload);
postRequest(
`${apiEndpoints.authentications.resetPassword}/${payload.token}`,
{ password: payload.password, password2: payload.password2 }
);

+ 63
- 11
src/request/index.js 查看文件

@@ -1,14 +1,14 @@
import axios from 'axios';
import queryString from 'qs';
import axios from "axios";
import queryString from "qs";

const request = axios.create({
baseURL: "http://192.168.88.175:3005/",
baseURL: "http://192.168.88.150:3001/",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
// withCredentials: true,
paramsSerializer: (params) =>
queryString.stringify(params, { arrayFormat: 'comma' }),
queryString.stringify(params, { arrayFormat: "comma" }),
});

export const getRequest = (url, params = null, options = null) =>
@@ -27,7 +27,7 @@ export const deleteRequest = (url, params = null, options = null) =>
request.delete(url, { params, ...options });

export const downloadRequest = (url, params = null, options = null) =>
request.get(url, { params, ...options, responseType: 'blob' });
request.get(url, { params, ...options, responseType: "blob" });

export const replaceInUrl = (url, pathVariables = {}) => {
const keys = Object.keys(pathVariables);
@@ -37,12 +37,11 @@ export const replaceInUrl = (url, pathVariables = {}) => {

return keys.reduce(
(acc, key) => acc.replace(`{${key}}`, pathVariables[`${key}`]),
url,
url
);
};

export const addHeaderToken = (token) => {
console.log(`Bearer ${token.toString()}`)
request.defaults.headers.Authorization = `Bearer ${token}`;
};

@@ -54,11 +53,64 @@ export const removeHeaderToken = () => {
delete request.defaults.headers.Authorization;
};

export const attachPostRequestListener = (postRequestListener) => {
request.interceptors.response.use(
// If you pass function to interceptor of axios, it only adds that function
// to existing array of interceptor functions. That causes that same function
// of interceptors getting called multiple times instead of just one time, as it
// is supposed to do. Thats why there is 'global' axios interceptor array, which indicates
// axios to eject previous interceptor. This approach requires that every middleware has its
// unique name from which it is being recognized. Every object in those arrays contains
// interceptor name and ID of interceptor function.
let axiosInterceptorRequests = [];
let axiosInterceptorResponses = [];

export const attachPostRequestListener = (
postRequestListener,
interceptorName
) => {
let previousAxiosInterceptor = axiosInterceptorResponses.find(
(item) => item.name === interceptorName
);
let previousAxiosInterceptorResponses = axiosInterceptorResponses;
if (previousAxiosInterceptor !== undefined) {
request.interceptors.response.eject(previousAxiosInterceptor.interceptorID);
previousAxiosInterceptorResponses = axiosInterceptorResponses.filter(
(item) => item.interceptorID !== previousAxiosInterceptor.interceptorID
);
}
let axiosInterceptorID = request.interceptors.response.use(
(response) => response,
(response) => postRequestListener(response),
(response) => postRequestListener(response)
);
previousAxiosInterceptorResponses.push({
name: interceptorName,
interceptorID: axiosInterceptorID,
});
axiosInterceptorResponses = [...previousAxiosInterceptorResponses];
};

export const attachBeforeRequestListener = (
beforeRequestListener,
interceptorName
) => {
let previousAxiosInterceptor = axiosInterceptorRequests.find(
(item) => item.name === interceptorName
);
let previousAxiosInterceptorRequests = axiosInterceptorRequests;
if (previousAxiosInterceptor !== undefined) {
request.interceptors.request.eject(previousAxiosInterceptor.interceptorID);
previousAxiosInterceptorRequests = axiosInterceptorRequests.filter(
(item) => item.interceptorID !== previousAxiosInterceptor.interceptorID
);
}
let axiosInterceptorID = request.interceptors.request.use(
(response) => beforeRequestListener(response),
(response) => response
);
previousAxiosInterceptorRequests.push({
name: interceptorName,
interceptorID: axiosInterceptorID,
});
axiosInterceptorRequests = [...previousAxiosInterceptorRequests];
};

export const apiDefaultUrl = request.defaults.baseURL;

+ 2
- 14
src/request/loginRequest.js 查看文件

@@ -1,22 +1,10 @@
import { getRequest, postRequest } from "./index";
import { postRequest } from "./index";
import apiEndpoints from "./apiEndpoints";

export const getUsernames = (emailorusername) =>
getRequest(apiEndpoints.authentications.getUsernames, {
emailorusername,
});

export const attemptLogin = (payload) =>
postRequest(apiEndpoints.authentications.login, payload);

export const updateSecurityAnswer = (payload) =>
postRequest(apiEndpoints.authentications.confirmSecurityQuestion, payload);

export const refreshTokenRequest = (payload) =>
postRequest(apiEndpoints.authentications.refreshToken, payload);

export const logoutUserRequest = (payload) =>
postRequest(apiEndpoints.users.logout, payload);
postRequest(apiEndpoints.authentications.logout, payload);

export const generateTokenRequest = (payload) =>
postRequest(apiEndpoints.authentications.generateToken, payload);

+ 4
- 1
src/request/offersRequest.js 查看文件

@@ -1,6 +1,9 @@
import { getRequest } from "."
import { getRequest, postRequest } from "."
import apiEndpoints from "./apiEndpoints"

export const attemptFetchOffers = () => {
return getRequest(apiEndpoints.offers.getOffers)
}
export const attemptAddOffer = (payload) => {
return postRequest(apiEndpoints.offers.addOffer, payload)
}

+ 2
- 1
src/store/actions/login/loginActions.js 查看文件

@@ -50,8 +50,9 @@ export const logoutUser = () => ({
type: LOGOUT_USER,
});

export const refreshUserToken = () => ({
export const refreshUserToken = (payload) => ({
type: REFRESH_TOKEN,
payload
});

export const generateToken = (payload) => ({

+ 2
- 1
src/store/actions/offers/offersActionConstants.js 查看文件

@@ -8,4 +8,5 @@ export const OFFERS_ERROR = createErrorType(OFFERS_SCOPE);
export const OFFERS_CLEAR = createClearType(OFFERS_SCOPE);

export const OFFERS_SET = "OFFERS_SET";
export const OFFERS_ADD = "OFFERS_ADD";
export const OFFERS_ADD = "OFFERS_ADD";
export const OFFER_ADD = "OFFER_ADD";

+ 5
- 1
src/store/actions/offers/offersActions.js 查看文件

@@ -1,4 +1,4 @@
import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_SET, OFFERS_SUCCESS } from "./offersActionConstants";
import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_SET, OFFERS_SUCCESS, OFFER_ADD } from "./offersActionConstants";

export const fetchOffers = (payload) => ({
type: OFFERS_FETCH,
@@ -22,4 +22,8 @@ export const setOffers = (payload) => ({
export const addOffers = (payload) => ({
type: OFFERS_ADD,
payload
})
export const addOffer = (payload) => ({
type: OFFER_ADD,
payload
})

+ 3
- 1
src/store/actions/user/userActionConstants.js 查看文件

@@ -2,4 +2,6 @@
export const SET_USER = 'SET_USER';
export const SET_USER_ERROR = 'SET_USER_ERROR';
export const FORGOT_PASSWORD = "FORGOT_PASSWORD";
export const RESET_PASSWORD = "RESET_PASSWORD";
export const RESET_PASSWORD = "RESET_PASSWORD";
export const SET_USER_ACCESS_TOKEN = "SET_USER_ACCESS_TOKEN";
export const SET_USER_REFRESH_TOKEN = "SET_USER_REFRESH_TOKEN";

+ 10
- 0
src/store/actions/user/userActions.js 查看文件

@@ -2,7 +2,9 @@ import {
FORGOT_PASSWORD,
RESET_PASSWORD,
SET_USER,
SET_USER_ACCESS_TOKEN,
SET_USER_ERROR,
SET_USER_REFRESH_TOKEN,
} from './userActionConstants';

export const setUser = (payload) => ({
@@ -21,4 +23,12 @@ export const forgotPassword = (payload) => ({
export const resetPassword = (payload) => ({
type: RESET_PASSWORD,
payload
})
export const setUserAccessToken = (payload) => ({
type: SET_USER_ACCESS_TOKEN,
payload
})
export const setUserRefreshToken = (payload) => ({
type: SET_USER_REFRESH_TOKEN,
payload
})

+ 2
- 0
src/store/index.js 查看文件

@@ -6,6 +6,7 @@ import loadingMiddleware from './middleware/loadingMiddleware';
import requestStatusMiddleware from './middleware/requestStatusMiddleware';
import internalServerErrorMiddleware from './middleware/internalServerErrorMiddleware';
import persistStore from 'redux-persist/es/persistStore';
import accessTokensMiddleware from './middleware/accessTokensMiddleware';


const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
@@ -18,6 +19,7 @@ export const store = createStore(
loadingMiddleware,
requestStatusMiddleware,
internalServerErrorMiddleware,
accessTokensMiddleware
),
),
);

+ 50
- 0
src/store/middleware/accessTokensMiddleware.js 查看文件

@@ -0,0 +1,50 @@
import axios from "axios";
import jwt from "jsonwebtoken";
import { JWT_REFRESH_TOKEN, JWT_TOKEN } from "../../constants/localStorage";
import {
// addHeaderToken,
attachBeforeRequestListener,
} from "../../request/index";
import {
authScopeStringGetHelper,
// authScopeSetHelper,
} from "../../util/helpers/authScopeHelpers";
import { logoutUser, refreshUserToken } from "../actions/login/loginActions";
// import { setUserAccessToken } from "../actions/user/userActions";

//Change URL with .env
const baseURL = "http://192.168.88.150:3001/";

//Interceptor unique name
export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR";

export default ({ dispatch }) =>
(next) =>
(action) => {
attachBeforeRequestListener(async (response) => {
const jwtToken = authScopeStringGetHelper(JWT_TOKEN);
const refresh = authScopeStringGetHelper(JWT_REFRESH_TOKEN);
if (!jwtToken || !refresh) return Promise.resolve(response);
if (!response.headers?.Authorization) {
response.headers.Authorization = `Bearer ${jwtToken}`;
}
const jwtTokenDecoded = jwt.decode(jwtToken);
const refreshTokenDecoded = jwt.decode(refresh);

// If refresh token is expired, log out user
if (new Date() > new Date(refreshTokenDecoded?.exp * 1000)) {
dispatch(logoutUser());
}
// If access token is expired, refresh access token
if (new Date() > new Date(jwtTokenDecoded.exp * 1000)) {
const axiosResponse = await axios.post(`${baseURL}auth/refresh`, {
token: refresh,
});
const newToken = axiosResponse.data.token;
dispatch(refreshUserToken(newToken));
}
return Promise.resolve(response);
}, accessTokensMiddlewareInterceptorName);

next(action);
};

+ 4
- 1
src/store/middleware/internalServerErrorMiddleware.js 查看文件

@@ -2,6 +2,9 @@ import { ERROR_PAGE } from '../../constants/pages';
import { attachPostRequestListener } from '../../request';
import history from '../utils/history';

//Interceptor unique name
export const serverErrorMiddlewareInterceptorName = "INTERNAL_SERVER_ERROR_MIDDLEWARE_INTERCEPTOR";

export default () => (next) => (action) => {
attachPostRequestListener((error) => {
if (!error.response) {
@@ -11,7 +14,7 @@ export default () => (next) => (action) => {
return history.push(ERROR_PAGE);
}
return Promise.reject(error);
});
}, serverErrorMiddlewareInterceptorName);

next(action);
};

+ 34
- 30
src/store/middleware/loadingMiddleware.js 查看文件

@@ -5,37 +5,41 @@ import {
SUCCESS,
UPDATE,
SUBMIT,
} from '../actions/actionHelpers';
} from "../actions/actionHelpers";

const promiseTypes = [FETCH, UPDATE, DELETE, SUBMIT];
export default ({ dispatch }) => (next) => (action) => {
const promiseType = promiseTypes.find((promiseType) =>
action.type.includes(promiseType),
);
if (promiseType) {
dispatch({
type: 'UPDATE_LOADER',
payload: {
actionType: action.type.replace(promiseType, '[LOADING]'),
isLoading: true,
},
});
return next(action);
}

if (action.type.includes(SUCCESS) || action.type.includes(ERROR)) {
const actionType = action.type.includes(SUCCESS)
? action.type.replace(SUCCESS, '[LOADING]')
: action.type.replace(ERROR, '[LOADING]');

dispatch({
type: 'UPDATE_LOADER',
payload: {
actionType,
isLoading: false,
},
});
return next(action);
}
next(action);
};
export default ({ dispatch }) =>
(next) =>
(action) => {
const promiseType = promiseTypes.find((promiseType) =>
action.type.includes(promiseType)
);
if (promiseType) {
dispatch({
type: "UPDATE_LOADER",
payload: {
actionType: action.type.replace(promiseType, "[LOADING]"),
isLoading: true,
},
});
return next(action);
}

if (action.type.includes(SUCCESS) || action.type.includes(ERROR)) {
const actionType = action.type.includes(SUCCESS)
? action.type.replace(SUCCESS, "[LOADING]")
: action.type.replace(ERROR, "[LOADING]");

dispatch({
type: "UPDATE_LOADER",
payload: {
actionType,
isLoading: false,
},
});
return next(action);
}
next(action);
};

+ 4
- 1
src/store/middleware/requestStatusMiddleware.js 查看文件

@@ -2,6 +2,9 @@ import { attachPostRequestListener } from '../../request';
import apiEndpoints from '../../request/apiEndpoints';
import { logoutUser } from '../actions/login/loginActions';

// Interceptor unique name
export const requestStatusMiddlewareInterceptorName = "REQUEST_STATUS_MIDDLEWARE_INTERCEPTOR";

export default ({ dispatch }) => (next) => (action) => {
attachPostRequestListener((error) => {
if (!error.response) {
@@ -16,7 +19,7 @@ export default ({ dispatch }) => (next) => (action) => {
return dispatch(logoutUser());
}
return Promise.reject(error);
});
}, requestStatusMiddlewareInterceptorName);

next(action);
};

+ 1
- 0
src/store/reducers/login/loginReducer.js 查看文件

@@ -14,6 +14,7 @@ const initialState = {
token: {
RefreshToken: '',
JwtToken: '',
userId: ''
},
errorMessage: '',
};

+ 9
- 0
src/store/reducers/offers/offersReducer.js 查看文件

@@ -3,12 +3,14 @@ import {
OFFERS_CLEAR,
OFFERS_ERROR,
OFFERS_SET,
OFFER_ADD,
} from "../../actions/offers/offersActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
offers: [],
error: "",
newOffer: "",
};

export default createReducer(
@@ -17,6 +19,7 @@ export default createReducer(
[OFFERS_CLEAR]: clearOffers,
[OFFERS_SET]: setOffers,
[OFFERS_ADD]: addOffers,
[OFFER_ADD]: addOffer,
},
initialState
);
@@ -40,3 +43,9 @@ function addOffers(state, action) {
offers: [...state.offers, ...action.payload]
}
}
function addOffer(state, action) {
return {
...state,
offer: action.payload
}
}

+ 20
- 2
src/store/reducers/user/userReducer.js 查看文件

@@ -1,17 +1,23 @@
import createReducer from '../../utils/createReducer';
import {
SET_USER,
SET_USER_ACCESS_TOKEN,
SET_USER_ERROR,
SET_USER_REFRESH_TOKEN,
} from '../../actions/user/userActionConstants';

const initialState = {
user: {},
accessToken: {},
refreshToken: {},
userId: {}
};

export default createReducer(
{
[SET_USER]: setUser,
[SET_USER_ERROR]: setUserError,
[SET_USER_ACCESS_TOKEN]: setUserAccessToken,
[SET_USER_REFRESH_TOKEN]: setUserRefreshToken
},
initialState,
);
@@ -19,7 +25,7 @@ export default createReducer(
function setUser(state, action) {
return {
...state,
user: action.payload,
userId: action.payload,
};
}

@@ -29,3 +35,15 @@ function setUserError(state, action) {
errorMessage: action.payload,
};
}
function setUserAccessToken(state, action) {
return {
...state,
accessToken: action.payload
}
}
function setUserRefreshToken(state, action) {
return {
...state,
refreshToken: action.payload
}
}

+ 3
- 0
src/store/saga/filtersSaga.js 查看文件

@@ -0,0 +1,3 @@
// function* setFilters({ payload }) {

// }

+ 5
- 1
src/store/saga/forgotPasswordSaga.js 查看文件

@@ -20,7 +20,11 @@ function* forgotPassword({payload}) {
}
function* resetPassword({payload}) {
try {
const data = yield call(resetPasswordRequest)
const data = yield call(resetPasswordRequest, {
password: payload.password,
password2: payload.password2,
token: payload.token
})
if (data) {
if (payload.handleResponseSuccess) {
yield call(payload.handleResponseSuccess);

+ 40
- 93
src/store/saga/loginSaga.js 查看文件

@@ -6,27 +6,18 @@ import {
LOGIN_USER_FETCH,
LOGOUT_USER,
REFRESH_TOKEN,
GENERATE_TOKEN,
} from "../actions/login/loginActionConstants";
import {
attemptLogin,
logoutUserRequest,
refreshTokenRequest,
generateTokenRequest,
} from "../../request/loginRequest";
import {
fetchUserError,
fetchUserSuccess,
resetLoginState,
updateUserToken,
} from "../actions/login/loginActions";
import { LOGIN_PAGE } from "../../constants/pages";
import { setUser } from "../actions/user/userActions";
import { addHeaderToken, removeHeaderToken } from "../../request";
import {
IMPERSONATE_USER_UID,
REGISTRATION_USER_UID,
} from "../../constants/sessionStorage";
import {
JWT_REFRESH_TOKEN,
JWT_TOKEN,
@@ -38,37 +29,46 @@ import {
authScopeRemoveHelper,
authScopeSetHelper,
} from "../../util/helpers/authScopeHelpers";
// import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper";
import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper";
import { setUserAccessToken } from "../actions/user/userActions";
import i18next from "i18next";

function* fetchUser({ payload }) {
try {
const { data } = yield call(attemptLogin, payload);
if (data.tokens) {
const token = data.tokens[data.tokens.length - 1].token.toString();

// cemu sluzi?
const user = jwt.decode(token);

if (data.token) {
const token = data.token;
const refresh = data.refresh;
const tokenDecoded = jwt.decode(token);
const refreshDecoded = jwt.decode(refresh);
const accessToken = {
token: token,
exp: tokenDecoded.exp,
};
const refreshToken = {
token: refresh,
exp: refreshDecoded.exp,
};
const userId = tokenDecoded._id;
yield call(authScopeSetHelper, JWT_TOKEN, token);
// yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken);
// yield call(authScopeSetHelper, REFRESH_TOKEN_CONST, data.RefreshToken);
yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, refresh);
yield call(addHeaderToken, token);
yield put(setUser(user));
}
yield put(fetchUserSuccess(data));
if (payload.handleApiResponseSuccess) {
yield call(payload.handleApiResponseSuccess);
yield put(fetchUserSuccess({JwtToken: accessToken, RefreshToken: refreshToken, userId}));
if (payload.handleApiResponseSuccess) {
yield call(payload.handleApiResponseSuccess);
}
}
} catch (e) {
if (e.response && e.response.data) {
if (payload.handleApiResponseError) {
yield call(payload.handleApiResponseError);
yield call(payload.handleApiResponseError, e.response.status);
}
let errorMessage = "Greska!";
if (e.response.status === 400) {
errorMessage = "Pogresan mail ili lozinka!";
let errorMessage = yield call(rejectErrorCodeHelper, e);
if (e.response.status === 401) {
errorMessage = i18next.t("login.wrongCredentials", {
lng: "rs"
});
}
// const errorMessage = yield call(rejectErrorCodeHelper, e);
yield put(fetchUserError(errorMessage));
}
}
@@ -87,11 +87,10 @@ function* authenticateUser() {
})
);
} catch (error) {
// const errorMessage = yield call(rejectErrorCodeHelper, error);
// yield put(fetchUserError(errorMessage));
const errorMessage = yield call(rejectErrorCodeHelper, error);
yield put(fetchUserError(errorMessage));
yield call(authScopeRemoveHelper, JWT_TOKEN);
// yield call(authScopeRemoveHelper, JWT_REFRESH_TOKEN);
// yield call(authScopeRemoveHelper, REFRESH_TOKEN_CONST);
yield call(authScopeRemoveHelper, JWT_REFRESH_TOKEN);
}
}

@@ -99,9 +98,8 @@ function* logoutUser() {
try {
const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN);
const user = jwt.decode(JwtToken);
console.log({token: JwtToken, userId: user._id})
if (user) {
yield call(logoutUserRequest, {token: JwtToken, userId: user._id});
yield call(logoutUserRequest, { token: JwtToken, userId: user._id });
}
} catch (error) {
console.log(error); // eslint-disable-line
@@ -112,65 +110,15 @@ function* logoutUser() {
yield call(history.replace, LOGIN_PAGE);
}
}

export function* refreshToken() {
function* refreshUserToken({payload}) {
try {
const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN);
const JwtRefreshToken = yield call(
authScopeStringGetHelper,
JWT_REFRESH_TOKEN
);

if (JwtToken && JwtRefreshToken) {
const { data } = yield call(refreshTokenRequest, {
JwtRefreshToken,
JwtToken,
});

yield call(authScopeSetHelper, JWT_TOKEN, data.JwtToken);
yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken);
const user = jwt.decode(data.JwtToken);
addHeaderToken(data.JwtToken);
yield put(setUser(user));
yield put(updateUserToken(data.JwtToken));
}
} catch (error) {
yield call(logoutUser);
console.log(error); // eslint-disable-line
const newTokenDecoded = jwt.decode(payload);
yield put(setUserAccessToken({token: payload, exp: newTokenDecoded.exp}));
yield call(addHeaderToken, payload);
yield call(authScopeSetHelper, JWT_TOKEN, payload);
}
}

export function* generateToken({ payload }) {
try {
const { data } = yield call(generateTokenRequest, payload.data);
const { JwtToken, JwtRefreshToken } = data;

if (JwtToken && JwtRefreshToken) {
yield call(authScopeSetHelper, JWT_TOKEN, data.JwtToken);
yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken);

if (payload.impersonate) {
sessionStorage.setItem(IMPERSONATE_USER_UID, payload.accountUid);
}

if (payload.registration) {
sessionStorage.setItem(REGISTRATION_USER_UID, payload.accountUid);
}

const user = jwt.decode(data.JwtToken);
addHeaderToken(data.JwtToken);
if (user) {
yield put(setUser(user));
}
yield put(updateUserToken(data.JwtToken));

if (payload.onSuccess) {
yield call(payload.onSuccess);
}
}
} catch (error) {
yield call(logoutUser);
console.log(error); // eslint-disable-line
catch(e){
console.log(e);
}
}

@@ -179,7 +127,6 @@ export default function* loginSaga() {
takeLatest(LOGIN_USER_FETCH, fetchUser),
takeLatest(AUTHENTICATE_USER, authenticateUser),
takeLatest(LOGOUT_USER, logoutUser),
takeLatest(REFRESH_TOKEN, refreshToken),
takeLatest(GENERATE_TOKEN, generateToken),
takeLatest(REFRESH_TOKEN, refreshUserToken)
]);
}

+ 15
- 3
src/store/saga/offersSaga.js 查看文件

@@ -1,6 +1,6 @@
import { all, takeLatest, call, put } from "@redux-saga/core/effects";
import { attemptFetchOffers } from "../../request/offersRequest";
import { OFFERS_FETCH } from "../actions/offers/offersActionConstants";
import { attemptAddOffer, attemptFetchOffers } from "../../request/offersRequest";
import { OFFERS_FETCH, OFFER_ADD } from "../actions/offers/offersActionConstants";
import { setOffers } from "../actions/offers/offersActions";

function* fetchOffers() {
@@ -13,6 +13,18 @@ function* fetchOffers() {
}
}

function* createOffer(payload) {
try {
const data = yield call(attemptAddOffer, payload);
console.log(data);
}
catch (e) {
console.log(e);
}
}

export default function* offersSaga() {
yield all([takeLatest(OFFERS_FETCH, fetchOffers)]);
yield all([takeLatest(OFFERS_FETCH, fetchOffers),
takeLatest(OFFER_ADD, createOffer)]);
}


Loading…
取消
儲存