From 1c9d9e202131419574bd0dd3b15970b15b4d8806 Mon Sep 17 00:00:00 2001 From: Jonasz Bigda Date: Thu, 17 Sep 2020 19:01:01 +0200 Subject: [PATCH] web client v 0.1 --- package-lock.json | 91 +++++++++++ package.json | 2 + src/App.js | 75 ++++++--- src/App.test.js | 9 -- src/actions/index.js | 58 +++++-- src/actions/toggles.js | 82 +++++----- src/components/Dialogs.js | 6 - src/components/Dialogs/ForgotPassword.js | 154 ++++++++++++++++++ src/components/Dialogs/LoginDialog.js | 24 +-- src/components/Dialogs/NewRestaurant.js | 43 +++-- src/components/Dialogs/RegisterDialog.js | 101 +++++++++--- src/components/Dialogs/RegulaminDialog.js | 12 +- src/components/Dialogs/ResetPassword.js | 184 ++++++++++++++++++++++ src/components/Input/ImageUpload.js | 8 +- src/components/Output/CardRestaurant.js | 1 + src/components/TopBar.js | 26 +-- src/components/UserMenu.js | 8 +- src/index.js | 15 +- src/reducers/data.js | 82 +++++----- src/reducers/index.js | 21 +-- 20 files changed, 794 insertions(+), 208 deletions(-) delete mode 100644 src/App.test.js create mode 100644 src/components/Dialogs/ForgotPassword.js create mode 100644 src/components/Dialogs/ResetPassword.js diff --git a/package-lock.json b/package-lock.json index 45d22dc..87f4dc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4010,6 +4010,14 @@ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" }, + "connected-react-router": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.8.0.tgz", + "integrity": "sha512-E64/6krdJM3Ag3MMmh2nKPtMbH15s3JQDuaYJvOVXzu6MbHbDyIvuwLOyhQIuP4Om9zqEfZYiVyflROibSsONg==", + "requires": { + "prop-types": "^15.7.2" + } + }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -6676,6 +6684,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -8804,6 +8825,15 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, + "mini-create-react-context": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", + "requires": { + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" + } + }, "mini-css-extract-plugin": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", @@ -11357,6 +11387,52 @@ "react-is": "^16.9.0" } }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.3.tgz", @@ -11752,6 +11828,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -13275,6 +13356,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -13669,6 +13755,11 @@ "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz", "integrity": "sha512-8GfPiwzzRoWTg7OV1zva1KvrSemuMkv07MA9TTl91hfhe+wKrsrgVN4H2QSFd/U/FhiU3iWPYVgvbsOGwhyFWw==" }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 5fc8e45..84f1acc 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", "axios": "^0.19.2", + "connected-react-router": "^6.8.0", "node-sass": "^4.14.1", "react": "^16.13.1", "react-dom": "^16.13.1", "react-ga": "^3.1.2", "react-redux": "^7.2.0", + "react-router-dom": "^5.2.0", "react-scripts": "^3.4.3", "redux": "^4.0.5", "redux-thunk": "^2.3.0", diff --git a/src/App.js b/src/App.js index 73ded5a..6764f00 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,5 @@ import React from "react"; +import { Router, Switch, Route } from "react-router-dom"; import "./App.scss"; import TopBar from "./components/TopBar"; import LogoMain from "./components/Output/logoMain"; @@ -8,7 +9,11 @@ import SearchResults from "./components/Output/SearchResults"; import Restaurant from "./components/Output/Restaurant"; import Dialogs from "./components/Dialogs"; import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles"; -import { useSelector } from "react-redux"; +import LoginDialog from "./components/Dialogs/LoginDialog"; +import NewRestaurant from "./components/Dialogs/NewRestaurant"; +import RegisterDialog from "./components/Dialogs/RegisterDialog"; +import ForgotPassword from "./components/Dialogs/ForgotPassword"; +import ResetPassword from "./components/Dialogs/ResetPassword"; const theme = createMuiTheme({ palette: { @@ -24,30 +29,54 @@ const theme = createMuiTheme({ }, }); -function App() { - const appMode = useSelector((store) => store.appMode); - +function App(props) { return ( - -
- -
- - {(appMode === "init" || appMode === "search results") && ( - - )} - {appMode === "init" && ( -

- Sprawdź co serwuje Twoja ulubiona restauracja. -

- )} - {appMode === "search results" && } - {appMode === "restaurant" && } + + +
+ +
+ + + + +

+ Sprawdź co serwuje Twoja ulubiona restauracja. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
- -
-
- + + ); } diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 4db7ebc..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/actions/index.js b/src/actions/index.js index e34be57..56f86a0 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,5 +1,7 @@ import axios from "axios"; import * as toggles from "./toggles"; +import { push } from "connected-react-router"; + const backend = "http://localhost:4000/"; const autocomplete = (input) => { @@ -23,7 +25,6 @@ export const fetchAutocomplete = (input) => { const cities = Array.from(response.data.cities); const restaurants = Array.from(response.data.restaurants); const options = cities.concat(restaurants); - dispatch(autocomplete(options)); }) .catch((err) => { @@ -40,7 +41,7 @@ export const fetchSearch = (input) => { const data = response.data; if (Object.keys(data).length > 0) { dispatch(setSearchResults(data)); - dispatch(setAppMode("APP_SEARCH_RESULTS")); + dispatch(push("/results")); } }) .catch((err) => { @@ -63,12 +64,6 @@ export const setSearchQuery = (input) => { }; }; -export const setAppMode = (mode) => { - return { - type: mode, - }; -}; - export const setRestaurant = (restaurant) => { return { type: "SET_RESTAURANT", @@ -83,7 +78,7 @@ export const fetchRestaurant = (id) => { .then((response) => { dispatch(setRestaurant(response.data)); dispatch(toggles.hideDishes()); - dispatch(setAppMode("APP_RESTAURANT")); + dispatch(push("/restaurant")); dispatch(fetchAllDishes(id)); }) .catch((err) => console.log(err)); @@ -112,11 +107,11 @@ export const tryLogin = (username, password) => { const data = { email: username, password: password }; return function (dispatch) { + dispatch(toggles.setLoginResult("")); axios .post(backend + "user/login", data) .then((response) => { const jwt = response.headers["x-auth-token"]; - console.log(response.headers); dispatch( toggles.setLoggedIn( response.data.firstname, @@ -125,7 +120,7 @@ export const tryLogin = (username, password) => { response.data.email ) ); - dispatch(toggles.hideLoginDialog()); + dispatch(push("/")); }) .catch((err) => { if (err.response.status === 404) { @@ -143,6 +138,46 @@ export const tryLogin = (username, password) => { }; }; +export const remindPassword = (email) => { + return function (dispatch) { + const data = { email: email }; + dispatch(toggles.setReminderResult("")); + dispatch(toggles.showReminderCircle()); + axios + .post(backend + "user/forgotpassword", data) + .then((response) => { + dispatch(toggles.hideReminderCircle()); + dispatch(toggles.setReminderResult(response.data)); + }) + .catch((e) => { + dispatch(toggles.hideReminderCircle()); + dispatch(toggles.setReminderResult(e.response.data)); + }); + }; +}; + +export const changePassword = (email, password, token) => { + return function (dispatch) { + const data = { + token: token, + email: email, + newPass: password, + }; + dispatch(toggles.setResetResult("")); + dispatch(toggles.showResetCircle()); + axios + .post(backend + "user/resetpass", data) + .then((response) => { + dispatch(toggles.hideResetCircle()); + dispatch(toggles.setResetResult(response.data)); + }) + .catch((e) => { + dispatch(toggles.hideResetCircle()); + dispatch(toggles.setResetResult(e.response.data)); + }); + }; +}; + export const logout = () => { return function (dispatch) { dispatch(toggles.setLoggedOut()); @@ -151,6 +186,7 @@ export const logout = () => { export const tryRegister = (data) => { return function (dispatch) { + dispatch(toggles.setRegisterResult("")); dispatch(toggles.showRegisterCircle()); axios .post(backend + "user/register", data) diff --git a/src/actions/toggles.js b/src/actions/toggles.js index 97264b0..2b66e2d 100644 --- a/src/actions/toggles.js +++ b/src/actions/toggles.js @@ -10,18 +10,6 @@ export const hideDishes = () => { }; }; -export const showLoginDialog = () => { - return { - type: "DIALOG_LOGIN_VISIBLE", - }; -}; - -export const hideLoginDialog = () => { - return { - type: "DIALOG_LOGIN_HIDDEN", - }; -}; - export const setLoggedIn = (username, jwt, id, email) => { return { type: "SET_LOGGEDIN", @@ -35,30 +23,6 @@ export const setLoggedOut = () => { }; }; -export const showRegisterDialog = () => { - return { - type: "DIALOG_REGISTER_VISIBLE", - }; -}; - -export const hideRegisterDialog = () => { - return { - type: "DIALOG_REGISTER_HIDDEN", - }; -}; - -export const showRegulaminDialog = () => { - return { - type: "DIALOG_REGULAMIN_VISIBLE", - }; -}; - -export const hideRegulaminDialog = () => { - return { - type: "DIALOG_REGULAMIN_HIDDEN", - }; -}; - export const showRegisterCircle = () => { return { type: "DIALOG_REGISTER_CIRCLE_SHOW", @@ -91,14 +55,52 @@ export const setLoginResult = (text) => { }; }; -export const hideNewRestaurantDialog = () => { +export const setReminderResult = (text) => { return { - type: "DIALOG_NEWRESTAURANT_HIDE", + type: "DIALOG_REMINDER_SET_RESULT", + payload: text, }; }; -export const showNewRestaurantDialog = () => { +export const setResetResult = (text) => { return { - type: "DIALOG_NEWRESTAURANT_SHOW", + type: "DIALOG_RESET_SET_RESULT", + payload: text, + }; +}; + +export const hideRegulamin = () => { + return { + type: "DIALOG_REGULAMIN_HIDE", + }; +}; + +export const showRegulamin = () => { + return { + type: "DIALOG_REGULAMIN_SHOW", + }; +}; + +export const showReminderCircle = () => { + return { + type: "DIALOG_REMINDER_CIRCLE_SHOW", + }; +}; + +export const hideReminderCircle = () => { + return { + type: "DIALOG_REMINDER_CIRCLE_HIDE", + }; +}; + +export const showResetCircle = () => { + return { + type: "DIALOG_RESET_CIRCLE_SHOW", + }; +}; + +export const hideResetCircle = () => { + return { + type: "DIALOG_RESET_CIRCLE_HIDE", }; }; diff --git a/src/components/Dialogs.js b/src/components/Dialogs.js index 5ca21b1..5fa5ca4 100644 --- a/src/components/Dialogs.js +++ b/src/components/Dialogs.js @@ -1,16 +1,10 @@ import React from "react"; -import LoginDialog from "./Dialogs/LoginDialog"; -import RegisterDialog from "./Dialogs/RegisterDialog"; import RegulaminDialog from "./Dialogs/RegulaminDialog"; -import NewRestaurant from "./Dialogs/NewRestaurant"; export default function (props) { return (
- - -
); } diff --git a/src/components/Dialogs/ForgotPassword.js b/src/components/Dialogs/ForgotPassword.js new file mode 100644 index 0000000..26603a5 --- /dev/null +++ b/src/components/Dialogs/ForgotPassword.js @@ -0,0 +1,154 @@ +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import DialogContent from "@material-ui/core/DialogContent"; +import Dialog from "@material-ui/core/Dialog"; +import Divider from "@material-ui/core/Divider"; +import ButtonSecondary from "../Input/ButtonSecondary"; +import IconButton from "@material-ui/core/IconButton"; +import TextField from "@material-ui/core/TextField"; +import CloseIcon from "@material-ui/icons/Close"; +import { useSelector, useDispatch } from "react-redux"; +import Link from "@material-ui/core/Link"; +import validator from "validator"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import AccountCircle from "@material-ui/icons/AccountCircle"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import { remindPassword } from "../../actions"; +import { useHistory } from "react-router-dom"; +import { setReminderResult } from "../../actions/toggles"; + +export default function ForgotPassword(props) { + const initialData = { + email: "", + emailError: false, + }; + const [data, setData] = useState(initialData); + const reminderResult = useSelector( + (state) => state.data.dialogs.reminderResult + ); + const reminderCircle = useSelector( + (state) => state.data.dialogs.reminderCircularProgress + ); + const dispatch = useDispatch(); + const history = useHistory(); + + const loginStyles = makeStyles((theme) => ({ + root: { + textAlign: "center", + "& .MuiPaper-root": { + backgroundColor: "#262626", + color: "#bbbbbb", + }, + }, + closeButton: { + color: "#bbbbbb", + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + }, + textInput: { + marginTop: "20px", + marginBottom: "10px", + width: "90%", + "& .MuiInputBase-root": { + color: "#01c3a9", + }, + "& .MuiInputLabel-root": { + color: "#bbbbbb", + }, + }, + link: { + fontSize: "0.9rem", + }, + })); + + const loginClass = loginStyles(); + + const validateLogin = () => { + var valid; + var validation = { + email: validator.isEmail(data.email), + }; + setData({ + ...data, + emailError: !validation.email, + }); + valid = validation.email; + return valid; + }; + + const handleRemind = () => { + if (validateLogin()) { + dispatch(remindPassword(data.email)); + } else { + dispatch(setReminderResult("Podaj poprawne dane.")); + } + }; + + // CODE + + return ( +
+ history.push("/")} + open={true} + aria-labelledby="login-title" + > + Odzyskiwanie hasła + history.push("/")} + aria-label="close" + > + + + + +

+ Podaj adres email do swojego konta. Link do zmiany hasła powinien + dotrzeć do Ciebie w ciągu maksymalnie 15 minut (sprawdź również + folder SPAM). Jeśli nie pamiętasz również adresu email, skontaktuj + się z obsługą klienta. +

+ (data.email = event.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> +

+ {reminderResult} + {reminderCircle && } +

+
+ handleRemind()} + text="Wyślij email" + /> +
+

+ Nie masz konta?{" "} + + history.push("/register")}> + Zarejestruj się. + + +

+
+
+
+ ); +} diff --git a/src/components/Dialogs/LoginDialog.js b/src/components/Dialogs/LoginDialog.js index da90838..0ce0576 100644 --- a/src/components/Dialogs/LoginDialog.js +++ b/src/components/Dialogs/LoginDialog.js @@ -9,17 +9,14 @@ import IconButton from "@material-ui/core/IconButton"; import TextField from "@material-ui/core/TextField"; import CloseIcon from "@material-ui/icons/Close"; import { useSelector, useDispatch } from "react-redux"; -import { - hideLoginDialog, - showRegisterDialog, - setLoginResult, -} from "../../actions/toggles"; +import { setLoginResult } from "../../actions/toggles"; import Link from "@material-ui/core/Link"; import LockIcon from "@material-ui/icons/Lock"; import validator from "validator"; import InputAdornment from "@material-ui/core/InputAdornment"; import AccountCircle from "@material-ui/icons/AccountCircle"; import { tryLogin } from "../../actions"; +import { useHistory } from "react-router-dom"; export default function LoginDialog(props) { const initialData = { @@ -29,9 +26,9 @@ export default function LoginDialog(props) { passwordError: false, }; const [loginData, setLoginData] = useState(initialData); - const loginDialog = useSelector((state) => state.data.dialogs.logIn); const loginResult = useSelector((state) => state.data.dialogs.loginResult); const dispatch = useDispatch(); + const history = useHistory(); const loginStyles = makeStyles((theme) => ({ root: { @@ -63,11 +60,6 @@ export default function LoginDialog(props) { }, })); - const handleRegisterClick = () => { - dispatch(hideLoginDialog()); - dispatch(showRegisterDialog()); - }; - const loginClass = loginStyles(); const validateLogin = () => { @@ -100,14 +92,14 @@ export default function LoginDialog(props) {
dispatch(hideLoginDialog())} - open={loginDialog} + onClose={() => history.push("/")} + open={true} aria-labelledby="login-title" > Logowanie dispatch(hideLoginDialog())} + onClick={() => history.push("/")} aria-label="close" > @@ -155,14 +147,14 @@ export default function LoginDialog(props) { handleRegisterClick()} + onClick={() => history.push("/forgotpassword")} > Nie pamiętam hasła.

Nie masz konta?{" "} - handleRegisterClick()}> + history.push("/register")}> Zarejestruj się. diff --git a/src/components/Dialogs/NewRestaurant.js b/src/components/Dialogs/NewRestaurant.js index c71135d..84532ae 100644 --- a/src/components/Dialogs/NewRestaurant.js +++ b/src/components/Dialogs/NewRestaurant.js @@ -8,8 +8,6 @@ import ButtonSecondary from "../Input/ButtonSecondary"; import IconButton from "@material-ui/core/IconButton"; import TextField from "@material-ui/core/TextField"; import CloseIcon from "@material-ui/icons/Close"; -import { useSelector, useDispatch } from "react-redux"; -import { hideNewRestaurantDialog } from "../../actions/toggles"; import Stepper from "@material-ui/core/Stepper"; import Step from "@material-ui/core/Step"; import StepLabel from "@material-ui/core/StepLabel"; @@ -18,6 +16,7 @@ import Autocomplete from "@material-ui/lab/Autocomplete"; import InputAdornment from "@material-ui/core/InputAdornment"; import ImageUpload from "../Input/ImageUpload"; import validator from "validator"; +import { useHistory } from "react-router-dom"; // ICONS import FastfoodIcon from "@material-ui/icons/Fastfood"; import LocationCityIcon from "@material-ui/icons/LocationCity"; @@ -62,6 +61,9 @@ const useStyles = makeStyles((theme) => ({ "& .MuiInputLabel-root": { color: "#bbbbbb", }, + "$ .MuiFormHelperText-root": { + color: "#bbbbbb", + }, }, timePicker: { margin: theme.spacing(2), @@ -98,13 +100,13 @@ export default function NewRestaurant() { nameError: false, cityError: false, adressError: false, + descriptionError: false, + charLeft: 400, }; const steps = ["Informacje", "Zdjęcie", "Lokalizacja"]; const [state, setState] = useState(initialState); const [activeStep, setActiveStep] = React.useState(0); const styles = useStyles(); - const dialogOpen = useSelector((state) => state.data.dialogs.newRestaurant); - const dispatch = useDispatch(); const availableTags = [ "Płatność kartą", "Lubimy zwierzaki", @@ -114,6 +116,7 @@ export default function NewRestaurant() { "Podajemy alkohol", "Dowozimy", ]; + const history = useHistory(); // HANDLERS @@ -125,9 +128,14 @@ export default function NewRestaurant() { } }; + const handleDescriptionChange = (event) => { + let stringLength = event.target.value.length; + let charleft = 400 - stringLength; + setState({ ...state, description: event.target.value, charLeft: charleft }); + }; + const handlePreviousButton = () => { setActiveStep(activeStep - 1); - console.log(activeStep); }; const validateForm = () => { @@ -135,16 +143,24 @@ export default function NewRestaurant() { nameValid: validator.isLength(state.name, { min: 1, max: 40 }), cityValid: validator.isLength(state.city, { min: 1, max: 40 }), adressValid: validator.isLength(state.name, { min: 1, max: 40 }), + descriptionValid: validator.isLength(state.description, { + min: 1, + max: 400, + }), }; setState({ ...state, nameError: !validation.nameValid, cityError: !validation.cityValid, adressError: !validation.adressValid, + descriptionError: !validation.descriptionValid, }); return ( - validation.nameValid && validation.cityValid && validation.adressValid + validation.nameValid && + validation.cityValid && + validation.adressValid && + validation.descriptionValid ); }; @@ -154,13 +170,13 @@ export default function NewRestaurant() {

Dodaj Lokal dispatch(hideNewRestaurantDialog())} + onClick={() => history.goBack()} aria-label="close" > @@ -273,12 +289,17 @@ export default function NewRestaurant() { fullWidth label="Opis" value={state.description} - onChange={(event) => - setState({ ...state, description: event.target.value }) - } + onChange={(event) => handleDescriptionChange(event)} multiline + error={state.descriptionError} rows={3} + rowsMax={8} variant="outlined" + helperText={"Pozostałe znaki: " + state.charLeft} + FormHelperTextProps={{ + style: { color: "#bbbbbb" }, + }} + required /> state.data.dialogs.register); var circularProgress = useSelector( (state) => state.data.dialogs.registerCircularProgress ); @@ -49,6 +51,7 @@ export default function RegisterDialog(props) { (state) => state.data.dialogs.registerResult ); const dispatch = useDispatch(); + const history = useHistory(); // STYLES @@ -88,7 +91,7 @@ export default function RegisterDialog(props) { const handleRegulaminClick = (event) => { event.preventDefault(); - dispatch(showRegulaminDialog()); + dispatch(showRegulamin()); }; const validateForm = () => { @@ -96,6 +99,9 @@ export default function RegisterDialog(props) { const validations = { firstname: !validator.isEmpty(formData.firstname), lastname: !validator.isEmpty(formData.lastname), + companyName: !validator.isEmpty(formData.companyName), + adress: !validator.isEmpty(formData.adress), + NIP: !validator.isEmpty(formData.NIP), email: validator.isEmail(formData.email), password: validator.isLength(formData.password, { min: 8, @@ -108,6 +114,9 @@ export default function RegisterDialog(props) { ...formData, firstnameError: !validations.firstname, lastnameError: !validations.lastname, + companyNameError: !validations.companyName, + adressError: !validations.adress, + NIPError: !validations.NIP, emailError: !validations.email, passwordError: !validations.password, repeatPasswordError: !validations.repeatPassword, @@ -117,7 +126,10 @@ export default function RegisterDialog(props) { validations.lastname && validations.email && validations.password && - validations.repeatPassword; + validations.repeatPassword && + validations.companyName && + validations.adress && + validations.NIP; return valid; }; @@ -126,29 +138,24 @@ export default function RegisterDialog(props) { if (validateForm()) { dispatch(tryRegister(form)); } else { - dispatch(setRegisterResult("Proszę poprawić poprawić formularz.")); + dispatch(setRegisterResult("Proszę poprawić formularz.")); } }; - const openLogin = () => { - dispatch(hideRegisterDialog()); - dispatch(showLoginDialog()); - }; - // CODE return (
dispatch(hideRegisterDialog())} - open={registerDialog} + onClose={() => history.goBack()} + open={true} aria-labelledby="login-title" > Rejestracja dispatch(hideRegisterDialog())} + onClick={() => history.goBack()} aria-label="close" > @@ -191,6 +198,59 @@ export default function RegisterDialog(props) { error={formData.lastnameError} onChange={(event) => (formData.lastname = event.target.value)} /> + + + + ), + }} + error={formData.companyNameError} + onChange={(event) => + (formData.companyName = event.target.value) + } + /> + + + + ), + }} + error={formData.adressError} + onChange={(event) => (formData.adress = event.target.value)} + /> + + + + ), + }} + error={formData.NIPError} + onChange={(event) => (formData.NIP = event.target.value)} + /> ) : ( - openLogin()} text="Logowanie" /> + history.push("/login")} + text="Logowanie" + /> )}
diff --git a/src/components/Dialogs/RegulaminDialog.js b/src/components/Dialogs/RegulaminDialog.js index 5f98828..6221e88 100644 --- a/src/components/Dialogs/RegulaminDialog.js +++ b/src/components/Dialogs/RegulaminDialog.js @@ -7,13 +7,13 @@ import Divider from "@material-ui/core/Divider"; import IconButton from "@material-ui/core/IconButton"; import CloseIcon from "@material-ui/icons/Close"; import Paper from "@material-ui/core/Paper"; -import { useSelector, useDispatch } from "react-redux"; -import { hideRegulaminDialog } from "../../actions/toggles"; import Regulamin from "./Regulamin"; +import { useSelector, useDispatch } from "react-redux"; +import { hideRegulamin } from "../../actions/toggles"; export default function RegulaminDialog(props) { - var regulaminDialog = useSelector((state) => state.data.dialogs.regulamin); const dispatch = useDispatch(); + const open = useSelector((state) => state.data.dialogs.regulamin); const loginStyles = makeStyles((theme) => ({ root: { @@ -47,14 +47,14 @@ export default function RegulaminDialog(props) {
dispatch(hideRegulaminDialog())} + open={open} + onClose={() => dispatch(hideRegulamin())} aria-labelledby="regulamin-title" > Regulamin dispatch(hideRegulaminDialog())} + onClick={() => dispatch(hideRegulamin())} aria-label="close" > diff --git a/src/components/Dialogs/ResetPassword.js b/src/components/Dialogs/ResetPassword.js new file mode 100644 index 0000000..ebba425 --- /dev/null +++ b/src/components/Dialogs/ResetPassword.js @@ -0,0 +1,184 @@ +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import DialogContent from "@material-ui/core/DialogContent"; +import Dialog from "@material-ui/core/Dialog"; +import Divider from "@material-ui/core/Divider"; +import ButtonSecondary from "../Input/ButtonSecondary"; +import IconButton from "@material-ui/core/IconButton"; +import TextField from "@material-ui/core/TextField"; +import CloseIcon from "@material-ui/icons/Close"; +import { useSelector, useDispatch } from "react-redux"; +import validator from "validator"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import AccountCircle from "@material-ui/icons/AccountCircle"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import { useHistory, useLocation } from "react-router-dom"; +import { changePassword } from "../../actions/index"; +import { setResetResult } from "../../actions/toggles"; + +function useQuery() { + return new URLSearchParams(useLocation().search); +} + +export default function ResetPassword(props) { + const initialData = { + email: "", + emailError: false, + password: "", + passwordError: false, + passwordRepeat: "", + passwordRepeatError: false, + }; + const [data, setData] = useState(initialData); + const resetResult = useSelector((state) => state.data.dialogs.resetResult); + const resetCircle = useSelector( + (state) => state.data.dialogs.resetCircularProgress + ); + const dispatch = useDispatch(); + const history = useHistory(); + const query = useQuery(); + const token = query.get("token"); + + const loginStyles = makeStyles((theme) => ({ + root: { + textAlign: "center", + "& .MuiPaper-root": { + backgroundColor: "#262626", + color: "#bbbbbb", + }, + }, + closeButton: { + color: "#bbbbbb", + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + }, + textInput: { + marginTop: "20px", + marginBottom: "10px", + width: "90%", + "& .MuiInputBase-root": { + color: "#01c3a9", + }, + "& .MuiInputLabel-root": { + color: "#bbbbbb", + }, + }, + link: { + fontSize: "0.9rem", + }, + })); + + const loginClass = loginStyles(); + + const validateLogin = () => { + var valid; + var validation = { + email: validator.isEmail(data.email), + password: validator.isLength(data.password, { min: 6 }), + passwordRepeat: data.passwordRepeat === data.password, + }; + setData({ + ...data, + emailError: !validation.email, + passwordError: !validation.password, + passwordRepeatError: !validation.passwordRepeat, + }); + valid = + validation.password && validation.passwordRepeat && validation.email; + return valid; + }; + + const handleReset = () => { + if (validateLogin()) { + dispatch(changePassword(data.email, data.password, token)); + } else { + dispatch(setResetResult("Popraw dane.")); + } + }; + + // CODE + + return ( +
+ history.push("/")} + open={true} + aria-labelledby="login-title" + > + Ustaw nowe hasło + history.push("/")} + aria-label="close" + > + + + + +

Podaj nowe bezpieczne hasło do konta.

+ (data.email = event.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + (data.password = event.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + (data.passwordRepeat = event.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> +
+ {resetCircle && } +

{resetResult}

+
+
+ handleReset()} text="Zmień hasło" /> +
+
+
+
+ ); +} diff --git a/src/components/Input/ImageUpload.js b/src/components/Input/ImageUpload.js index b02720a..aaa3a97 100644 --- a/src/components/Input/ImageUpload.js +++ b/src/components/Input/ImageUpload.js @@ -1,8 +1,10 @@ import React, { useState } from "react"; +import CircularProgress from "@material-ui/core/CircularProgress"; import axios from "axios"; export default function ImageUpload() { const [imagePreviewURL, setPreviewURL] = useState(""); + let showCircle = false; const handleInputChange = (event) => { let data = new FormData(); @@ -31,7 +33,11 @@ export default function ImageUpload() { reader.readAsDataURL(event.target.files[0]); }; - let imagePreview =
Proszę wybrać obraz.
; + let imagePreview = ( +
+ {showCircle ? : "Proszę wybrać obraz."} +
+ ); if (imagePreviewURL) { imagePreview = (
({ paper: { @@ -40,30 +39,28 @@ export default function TopBar() { const classes = useStyles(); const loggedIn = useSelector((state) => state.data.loggedIn); const username = useSelector((state) => state.data.username); + const history = useHistory(); const dispatch = useDispatch(); - const imgClick = () => { - dispatch(setAppMode("APP_INIT")); - }; const handleClick = (button) => { setState((state.menuOpen = false)); switch (button) { case "menui": - dispatch(setAppMode("APP_INIT")); + history.push("/"); break; case "logIn": - dispatch(showLoginDialog()); + history.push("/login"); break; case "register": - dispatch(showRegisterDialog()); + history.push("/register"); break; case "logOut": dispatch(logout()); break; case "myRestaurant": - dispatch(setAppMode("APP_RESTAURANT")); + history.push("/restaurant"); break; case "addDish": - dispatch(setAppMode("APP_ADD_DISH")); + history.push("/"); break; default: return true; @@ -73,6 +70,9 @@ export default function TopBar() { menuOpen: false, }); + const closeDrawer = () => { + setState({ menuOpen: false }); + }; const toggleDrawer = (open) => (event) => { if ( event.type === "keydown" && @@ -91,7 +91,7 @@ export default function TopBar() { src={logo} className="topBarLogo" alt="Menui logo" - onClick={() => imgClick()} + onClick={() => history.push("/")} />
Food guide
@@ -141,7 +141,9 @@ export default function TopBar() { - {loggedIn && } + + {loggedIn && } +
diff --git a/src/components/UserMenu.js b/src/components/UserMenu.js index 743ed6f..136bd1c 100644 --- a/src/components/UserMenu.js +++ b/src/components/UserMenu.js @@ -11,6 +11,7 @@ import ExpandMore from "@material-ui/icons/ExpandMore"; import FastfoodIcon from "@material-ui/icons/Fastfood"; import AddIcon from "@material-ui/icons/Add"; import SettingsIcon from "@material-ui/icons/Settings"; +import { useHistory } from "react-router-dom"; const useStyles = makeStyles((theme) => ({ root: { @@ -33,6 +34,11 @@ export default function UserMenu(props) { const handleClick = () => { setOpen(!open); }; + const handleAddRestaurant = () => { + props.closeMenu(); + history.push("/newRestaurant"); + }; + const history = useHistory(); return ( - + diff --git a/src/index.js b/src/index.js index 3c558ff..e3990e0 100644 --- a/src/index.js +++ b/src/index.js @@ -5,23 +5,30 @@ import { Provider } from "react-redux"; import "./index.scss"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; -import rootReducer from "./reducers"; import thunk from "redux-thunk"; +import { createBrowserHistory } from "history"; import { createStore, applyMiddleware, compose } from "redux"; +import { routerMiddleware, ConnectedRouter } from "connected-react-router"; +import rootReducer from "./reducers"; + +const history = createBrowserHistory(); ReactGA.initialize("G-SHB9LXPWWM"); ReactGA.pageview("/"); + const store = createStore( - rootReducer, + rootReducer(history), compose( - applyMiddleware(thunk), + applyMiddleware(routerMiddleware(history), thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ) ); ReactDOM.render( - + + + , document.getElementById("root") ); diff --git a/src/reducers/data.js b/src/reducers/data.js index fc78101..c6d9092 100644 --- a/src/reducers/data.js +++ b/src/reducers/data.js @@ -6,16 +6,15 @@ const initialState = { userId: "", userEmail: "", dialogs: { - logIn: false, - register: false, - newRestaurant: true, - contact: false, - pricing: false, - regulamin: false, registerCircularProgress: false, registerForm: true, registerResult: "", loginResult: "", + regulamin: false, + reminderResult: "", + reminderCircularProgress: false, + resetResult: "", + resetCircularProgress: false, }, tempData: {}, }; @@ -26,8 +25,6 @@ const data = (state = initialState, action) => { return (state = { ...state, showDishList: true }); case "SET_DISHLIST_HIDDEN": return (state = { ...state, showDishList: false }); - case "DIALOG_LOGIN_VISIBLE": - return (state = { ...state, dialogs: { ...state.dialogs, logIn: true } }); case "SET_LOGGEDIN": return (state = { ...state, @@ -46,36 +43,21 @@ const data = (state = initialState, action) => { userEmail: "", userId: "", }); - case "DIALOG_LOGIN_HIDDEN": - return (state = { - ...state, - dialogs: { ...state.dialogs, logIn: false }, - }); - case "DIALOG_REGISTER_VISIBLE": - return (state = { - ...state, - dialogs: { ...state.dialogs, register: true }, - }); - case "DIALOG_REGISTER_HIDDEN": - return (state = { - ...state, - dialogs: { ...state.dialogs, register: false }, - }); - case "DIALOG_REGULAMIN_VISIBLE": - return (state = { - ...state, - dialogs: { ...state.dialogs, regulamin: true }, - }); - case "DIALOG_REGULAMIN_HIDDEN": - return (state = { - ...state, - dialogs: { ...state.dialogs, regulamin: false }, - }); case "DIALOG_REGISTER_CIRCLE_SHOW": return (state = { ...state, dialogs: { ...state.dialogs, registerCircularProgress: true }, }); + case "DIALOG_REGULAMIN_SHOW": + return (state = { + ...state, + dialogs: { ...state.dialogs, regulamin: true }, + }); + case "DIALOG_REGULAMIN_HIDE": + return (state = { + ...state, + dialogs: { ...state.dialogs, regulamin: false }, + }); case "DIALOG_REGISTER_CIRCLE_HIDE": return (state = { ...state, @@ -91,20 +73,40 @@ const data = (state = initialState, action) => { ...state, dialogs: { ...state.dialogs, registerResult: action.payload }, }); + case "DIALOG_REMINDER_SET_RESULT": + return (state = { + ...state, + dialogs: { ...state.dialogs, reminderResult: action.payload }, + }); + case "DIALOG_REMINDER_CIRCLE_SHOW": + return (state = { + ...state, + dialogs: { ...state.dialogs, reminderCircularProgress: true }, + }); + case "DIALOG_REMINDER_CIRCLE_HIDE": + return (state = { + ...state, + dialogs: { ...state.dialogs, reminderCircularProgress: false }, + }); + case "DIALOG_RESET_CIRCLE_SHOW": + return (state = { + ...state, + dialogs: { ...state.dialogs, resetCircularProgress: true }, + }); + case "DIALOG_RESET_CIRCLE_HIDE": + return (state = { + ...state, + dialogs: { ...state.dialogs, resetCircularProgress: false }, + }); case "DIALOG_LOGIN_SET_RESULT": return (state = { ...state, dialogs: { ...state.dialogs, loginResult: action.payload }, }); - case "DIALOG_NEWRESTAURANT_HIDE": + case "DIALOG_RESET_SET_RESULT": return (state = { ...state, - dialogs: { ...state.dialogs, newRestaurant: false }, - }); - case "DIALOG_NEWRESTAURANT_SHOW": - return (state = { - ...state, - dialogs: { ...state.dialogs, newRestaurant: true }, + dialogs: { ...state.dialogs, resetResult: action.payload }, }); case "SET_TEMP_DATA": return (state = { ...state, tempData: action.payload }); diff --git a/src/reducers/index.js b/src/reducers/index.js index d437e3f..b65e083 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,4 +1,5 @@ import { combineReducers } from "redux"; +import { connectRouter } from "connected-react-router"; import autoCompleteReducer from "./autoComplete"; import searchResults from "./searchResults"; import appMode from "./appMode"; @@ -7,14 +8,16 @@ import restaurant from "./restaurant"; import dishes from "./dishes"; import data from "./data"; -const rootReducer = combineReducers({ - autocomplete: autoCompleteReducer, - appMode: appMode, - searchResults: searchResults, - searchQuery: searchQuery, - restaurant: restaurant, - dishes: dishes, - data: data, -}); +const rootReducer = (history) => + combineReducers({ + router: connectRouter(history), + autocomplete: autoCompleteReducer, + appMode: appMode, + searchResults: searchResults, + searchQuery: searchQuery, + restaurant: restaurant, + dishes: dishes, + data: data, + }); export default rootReducer;