| @@ -21,3 +21,5 @@ | |||
| npm-debug.log* | |||
| yarn-debug.log* | |||
| yarn-error.log* | |||
| /.idea | |||
| @@ -4,6 +4,7 @@ import { Redirect, Route, Switch } from "react-router-dom"; | |||
| import { | |||
| LOGIN_PAGE, | |||
| ADMIN_LOGIN_PAGE, | |||
| HOME_PAGE, | |||
| NOT_FOUND_PAGE, | |||
| ERROR_PAGE, | |||
| @@ -21,9 +22,11 @@ import { | |||
| MY_OFFERS_PAGE, | |||
| // PRICES_PAGE, | |||
| ABOUT_PAGE, | |||
| ADMIN_HOME_PAGE, | |||
| // POLICY_PRIVACY_PAGE, | |||
| } from "./constants/pages"; | |||
| import LoginPage from "./pages/LoginPage/LoginPage"; | |||
| import AdminLoginPage from "./pages/AdminLoginPage/AdminLoginPage"; | |||
| import HomePage from "./pages/HomePage/HomePageMUI"; | |||
| import NotFoundPage from "./pages/ErrorPages/NotFoundPage"; | |||
| import ErrorPage from "./pages/ErrorPages/ErrorPage"; | |||
| @@ -42,6 +45,7 @@ import MyOffers from "./pages/MyOffers/MyOffers"; | |||
| // import PricesPage from "./pages/Prices/PricesPage"; | |||
| import AboutPage from "./pages/About/AboutPage"; | |||
| import AuthRoute from "./components/Router/AuthRoute"; | |||
| import AdminHomePage from "./pages/AdminHomePage/AdminHomePage"; | |||
| // import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage"; | |||
| const AppRoutes = () => { | |||
| @@ -49,6 +53,8 @@ const AppRoutes = () => { | |||
| <Switch> | |||
| <Route exact path={BASE_PAGE} component={HomePage} /> | |||
| <AuthRoute exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <AuthRoute exact path={ADMIN_LOGIN_PAGE} component={AdminLoginPage} /> | |||
| <Route path={ADMIN_HOME_PAGE} component={AdminHomePage} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <AuthRoute path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | |||
| @@ -3,17 +3,18 @@ import PropTypes from "prop-types"; | |||
| import { LoginDescription as Description } from "./LoginDescription.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const LoginDescription = () => { | |||
| const LoginDescription = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Description component="h1" variant="h6"> | |||
| {t("login.welcomeText")} | |||
| {props.isAdmin?t("admin.login.welcomeText"):t("login.welcomeText")} | |||
| </Description> | |||
| ); | |||
| }; | |||
| LoginDescription.propTypes = { | |||
| children: PropTypes.node, | |||
| isAdmin: PropTypes.bool | |||
| }; | |||
| export default LoginDescription; | |||
| @@ -8,8 +8,9 @@ import { | |||
| fetchLogin, | |||
| } from "../../store/actions/login/loginActions"; | |||
| import { selectLoginError } from "../../store/selectors/loginSelectors"; | |||
| import { HOME_PAGE } from "../../constants/pages"; | |||
| import { HOME_PAGE, ADMIN_HOME_PAGE } from "../../constants/pages"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| import { ReactComponent as LogoAdmin } from "../../assets/images/svg/logo-vertical-admin.svg"; | |||
| import { LoginPageContainer, LoginFormContainer } from "./Login.styled"; | |||
| import loginValidation from "../../validations/loginValidation"; | |||
| import loginInitialValues from "../../initialValues/loginInitialValues"; | |||
| @@ -22,7 +23,7 @@ import ForgotPasswordLink from "./ForgotPasswordLink/ForgotPasswordLink"; | |||
| import LoginButton from "./LoginButton/LoginButton"; | |||
| import RegisterLink from "./RegisterLink/RegisterLink"; | |||
| const Login = () => { | |||
| const Login = (props) => { | |||
| const dispatch = useDispatch(); | |||
| const error = useSelector(selectLoginError); | |||
| const history = useHistory(); | |||
| @@ -36,6 +37,12 @@ const Login = () => { | |||
| // Api response callback function on success | |||
| const handleApiResponseSuccess = () => { | |||
| props.isAdmin ? history.push({ | |||
| pathname: ADMIN_HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }) : | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| @@ -75,6 +82,7 @@ const Login = () => { | |||
| password, | |||
| handleApiResponseSuccess, | |||
| handleApiResponseError, | |||
| isAdmin: props.isAdmin | |||
| }) | |||
| ); | |||
| } | |||
| @@ -103,16 +111,18 @@ const Login = () => { | |||
| return ( | |||
| <LoginPageContainer> | |||
| <Logo /> | |||
| <LoginTitle /> | |||
| <LoginDescription /> | |||
| { | |||
| props.isAdmin ? <LogoAdmin/> : <Logo/> | |||
| } | |||
| <LoginTitle isAdmin={props.isAdmin} /> | |||
| <LoginDescription isAdmin={props.isAdmin}/> | |||
| <LoginFormContainer component="form" onSubmit={handleSubmitForm}> | |||
| <EmailField formik={formik} /> | |||
| <PasswordField formik={formik} ref={passwordRef} /> | |||
| <ErrorMessage formik={formik} /> | |||
| <ForgotPasswordLink /> | |||
| {!props.isAdmin ? <ForgotPasswordLink/>:<></>} | |||
| <LoginButton formik={formik} /> | |||
| <RegisterLink /> | |||
| {!props.isAdmin ? <RegisterLink/>:<></>} | |||
| </LoginFormContainer> | |||
| </LoginPageContainer> | |||
| ); | |||
| @@ -126,5 +136,6 @@ Login.propTypes = { | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| isAdmin: PropTypes.bool | |||
| }; | |||
| export default Login; | |||
| @@ -3,17 +3,20 @@ import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { LoginTitle as Title } from "./LoginTitle.styled"; | |||
| const LoginTitle = () => { | |||
| const LoginTitle = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Title component="h1" variant="h5"> | |||
| {t("login.logInTitle")} | |||
| { | |||
| props.isAdmin ? t("admin.login.logInTitle") : t("login.logInTitle") | |||
| } | |||
| </Title> | |||
| ); | |||
| }; | |||
| LoginTitle.propTypes = { | |||
| children: PropTypes.node, | |||
| isAdmin: PropTypes.bool | |||
| }; | |||
| export default LoginTitle; | |||
| @@ -1,6 +1,7 @@ | |||
| export const BASE_PAGE = '/'; | |||
| export const LOGIN_PAGE = '/login'; | |||
| export const FORGOT_PASSWORD_PAGE = '/forgot-password'; | |||
| export const ADMIN_LOGIN_PAGE = '/admin/login'; | |||
| export const HOME_PAGE = '/home'; | |||
| export const ERROR_PAGE = '/error-page'; | |||
| export const NOT_FOUND_PAGE = '/not-found'; | |||
| @@ -17,3 +18,4 @@ export const MY_OFFERS_PAGE = "/myoffers" | |||
| export const ABOUT_PAGE = "/about"; | |||
| export const PRICES_PAGE = "/prices"; | |||
| export const POLICY_PRIVACY_PAGE = "/policy"; | |||
| export const ADMIN_HOME_PAGE = "/admin/home"; | |||
| @@ -407,4 +407,22 @@ export default { | |||
| imagesReview: "Pregled fotografija", | |||
| offer: "Proizvod:", | |||
| }, | |||
| admin: { | |||
| login:{ | |||
| welcome: "React template", | |||
| welcomeText: "Trampa sa kolegama na dohvat ruke", | |||
| 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!", | |||
| email: "Unesite email adresu kako biste se prijavili", | |||
| logInTitle: "Uloguj se", | |||
| logIn: "Uloguj se", | |||
| usernameRequired: "Username je obavezan!", | |||
| passwordRequired: "Lozinka je obavezna!", | |||
| wrongCredentials: "Pogrešan mail ili lozinka!", | |||
| headerTitle: "Ulogujte se", | |||
| } | |||
| } | |||
| }; | |||
| @@ -0,0 +1,14 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| const AdminHomePage = () => { | |||
| return ( | |||
| <div>Admin home page brateee</div> | |||
| ) | |||
| } | |||
| AdminHomePage.propTypes = { | |||
| children: PropTypes.node, | |||
| } | |||
| export default AdminHomePage | |||
| @@ -0,0 +1,15 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import Login from '../../components/Login/Login' | |||
| const LoginPage = () => { | |||
| return ( | |||
| <Login isAdmin/> | |||
| ) | |||
| } | |||
| LoginPage.propTypes = { | |||
| children: PropTypes.node, | |||
| } | |||
| export default LoginPage | |||
| @@ -4,7 +4,7 @@ import Login from '../../components/Login/Login' | |||
| const LoginPage = () => { | |||
| return ( | |||
| <Login /> | |||
| <Login isAdmin={false}/> | |||
| ) | |||
| } | |||
| @@ -39,12 +39,17 @@ import { clearChat } from "../actions/chat/chatActions"; | |||
| function* fetchLogin({ payload }) { | |||
| try { | |||
| const isAdmin = payload.isAdmin; | |||
| // delete payload.isAdmin | |||
| const { data } = yield call(attemptLogin, payload); | |||
| if (data.token) { | |||
| const token = data.token; | |||
| const refresh = data.refresh; | |||
| const tokenDecoded = jwt.decode(token); | |||
| const refreshDecoded = jwt.decode(refresh); | |||
| if(isAdmin && !tokenDecoded.roles.includes("Admin")){ | |||
| throw Error("Not an admin login on /login"); | |||
| } | |||
| const accessToken = { | |||
| token: token, | |||
| exp: tokenDecoded.exp, | |||
| @@ -65,6 +70,9 @@ function* fetchLogin({ payload }) { | |||
| } | |||
| } | |||
| } catch (e) { | |||
| if(e.message){ | |||
| yield put(fetchUserError(e.message)); | |||
| } | |||
| if (e.response && e.response.data) { | |||
| if (payload.handleApiResponseError) { | |||
| yield call(payload.handleApiResponseError, e.response.status); | |||
| @@ -1,4 +1,5 @@ | |||
| import { | |||
| ADMIN_LOGIN_PAGE, | |||
| FORGOT_PASSWORD_MAIL_SENT, | |||
| FORGOT_PASSWORD_PAGE, | |||
| LOGIN_PAGE, | |||