Parcourir la source

Merged with master

feature/code-cleanup-joca
Djordje Mitrovic il y a 3 ans
Parent
révision
5d5b8dc602
27 fichiers modifiés avec 560 ajouts et 342 suppressions
  1. 3
    0
      src/assets/images/profile-picture.svg
  2. 67
    74
      src/components/ImagePicker/ImagePicker.js
  3. 61
    17
      src/components/ImagePicker/ImagePicker.styled.js
  4. 144
    134
      src/i18n/resources/rs.js
  5. 3
    0
      src/initialValues/forgotPasswordInitialValues.js
  6. 4
    0
      src/initialValues/loginInitialValues.js
  7. 4
    0
      src/initialValues/registerInitialValues/firstPartInitialValues.js
  8. 4
    0
      src/initialValues/registerInitialValues/secondPartInitialValues.js
  9. 5
    0
      src/initialValues/registerInitialValues/thirdPartInitialValues.js
  10. 4
    0
      src/initialValues/resetPasswordInitialValues.js
  11. 17
    1
      src/pages/ForgotPasswordPage/ForgotPassword.styled.js
  12. 5
    1
      src/pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent.js
  13. 21
    12
      src/pages/ForgotPasswordPage/ForgotPasswordPage.js
  14. 4
    10
      src/pages/LoginPage/LoginPage.js
  15. 6
    8
      src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.js
  16. 86
    55
      src/pages/RegisterPages/Register/Register.js
  17. 58
    12
      src/pages/RegisterPages/Register/Register.styled.js
  18. 0
    1
      src/store/middleware/accessTokensMiddleware.js
  19. 3
    2
      src/store/saga/loginSaga.js
  20. 2
    4
      src/store/saga/registerSaga.js
  21. 6
    11
      src/util/helpers/rejectErrorCodeHelper.js
  22. 8
    0
      src/validations/forgotPasswordValidation.js
  23. 9
    0
      src/validations/loginValidation.js
  24. 10
    0
      src/validations/registerValidations/firstPartValidation.js
  25. 10
    0
      src/validations/registerValidations/secondPartValidation.js
  26. 10
    0
      src/validations/registerValidations/thirdPartValidation.js
  27. 6
    0
      src/validations/resetPasswordValidation.js

+ 3
- 0
src/assets/images/profile-picture.svg Voir le fichier

@@ -0,0 +1,3 @@
<svg width="72" height="82" viewBox="0 0 72 82" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M55.9821 20.9284C55.9821 32.1726 46.8691 41.2856 35.625 41.2856C24.3809 41.2856 15.2679 32.1726 15.2679 20.9284C15.2679 9.68588 24.3809 0.571289 35.625 0.571289C46.8691 0.571289 55.9821 9.68588 55.9821 20.9284ZM33.2553 57.6985L27.9911 48.9195H43.2589L37.9947 57.6985L43.2907 77.4036L49.5728 51.7663C61.8507 53.6748 71.25 64.2987 71.25 77.1173C71.25 79.8051 69.0552 81.9999 66.3675 81.9999H4.88571C2.1868 81.9999 0 79.8051 0 77.1173C0 64.2987 9.39769 53.6748 21.6772 51.7663L27.9593 77.4036L33.2553 57.6985Z" fill="#D4D4D4"/>
</svg>

+ 67
- 74
src/components/ImagePicker/ImagePicker.js Voir le fichier

