diff --git a/package-lock.json b/package-lock.json index 1dbd6d7..9f66665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9368,6 +9368,15 @@ "sort-keys": "^1.0.0" } }, + "notistack": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.0.tgz", + "integrity": "sha512-kIGwVy8qs2hpcDegLhjTQMlGkJvr7OVUg6mnOcjqwKOCZhCnkMt5h53kP9bPRKQQ3UckCzhkvuEPbyMbyYyRNQ==", + "requires": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", diff --git a/package.json b/package.json index fd660c4..5c6b162 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "axios": "^0.19.2", "connected-react-router": "^6.8.0", "node-sass": "^4.14.1", + "notistack": "^1.0.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-ga": "^3.1.2", diff --git a/src/App.js b/src/App.js index dff6965..8ec2e74 100644 --- a/src/App.js +++ b/src/App.js @@ -1,8 +1,11 @@ import React from "react"; import { Router, Switch, Route } from "react-router-dom"; +import { SnackbarProvider } from "notistack"; import PrivateRoute from "./components/PrivateRoute"; import "./App.scss"; import TopBar from "./components/TopBar"; +import Notifier from "./components/Notifier"; +import AppBackdrop from "./components/Output/AppBackdrop"; import LogoMain from "./components/Output/logoMain"; import Footer from "./components/Output/Footer"; import SearchPanel from "./components/Input/SearchPanel"; @@ -37,57 +40,67 @@ function App(props) { return ( -
- -
- - - - -

- Sprawdź co serwuje Twoja ulubiona restauracja. -

-
- - - - - - - - - - - - - - - - - - - - - } - /> - } /> - } - /> - - - - - - -
+ +
+ + + +
+ + + + +

+ Sprawdź co serwuje Twoja ulubiona restauracja. +

