web client v0.5

//notifications
//delete restaurant
//change picture
//redesigned data store
This commit is contained in:
2020-09-27 18:40:39 +02:00
parent 3fdc93ef28
commit d2842a1db3
24 changed files with 810 additions and 501 deletions

9
package-lock.json generated
View File

@@ -9368,6 +9368,15 @@
"sort-keys": "^1.0.0" "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": { "npm-run-path": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",

View File

@@ -14,6 +14,7 @@
"axios": "^0.19.2", "axios": "^0.19.2",
"connected-react-router": "^6.8.0", "connected-react-router": "^6.8.0",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"notistack": "^1.0.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-ga": "^3.1.2", "react-ga": "^3.1.2",

View File

@@ -1,8 +1,11 @@
import React from "react"; import React from "react";
import { Router, Switch, Route } from "react-router-dom"; import { Router, Switch, Route } from "react-router-dom";
import { SnackbarProvider } from "notistack";
import PrivateRoute from "./components/PrivateRoute"; import PrivateRoute from "./components/PrivateRoute";
import "./App.scss"; import "./App.scss";
import TopBar from "./components/TopBar"; import TopBar from "./components/TopBar";
import Notifier from "./components/Notifier";
import AppBackdrop from "./components/Output/AppBackdrop";
import LogoMain from "./components/Output/logoMain"; import LogoMain from "./components/Output/logoMain";
import Footer from "./components/Output/Footer"; import Footer from "./components/Output/Footer";
import SearchPanel from "./components/Input/SearchPanel"; import SearchPanel from "./components/Input/SearchPanel";
@@ -37,57 +40,67 @@ function App(props) {
return ( return (
<Router history={props.history}> <Router history={props.history}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<div className="App"> <SnackbarProvider
<TopBar /> maxSnack={3}
<div className="main-container"> anchorOrigin={{
<Switch> vertical: "bottom",
<Route exact path="/"> horizontal: "center",
<LogoMain /> }}
<SearchPanel /> >
<p className="darkParagraph"> <div className="App">
Sprawdź co serwuje Twoja ulubiona restauracja. <AppBackdrop />
</p> <Notifier />
</Route> <TopBar />
<Route path="/results"> <div className="main-container">
<LogoMain /> <Switch>
<SearchPanel /> <Route exact path="/">
<SearchResults /> <LogoMain />
</Route> <SearchPanel />
<Route path="/restaurant"> <p className="darkParagraph">
<Restaurant /> Sprawdź co serwuje Twoja ulubiona restauracja.
</Route> </p>
<Route path="/dish"> </Route>
<LogoMain /> <Route path="/results">
</Route> <LogoMain />
<Route path="/login"> <SearchPanel />
<LoginDialog /> <SearchResults />
</Route> </Route>
<Route path="/register"> <Route path="/restaurant/:id">
<RegisterDialog /> <Restaurant />
</Route> </Route>
<Route path="/kontakt"> <Route path="/dish">
<Contact /> <LogoMain />
</Route> </Route>
<PrivateRoute <Route path="/login">
path="/newRestaurant" <LoginDialog />
component={<NewRestaurant />} </Route>
/> <Route path="/register">
<PrivateRoute path="/settings" component={<Settings />} /> <RegisterDialog />
<PrivateRoute </Route>
path="/editRestaurant/:id" <Route path="/kontakt">
component={<EditRestaurant />} <Contact />
/> </Route>
<Route path="/forgotpassword"> <PrivateRoute
<ForgotPassword /> path="/newRestaurant"
</Route> component={<NewRestaurant />}
<Route path="/resetpassword"> />
<ResetPassword /> <PrivateRoute path="/settings" component={<Settings />} />
</Route> <PrivateRoute
</Switch> path="/editRestaurant/:id"
component={<EditRestaurant />}
/>
<Route path="/forgotpassword">
<ForgotPassword />
</Route>
<Route path="/resetpassword">
<ResetPassword />
</Route>
</Switch>
</div>
<Dialogs />
<Footer />
</div> </div>
<Dialogs /> </SnackbarProvider>
<Footer />
</div>
</ThemeProvider> </ThemeProvider>
</Router> </Router>
); );

View File

@@ -81,7 +81,9 @@ export const fetchRestaurant = (id) => {
dispatch(push("/restaurant")); dispatch(push("/restaurant"));
dispatch(fetchAllDishes(id)); 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) => { export const tryLogin = (username, password) => {
const data = { email: username, password: password }; const data = { email: username, password: password };
return function (dispatch) { return function (dispatch) {
dispatch(toggles.setLoginResult("")); dispatch(toggles.showBackdrop());
axios axios
.post(backend + "user/login", data) .post(backend + "user/login", data)
.then((response) => { .then((response) => {
@@ -125,21 +157,22 @@ export const tryLogin = (username, password) => {
response.data.restaurants response.data.restaurants
) )
); );
dispatch(toggles.hideBackdrop());
dispatch(notification(`Witaj ${response.data.firstname}!`, "success"));
dispatch(push("/")); dispatch(push("/"));
}) })
.catch((err) => { .catch((err) => {
if (!err.response) { if (!err.response) {
console.log(err); console.log(err);
} else if (err.response.status === 404) { } else if (err.response.status === 404) {
dispatch( dispatch(toggles.hideBackdrop());
toggles.setLoginResult( dispatch(notification("Użytkownik nie istnieje :(", "error"));
"Użytkownik o podanym adresie email nie istnieje."
)
);
} else if (err.response.status === 401) { } 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 { } 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) => { export const remindPassword = (email) => {
return function (dispatch) { return function (dispatch) {
const data = { email: email }; const data = { email: email };
dispatch(toggles.setReminderResult("")); dispatch(toggles.showBackdrop());
dispatch(toggles.showReminderCircle());
axios axios
.post(backend + "user/forgotpassword", data) .post(backend + "user/forgotpassword", data)
.then((response) => { .then((response) => {
dispatch(toggles.hideReminderCircle()); dispatch(toggles.hideBackdrop());
dispatch(toggles.setReminderResult(response.data)); dispatch(notification(response.data, "info"));
}) })
.catch((e) => { .catch((e) => {
dispatch(toggles.hideReminderCircle()); dispatch(toggles.hideBackdrop());
dispatch(toggles.setReminderResult(e.response.data)); dispatch(notification(e.response.data, "error"));
}); });
}; };
}; };
@@ -170,59 +202,60 @@ export const changePassword = (email, password, token) => {
email: email, email: email,
newPass: password, newPass: password,
}; };
dispatch(toggles.setResetResult("")); dispatch(toggles.showBackdrop());
dispatch(toggles.showResetCircle());
axios axios
.post(backend + "user/resetpass", data) .post(backend + "user/resetpass", data)
.then((response) => { .then((response) => {
dispatch(toggles.hideResetCircle()); dispatch(toggles.hideBackdrop());
dispatch(toggles.setResetResult(response.data)); dispatch(notification(response.data, "info"));
}) })
.catch((e) => { .catch((e) => {
dispatch(toggles.hideResetCircle()); dispatch(toggles.hideBackdrop());
dispatch(toggles.setResetResult(e.response.data)); dispatch(notification(e.response.data, "error"));
}); });
}; };
}; };
export const logout = () => { export const logout = () => {
return function (dispatch) { return function (dispatch) {
dispatch(notification("Poprawnie wylogowano.", "success"));
dispatch(toggles.setLoggedOut()); dispatch(toggles.setLoggedOut());
}; };
}; };
export const tryRegister = (data) => { export const tryRegister = (data) => {
return function (dispatch) { return function (dispatch) {
dispatch(toggles.setRegisterResult("")); dispatch(toggles.showBackdrop());
dispatch(toggles.showRegisterCircle());
axios axios
.post(backend + "user/register", data) .post(backend + "user/register", data)
.then((response) => { .then((response) => {
if (response.status === 201) { if (response.status === 201) {
dispatch( dispatch(
toggles.setRegisterResult( notification(
"Dziękujemy za rejestrację. Teraz możesz zalogować się do swojego konta." "Rejestracja przebiegła pomyślnie, możesz teraz zalogować się do swojego konta.",
"success"
) )
); );
dispatch(toggles.hideRegisterCircle()); dispatch(toggles.hideBackdrop());
dispatch(toggles.hideRegisterForm());
} }
}) })
.catch((err) => { .catch((err) => {
if (err.response.status === 500) { if (err.response.status === 500) {
dispatch( dispatch(
toggles.setRegisterResult( notification(
"Wystąpił nieoczekiwany błąd serwera, przepraszamy..." "Wystąpił nieoczekiwany błąd serwera, przepraszamy...",
"error"
) )
); );
dispatch(toggles.hideRegisterCircle()); dispatch(toggles.hideBackdrop());
} else if (err.response.status === 409) { } else if (err.response.status === 409) {
dispatch( dispatch(
toggles.setRegisterResult( notification(
"Adres email jest już zajęty, proszę użyć innego." "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", 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,
};
};

View File

@@ -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 = () => { export const hideRegulamin = () => {
return { return {
type: "DIALOG_REGULAMIN_HIDE", 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 { 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 { return {
type: "DIALOG_REMINDER_CIRCLE_HIDE", type: "SHOW_BACKDROP",
}; };
}; };
export const showResetCircle = () => { export const hideBackdrop = () => {
return { return {
type: "DIALOG_RESET_CIRCLE_SHOW", type: "HIDE_BACKDROP",
};
};
export const hideResetCircle = () => {
return {
type: "DIALOG_RESET_CIRCLE_HIDE",
}; };
}; };

View File

@@ -14,6 +14,7 @@ import RestaurantMenuIcon from "@material-ui/icons/RestaurantMenu";
import FastfoodIcon from "@material-ui/icons/Fastfood"; import FastfoodIcon from "@material-ui/icons/Fastfood";
import AddIcon from "@material-ui/icons/Add"; import AddIcon from "@material-ui/icons/Add";
import Badge from "@material-ui/core/Badge"; import Badge from "@material-ui/core/Badge";
import SearchIcon from "@material-ui/icons/Search";
//-------------- //--------------
import EditRestaurantInfo from "../EditRestaurant/EditRestaurantInfo"; import EditRestaurantInfo from "../EditRestaurant/EditRestaurantInfo";
import EditRestaurantLocation from "../EditRestaurant/EditRestaurantLocation"; import EditRestaurantLocation from "../EditRestaurant/EditRestaurantLocation";
@@ -84,6 +85,15 @@ export default function EditRestaurant(props) {
<h3 className="editRestaurant-title">{restaurant.name}</h3> <h3 className="editRestaurant-title">{restaurant.name}</h3>
<Divider /> <Divider />
<List className={classes.main} component="nav"> <List className={classes.main} component="nav">
<ListItem
button
onClick={() => history.push(`/restaurant/${restaurant._id}`)}
>
<ListItemIcon>
<SearchIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Podgląd" />
</ListItem>
<ListItem <ListItem
button button
selected={tab === 0} selected={tab === 0}

View File

@@ -8,15 +8,13 @@ import ButtonSecondary from "../Input/ButtonSecondary";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import { useSelector, useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import Link from "@material-ui/core/Link"; import Link from "@material-ui/core/Link";
import validator from "validator"; import validator from "validator";
import InputAdornment from "@material-ui/core/InputAdornment"; import InputAdornment from "@material-ui/core/InputAdornment";
import AccountCircle from "@material-ui/icons/AccountCircle"; import AccountCircle from "@material-ui/icons/AccountCircle";
import CircularProgress from "@material-ui/core/CircularProgress"; import { remindPassword, notification } from "../../actions";
import { remindPassword } from "../../actions";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { setReminderResult } from "../../actions/toggles";
export default function ForgotPassword(props) { export default function ForgotPassword(props) {
const initialData = { const initialData = {
@@ -24,12 +22,6 @@ export default function ForgotPassword(props) {
emailError: false, emailError: false,
}; };
const [data, setData] = useState(initialData); 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 dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@@ -82,7 +74,7 @@ export default function ForgotPassword(props) {
if (validateLogin()) { if (validateLogin()) {
dispatch(remindPassword(data.email)); dispatch(remindPassword(data.email));
} else { } else {
dispatch(setReminderResult("Podaj poprawne dane.")); dispatch(notification("Podaj poprawne dane.", "error"));
} }
}; };
@@ -129,10 +121,6 @@ export default function ForgotPassword(props) {
), ),
}} }}
/> />
<p>
{reminderResult}
<span>{reminderCircle && <CircularProgress />}</span>
</p>
<div className="login-dialog-buttons"> <div className="login-dialog-buttons">
<ButtonSecondary <ButtonSecondary
onClick={() => handleRemind()} onClick={() => handleRemind()}

View File

@@ -8,14 +8,13 @@ import ButtonSecondary from "../Input/ButtonSecondary";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import { useSelector, useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { setLoginResult } from "../../actions/toggles";
import Link from "@material-ui/core/Link"; import Link from "@material-ui/core/Link";
import LockIcon from "@material-ui/icons/Lock"; import LockIcon from "@material-ui/icons/Lock";
import validator from "validator"; import validator from "validator";
import InputAdornment from "@material-ui/core/InputAdornment"; import InputAdornment from "@material-ui/core/InputAdornment";
import AccountCircle from "@material-ui/icons/AccountCircle"; import AccountCircle from "@material-ui/icons/AccountCircle";
import { tryLogin } from "../../actions"; import { tryLogin, notification } from "../../actions";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
export default function LoginDialog(props) { export default function LoginDialog(props) {
@@ -26,7 +25,6 @@ export default function LoginDialog(props) {
passwordError: false, passwordError: false,
}; };
const [loginData, setLoginData] = useState(initialData); const [loginData, setLoginData] = useState(initialData);
const loginResult = useSelector((state) => state.data.dialogs.loginResult);
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@@ -82,7 +80,7 @@ export default function LoginDialog(props) {
if (validateLogin()) { if (validateLogin()) {
dispatch(tryLogin(loginData.email, loginData.password)); dispatch(tryLogin(loginData.email, loginData.password));
} else { } else {
dispatch(setLoginResult("Podaj poprawne dane logowania.")); dispatch(notification("Podaj poprawne dane logowania.", "error"));
} }
}; };
@@ -140,7 +138,6 @@ export default function LoginDialog(props) {
), ),
}} }}
/> />
<p>{loginResult}</p>
<div className="login-dialog-buttons"> <div className="login-dialog-buttons">
<ButtonSecondary onClick={() => handleLogin()} text="Zaloguj" /> <ButtonSecondary onClick={() => handleLogin()} text="Zaloguj" />
</div> </div>

View File

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

View File

@@ -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 (
<Dialog
className={loginClass.root}
onClose={props.onCancel}
open={props.open}
aria-labelledby="login-title"
>
<DialogTitle id="login-title">Potwierdź hasłem</DialogTitle>
<IconButton
className={loginClass.closeButton}
onClick={props.onCancel}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Divider />
<DialogContent>
<TextField
className={loginClass.textInput}
id="password"
label="Hasło"
type="password"
value={password}
variant="outlined"
onChange={(event) => setPassword(event.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LockIcon color="primary" />
</InputAdornment>
),
}}
/>
<div className="login-dialog-buttons">
<ButtonSecondary
onClick={() => props.onSubmit(password)}
text="Potwierdź"
/>
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -8,9 +8,9 @@ import ButtonSecondary from "../Input/ButtonSecondary";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import { useSelector, useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { setRegisterResult, showRegulamin } from "../../actions/toggles"; import { showRegulamin } from "../../actions/toggles";
import { tryRegister } from "../../actions"; import { tryRegister, notification } from "../../actions";
import InputAdornment from "@material-ui/core/InputAdornment"; import InputAdornment from "@material-ui/core/InputAdornment";
import AccountCircle from "@material-ui/icons/AccountCircle"; import AccountCircle from "@material-ui/icons/AccountCircle";
import BusinessIcon from "@material-ui/icons/Business"; 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 LockIcon from "@material-ui/icons/Lock";
import Link from "@material-ui/core/Link"; import Link from "@material-ui/core/Link";
import validator from "validator"; import validator from "validator";
import CircularProgress from "@material-ui/core/CircularProgress";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
export default function RegisterDialog(props) { export default function RegisterDialog(props) {
// SETUP
const initialFormData = { const initialFormData = {
firstname: "", firstname: "",
lastname: "", lastname: "",
@@ -43,13 +40,6 @@ export default function RegisterDialog(props) {
repeatPasswordError: false, repeatPasswordError: false,
}; };
const [formData, setFormData] = useState(initialFormData); 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 dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@@ -138,7 +128,7 @@ export default function RegisterDialog(props) {
if (validateForm()) { if (validateForm()) {
dispatch(tryRegister(form)); dispatch(tryRegister(form));
} else { } else {
dispatch(setRegisterResult("Proszę poprawić formularz.")); dispatch(notification("Proszę poprawić formularz.", "error"));
} }
}; };
@@ -162,179 +152,161 @@ export default function RegisterDialog(props) {
</IconButton> </IconButton>
<Divider /> <Divider />
<DialogContent> <DialogContent>
{registerForm && ( <div>
<div> <TextField
<TextField className={loginClass.textInput}
className={loginClass.textInput} required
required id="firstname"
id="firstname" label="Imię"
label="Imię" type="name"
type="name" variant="outlined"
variant="outlined" InputProps={{
InputProps={{ startAdornment: (
startAdornment: ( <InputAdornment position="start">
<InputAdornment position="start"> <AccountCircle color="primary" />
<AccountCircle color="primary" /> </InputAdornment>
</InputAdornment> ),
), }}
}} error={formData.firstnameError}
error={formData.firstnameError} onChange={(event) => (formData.firstname = event.target.value)}
onChange={(event) => (formData.firstname = event.target.value)} />
/> <TextField
<TextField className={loginClass.textInput}
className={loginClass.textInput} required
required id="lastname"
id="lastname" label="Nazwisko"
label="Nazwisko" type="name"
type="name" variant="outlined"
variant="outlined" InputProps={{
InputProps={{ startAdornment: (
startAdornment: ( <InputAdornment position="start">
<InputAdornment position="start"> <AccountCircle color="primary" />
<AccountCircle color="primary" /> </InputAdornment>
</InputAdornment> ),
), }}
}} error={formData.lastnameError}
error={formData.lastnameError} onChange={(event) => (formData.lastname = event.target.value)}
onChange={(event) => (formData.lastname = event.target.value)} />
/> <TextField
<TextField className={loginClass.textInput}
className={loginClass.textInput} required
required id="companyName"
id="companyName" label="Nazwa firmy"
label="Nazwa firmy" type="name"
type="name" variant="outlined"
variant="outlined" InputProps={{
InputProps={{ startAdornment: (
startAdornment: ( <InputAdornment position="start">
<InputAdornment position="start"> <BusinessIcon color="primary" />
<BusinessIcon color="primary" /> </InputAdornment>
</InputAdornment> ),
), }}
}} error={formData.companyNameError}
error={formData.companyNameError} onChange={(event) => (formData.companyName = event.target.value)}
onChange={(event) => />
(formData.companyName = event.target.value) <TextField
} className={loginClass.textInput}
/> required
<TextField id="adress"
className={loginClass.textInput} label="Adres firmy"
required type="name"
id="adress" variant="outlined"
label="Adres firmy" InputProps={{
type="name" startAdornment: (
variant="outlined" <InputAdornment position="start">
InputProps={{ <BusinessIcon color="primary" />
startAdornment: ( </InputAdornment>
<InputAdornment position="start"> ),
<BusinessIcon color="primary" /> }}
</InputAdornment> error={formData.adressError}
), onChange={(event) => (formData.adress = event.target.value)}
}} />
error={formData.adressError} <TextField
onChange={(event) => (formData.adress = event.target.value)} className={loginClass.textInput}
/> required
<TextField id="NIP"
className={loginClass.textInput} label="NIP"
required type="name"
id="NIP" variant="outlined"
label="NIP" InputProps={{
type="name" startAdornment: (
variant="outlined" <InputAdornment position="start">
InputProps={{ <BusinessIcon color="primary" />
startAdornment: ( </InputAdornment>
<InputAdornment position="start"> ),
<BusinessIcon color="primary" /> }}
</InputAdornment> error={formData.NIPError}
), onChange={(event) => (formData.NIP = event.target.value)}
}} />
error={formData.NIPError} <TextField
onChange={(event) => (formData.NIP = event.target.value)} className={loginClass.textInput}
/> required
<TextField id="email"
className={loginClass.textInput} label="Email"
required type="email"
id="email" variant="outlined"
label="Email" InputProps={{
type="email" startAdornment: (
variant="outlined" <InputAdornment position="start">
InputProps={{ <EmailIcon color="primary" />
startAdornment: ( </InputAdornment>
<InputAdornment position="start"> ),
<EmailIcon color="primary" /> }}
</InputAdornment> error={formData.emailError}
), onChange={(event) => (formData.email = event.target.value)}
}} />
error={formData.emailError} <TextField
onChange={(event) => (formData.email = event.target.value)} className={loginClass.textInput}
/> required
<TextField id="password"
className={loginClass.textInput} label="Hasło (min. 8 znaków)"
required type="password"
id="password" variant="outlined"
label="Hasło (min. 8 znaków)" InputProps={{
type="password" startAdornment: (
variant="outlined" <InputAdornment position="start">
InputProps={{ <LockIcon color="primary" />
startAdornment: ( </InputAdornment>
<InputAdornment position="start"> ),
<LockIcon color="primary" /> }}
</InputAdornment> error={formData.passwordError}
), onChange={(event) => (formData.password = event.target.value)}
}} />
error={formData.passwordError} <TextField
onChange={(event) => (formData.password = event.target.value)} className={loginClass.textInput}
/> required
<TextField id="repeat-password"
className={loginClass.textInput} label="Powtórz hasło"
required type="password"
id="repeat-password" variant="outlined"
label="Powtórz hasło" InputProps={{
type="password" startAdornment: (
variant="outlined" <InputAdornment position="start">
InputProps={{ <LockIcon color="primary" />
startAdornment: ( </InputAdornment>
<InputAdornment position="start"> ),
<LockIcon color="primary" /> }}
</InputAdornment> error={formData.repeatPasswordError}
), onChange={(event) =>
}} (formData.repeatPassword = event.target.value)
error={formData.repeatPasswordError} }
onChange={(event) => />
(formData.repeatPassword = event.target.value) </div>
}
/>
</div>
)}
<p>{registerResult}</p>
<Divider /> <Divider />
<div className="register-dialog-actions"> <div className="register-dialog-actions">
{circularProgress && <CircularProgress />} <p>
{registerForm && ( Rejestracja oznacza akceptację{" "}
<p> <span>
Rejestracja oznacza akceptację{" "} <Link href="#" onClick={(event) => handleRegulaminClick(event)}>
<span> regulaminu
<Link </Link>
href="#" </span>
onClick={(event) => handleRegulaminClick(event)} </p>
>
regulaminu
</Link>
</span>
</p>
)}
<div className="register-dialog-button"> <div className="register-dialog-button">
{registerForm ? ( <ButtonSecondary
<ButtonSecondary onClick={() => sendForm(formData)}
onClick={() => sendForm(formData)} text="Zarejestruj"
text="Zarejestruj" />
/>
) : (
<ButtonSecondary
onClick={() => history.push("/login")}
text="Logowanie"
/>
)}
</div> </div>
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -8,14 +8,12 @@ import ButtonSecondary from "../Input/ButtonSecondary";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import { useSelector, useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import validator from "validator"; import validator from "validator";
import InputAdornment from "@material-ui/core/InputAdornment"; import InputAdornment from "@material-ui/core/InputAdornment";
import AccountCircle from "@material-ui/icons/AccountCircle"; import AccountCircle from "@material-ui/icons/AccountCircle";
import CircularProgress from "@material-ui/core/CircularProgress";
import { useHistory, useLocation } from "react-router-dom"; import { useHistory, useLocation } from "react-router-dom";
import { changePassword } from "../../actions/index"; import { changePassword, notification } from "../../actions/index";
import { setResetResult } from "../../actions/toggles";
function useQuery() { function useQuery() {
return new URLSearchParams(useLocation().search); return new URLSearchParams(useLocation().search);
@@ -31,10 +29,6 @@ export default function ResetPassword(props) {
passwordRepeatError: false, passwordRepeatError: false,
}; };
const [data, setData] = useState(initialData); 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 dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
const query = useQuery(); const query = useQuery();
@@ -94,7 +88,7 @@ export default function ResetPassword(props) {
if (validateLogin()) { if (validateLogin()) {
dispatch(changePassword(data.email, data.password, token)); dispatch(changePassword(data.email, data.password, token));
} else { } else {
dispatch(setResetResult("Popraw dane.")); dispatch(notification("Popraw dane.", "error"));
} }
}; };
@@ -170,10 +164,6 @@ export default function ResetPassword(props) {
), ),
}} }}
/> />
<div>
{resetCircle && <CircularProgress />}
<p>{resetResult}</p>
</div>
<div className="login-dialog-buttons"> <div className="login-dialog-buttons">
<ButtonSecondary onClick={() => handleReset()} text="Zmień hasło" /> <ButtonSecondary onClick={() => handleReset()} text="Zmień hasło" />
</div> </div>

View File

@@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import ButtonPrimary from "../Input/ButtonPrimary"; import ButtonPrimary from "../Input/ButtonPrimary";
import ButtonSecondary from "../Input/ButtonSecondary"; import ButtonSecondary from "../Input/ButtonSecondary";
import TextField from "@material-ui/core/TextField"; 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 FacebookIcon from "@material-ui/icons/Facebook";
import InstagramIcon from "@material-ui/icons/Instagram"; import InstagramIcon from "@material-ui/icons/Instagram";
import LanguageIcon from "@material-ui/icons/Language"; 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 Divider from "@material-ui/core/Divider";
import Link from "@material-ui/core/Link";
import { decodeTags } from "../../Services"; 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) => ({ const useStyles = makeStyles((theme) => ({
textInput: { textInput: {
@@ -36,6 +47,9 @@ const useStyles = makeStyles((theme) => ({
color: "#bbbbbb", color: "#bbbbbb",
}, },
}, },
link: {
cursor: "pointer",
},
})); }));
const calculateCharLeft = (from) => { const calculateCharLeft = (from) => {
@@ -43,6 +57,7 @@ const calculateCharLeft = (from) => {
}; };
export default function EditRestaurantInfo(props) { export default function EditRestaurantInfo(props) {
const history = useHistory();
const initialState = { const initialState = {
name: props.restaurant.name, name: props.restaurant.name,
city: props.restaurant.city, city: props.restaurant.city,
@@ -54,9 +69,17 @@ export default function EditRestaurantInfo(props) {
tags: decodeTags(props.restaurant.tags), tags: decodeTags(props.restaurant.tags),
workingHours: props.restaurant.workingHours, workingHours: props.restaurant.workingHours,
charleft: calculateCharLeft(props.restaurant.description), charleft: calculateCharLeft(props.restaurant.description),
nameError: false,
cityError: false,
adressError: false,
descriptionError: false,
}; };
const [state, setState] = useState(initialState); const [state, setState] = useState(initialState);
const [passwordDialog, setPasswordDialog] = useState(false);
const styles = useStyles(); 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) => { const handleDescriptionChange = (event) => {
let stringLength = event.target.value.length; let stringLength = event.target.value.length;
let charleft = 400 - stringLength; let charleft = 400 - stringLength;
@@ -72,8 +95,112 @@ export default function EditRestaurantInfo(props) {
"Dowozimy", "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 ( return (
<div className="editRestaurant-tab"> <div className="editRestaurant-tab">
<PasswordConfirmation
open={passwordDialog}
onCancel={() => setPasswordDialog(false)}
onSubmit={handleDelete}
/>
<div className="editRestaurant-doubleColumn"> <div className="editRestaurant-doubleColumn">
<div className="editRestaurant-sectiontitle"> <div className="editRestaurant-sectiontitle">
<h4>Podstawowe dane</h4> <h4>Podstawowe dane</h4>
@@ -84,10 +211,18 @@ export default function EditRestaurantInfo(props) {
className={styles.textInputFullWidth} className={styles.textInputFullWidth}
fullWidth fullWidth
value={state.name} value={state.name}
error={state.nameError}
onChange={(event) => onChange={(event) =>
setState({ ...state, name: event.target.value }) setState({ ...state, name: event.target.value })
} }
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FastfoodIcon color="primary" />
</InputAdornment>
),
}}
label="Nazwa lokalu" label="Nazwa lokalu"
variant="outlined" variant="outlined"
/> />
@@ -96,18 +231,34 @@ export default function EditRestaurantInfo(props) {
<TextField <TextField
className={styles.textInput} className={styles.textInput}
value={state.city} value={state.city}
error={state.cityError}
onChange={(event) => setState({ ...state, city: event.target.value })} onChange={(event) => setState({ ...state, city: event.target.value })}
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LocationCityIcon color="primary" />
</InputAdornment>
),
}}
label="Miasto" label="Miasto"
variant="outlined" variant="outlined"
/> />
<TextField <TextField
className={styles.textInput} className={styles.textInput}
value={state.adress} value={state.adress}
error={state.adressError}
onChange={(event) => onChange={(event) =>
setState({ ...state, adress: event.target.value }) setState({ ...state, adress: event.target.value })
} }
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LocationCityIcon color="primary" />
</InputAdornment>
),
}}
label="Adres" label="Adres"
variant="outlined" variant="outlined"
/> />
@@ -117,6 +268,7 @@ export default function EditRestaurantInfo(props) {
fullWidth fullWidth
label="Opis" label="Opis"
value={state.description} value={state.description}
error={state.descriptionError}
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
multiline multiline
rows={3} rows={3}
@@ -230,10 +382,17 @@ export default function EditRestaurantInfo(props) {
), ),
}} }}
/> />
<div className="editRestaurant-sectiontitle">
<h4>Zaawansowane</h4>
<Divider />
</div>
<Link className={styles.link} onClick={() => setPasswordDialog(true)}>
Usuń restaurację
</Link>
</div> </div>
<div className="editRestaurant-bottom"> <div className="editRestaurant-bottom">
<ButtonPrimary text="Anuluj" /> <ButtonPrimary text="Anuluj" onClick={cancelChanges} />
<ButtonSecondary text="Zapisz" /> <ButtonSecondary onClick={handleSendForm} text="Zapisz" />
</div> </div>
</div> </div>
); );

View File

@@ -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) { 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 ( return (
<div className="editRestaurant-tab"> <div className="editRestaurant-tab">
<p>Phottttto</p> <ImageUpload img={url} onUpload={(newUrl) => setUrl(newUrl)} />
<div className="editRestaurant-bottom">
<ButtonPrimary text="Anuluj" onClick={() => setUrl(imgUrl)} />
<ButtonSecondary onClick={handleSave} text="Zapisz" />
</div>
</div> </div>
); );
} }

View File

@@ -4,14 +4,14 @@ import { useSelector } from "react-redux";
import axios from "axios"; import axios from "axios";
export default function ImageUpload(props) { export default function ImageUpload(props) {
const [imagePreviewURL, setPreviewURL] = useState(props.img); const { img } = props;
let showCircle = false; const [loading, setLoading] = useState(false);
const token = useSelector((state) => state.data.userData.jwt); const token = useSelector((state) => state.data.userData.jwt);
const handleInputChange = (event) => { const handleInputChange = (event) => {
let data = new FormData(); let data = new FormData();
data.append("menuiImage", event.target.files[0]); data.append("menuiImage", event.target.files[0]);
setLoading(true);
axios({ axios({
url: "http://localhost:4000/img", url: "http://localhost:4000/img",
method: "POST", method: "POST",
@@ -23,24 +23,25 @@ export default function ImageUpload(props) {
}, },
}) })
.then((response) => { .then((response) => {
setPreviewURL(response.data.imgURL);
props.onUpload(response.data.imgURL); props.onUpload(response.data.imgURL);
setLoading(false);
}) })
.catch((error) => { .catch((error) => {
console.log("Wystąpił błąd podczas wgrywania pliku."); console.log("Wystąpił błąd podczas wgrywania pliku.");
setLoading(false);
}); });
}; };
let imagePreview = ( let imagePreview = (
<div className="image-preview"> <div className="image-preview">
{showCircle ? <CircularProgress /> : "Proszę wybrać obraz. (max. 2MB)"} {loading ? <CircularProgress /> : "Proszę wybrać obraz. (max. 2MB)"}
</div> </div>
); );
if (imagePreviewURL) { if (img) {
imagePreview = ( imagePreview = (
<div <div
className="image-preview" className="image-preview"
style={{ backgroundImage: `url(${imagePreviewURL})` }} style={{ backgroundImage: `url(${img})` }}
></div> ></div>
); );
} }

View File

@@ -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;

View File

@@ -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 (
<Backdrop className={classes.backdrop} open={open}>
<CircularProgress color="inherit" />
</Backdrop>
);
}

View File

@@ -5,7 +5,6 @@ import ListItem from "@material-ui/core/ListItem";
import FastfoodIcon from "@material-ui/icons/Fastfood"; import FastfoodIcon from "@material-ui/icons/Fastfood";
import Badge from "@material-ui/core/Badge"; import Badge from "@material-ui/core/Badge";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { useHistory } from "react-router-dom";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
@@ -25,7 +24,6 @@ const useStyles = makeStyles((theme) => ({
})); }));
export default function ListItemRestaurant(props) { export default function ListItemRestaurant(props) {
const history = useHistory();
const styles = useStyles(); const styles = useStyles();
const badgeData = { const badgeData = {
color: "", color: "",
@@ -45,7 +43,7 @@ export default function ListItemRestaurant(props) {
return ( return (
<ListItem <ListItem
button button
onClick={() => history.push(`/editRestaurant/${props.id}`)} onClick={() => props.onClick(`/editRestaurant/${props.id}`)}
> >
<ListItemIcon> <ListItemIcon>
<Badge <Badge

View File

@@ -1,13 +1,6 @@
import logo from "../../public/logo_mint.svg"; import logo from "../../public/logo_mint.svg";
import React from "react"; import React from "react";
import { useSelector } from "react-redux";
export default function LogoMain() { export default function LogoMain() {
let appMode = useSelector((store) => store.appMode); return <img src={logo} alt="Menui logo" className="logo" />;
if (appMode === "init") {
return <img src={logo} alt="Menui logo" className="logo" />;
} else {
return "";
}
} }

View File

@@ -50,6 +50,7 @@ export default function UserMenu(props) {
key={restaurant._id} key={restaurant._id}
subscriptionActive={restaurant.subscriptionActive} subscriptionActive={restaurant.subscriptionActive}
id={restaurant._id} id={restaurant._id}
onClick={(link) => handleButtonClick(link)}
/> />
); );
}); });

View File

@@ -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;

View File

@@ -1,7 +1,6 @@
const initialState = { const initialState = {
showDishList: false, showDishList: false,
loggedIn: false, loggedIn: false,
jwt: "",
username: "", username: "",
userId: "", userId: "",
userEmail: "", userEmail: "",
@@ -19,16 +18,9 @@ const initialState = {
restaurants: [], restaurants: [],
}, },
dialogs: { dialogs: {
registerCircularProgress: false,
registerForm: true,
registerResult: "",
loginResult: "",
regulamin: false, regulamin: false,
reminderResult: "",
reminderCircularProgress: false,
resetResult: "",
resetCircularProgress: false,
}, },
backdrop: false,
tempData: {}, tempData: {},
}; };
@@ -74,11 +66,6 @@ const data = (state = initialState, action) => {
restaurants: [], restaurants: [],
}, },
}); });
case "DIALOG_REGISTER_CIRCLE_SHOW":
return (state = {
...state,
dialogs: { ...state.dialogs, registerCircularProgress: true },
});
case "DIALOG_REGULAMIN_SHOW": case "DIALOG_REGULAMIN_SHOW":
return (state = { return (state = {
...state, ...state,
@@ -89,60 +76,28 @@ const data = (state = initialState, action) => {
...state, ...state,
dialogs: { ...state.dialogs, regulamin: false }, 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": case "SET_TEMP_DATA":
return (state = { ...state, tempData: action.payload }); return (state = { ...state, tempData: action.payload });
case "CLEAR_TEMP_DATA": case "CLEAR_TEMP_DATA":
return (state = { ...state, tempData: {} }); 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: default:
return state; return state;
} }

View File

@@ -2,22 +2,22 @@ import { combineReducers } from "redux";
import { connectRouter } from "connected-react-router"; import { connectRouter } from "connected-react-router";
import autoCompleteReducer from "./autoComplete"; import autoCompleteReducer from "./autoComplete";
import searchResults from "./searchResults"; import searchResults from "./searchResults";
import appMode from "./appMode";
import searchQuery from "./searchQuery"; import searchQuery from "./searchQuery";
import restaurant from "./restaurant"; import restaurant from "./restaurant";
import dishes from "./dishes"; import dishes from "./dishes";
import data from "./data"; import data from "./data";
import notifications from "./notifications";
const rootReducer = (history) => const rootReducer = (history) =>
combineReducers({ combineReducers({
router: connectRouter(history), router: connectRouter(history),
autocomplete: autoCompleteReducer, autocomplete: autoCompleteReducer,
appMode: appMode,
searchResults: searchResults, searchResults: searchResults,
searchQuery: searchQuery, searchQuery: searchQuery,
restaurant: restaurant, restaurant: restaurant,
dishes: dishes, dishes: dishes,
data: data, data: data,
notifications: notifications,
}); });
export default rootReducer; export default rootReducer;

View File

@@ -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;