@@ -1,100 +1,92 @@
import React, { useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import {
AddFile,
AddIcon,
ImageOverlay,
ImagePickerContainer,
ImageUploaded,
Tools,
} from "./ImagePicker.styled";
import { Tooltip } from "@mui/material";
import { Trash } from "../Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled";
import { Icon } from "../Icon/Icon";
import { IconButton } from "../Buttons/IconButton/IconButton";
import { ReactComponent as EditIcon } from "../../assets/images/svg/edit.svg";
import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg";

// import { Input } from "@mui/material";

const ImagePicker = (props) => {
const fileInputRef = useRef(null);
const [isOpened, setIsOpened] = useState(false);
const imageRef = useRef(null);
const [image, setImage] = useState("");
const [isEditing, setIsEditing] = useState(false);

let listener;
useEffect(() => {
listener = (event) => {
if (imageRef.current) {
if (imageRef.current.contains(event.target)) {
setIsEditing(true);
} else {
setIsEditing(false);
}
}
};
window.addEventListener("click", listener);
return () => window.removeEventListener("click", listener);
}, [imageRef]);

const handleChange = () => {
fileInputRef.current.value = "";
fileInputRef.current.click();
console.log(fileInputRef.current.click);
};
const handleImage = (event) => {
console.log("fileEvent: ", event.target.files[0]);
let reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload = () => {
console.log(reader.result.toString());
props.setImage(reader.result);
if (props.setImage) props.setImage(reader.result);
setImage(reader.result);
};
reader.onerror = (error) => {
console.log(error);
};
reader.onerror = () => {};
};
// let timeoutObject = {
// timeoutFunctionSet: false,
// timeoutFunction: () => {},
// };
// const showMessage = (event) => {
// console.log(event);
// };
// const handleMouseEnter = (event) => {
// timeoutObject.timeoutFunctionSet = true;
// timeoutObject.timeoutFunction = setTimeout(() => {
// showMessage(event);
// }, 1000);
// };
// const handleMouseMove = (event) => {
// if (timeoutObject.timeoutFunctionSet) {
// clearTimeout(timeoutObject.timeoutFunction);
// timeoutObject.timeoutFunction = setTimeout(() => {
// showMessage(event);
// }, 1000);
// }
// };
// const handleMouseLeave = () => {
// if (timeoutObject.timeoutFunctionSet) {
// clearTimeout(timeoutObject.timeoutFunction);
// timeoutObject.timeoutFunctionSet = false;
// }
// };
const handleOpen = () => {
setIsOpened(true);
if (!props.image) setIsOpened(false);
};
const handleClose = () => {
setIsOpened(false);
};

const handleDelete = () => {
props.deleteImage();
setIsOpened(false)
}
if (props.deleteImage) props.deleteImage();
setImage("");
setIsEditing(false);
};
return (
<Tooltip
open={isOpened}
onOpen={handleOpen}
onClose={handleClose}
enterDelay={500}
enterNextDelay={500}
arrow
title={
<Icon style={{width: "50px", height: "42px", paddingTop: "10px"}}>
<Trash onClick={handleDelete} />
</Icon>
}
d
<ImagePickerContainer
className={props.className}
onClick={!image ? handleChange : () => {}}
hasImage={props.image}
>
<ImagePickerContainer
className={props.className}
onClick={!props.image ? handleChange : () => {}}
hasImage={props.image}
>
<AddFile type="file" ref={fileInputRef} onInput={handleImage} />
{props.image ? (
<ImageUploaded src={props.image} draggable={false}></ImageUploaded>
) : (
<AddIcon />
)}
</ImagePickerContainer>
</Tooltip>
<AddFile type="file" ref={fileInputRef} onInput={handleImage} />
{image ? (
<React.Fragment>
<ImageUploaded src={image} draggable={false} ref={imageRef} />
{isEditing && (
<React.Fragment>
<ImageOverlay />
<Tools showDeleteIcon={props.showDeleteIcon}>
<IconButton onClick={handleChange}>
<EditIcon />
</IconButton>
{props.showDeleteIcon && (
<IconButton onClick={handleDelete}>
<TrashIcon />
</IconButton>
)}
</Tools>
</React.Fragment>
)}
</React.Fragment>
) : (
<AddIcon />
)}
{props.children}
</ImagePickerContainer>
);
};

@@ -103,7 +95,8 @@ ImagePicker.propTypes = {
className: PropTypes.string,
setImage: PropTypes.func,
image: PropTypes.func,
deleteImage: PropTypes.func
deleteImage: PropTypes.func,
showDeleteIcon: PropTypes.bool,
};

export default ImagePicker;

+ 61
- 17
src/components/ImagePicker/ImagePicker.styled.js Voir le fichier

@@ -1,17 +1,20 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../themes";
import {ReactComponent as Plus} from "../../assets/images/svg/plus.svg";
import { ReactComponent as Plus } from "../../assets/images/svg/plus.svg";
import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg";