+
+ + + + + + + + + + + + + + + + + + + + + } + /> + } /> + } + /> + + + + + + +
+
+ +
- -
-
+ ); diff --git a/src/actions/index.js b/src/actions/index.js index ea51be4..42416e9 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -81,7 +81,9 @@ export const fetchRestaurant = (id) => { dispatch(push("/restaurant")); dispatch(fetchAllDishes(id)); }) - .catch((err) => console.log(err)); + .catch((err) => + dispatch(notification("Nie udało się pobrać restauracji.", "error")) + ); }; }; @@ -103,11 +105,41 @@ export const fetchAllDishes = (id) => { }; }; +export const refreshUserData = (token) => { + return function (dispatch) { + axios({ + url: backend + "user/refresh", + method: "POST", + headers: { + "x-auth-token": token, + }, + }) + .then((response) => { + dispatch( + toggles.setLoggedIn( + response.data.firstname, + response.data.lastname, + token, + response.data.id, + response.data.email, + response.data.NIP, + response.data.adress, + response.data.companyName, + response.data.restaurants + ) + ); + }) + .catch((err) => { + console.log(err); + }); + }; +}; + export const tryLogin = (username, password) => { const data = { email: username, password: password }; return function (dispatch) { - dispatch(toggles.setLoginResult("")); + dispatch(toggles.showBackdrop()); axios .post(backend + "user/login", data) .then((response) => { @@ -125,21 +157,22 @@ export const tryLogin = (username, password) => { response.data.restaurants ) ); + dispatch(toggles.hideBackdrop()); + dispatch(notification(`Witaj ${response.data.firstname}!`, "success")); dispatch(push("/")); }) .catch((err) => { if (!err.response) { console.log(err); } else if (err.response.status === 404) { - dispatch( - toggles.setLoginResult( - "Użytkownik o podanym adresie email nie istnieje." - ) - ); + dispatch(toggles.hideBackdrop()); + dispatch(notification("Użytkownik nie istnieje :(", "error")); } else if (err.response.status === 401) { - dispatch(toggles.setLoginResult("Podane hasło jest nieprawidłowe")); + dispatch(toggles.hideBackdrop()); + dispatch(notification("Błędne dane logowania :(", "error")); } else { - dispatch(toggles.setLoginResult("Błąd serwera...")); + dispatch(toggles.hideBackdrop()); + dispatch(notification("Błąd serwera :(", "error")); } }); }; @@ -148,17 +181,16 @@ export const tryLogin = (username, password) => { export const remindPassword = (email) => { return function (dispatch) { const data = { email: email }; - dispatch(toggles.setReminderResult("")); - dispatch(toggles.showReminderCircle()); + dispatch(toggles.showBackdrop()); axios .post(backend + "user/forgotpassword", data) .then((response) => { - dispatch(toggles.hideReminderCircle()); - dispatch(toggles.setReminderResult(response.data)); + dispatch(toggles.hideBackdrop()); + dispatch(notification(response.data, "info")); }) .catch((e) => { - dispatch(toggles.hideReminderCircle()); - dispatch(toggles.setReminderResult(e.response.data)); + dispatch(toggles.hideBackdrop()); + dispatch(notification(e.response.data, "error")); }); }; }; @@ -170,59 +202,60 @@ export const changePassword = (email, password, token) => { email: email, newPass: password, }; - dispatch(toggles.setResetResult("")); - dispatch(toggles.showResetCircle()); + dispatch(toggles.showBackdrop()); axios .post(backend + "user/resetpass", data) .then((response) => { - dispatch(toggles.hideResetCircle()); - dispatch(toggles.setResetResult(response.data)); + dispatch(toggles.hideBackdrop()); + dispatch(notification(response.data, "info")); }) .catch((e) => { - dispatch(toggles.hideResetCircle()); - dispatch(toggles.setResetResult(e.response.data)); + dispatch(toggles.hideBackdrop()); + dispatch(notification(e.response.data, "error")); }); }; }; export const logout = () => { return function (dispatch) { + dispatch(notification("Poprawnie wylogowano.", "success")); dispatch(toggles.setLoggedOut()); }; }; export const tryRegister = (data) => { return function (dispatch) { - dispatch(toggles.setRegisterResult("")); - dispatch(toggles.showRegisterCircle()); + dispatch(toggles.showBackdrop()); axios .post(backend + "user/register", data) .then((response) => { if (response.status === 201) { dispatch( - toggles.setRegisterResult( - "Dziękujemy za rejestrację. Teraz możesz zalogować się do swojego konta." + notification( + "Rejestracja przebiegła pomyślnie, możesz teraz zalogować się do swojego konta.", + "success" ) ); - dispatch(toggles.hideRegisterCircle()); - dispatch(toggles.hideRegisterForm()); + dispatch(toggles.hideBackdrop()); } }) .catch((err) => { if (err.response.status === 500) { dispatch( - toggles.setRegisterResult( - "Wystąpił nieoczekiwany błąd serwera, przepraszamy..." + notification( + "Wystąpił nieoczekiwany błąd serwera, przepraszamy...", + "error" ) ); - dispatch(toggles.hideRegisterCircle()); + dispatch(toggles.hideBackdrop()); } else if (err.response.status === 409) { dispatch( - toggles.setRegisterResult( - "Adres email jest już zajęty, proszę użyć innego." + notification( + "Adres email jest już zajęty, proszę użyć innego.", + "error" ) ); - dispatch(toggles.hideRegisterCircle()); + dispatch(toggles.hideBackdrop()); } }); }; @@ -240,3 +273,24 @@ export const clearTempData = () => { type: "CLEAR_TEMP_DATA", }; }; + +export const notification = (message, type) => { + return function (dispatch) { + dispatch( + toggles.enqueueSnackbar({ + message: message, + options: { + key: new Date().getTime() + Math.random(), + variant: type, + }, + }) + ); + }; +}; + +export const updateRestaurant = (restaurant) => { + return { + type: "UPDATE_RESTAURANT", + payload: restaurant, + }; +}; diff --git a/src/actions/toggles.js b/src/actions/toggles.js index 178dece..7a2540d 100644 --- a/src/actions/toggles.js +++ b/src/actions/toggles.js @@ -43,52 +43,6 @@ export const setLoggedOut = () => { }; }; -export const showRegisterCircle = () => { - return { - type: "DIALOG_REGISTER_CIRCLE_SHOW", - }; -}; - -export const hideRegisterCircle = () => { - return { - type: "DIALOG_REGISTER_CIRCLE_HIDE", - }; -}; - -export const hideRegisterForm = () => { - return { - type: "DIALOG_REGISTER_FORM_HIDE", - }; -}; - -export const setRegisterResult = (text) => { - return { - type: "DIALOG_REGISTER_SET_RESULT", - payload: text, - }; -}; - -export const setLoginResult = (text) => { - return { - type: "DIALOG_LOGIN_SET_RESULT", - payload: text, - }; -}; - -export const setReminderResult = (text) => { - return { - type: "DIALOG_REMINDER_SET_RESULT", - payload: text, - }; -}; - -export const setResetResult = (text) => { - return { - type: "DIALOG_RESET_SET_RESULT", - payload: text, - }; -}; - export const hideRegulamin = () => { return { type: "DIALOG_REGULAMIN_HIDE", @@ -101,26 +55,31 @@ export const showRegulamin = () => { }; }; -export const showReminderCircle = () => { +export const enqueueSnackbar = (notification) => { + const key = notification.options && notification.options.key; + return { - type: "DIALOG_REMINDER_CIRCLE_SHOW", + type: "ENQUEUE_SNACKBAR", + notification: { + ...notification, + key: key || new Date().getTime() + Math.random(), + }, }; }; -export const hideReminderCircle = () => { +export const removeSnackbar = (key) => ({ + type: "REMOVE_SNACKBAR", + key, +}); + +export const showBackdrop = () => { return { - type: "DIALOG_REMINDER_CIRCLE_HIDE", + type: "SHOW_BACKDROP", }; }; -export const showResetCircle = () => { +export const hideBackdrop = () => { return { - type: "DIALOG_RESET_CIRCLE_SHOW", - }; -}; - -export const hideResetCircle = () => { - return { - type: "DIALOG_RESET_CIRCLE_HIDE", + type: "HIDE_BACKDROP", }; }; diff --git a/src/components/Dialogs/EditRestaurant.js b/src/components/Dialogs/EditRestaurant.js index abdb79e..121d713 100644 --- a/src/components/Dialogs/EditRestaurant.js +++ b/src/components/Dialogs/EditRestaurant.js @@ -14,6 +14,7 @@ import RestaurantMenuIcon from "@material-ui/icons/RestaurantMenu"; import FastfoodIcon from "@material-ui/icons/Fastfood"; import AddIcon from "@material-ui/icons/Add"; import Badge from "@material-ui/core/Badge"; +import SearchIcon from "@material-ui/icons/Search"; //-------------- import EditRestaurantInfo from "../EditRestaurant/EditRestaurantInfo"; import EditRestaurantLocation from "../EditRestaurant/EditRestaurantLocation"; @@ -84,6 +85,15 @@ export default function EditRestaurant(props) {

{restaurant.name}

+ history.push(`/restaurant/${restaurant._id}`)} + > + + + + + state.data.dialogs.reminderResult - ); - const reminderCircle = useSelector( - (state) => state.data.dialogs.reminderCircularProgress - ); const dispatch = useDispatch(); const history = useHistory(); @@ -82,7 +74,7 @@ export default function ForgotPassword(props) { if (validateLogin()) { dispatch(remindPassword(data.email)); } else { - dispatch(setReminderResult("Podaj poprawne dane.")); + dispatch(notification("Podaj poprawne dane.", "error")); } }; @@ -129,10 +121,6 @@ export default function ForgotPassword(props) { ), }} /> -

- {reminderResult} - {reminderCircle && } -

handleRemind()} diff --git a/src/components/Dialogs/LoginDialog.js b/src/components/Dialogs/LoginDialog.js index 0ce0576..f983284 100644 --- a/src/components/Dialogs/LoginDialog.js +++ b/src/components/Dialogs/LoginDialog.js @@ -8,14 +8,13 @@ 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 { setLoginResult } from "../../actions/toggles"; +import { useDispatch } from "react-redux"; 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 { tryLogin, notification } from "../../actions"; import { useHistory } from "react-router-dom"; export default function LoginDialog(props) { @@ -26,7 +25,6 @@ export default function LoginDialog(props) { passwordError: false, }; const [loginData, setLoginData] = useState(initialData); - const loginResult = useSelector((state) => state.data.dialogs.loginResult); const dispatch = useDispatch(); const history = useHistory(); @@ -82,7 +80,7 @@ export default function LoginDialog(props) { if (validateLogin()) { dispatch(tryLogin(loginData.email, loginData.password)); } else { - dispatch(setLoginResult("Podaj poprawne dane logowania.")); + dispatch(notification("Podaj poprawne dane logowania.", "error")); } }; @@ -140,7 +138,6 @@ export default function LoginDialog(props) { ), }} /> -

{loginResult}

handleLogin()} text="Zaloguj" />
diff --git a/src/components/Dialogs/NewRestaurant.js b/src/components/Dialogs/NewRestaurant.js index c13a28b..07ec6ee 100644 --- a/src/components/Dialogs/NewRestaurant.js +++ b/src/components/Dialogs/NewRestaurant.js @@ -18,10 +18,11 @@ import ImageUpload from "../Input/ImageUpload"; import validator from "validator"; import { useHistory } from "react-router-dom"; import InputGoogleMaps from "../Input/InputGoogleMaps"; -import InfoDialog from "../Output/InfoDialog"; import { prepareTags } from "../../Services.js"; import axios from "axios"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; +import { notification, refreshUserData } from "../../actions"; +import { showBackdrop, hideBackdrop } from "../../actions/toggles.js"; import InputWorkingHours from "../Input/InputWorkingHours"; // ICONS import FastfoodIcon from "@material-ui/icons/Fastfood"; @@ -84,13 +85,14 @@ const useStyles = makeStyles((theme) => ({ })); export default function NewRestaurant() { + const dispatch = useDispatch(); const initialState = { name: "", city: "", adress: "", coordinates: [52.354293, 19.42377], placesId: "", - imgURL: "", + imgUrl: "", workingHours: { pn: "8:00 - 22:00", wt: "8:00 - 22:00", @@ -113,12 +115,9 @@ export default function NewRestaurant() { adressError: false, descriptionError: false, charLeft: 400, - response: "Dodawanie lokalu, prosimy o chwilę cierpliwości...", }; const steps = ["Informacje", "Zdjęcie", "Lokalizacja"]; const [state, setState] = useState(initialState); - const [open, setOpen] = useState(true); - const [loading, setLoading] = useState(true); const [activeStep, setActiveStep] = React.useState(0); const styles = useStyles(); const availableTags = [ @@ -131,7 +130,7 @@ export default function NewRestaurant() { "Dowozimy", ]; const history = useHistory(); - const token = useSelector((state) => state.data.jwt); + const token = useSelector((state) => state.data.userData.jwt); // HANDLERS @@ -143,7 +142,7 @@ export default function NewRestaurant() { adress: state.adress, coordinates: state.coordinates, placesId: state.placesId, - imgURL: state.imgURL, + imgUrl: state.imgUrl, workingHours: state.workingHours, description: state.description, tags: formattedTags, @@ -151,7 +150,7 @@ export default function NewRestaurant() { phone: state.phone, hidden: false, }; - setOpen(false); + dispatch(showBackdrop()); axios({ url: "http://localhost:4000/restaurant", method: "POST", @@ -161,26 +160,26 @@ export default function NewRestaurant() { }, }) .then((response) => { - setLoading(false); - setState({ - ...state, - response: + dispatch(hideBackdrop()); + dispatch( + notification( "Lokal został dodany, aktywuj subskrypcję, aby był widoczny w wynikach wyszukiwania.", - }); - setTimeout(() => { - history.push("/"); - }, 5000); + "success" + ) + ); + dispatch(refreshUserData(token)); + history.push("/"); }) .catch((error) => { - setLoading(false); - setState({ - ...state, - response: + dispatch(hideBackdrop()); + console.log(error); + dispatch( + notification( "Wystąpił nieoczekiwany błąd, przepraszamy za utrudnienia. Spróbuj ponownie za chwilę.", - }); - setTimeout(() => { - history.push("/"); - }, 5000); + "error" + ) + ); + history.push("/"); }); }; @@ -200,7 +199,7 @@ export default function NewRestaurant() { } break; case 1: - if (!validator.isEmpty(state.imgURL)) { + if (!validator.isEmpty(state.imgUrl)) { setActiveStep(2); } break; @@ -213,7 +212,7 @@ export default function NewRestaurant() { }; const handleImageUploaded = (link) => { - setState({ ...state, imgURL: link }); + setState({ ...state, imgUrl: link }); }; const handleDescriptionChange = (event) => { @@ -256,16 +255,9 @@ export default function NewRestaurant() { return (
- {!open && ( - - )} Dodaj Lokal @@ -469,7 +461,7 @@ export default function NewRestaurant() {

Dodaj zdjęcie lokalu.

handleImageUploaded(link)} />
diff --git a/src/components/Dialogs/PasswordConfirmation.js b/src/components/Dialogs/PasswordConfirmation.js new file mode 100644 index 0000000..2103542 --- /dev/null +++ b/src/components/Dialogs/PasswordConfirmation.js @@ -0,0 +1,88 @@ +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 LockIcon from "@material-ui/icons/Lock"; +import InputAdornment from "@material-ui/core/InputAdornment"; + +export default function PasswordConfirmation(props) { + const [password, setPassword] = useState(""); + + 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", + }, + }, + })); + + const loginClass = loginStyles(); + + return ( + + Potwierdź hasłem + + + + + + setPassword(event.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> +
+ props.onSubmit(password)} + text="Potwierdź" + /> +
+
+
+ ); +} diff --git a/src/components/Dialogs/RegisterDialog.js b/src/components/Dialogs/RegisterDialog.js index e91768d..bf344ea 100644 --- a/src/components/Dialogs/RegisterDialog.js +++ b/src/components/Dialogs/RegisterDialog.js @@ -8,9 +8,9 @@ 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 { setRegisterResult, showRegulamin } from "../../actions/toggles"; -import { tryRegister } from "../../actions"; +import { useDispatch } from "react-redux"; +import { showRegulamin } from "../../actions/toggles"; +import { tryRegister, notification } from "../../actions"; import InputAdornment from "@material-ui/core/InputAdornment"; import AccountCircle from "@material-ui/icons/AccountCircle"; import BusinessIcon from "@material-ui/icons/Business"; @@ -18,12 +18,9 @@ import EmailIcon from "@material-ui/icons/Email"; import LockIcon from "@material-ui/icons/Lock"; import Link from "@material-ui/core/Link"; import validator from "validator"; -import CircularProgress from "@material-ui/core/CircularProgress"; import { useHistory } from "react-router-dom"; export default function RegisterDialog(props) { - // SETUP - const initialFormData = { firstname: "", lastname: "", @@ -43,13 +40,6 @@ export default function RegisterDialog(props) { repeatPasswordError: false, }; const [formData, setFormData] = useState(initialFormData); - var circularProgress = useSelector( - (state) => state.data.dialogs.registerCircularProgress - ); - var registerForm = useSelector((state) => state.data.dialogs.registerForm); - var registerResult = useSelector( - (state) => state.data.dialogs.registerResult - ); const dispatch = useDispatch(); const history = useHistory(); @@ -138,7 +128,7 @@ export default function RegisterDialog(props) { if (validateForm()) { dispatch(tryRegister(form)); } else { - dispatch(setRegisterResult("Proszę poprawić formularz.")); + dispatch(notification("Proszę poprawić formularz.", "error")); } }; @@ -162,179 +152,161 @@ export default function RegisterDialog(props) { - {registerForm && ( -
- - - - ), - }} - error={formData.firstnameError} - onChange={(event) => (formData.firstname = event.target.value)} - /> - - - - ), - }} - 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)} - /> - - - - ), - }} - error={formData.emailError} - onChange={(event) => (formData.email = event.target.value)} - /> - - - - ), - }} - error={formData.passwordError} - onChange={(event) => (formData.password = event.target.value)} - /> - - - - ), - }} - error={formData.repeatPasswordError} - onChange={(event) => - (formData.repeatPassword = event.target.value) - } - /> -
- )} -