export const ImagePickerContainer = styled(Box)`
flex: 1;
display: flex;
flex-basis: 216px;
flex-basis: 144px;
flex-grow: 0;
flex-shrink: 0;
height: 144px;
width: 144px;
margin: 0 9px;
border-radius: 4px;
position: relative;
cursor: pointer;
background-color: ${selectedTheme.imagePickerBackground};
background-image: linear-gradient(
@@ -41,27 +44,68 @@ export const ImagePickerContainer = styled(Box)`
&:last-of-type {
margin-right: 0;
}
${props => props.hasImage && `
${(props) =>
props.hasImage &&
`
background-image: none;
border: 1px solid ${selectedTheme.primaryPurple};
`}
`;
export const AddIcon = styled(Plus)`
margin: auto;
`
margin: auto;
z-index: 1;
width: 60px;
height: 60px;
`;
export const AddFile = styled.input`
display: none;
`
display: none;
`;
export const ImageUploaded = styled.img`
width: 216px;
height: 144px;
object-fit: scale-down;
width: 144px;
height: 144px;
border-radius: 100px;
object-fit: scale-down;
z-index: 1;
`;
export const ImageOverlay = styled(Box)`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 3;
overflow: hidden;
`
export const Tooltip = styled(Box)`
background-color: rgba(255, 255, 255, 0.5);
width: 100px;
height: 100px;
position: absolute;
left: 0;
top: 0;
`
background-color: rgba(255, 255, 255, 0.5);
width: 100px;
height: 100px;
position: absolute;
left: 0;
top: 0;
`;
export const Trash = styled(TrashIcon)`
cursor: pointer;
margin: auto;
width: 22px;
height: 22px;
& path {
stroke: white;
}
`;
export const Tools = styled(Box)`
position: absolute;
padding-top: 44px;
padding-left: ${props => props.showDeleteIcon ? "16px" : "45px"};
z-index: 4;
flex-direction: row;
display: flex;
& div {
background-color: ${selectedTheme.primaryIconBackgroundColor};
border-radius: 100px;
display: flex;
flex: 1;
margin: 10px;
}
`;

+ 144
- 134
src/i18n/resources/rs.js Voir le fichier

@@ -1,135 +1,145 @@
export default {
app: {
title: 'Trampa'
},
refresh: {
title: 'Jel si aktivan?',
cta:
"You were registered as not active, please confirm that you are active in the next minute, if you don't you will be logged out.",
},
common: {
close: 'Zatvori',
send: "Pošalji",
sendAgain: "Pošalji ponovo.",
trademark: 'TM',
search: 'Pretraga',
error: 'Greška',
continue: 'Nastavi',
labelUsername: 'Username',
labelEmail: 'Email',
labelPassword: 'Lozinka',
labelFirm: "Ime Firme",
labelPIB: "PIB",
labelPhone: "Telefon",
labelLocation: "Lokacija",
labelWebsite: "Adresa Websajta",
next: 'Sledeće',
nextPage: 'Sledeća strana',
previousPage: 'Prethodna strana',
back: 'Nazad',
goBack: 'Idi nazad',
ok: 'Ok',
done: 'Gotovo',
confirm: 'Potvrdi',
printDownload: 'Print/Download',
cancel: 'Obustavi',
remove: 'Izbriši',
invite: 'Pozovi',
save: 'Sačuvaj',
complete: 'Završi',
download: 'Download',
yes: 'Da',
no: 'Ne',
to: 'do',
select: 'Izaberi...',
none: 'Ni jedan',
date: {
range: '{{start}} do {{end}}',
},
},
login: {
welcome: 'React template',
welcomeText: 'Trampa sa kolegama na dohvat ruke',
dontHaveAccount: "Nemate nalog? ",
emailFormat: 'Nevalidan format email adrese!',
emailRequired: 'Email adresa je obavezna!',
noUsers: 'Ne postoji korisnik sa zadatom email adresom.',
passwordStrength: 'Your password is {{strength}}.',
passwordLength: 'Lozinka mora imati najmanje 8 karaktera!',
signUpRecommendation: 'Registrujte se.',
email: 'Unesite email adresu kako biste se prijavili',
logInTitle: 'Uloguj se',
logIn: 'Uloguj se',
signUp: 'Registrujte se.',
usernameRequired: 'Username je obavezan!',
passwordRequired: 'Lozinka je obavezna!',
forgotYourPassword: 'Zaboravili ste lozinku?',
forgotPasswordEmail:'Email',
useDifferentEmail: 'Iskoristite drugačiju lozinku.',
wrongCredentials: 'Pogrešan mail ili lozinka!'
},
password: {
weak: 'slaba',
average: 'srednja',
good: 'dobra',
strong: 'jaka',
},
register: {
title: "Registruj se",
descriptionMain: "Trampa sa kolegama na dohvat ruke",
descriptionFirst: "Email i Lozinka biće Vam primarni način da se ulogujete u aplikaciju",
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',
PIBTaken: "PIB je zauzet!",
welcome: 'Dobro došli na trampu, želimo vam uspešno trampovanje!',
},
forgotPassword: {
title: 'Povrati lozinku',
description: 'Molimo vas unesite email sa koji cemo vam poslati link za povratak lozinke',
label: 'Pošalji email',
emailRequired: 'Email je obavezan!',
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: {
title: 'Zaboravili ste lozinku',
subtitle:
'Odgovorite na tajno pitanje kako biste povratili svoj nalog: ',
label: 'Obnovite lozinku',
},
},
resetPassword: {
title: "Unesite novu lozinku",
description: "Poslali ste zahtev za promenu lozinke, molimo Vas da unesete novu željenu lozinku",
passwordLabel: "Nova Lozinka",
passwordConfirmLabel: "Potvrdite Lozinku",
buttonText: "Postavi lozinku"
},
filters: {
title: "Filteri",
cancel: "Poništi filtere",
usefilters: 'Primeni filtere',
categories: {
title: "Kategorija",
placeholder: 'Pretraži kategorije...'
},
subcategories: {
title: "Podkategorija",
placeholder: "Pretraži podkategorije..."
},
location: {
title: "Lokacija",
placeholder: "Pretraži gradove..."
}
}
}
app: {
title: "Trampa",
},
refresh: {
title: "Jel si aktivan?",
cta: "You were registered as not active, please confirm that you are active in the next minute, if you don't you will be logged out.",
},
common: {
close: "Zatvori",
send: "Pošalji",
sendAgain: "Pošalji ponovo.",
trademark: "TM",
search: "Pretraga",
error: "Greška",
continue: "Nastavi",
labelUsername: "Username",
labelEmail: "Email",
labelPassword: "Lozinka",
labelFirm: "Ime Firme",
labelPIB: "PIB",
labelPhone: "Telefon",
labelLocation: "Lokacija",
labelWebsite: "Adresa Websajta",
next: "Sledeće",
nextPage: "Sledeća strana",
previousPage: "Prethodna strana",
back: "Nazad",
goBack: "Idi nazad",
ok: "Ok",
done: "Gotovo",
confirm: "Potvrdi",
printDownload: "Print/Download",
cancel: "Obustavi",
remove: "Izbriši",
invite: "Pozovi",
save: "Sačuvaj",
complete: "Završi",
download: "Download",
yes: "Da",
no: "Ne",
to: "do",
select: "Izaberi...",
none: "Ni jedan",
date: {
range: "{{start}} do {{end}}",
},
},
login: {
welcome: "React template",
welcomeText: "Trampa sa kolegama na dohvat ruke",
dontHaveAccount: "Nemate nalog? ",
emailFormat: "Nevalidan format email adrese!",
emailRequired: "Email adresa je obavezna!",
noUsers: "Ne postoji korisnik sa zadatom email adresom.",
passwordStrength: "Your password is {{strength}}.",
passwordLength: "Lozinka mora imati najmanje 8 karaktera!",
signUpRecommendation: "Registrujte se.",
email: "Unesite email adresu kako biste se prijavili",
logInTitle: "Uloguj se",
logIn: "Uloguj se",
signUp: "Registrujte se.",
usernameRequired: "Username je obavezan!",
passwordRequired: "Lozinka je obavezna!",
forgotYourPassword: "Zaboravili ste lozinku?",
forgotPasswordEmail: "Email",
useDifferentEmail: "Iskoristite drugačiju lozinku.",
wrongCredentials: "Pogrešan mail ili lozinka!",
},
password: {
weak: "slaba",
average: "srednja",
good: "dobra",
strong: "jaka",
},
register: {
title: "Registruj se",
descriptionMain: "Trampa sa kolegama na dohvat ruke",
descriptionFirst:
"Email i Lozinka biće Vam primarni način da se ulogujete u aplikaciju",
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",
PIBTaken: "PIB je zauzet!",
PIBnoOfCharacters: "PIB mora imati 9 karaktera!",
welcome: "Dobro došli na trampu, želimo vam uspešno trampovanje!",
imageError: "Slika je obavezna!",
},
forgotPassword: {
title: "Povrati lozinku",
description:
"Molimo vas unesite email sa koji cemo vam poslati link za povratak lozinke",
label: "Pošalji email",
emailRequired: "Email je obavezan!",
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: {
title: "Zaboravili ste lozinku",
subtitle: "Odgovorite na tajno pitanje kako biste povratili svoj nalog: ",
label: "Obnovite lozinku",
},
},
resetPassword: {
title: "Unesite novu lozinku",
description:
"Poslali ste zahtev za promenu lozinke, molimo Vas da unesete novu željenu lozinku",
passwordLabel: "Nova Lozinka",
passwordConfirmLabel: "Potvrdite Lozinku",
buttonText: "Postavi lozinku",
},
filters: {
title: "Filteri",
cancel: "Poništi filtere",
usefilters: "Primeni filtere",
categories: {
title: "Kategorija",
placeholder: "Pretraži kategorije...",
},
subcategories: {
title: "Podkategorija",
placeholder: "Pretraži podkategorije...",
},
location: {
title: "Lokacija",
placeholder: "Pretraži gradove...",
},
},
apiErrors: {
somethingWentWrong: "Greska sa serverom!"
}
};

+ 3
- 0
src/initialValues/forgotPasswordInitialValues.js Voir le fichier

@@ -0,0 +1,3 @@
export default {
email: "",
};

+ 4
- 0
src/initialValues/loginInitialValues.js Voir le fichier

@@ -0,0 +1,4 @@
export default {
email: "",
password: "",
};

+ 4
- 0
src/initialValues/registerInitialValues/firstPartInitialValues.js Voir le fichier

@@ -0,0 +1,4 @@
export default {
mail: "",
password: "",
};

+ 4
- 0
src/initialValues/registerInitialValues/secondPartInitialValues.js Voir le fichier

@@ -0,0 +1,4 @@
export default {
nameOfFirm: "",
PIB: "",
};

+ 5
- 0
src/initialValues/registerInitialValues/thirdPartInitialValues.js Voir le fichier

@@ -0,0 +1,5 @@
export default {
phoneNumber: "",
location: "",
website: "",
};

+ 4
- 0
src/initialValues/resetPasswordInitialValues.js Voir le fichier

@@ -0,0 +1,4 @@
export default {
password: "",
passwordConfirm: "",
};

+ 17
- 1
src/pages/ForgotPasswordPage/ForgotPassword.styled.js Voir le fichier

@@ -51,4 +51,20 @@ export const ErrorMessage = styled(Typography)`
position: relative;
top: -7px;
font-size: 14px;
`
`
export const LoginAltText = styled(Typography)`
font-family: "Poppins";
color: ${selectedTheme.primaryText};
font-size: 14px;
padding-right: 6px;
line-height: 14px;
`;
export const LoginTextContainer = styled(Box)`
display: flex;
flex-direction: row;
margin-top: 36px;
justify-content: center;
@media (max-height: 800px) {
margin-top: 26px;
}
`;

+ 5
- 1
src/pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent.js Voir le fichier

@@ -28,7 +28,11 @@ const MailSent = () => {
const dispatch = useDispatch();

useEffect(() => {
setEmail(location.state.email);
if (location.state.email) {
setEmail(location.state.email);
} else {
history.push("/login");
}
}, []);

const navigateLogin = () => {

+ 21
- 12
src/pages/ForgotPasswordPage/ForgotPasswordPage.js Voir le fichier

@@ -1,7 +1,6 @@
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 {
@@ -10,14 +9,19 @@ import {
ForgotPasswordTitle,
FormContainer,
ErrorMessage,
LoginTextContainer,
LoginAltText,
} from "./ForgotPassword.styled";
import { TextField } from "../../components/TextFields/TextField/TextField";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import { useHistory } from "react-router-dom";
import { NavLink, 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";
import forgotPasswordValidation from "../../validations/forgotPasswordValidation";
import forgotPasswordInitialValues from "../../initialValues/forgotPasswordInitialValues";
import Link from "../../components/Link/Link";

const ForgotPasswordPage = () => {
const history = useHistory();
@@ -25,12 +29,6 @@ const ForgotPasswordPage = () => {
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,
@@ -52,10 +50,8 @@ const ForgotPasswordPage = () => {
};

const formik = useFormik({
initialValues: {
email: "",
},
validationSchema: forgotPasswordValidationSchema,
initialValues: forgotPasswordInitialValues,
validationSchema: forgotPasswordValidation,
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
@@ -109,6 +105,19 @@ const ForgotPasswordPage = () => {
>
{t("common.send")}
</PrimaryButton>

<LoginTextContainer>
<LoginAltText>{t("register.loginText")}</LoginAltText>

<Link
to="/login"
component={NavLink}
underline="hover"
align="center"
>
{t("register.login")}
</Link>
</LoginTextContainer>
</FormContainer>
</ForgotPasswordPageContainer>
);

+ 4
- 10
src/pages/LoginPage/LoginPage.js Voir le fichier

@@ -32,6 +32,8 @@ import {
ErrorMessage,
} from "./Login.styled";
import selectedTheme from "../../themes";
import loginValidation from "../../validations/loginValidation";
import loginInitialValues from "../../initialValues/loginInitialValues";

const LoginPage = ({ history }) => {
const dispatch = useDispatch();
@@ -83,16 +85,8 @@ const LoginPage = ({ history }) => {
};

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")),
}),
initialValues: loginInitialValues,
validationSchema: loginValidation,
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,

+ 6
- 8
src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.js Voir le fichier

@@ -68,12 +68,6 @@ const FirstPartOfRegistration = (props) => {
fullWidth
/>

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

<TextField
name="password"
placeholder={t("common.labelPassword")}
@@ -89,16 +83,20 @@ const FirstPartOfRegistration = (props) => {
InputProps={{
endAdornment: (
<IconButton onClick={handleClickShowPassword}>
{showPassword ? <VisibilityOn /> : <VisibilityOff />}
{showPassword ? <VisibilityOff /> : <VisibilityOn />}
</IconButton>
),
}}
/>
{formik.errors.password && formik.touched.password ? (

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

<PrimaryButton

+ 86
- 55
src/pages/RegisterPages/Register/Register.js Voir le fichier

@@ -9,6 +9,10 @@ import {
ProgressContainer,
RegisterDescription,
RegisterTitle,
ProfileImagePicker,
ProfilePicture,
RegisterPageContent,
ErrorMessage,
} from "./Register.styled";
import { ReactComponent as Logo } from "../../../assets/images/svg/logo-vertical.svg";
import { NavLink, useHistory } from "react-router-dom";
@@ -28,19 +32,21 @@ const Register = () => {
const dispatch = useDispatch();
const [currentStep, setCurrentStep] = useState(1);
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 [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 [imageError, setImageError] = useState(false); // Not picked image

const handleResponseSuccess = () => {
history.push(REGISTER_SUCCESSFUL_PAGE);
};

const handleResponseError = (error) => {
console.log(error);
const { mail, password, PIB, image } = informations;
if (error.type === "mail") {
const { mail } = informations;
setInformations({});
setInformations({image});
setCurrentStep(1);
setMailError(mail);
if (
@@ -52,8 +58,7 @@ const Register = () => {
setMailErrorMessage(t("register.emailFormat"));
}
} else {
const { mail, password, PIB } = informations;
setInformations({ mail, password });
setInformations({ mail, password, image });
setCurrentStep(2);
setPIBError(PIB.toString());
setPIBErrorMessage(t("register.PIBTaken"));
@@ -70,67 +75,93 @@ const Register = () => {
if (currentStep !== 3) {
setCurrentStep((prevState) => prevState + 1);
} else {
registerUser({ ...informations, ...values });
if (!informations.image) {
setImageError(true);
} else {
registerUser({ ...informations, ...values });
}
}
setInformations({ ...informations, ...values });
};

const setImage = (image) => {
setImageError(false);
setInformations(prevInfo => ({
...prevInfo,
image
}))
}

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

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

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

<RegisterDescription component="h1" variant="h6">
{t("register.descriptionMain")}
</RegisterDescription>

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

{currentStep === 1 && (
<FirstPartOfRegistration
handleSubmit={handleSubmit}
error={mailError}
errorMessage={mailErrorMessage}
/>
)}
{currentStep === 2 && (
<SecondPartOfRegistration
handleSubmit={handleSubmit}
error={PIBError}
errorMessage={PIBErrorMessage}
/>
)}
{currentStep === 3 && (
<ThirdPartOfRegistration handleSubmit={handleSubmit} />
)}

<LoginTextContainer>
<LoginAltText>{t("register.loginText")}</LoginAltText>

<Link to="/login" component={NavLink} underline="hover" align="center">
{t("register.login")}
</Link>
</LoginTextContainer>
<RegisterPageContent>
<Logo />

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

<RegisterDescription component="h1" variant="h6">
{t("register.descriptionMain")}
</RegisterDescription>

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

<ProfileImagePicker setImage={setImage} >
<ProfilePicture />
</ProfileImagePicker>

{currentStep === 1 && (
<FirstPartOfRegistration
handleSubmit={handleSubmit}
error={mailError}
errorMessage={mailErrorMessage}
/>
)}
{currentStep === 2 && (
<SecondPartOfRegistration
handleSubmit={handleSubmit}
error={PIBError}
errorMessage={PIBErrorMessage}
/>
)}
{currentStep === 3 && (
<ThirdPartOfRegistration handleSubmit={handleSubmit} />
)}

{imageError && <ErrorMessage>{t("register.imageError")}</ErrorMessage>}

<LoginTextContainer>
<LoginAltText>{t("register.loginText")}</LoginAltText>

<Link
to="/login"
component={NavLink}
underline="hover"
align="center"
>
{t("register.login")}
</Link>
</LoginTextContainer>
</RegisterPageContent>

<Footer>
<FooterText>

+ 58
- 12
src/pages/RegisterPages/Register/Register.styled.js Voir le fichier

@@ -1,9 +1,11 @@
import { Box, Container, Typography } from "@mui/material";
import styled from "styled-components";
import ImagePicker from "../../../components/ImagePicker/ImagePicker";
import selectedTheme from "../../../themes";
import { ReactComponent as ProfilePictureSVG } from "../../../assets/images/profile-picture.svg";

export const RegisterPageContainer = styled(Container)`
margin-top: 100px;
margin-top: 72px;
display: flex;
flex-direction: column;
align-items: center;
@@ -11,6 +13,8 @@ export const RegisterPageContainer = styled(Container)`
padding: 0;
flex: 1;
position: relative;
transition: 1s all;
${props => props.currentstep === 3 && `margin-top: 40px`};
@media (max-height: 900px) {
margin-top: 60px;
}
@@ -18,8 +22,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)`
@@ -34,7 +38,7 @@ export const RegisterTitle = styled(Typography)`
color: ${selectedTheme.primaryPurple};
margin-top: 34px;
@media (max-height: 800px) {
margin-top: 26px;
margin-top: 26px;
}
`;
export const RegisterDescription = styled(Typography)`
@@ -51,8 +55,8 @@ export const RegisterDescription = styled(Typography)`
color: ${selectedTheme.primaryGrayText};
margin-bottom: 20px;
@media (max-height: 800px) {
margin-bottom: 14px;
margin-top: 6px;
margin-bottom: 14px;
margin-top: 6px;
}
`;
export const FormContainer = styled(Box)`
@@ -64,7 +68,7 @@ export const LoginAltText = styled(Typography)`
font-size: 14px;
padding-right: 6px;
line-height: 14px;
`
`;
export const LoginTextContainer = styled(Box)`
display: flex;
flex-direction: row;
@@ -73,11 +77,11 @@ export const LoginTextContainer = styled(Box)`
@media (max-height: 800px) {
margin-top: 26px;
}
`
`;
export const ProgressContainer = styled(Container)`
width: 100%;
padding: 0;
`
width: 100%;
padding: 0;
`;
export const Footer = styled(Box)`
position: absolute;
bottom: 36px;
@@ -88,7 +92,7 @@ export const Footer = styled(Box)`
@media (max-height: 800px) {
bottom: 10px;
}
`
`;
export const FooterText = styled(Typography)`
font-family: "Open Sans";
color: #505050;
@@ -98,4 +102,46 @@ export const FooterText = styled(Typography)`
font-weight: 400;
padding: 0;
font-size: 12px;
`;

export const ProfileImagePicker = styled(ImagePicker)`
background: none;
margin: 36px;
background: ${selectedTheme.primaryIconBackgroundColor};
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='100' ry='100' stroke='%235A3984FF' stroke-width='2' stroke-dasharray='7%2c 12' stroke-dashoffset='44' stroke-linecap='square'/%3e%3c/svg%3e");
border-radius: 100px;
overflow: hidden;
position: relative;
margin-bottom: 5px;
`;
export const ProfilePicture = styled(ProfilePictureSVG)`
position: absolute;
left: 36px;
top: 24px;
z-index: 0;
`;
export const RegisterPageContent = styled(Box)`
display: flex;
flex-direction: column;
align-items: center;
width: 335px;
padding: 0;
flex: 1;
position: relative;
margin-bottom: 100px;
@media (max-height: 800px) {
flex: none;
height: 95vh;
${(props) => props.currentstep === 3 && `height: 105vh`};
${(props) => props.currentstep === 2 && `height: 100vh`};
}
`;
export const ErrorMessage = styled(Box)`
color: red;
font-family: "Open Sans";
position: relative;
top: 5px;
text-align: left;
font-size: 14px;
width: 100%;
`

+ 0
- 1
src/store/middleware/accessTokensMiddleware.js Voir le fichier

@@ -36,7 +36,6 @@ export default ({ dispatch }) =>
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`, {

+ 3
- 2
src/store/saga/loginSaga.js Voir le fichier

@@ -63,8 +63,9 @@ function* fetchUser({ payload }) {
if (payload.handleApiResponseError) {
yield call(payload.handleApiResponseError, e.response.status);
}
let errorMessage = yield call(rejectErrorCodeHelper, e);
if (e.response.status === 401) {
console.log(e.response.status);
let errorMessage = yield call(rejectErrorCodeHelper, e.response.status);
if (e.response.status === 400) {
errorMessage = i18next.t("login.wrongCredentials", {
lng: "rs"
});

+ 2
- 4
src/store/saga/registerSaga.js Voir le fichier

@@ -4,13 +4,11 @@ import { REGISTER_USER_FETCH } from "../actions/register/registerActionConstants

function* fetchRegisterUser({ payload }) {
try {

const requestData = {
email: payload.values.mail.toString(),
password: payload.values.password.toString(),
roles: [
"User",
],
roles: ["User"],
image: payload.values.image.replace("data:image/png;base64,", ""),
company: {
name: payload.values.nameOfFirm.toString(),
PIB: payload.values.PIB.toString(),

+ 6
- 11
src/util/helpers/rejectErrorCodeHelper.js Voir le fichier

@@ -1,14 +1,9 @@
import i18next from 'i18next';
import i18next from "i18next";

export const rejectErrorCodeHelper = (error) => {
if (error?.response?.data?.Errors) {
const errorCode = error?.response?.data?.Errors[0]?.Code;
const errorMessage = errorCode
? i18next.t(`apiErrors.${errorCode}`)
: i18next.t('apiErrors.SomethingWentWrong');
export const rejectErrorCodeHelper = (errorCode) => {
const errorMessage = errorCode
? i18next.t(`apiErrors.${errorCode}`)
: i18next.t("apiErrors.SomethingWentWrong");

return errorMessage;
}

return i18next.t('apiErrors.SomethingWentWrong');
return errorMessage;
};

+ 8
- 0
src/validations/forgotPasswordValidation.js Voir le fichier

@@ -0,0 +1,8 @@
import * as Yup from "yup";
import i18n from "../i18n";

export default Yup.object().shape({
email: Yup.string()
.required(i18n.t("forgotPassword.emailRequired"))
.email(i18n.t("forgotPassword.emailFormat")),
});

+ 9
- 0
src/validations/loginValidation.js Voir le fichier

@@ -0,0 +1,9 @@
import * as Yup from "yup";
import i18n from "../i18n";

export default Yup.object().shape({
email: Yup.string().email(i18n.t("login.emailFormat")).required(i18n.t("login.mailRequired")),
password: Yup.string()
.required(i18n.t("login.passwordRequired"))
.min(8, i18n.t("login.passwordLength")),
});

+ 10
- 0
src/validations/registerValidations/firstPartValidation.js Voir le fichier

@@ -0,0 +1,10 @@
import * as Yup from "yup";
import i18n from "../../i18n";
export default Yup.object().shape({
mail: Yup.string()
.email(i18n.t("forgotPassword.emailFormat"))
.required(i18n.t("login.usernameRequired")),
password: Yup.string()
.required(i18n.t("login.passwordRequired"))
.min(8, i18n.t("login.passwordLength")),
});

+ 10
- 0
src/validations/registerValidations/secondPartValidation.js Voir le fichier

@@ -0,0 +1,10 @@
import * as Yup from "yup";
import i18n from "../../i18n";

export default Yup.object().shape({
nameOfFirm: Yup.string().required(i18n.t("login.usernameRequired")),
PIB: Yup.number()
.required(i18n.t("login.passwordRequired"))
.min(100000000, i18n.t("register.PIBnoOfCharacters"))
.max(999999999, i18n.t("register.PIBnoOfCharacters")),
});

+ 10
- 0
src/validations/registerValidations/thirdPartValidation.js Voir le fichier

@@ -0,0 +1,10 @@
import * as Yup from "yup";
import i18n from "../../i18n";

export default Yup.object().shape({
phoneNumber: Yup.number().required(i18n.t("login.usernameRequired")),
location: Yup.string().required(i18n.t("login.passwordRequired")),
website: Yup.string().matches(
/^((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
),
});

+ 6
- 0
src/validations/resetPasswordValidation.js Voir le fichier

@@ -0,0 +1,6 @@
import * as Yup from "yup";

export default Yup.object().shape({
password: Yup.string().required().min(8),
passwordConfirm: Yup.string().oneOf([Yup.ref("password"), null]),
});

Chargement…
Annuler
Enregistrer