{registerResult}

+
+ + + + ), + }} + error={formData.firstnameError} + onChange={(event) => (formData.firstname = event.target.value)} + /> + + + + ), + }} + 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)} + /> + + + + ), + }} + error={formData.emailError} + onChange={(event) => (formData.email = event.target.value)} + /> + + + + ), + }} + error={formData.passwordError} + onChange={(event) => (formData.password = event.target.value)} + /> + + + + ), + }} + error={formData.repeatPasswordError} + onChange={(event) => + (formData.repeatPassword = event.target.value) + } + /> +
- {circularProgress && } - {registerForm && ( -

- Rejestracja oznacza akceptację{" "} - - handleRegulaminClick(event)} - > - regulaminu - - -

- )} +

+ Rejestracja oznacza akceptację{" "} + + handleRegulaminClick(event)}> + regulaminu + + +

- {registerForm ? ( - sendForm(formData)} - text="Zarejestruj" - /> - ) : ( - history.push("/login")} - text="Logowanie" - /> - )} + sendForm(formData)} + text="Zarejestruj" + />
diff --git a/src/components/Dialogs/ResetPassword.js b/src/components/Dialogs/ResetPassword.js index ebba425..2bef4f1 100644 --- a/src/components/Dialogs/ResetPassword.js +++ b/src/components/Dialogs/ResetPassword.js @@ -8,14 +8,12 @@ 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 { 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"; +import { changePassword, notification } from "../../actions/index"; function useQuery() { return new URLSearchParams(useLocation().search); @@ -31,10 +29,6 @@ export default function ResetPassword(props) { 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(); @@ -94,7 +88,7 @@ export default function ResetPassword(props) { if (validateLogin()) { dispatch(changePassword(data.email, data.password, token)); } else { - dispatch(setResetResult("Popraw dane.")); + dispatch(notification("Popraw dane.", "error")); } }; @@ -170,10 +164,6 @@ export default function ResetPassword(props) { ), }} /> -
- {resetCircle && } -

{resetResult}

-
handleReset()} text="Zmień hasło" />
diff --git a/src/components/EditRestaurant/EditRestaurantInfo.js b/src/components/EditRestaurant/EditRestaurantInfo.js index 8a3235e..5bd1e77 100644 --- a/src/components/EditRestaurant/EditRestaurantInfo.js +++ b/src/components/EditRestaurant/EditRestaurantInfo.js @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useHistory } from "react-router-dom"; import ButtonPrimary from "../Input/ButtonPrimary"; import ButtonSecondary from "../Input/ButtonSecondary"; import TextField from "@material-ui/core/TextField"; @@ -10,8 +11,18 @@ import PhoneIcon from "@material-ui/icons/Phone"; import FacebookIcon from "@material-ui/icons/Facebook"; import InstagramIcon from "@material-ui/icons/Instagram"; import LanguageIcon from "@material-ui/icons/Language"; +import FastfoodIcon from "@material-ui/icons/Fastfood"; +import LocationCityIcon from "@material-ui/icons/LocationCity"; import Divider from "@material-ui/core/Divider"; +import Link from "@material-ui/core/Link"; import { decodeTags } from "../../Services"; +import validator from "validator"; +import { useSelector, useDispatch } from "react-redux"; +import { notification, refreshUserData, updateRestaurant } from "../../actions"; +import { showBackdrop, hideBackdrop } from "../../actions/toggles.js"; +import { prepareTags } from "../../Services.js"; +import axios from "axios"; +import PasswordConfirmation from "../Dialogs/PasswordConfirmation"; const useStyles = makeStyles((theme) => ({ textInput: { @@ -36,6 +47,9 @@ const useStyles = makeStyles((theme) => ({ color: "#bbbbbb", }, }, + link: { + cursor: "pointer", + }, })); const calculateCharLeft = (from) => { @@ -43,6 +57,7 @@ const calculateCharLeft = (from) => { }; export default function EditRestaurantInfo(props) { + const history = useHistory(); const initialState = { name: props.restaurant.name, city: props.restaurant.city, @@ -54,9 +69,17 @@ export default function EditRestaurantInfo(props) { tags: decodeTags(props.restaurant.tags), workingHours: props.restaurant.workingHours, charleft: calculateCharLeft(props.restaurant.description), + nameError: false, + cityError: false, + adressError: false, + descriptionError: false, }; const [state, setState] = useState(initialState); + const [passwordDialog, setPasswordDialog] = useState(false); const styles = useStyles(); + const dispatch = useDispatch(); + const jwt = useSelector((state) => state.data.userData.jwt); + const email = useSelector((state) => state.data.userData.userEmail); const handleDescriptionChange = (event) => { let stringLength = event.target.value.length; let charleft = 400 - stringLength; @@ -72,8 +95,112 @@ export default function EditRestaurantInfo(props) { "Dowozimy", ]; + const validateForm = () => { + const validation = { + 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.descriptionValid + ); + }; + + const cancelChanges = () => { + setState(initialState); + }; + + const handleDelete = (password) => { + axios({ + url: "http://localhost:4000/restaurant/delete", + method: "POST", + data: { + restaurantId: props.restaurant._id, + password: password, + email: email, + }, + headers: { + "x-auth-token": jwt, + }, + }) + .then((response) => { + dispatch(refreshUserData(jwt)); + dispatch(hideBackdrop()); + dispatch(notification("Restauracja została usunięta", "success")); + history.push("/"); + }) + .catch((err) => { + console.log(err); + dispatch(hideBackdrop()); + dispatch(notification("Wystąpił nieoczekiwany błąd :(", "error")); + }); + }; + + const handleSendForm = () => { + if (validateForm()) { + const formattedTags = prepareTags(state.tags); + const data = { + restaurantId: props.restaurant._id, + dishes: props.restaurant.dishes, + categories: props.restaurant.categories, + lunchMenu: props.restaurant.lunchMenu, + name: state.name, + city: state.city, + adress: state.adress, + coordinates: props.restaurant.location.coordinates, + placesId: props.restaurant.placesId, + imgUrl: props.restaurant.imgUrl, + workingHours: state.workingHours, + description: state.description, + tags: formattedTags, + links: state.links, + phone: state.phone, + hidden: props.restaurant.hidden, + }; + dispatch(showBackdrop()); + axios({ + url: "http://localhost:4000/restaurant", + method: "PUT", + data: data, + headers: { + "x-auth-token": jwt, + }, + }) + .then((response) => { + dispatch(hideBackdrop()); + dispatch(notification("Dane zostały zapisane.", "success")); + dispatch(updateRestaurant(response)); + }) + .catch((err) => { + console.log(err); + dispatch(hideBackdrop()); + dispatch(notification("Wystąpił nieoczekiwany błąd :(", "error")); + }); + } + }; + return (
+ setPasswordDialog(false)} + onSubmit={handleDelete} + />

Podstawowe dane

@@ -84,10 +211,18 @@ export default function EditRestaurantInfo(props) { className={styles.textInputFullWidth} fullWidth value={state.name} + error={state.nameError} onChange={(event) => setState({ ...state, name: event.target.value }) } InputLabelProps={{ shrink: true }} + InputProps={{ + startAdornment: ( + + + + ), + }} label="Nazwa lokalu" variant="outlined" /> @@ -96,18 +231,34 @@ export default function EditRestaurantInfo(props) { setState({ ...state, city: event.target.value })} InputLabelProps={{ shrink: true }} + InputProps={{ + startAdornment: ( + + + + ), + }} label="Miasto" variant="outlined" /> setState({ ...state, adress: event.target.value }) } InputLabelProps={{ shrink: true }} + InputProps={{ + startAdornment: ( + + + + ), + }} label="Adres" variant="outlined" /> @@ -117,6 +268,7 @@ export default function EditRestaurantInfo(props) { fullWidth label="Opis" value={state.description} + error={state.descriptionError} onChange={handleDescriptionChange} multiline rows={3} @@ -230,10 +382,17 @@ export default function EditRestaurantInfo(props) { ), }} /> +
+

Zaawansowane

+ +
+ setPasswordDialog(true)}> + Usuń restaurację +
- - + +
); diff --git a/src/components/EditRestaurant/EditRestaurantPhoto.js b/src/components/EditRestaurant/EditRestaurantPhoto.js index 72e9f17..fb95d06 100644 --- a/src/components/EditRestaurant/EditRestaurantPhoto.js +++ b/src/components/EditRestaurant/EditRestaurantPhoto.js @@ -1,9 +1,77 @@ -import React from "react"; +import React, { useState } from "react"; +import ImageUpload from "../Input/ImageUpload"; +import ButtonSecondary from "../Input/ButtonSecondary"; +import ButtonPrimary from "../Input/ButtonPrimary"; +import { useDispatch, useSelector } from "react-redux"; +import { notification } from "../../actions"; +import { showBackdrop, hideBackdrop } from "../../actions/toggles.js"; +import axios from "axios"; export default function EditRestaurantPhoto(props) { + const { + imgUrl, + dishes, + categories, + lunchMenu, + name, + city, + adress, + placesId, + location, + workingHours, + description, + tags, + links, + phone, + hidden, + } = props.restaurant; + const [url, setUrl] = useState(imgUrl); + const token = useSelector((state) => state.data.userData.jwt); + const dispatch = useDispatch(); + const handleSave = () => { + dispatch(showBackdrop()); + const data = { + restaurantId: props.restaurant._id, + dishes: dishes, + categories: categories, + lunchMenu: lunchMenu, + name: name, + city: city, + adress: adress, + coordinates: location.coordinates, + placesId: placesId, + imgUrl: url, + workingHours: workingHours, + description: description, + tags: tags, + links: links, + phone: phone, + hidden: hidden, + }; + axios({ + url: "http://localhost:4000/restaurant", + method: "PUT", + data: data, + headers: { + "x-auth-token": token, + }, + }) + .then((res) => { + dispatch(hideBackdrop()); + dispatch(notification("Zmieniono zdjęcie.", "success")); + }) + .catch((e) => { + dispatch(hideBackdrop()); + dispatch(notification("Nie udało się zmienić zdjęcia :(", "error")); + }); + }; return (
-

Phottttto

+ setUrl(newUrl)} /> +
+ setUrl(imgUrl)} /> + +
); } diff --git a/src/components/Input/ImageUpload.js b/src/components/Input/ImageUpload.js index e362081..ccf9f9e 100644 --- a/src/components/Input/ImageUpload.js +++ b/src/components/Input/ImageUpload.js @@ -4,14 +4,14 @@ import { useSelector } from "react-redux"; import axios from "axios"; export default function ImageUpload(props) { - const [imagePreviewURL, setPreviewURL] = useState(props.img); - let showCircle = false; + const { img } = props; + const [loading, setLoading] = useState(false); const token = useSelector((state) => state.data.userData.jwt); const handleInputChange = (event) => { let data = new FormData(); data.append("menuiImage", event.target.files[0]); - + setLoading(true); axios({ url: "http://localhost:4000/img", method: "POST", @@ -23,24 +23,25 @@ export default function ImageUpload(props) { }, }) .then((response) => { - setPreviewURL(response.data.imgURL); props.onUpload(response.data.imgURL); + setLoading(false); }) .catch((error) => { console.log("Wystąpił błąd podczas wgrywania pliku."); + setLoading(false); }); }; let imagePreview = (
- {showCircle ? : "Proszę wybrać obraz. (max. 2MB)"} + {loading ? : "Proszę wybrać obraz. (max. 2MB)"}
); - if (imagePreviewURL) { + if (img) { imagePreview = (
); } diff --git a/src/components/Notifier.js b/src/components/Notifier.js new file mode 100644 index 0000000..c950063 --- /dev/null +++ b/src/components/Notifier.js @@ -0,0 +1,45 @@ +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useSnackbar } from "notistack"; +import { removeSnackbar } from "../actions/toggles.js"; + +let displayed = []; + +const Notifier = () => { + const dispatch = useDispatch(); + const notifications = useSelector((store) => store.notifications || []); + const { enqueueSnackbar } = useSnackbar(); + + const storeDisplayed = (id) => { + displayed = [...displayed, id]; + }; + + const removeDisplayed = (id) => { + displayed = [...displayed.filter((key) => id !== key)]; + }; + + React.useEffect(() => { + notifications.forEach(({ key, message, options = {} }) => { + // do nothing if snackbar is already displayed + if (displayed.includes(key)) return; + + // display snackbar using notistack + enqueueSnackbar(message, { + key, + ...options, + onExited: (event, myKey) => { + // remove this snackbar from redux store + dispatch(removeSnackbar(myKey)); + removeDisplayed(myKey); + }, + }); + + // keep track of snackbars that we've displayed + storeDisplayed(key); + }); + }, [notifications, enqueueSnackbar, dispatch]); + + return null; +}; + +export default Notifier; diff --git a/src/components/Output/AppBackdrop.js b/src/components/Output/AppBackdrop.js new file mode 100644 index 0000000..b89d94e --- /dev/null +++ b/src/components/Output/AppBackdrop.js @@ -0,0 +1,22 @@ +import React from "react"; +import Backdrop from "@material-ui/core/Backdrop"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import { useSelector } from "react-redux"; +import { makeStyles } from "@material-ui/core/styles"; + +const useStyles = makeStyles((theme) => ({ + backdrop: { + zIndex: theme.zIndex.drawer + 1, + color: "#fff", + }, +})); + +export default function AppBackdrop() { + const open = useSelector((state) => state.data.backdrop); + const classes = useStyles(); + return ( + + + + ); +} diff --git a/src/components/Output/ListItemRestaurant.js b/src/components/Output/ListItemRestaurant.js index c98ff47..43a52e9 100644 --- a/src/components/Output/ListItemRestaurant.js +++ b/src/components/Output/ListItemRestaurant.js @@ -5,7 +5,6 @@ import ListItem from "@material-ui/core/ListItem"; import FastfoodIcon from "@material-ui/icons/Fastfood"; import Badge from "@material-ui/core/Badge"; import { makeStyles } from "@material-ui/core/styles"; -import { useHistory } from "react-router-dom"; const useStyles = makeStyles((theme) => ({ root: { @@ -25,7 +24,6 @@ const useStyles = makeStyles((theme) => ({ })); export default function ListItemRestaurant(props) { - const history = useHistory(); const styles = useStyles(); const badgeData = { color: "", @@ -45,7 +43,7 @@ export default function ListItemRestaurant(props) { return ( history.push(`/editRestaurant/${props.id}`)} + onClick={() => props.onClick(`/editRestaurant/${props.id}`)} > store.appMode); - - if (appMode === "init") { - return Menui logo; - } else { - return ""; - } + return Menui logo; } diff --git a/src/components/UserMenu.js b/src/components/UserMenu.js index ee0db78..d5eb8b7 100644 --- a/src/components/UserMenu.js +++ b/src/components/UserMenu.js @@ -50,6 +50,7 @@ export default function UserMenu(props) { key={restaurant._id} subscriptionActive={restaurant.subscriptionActive} id={restaurant._id} + onClick={(link) => handleButtonClick(link)} /> ); }); diff --git a/src/reducers/appMode.js b/src/reducers/appMode.js deleted file mode 100644 index fc7edc3..0000000 --- a/src/reducers/appMode.js +++ /dev/null @@ -1,18 +0,0 @@ -const appModeReducer = (state = "init", action) => { - switch (action.type) { - case "APP_INIT": - return (state = "init"); - case "APP_EMPTY": - return (state = "empty"); - case "APP_SEARCH_RESULTS": - return (state = "search results"); - case "APP_RESTAURANT": - return (state = "restaurant"); - case "APP_DISH": - return (state = "dish"); - default: - return state; - } -}; - -export default appModeReducer; diff --git a/src/reducers/data.js b/src/reducers/data.js index cad8130..6f06af4 100644 --- a/src/reducers/data.js +++ b/src/reducers/data.js @@ -1,7 +1,6 @@ const initialState = { showDishList: false, loggedIn: false, - jwt: "", username: "", userId: "", userEmail: "", @@ -19,16 +18,9 @@ const initialState = { restaurants: [], }, dialogs: { - registerCircularProgress: false, - registerForm: true, - registerResult: "", - loginResult: "", regulamin: false, - reminderResult: "", - reminderCircularProgress: false, - resetResult: "", - resetCircularProgress: false, }, + backdrop: false, tempData: {}, }; @@ -74,11 +66,6 @@ const data = (state = initialState, action) => { restaurants: [], }, }); - case "DIALOG_REGISTER_CIRCLE_SHOW": - return (state = { - ...state, - dialogs: { ...state.dialogs, registerCircularProgress: true }, - }); case "DIALOG_REGULAMIN_SHOW": return (state = { ...state, @@ -89,60 +76,28 @@ const data = (state = initialState, action) => { ...state, dialogs: { ...state.dialogs, regulamin: false }, }); - case "DIALOG_REGISTER_CIRCLE_HIDE": - return (state = { - ...state, - dialogs: { ...state.dialogs, registerCircularProgress: false }, - }); - case "DIALOG_REGISTER_FORM_HIDE": - return (state = { - ...state, - dialogs: { ...state.dialogs, registerForm: false }, - }); - case "DIALOG_REGISTER_SET_RESULT": - return (state = { - ...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_RESET_SET_RESULT": - return (state = { - ...state, - dialogs: { ...state.dialogs, resetResult: action.payload }, - }); case "SET_TEMP_DATA": return (state = { ...state, tempData: action.payload }); case "CLEAR_TEMP_DATA": return (state = { ...state, tempData: {} }); + case "SHOW_BACKDROP": + return (state = { ...state, backdrop: true }); + case "HIDE_BACKDROP": + return (state = { ...state, backdrop: false }); + case "UPDATE_RESTAURANT": + return (state = { + ...state, + userData: { + ...state.userData, + restaurants: state.userData.restaurants.map((restaurant) => { + if (restaurant._id === action.payload._id) { + return action.payload; + } else { + return restaurant; + } + }), + }, + }); default: return state; } diff --git a/src/reducers/index.js b/src/reducers/index.js index b65e083..3ddc000 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -2,22 +2,22 @@ import { combineReducers } from "redux"; import { connectRouter } from "connected-react-router"; import autoCompleteReducer from "./autoComplete"; import searchResults from "./searchResults"; -import appMode from "./appMode"; import searchQuery from "./searchQuery"; import restaurant from "./restaurant"; import dishes from "./dishes"; import data from "./data"; +import notifications from "./notifications"; const rootReducer = (history) => combineReducers({ router: connectRouter(history), autocomplete: autoCompleteReducer, - appMode: appMode, searchResults: searchResults, searchQuery: searchQuery, restaurant: restaurant, dishes: dishes, data: data, + notifications: notifications, }); export default rootReducer; diff --git a/src/reducers/notifications.js b/src/reducers/notifications.js new file mode 100644 index 0000000..cc00742 --- /dev/null +++ b/src/reducers/notifications.js @@ -0,0 +1,12 @@ +const notifications = (state = [], action) => { + switch (action.type) { + case "ENQUEUE_SNACKBAR": + return [...state, { key: action.key, ...action.notification }]; + case "REMOVE_SNACKBAR": + return state.filter((notification) => notification.key !== action.key); + default: + return state; + } +}; + +export default notifications;