Browse Source

Edits after Camilo's usability test:
change plan routing and thus eliminate plan wrapper (which was stupid anyway! → in future need to delete meal wrapper as well)
add routing to shopping list and contact tabs
add missing translations for meal import
deactivate add missing ingredient button as long as no ingredient was put in

Ramona Plogmann 4 years ago
parent
commit
a2e21a8a42

+ 2 - 2
client/src/components/ContentWrapper.jsx

@@ -10,7 +10,7 @@ import AddMeal from "./Meals/AddMeal";
 import Home from "./Home";
 import MealDetailViewExtern from "./Meals/MealDetailViewExtern";
 import MealWrapper from "./Meals/MealWrapper";
-import PlanWrapper from "./Plans/PlanWrapper";
+import OwnPlans from "./Plans/OwnPlans";
 
 const useStyles = makeStyles(theme => ({
   content: {
@@ -54,7 +54,7 @@ const ContentWrapper = (props) => {
       contentPage = <MealDetailViewExtern />;
       break;
     case "plans":
-      contentPage = <PlanWrapper />;
+      contentPage = <OwnPlans />;
       break;
     case "home":
     default:

+ 1 - 1
client/src/components/Meals/MealDetailView.jsx

@@ -97,7 +97,7 @@ const MealDetailView = (props) => {
     <>
       {meal ?
         <FullScreenDialog open={open} onClose={closeDialog}>
-          <Navbar pageTitle={t('Meal')} rightSideComponent={rightSideComponent} leftSideComponent={isAuthenticated && <BackButton onClick={closeDialog} />} />
+          <Navbar pageTitle={t('Meal')} rightSideComponent={rightSideComponent()} leftSideComponent={isAuthenticated && <BackButton onClick={closeDialog} />} />
           <Box className={classes.content}>
             <Grid container spacing={0} justify="space-between" alignItems="flex-start" wrap="nowrap">
               <Grid item xs className={classes.mealTitle}>

+ 1 - 2
client/src/components/Meals/Meals.jsx

@@ -7,7 +7,6 @@ import MealDetailView from "./MealDetailView";
 import { fetchAndUpdateMeal, fetchAndUpdateMealsFromUser } from "./meals.util";
 import useCategoryIcons from "./useCategoryIcons";
 import { bool, string } from "prop-types";
-import { withLoginRequired } from "../util";
 import MealAvatar from "./MealAvatar";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import SelectMealTags from "./SelectMealTags";
@@ -256,4 +255,4 @@ Meals.propTypes = {
   own: bool.isRequired,
 }
 
-export default withLoginRequired(Meals);
+export default Meals;

+ 2 - 7
client/src/components/Plans/AddPlanItem.jsx

@@ -1,7 +1,6 @@
 import React, { useState } from 'react';
 import { Button } from '@material-ui/core';
 import { makeStyles } from '@material-ui/styles';
-import axios from 'axios';
 import Navbar from "../Navbar";
 import { useHistory } from "react-router-dom";
 import BackButton from "../Buttons/BackButton";
@@ -11,6 +10,7 @@ import { useAuth0 } from "@auth0/auth0-react";
 import { withLoginRequired } from "../util";
 import { func } from "prop-types";
 import DoneButton from "../Buttons/DoneButton";
+import { addPlan } from "./plans.util";
 
 const useStyles = makeStyles(theme => ({
   form: {
@@ -24,8 +24,6 @@ const useStyles = makeStyles(theme => ({
   }
 }));
 
-const serverURL = process.env.REACT_APP_SERVER_URL;
-
 /** page that allows adding a plan */
 const AddPlanItem = (props) => {
   const classes = useStyles();
@@ -67,10 +65,7 @@ const AddPlanItem = (props) => {
         connectedMealId: planItem.connectedMeal ? planItem.connectedMeal._id : null,
       }
 
-      axios.post(serverURL + '/plans/add', newPlan).then((result) => {
-        console.log('add request sent', result.data);
-        onDoneAdding();
-      });
+      addPlan(newPlan, onDoneAdding);
     }
   }
 

+ 24 - 42
client/src/components/Plans/EditPlanItem.jsx

@@ -10,9 +10,7 @@ import EditPlanItemCore from "./EditPlanItemCore";
 import BackButton from "../Buttons/BackButton";
 import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
 import Loading from "../Loading";
-import useSnackbars from "../util/useSnackbars";
 import DoneButton from "../Buttons/DoneButton";
-import FullScreenDialog from "../util/FullScreenDialog";
 
 /** Dialog page that allows user to edit plan */
 const useStyles = makeStyles((theme) => ({
@@ -57,12 +55,10 @@ const EditPlanItem = (props) => {
   const { t } = useTranslation();
   const { user } = useAuth0();
 
-  const { closeDialog, onDoneEditing, planItem: givenPlanItem, open } = props;
+  const { closeDialog, onDoneEditing, onDelete, planItem: givenPlanItem } = props;
 
   const [planItem, setPlanItem] = useState(givenPlanItem);
 
-  const [deletedItem, setDeletedItem] = useState(null);
-
   useEffect(() => {
     if (givenPlanItem) {
       const newPlanItem = givenPlanItem;
@@ -93,24 +89,13 @@ const EditPlanItem = (props) => {
 
   const deletePlan = () => {
     axios.post(serverURL + '/plans/delete/' + planItem._id).then((result) => {
-      setDeletedItem(planItem);
-      showDeletedItemMessage();
+      onDelete(planItem);
       console.log('delete request sent', result.data);
       onDoneEditing();
       closeDialog();
     });
   }
 
-  const undoDeletion = () => {
-    axios.post(serverURL + '/plans/add', deletedItem).then((result) => {
-      console.log('re-add request sent', result.data);
-      showReaddedItemMessage();
-      onDoneEditing();
-    });
-  }
-
-  const { Snackbars, showDeletedItemMessage, showReaddedItemMessage } = useSnackbars('Plan', deletedItem, undoDeletion);
-
   const editAndClose = (event) => {
     event.preventDefault();
     editPlan();
@@ -125,30 +110,27 @@ const EditPlanItem = (props) => {
   }
 
   return (
-    <>{planItem ?
-      <FullScreenDialog open={open} onClose={closeDialog}>
-        <Navbar pageTitle={t('Edit Plan')}
-                leftSideComponent={<BackButton onClick={closeDialog} />}
-                rightSideComponent={planItem.title ? <DoneButton onClick={editAndClose} /> : null}
-                secondary={inverseColors} />
-
-        <form noValidate onSubmit={editAndClose} className={classes.form}>
-          <EditPlanItemCore planItem={planItem} updatePlanItem={updatePlanItem} isSecondary={inverseColors} />
-          <Grid container spacing={0} justify="space-between" alignItems="center" wrap="nowrap" className={classes.actionButtonWrapper}>
-            <Grid item xs className={classes.cancelButton}>
-              <Button type="button" color={inverseColors ? "secondary" : "primary"} variant="outlined" onClick={closeDialog}>{t('Cancel')}</Button>
-            </Grid>
-            <Grid item xs className={classes.deleteButton}>
-              <DeleteButton onClick={deletePlan} />
-            </Grid>
-            <Grid item xs className={classes.saveButton}>
-              <Button type="submit" disabled={!planItem.title} color={inverseColors ? "secondary" : "primary"} variant="contained">{t('Save')}</Button>
-            </Grid>
+    planItem &&
+    <>
+      <Navbar pageTitle={t('Edit Plan')}
+              leftSideComponent={<BackButton onClick={closeDialog} />}
+              rightSideComponent={planItem.title ? <DoneButton onClick={editAndClose} /> : null}
+              secondary={inverseColors} />
+
+      <form noValidate onSubmit={editAndClose} className={classes.form}>
+        <EditPlanItemCore planItem={planItem} updatePlanItem={updatePlanItem} isSecondary={inverseColors} />
+        <Grid container spacing={0} justify="space-between" alignItems="center" wrap="nowrap" className={classes.actionButtonWrapper}>
+          <Grid item xs className={classes.cancelButton}>
+            <Button type="button" color={inverseColors ? "secondary" : "primary"} variant="outlined" onClick={closeDialog}>{t('Cancel')}</Button>
+          </Grid>
+          <Grid item xs className={classes.deleteButton}>
+            <DeleteButton onClick={deletePlan} />
+          </Grid>
+          <Grid item xs className={classes.saveButton}>
+            <Button type="submit" disabled={!planItem.title} color={inverseColors ? "secondary" : "primary"} variant="contained">{t('Save')}</Button>
           </Grid>
-        </form>
-      </FullScreenDialog>
-      : ''}
-      {Snackbars}
+        </Grid>
+      </form>
     </>
   );
 }
@@ -166,10 +148,10 @@ EditPlanItem.propTypes = {
     })),
     connectedMealId: string,
   }),
-  /** is component visible? */
-  open: bool.isRequired,
   /** function to be executed after editing complete (receives no parameters) */
   onDoneEditing: func.isRequired,
+  /** function to be executed after deleting an item (receives deleted item as parameter) */
+  onDelete: func.isRequired,
   /** function that closes Dialog / sets open to false */
   closeDialog: func.isRequired,
 }

+ 26 - 14
client/src/components/Plans/EditPlanItemCore.jsx

@@ -1,6 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { Box, Button, Checkbox, FormControlLabel, Grid, TextField } from '@material-ui/core';
-// import MuiAlert from '@material-ui/lab/Alert';
+import { Box, Button, IconButton, Checkbox, FormControlLabel, Grid, TextField, fade } from '@material-ui/core';
 import { makeStyles } from '@material-ui/styles';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faMinusCircle, faPlusCircle } from "@fortawesome/free-solid-svg-icons";
@@ -44,6 +43,10 @@ const useStyles = makeStyles((theme) => ({
   newIngredientInputGrid: {
     marginBottom: '0.5em',
   },
+  addIngredientTextField: {
+    width: '100%',
+    paddingRight: '1rem',
+  },
   addIngredientButton: {
     borderRadius: '100%',
     minWidth: '1em',
@@ -53,6 +56,9 @@ const useStyles = makeStyles((theme) => ({
   addIngredientButtonIcon: {
     color: theme.palette.primary.main,
   },
+  addIngredientButtonIconDisabled: {
+    color: fade(theme.palette.primary.main, 0.3),
+  },
   removeIngredientButtonIcon: {
     color: theme.palette.error.light,
   },
@@ -140,7 +146,7 @@ const EditPlanItemCore = (props) => {
     console.log(updatedIngredients);
     updatePlanItem('missingIngredients', updatedIngredients);
   }
-
+  console.log('newIngredient', newIngredient);
   return (
     <>
       <Autocomplete id="planTitle" freeSolo clearOnBlur={false} clearOnEscape={false} value={connectedMeal} onChange={(event, newValue) => {
@@ -179,19 +185,25 @@ const EditPlanItemCore = (props) => {
       <Box className={classes.missingIngredientsBox} hidden={gotEverything}>
         <Grid container spacing={1} justify="space-between" alignItems="flex-end" className={classes.newIngredientInputGrid}>
           <Grid item xs style={{ width: 'calc(100% - (2em + 8px))' }}>
-            <TextField value={newIngredient} color={colorA} name="newIngredient" onChange={e => setNewIngredient(e.target.value)} onKeyDown={(event) => {
-              if (event.key === 'Enter') {
-                event.preventDefault();
-                if (newIngredient) addIngredient();
-              }
-            }} label={t('Missing Ingredient')} InputLabelProps={{ className: classes.missingIngredientInputLabel }} variant="standard" />
+            <TextField value={newIngredient}
+                       color={colorA}
+                       name="newIngredient"
+                       className={classes.addIngredientTextField}
+                       label={t('Missing Ingredient')}
+                       InputLabelProps={{ className: classes.missingIngredientInputLabel }}
+                       variant="standard"
+                       onChange={e => setNewIngredient(e.target.value)}
+                       onKeyDown={(event) => {
+                         if (event.key === 'Enter') {
+                           event.preventDefault();
+                           if (newIngredient) addIngredient();
+                         }
+                       }} />
           </Grid>
           <Grid item className={classes.buttonGridCell}>
-            <Button type="button"
-                    disabled={!newIngredient}
-                    className={classes.addIngredientButton}
-                    onClick={addIngredient}
-                    variant='text'><FontAwesomeIcon className={classes.addIngredientButtonIcon} icon={faPlusCircle} size="2x" /></Button>
+            <IconButton type="button" disabled={!newIngredient} className={classes.addIngredientButton} onClick={addIngredient} variant='text'>
+              <FontAwesomeIcon className={newIngredient ? classes.addIngredientButtonIcon : classes.addIngredientButtonIconDisabled} icon={faPlusCircle} />
+            </IconButton>
           </Grid>
         </Grid>
 

+ 21 - 5
client/src/components/Plans/MissingIngredients.jsx

@@ -4,8 +4,22 @@ import { any, arrayOf, bool, func, shape, string } from "prop-types";
 import { useTranslation } from "react-i18next";
 import { checkOrUncheckIngredient } from "./plans.util";
 
+import { makeStyles } from '@material-ui/styles';
+
+const useStyles = makeStyles({
+  dialogHeading: {
+    lineHeight: '1.5rem',
+    marginBottom: '0.5rem',
+  },
+  dialog: {
+    minWidth: '200px',
+    padding: '1.5rem 2rem',
+  }
+});
+
 /** Dialog that displays a plans missing ingredients and allows to check or uncheck them */
 const MissingIngredients = (props) => {
+  const classes = useStyles();
   const { t } = useTranslation();
   const [, updateState] = React.useState();
   const forceUpdate = React.useCallback(() => updateState({}), []);
@@ -18,15 +32,17 @@ const MissingIngredients = (props) => {
     });
   }
 
-  if(!planItem) return null;
+  if (!planItem) return null;
 
   return (
-    <Dialog open={open} onClose={closeDialog} >
-      <FormControl style={{minWidth: '200px', padding: '1.5rem 2rem'}}>
-        <FormLabel style={{marginBottom: '1rem'}}>{t('Missing Ingredients for {{plan}}', {plan: planItem.title})}</FormLabel>
+    <Dialog open={open} onClose={closeDialog}>
+      <FormControl className={classes.dialog}>
+        <FormLabel className={classes.dialogHeading}>{t('Missing Ingredients for {{plan}}', { plan: planItem.title })}</FormLabel>
         <FormGroup>
           {planItem.missingIngredients.map((ingredient) => (
-            <FormControlLabel key={ingredient.name} control={<Checkbox checked={ingredient.checked} onChange={(event) => {checkIngredient(ingredient, event.target.checked);}} />} label={ingredient.name} />))}
+            <FormControlLabel key={ingredient.name}
+                              control={<Checkbox checked={ingredient.checked} onChange={(event) => {checkIngredient(ingredient, event.target.checked);}} />}
+                              label={ingredient.name} />))}
         </FormGroup>
       </FormControl>
     </Dialog>

+ 2 - 16
client/src/components/Plans/OwnPlans.jsx

@@ -1,27 +1,13 @@
 import React from 'react';
-import Navbar from "../Navbar";
-import { useTranslation } from "react-i18next";
 import { useAuth0 } from "@auth0/auth0-react";
 import Plans from "./Plans";
-import AddButton from "../Buttons/AddButton";
-import { useHistory } from "react-router-dom";
 import { withLoginRequired } from "../util";
 
-/** Page that displays a user's own plans and adds a Navbar to the plans list that allows adding a plan */
+/** Page that displays a user's own plans */
 const OwnPlans = () => {
-  const { t } = useTranslation();
   const { user } = useAuth0();
 
-  let history = useHistory();
-  const goToAddPlan = () => {history.push('/plans/add');};
-
-  return (
-    <>
-      <Navbar pageTitle={t('Plans')} rightSideComponent={<AddButton onClick={goToAddPlan} />} />
-
-      <Plans own userId={user.sub} />
-    </>
-  );
+  return <Plans own userId={user.sub} />;
 }
 
 export default withLoginRequired(OwnPlans);

+ 0 - 23
client/src/components/Plans/PlanWrapper.jsx

@@ -1,23 +0,0 @@
-import { Route, Switch, useRouteMatch } from "react-router-dom";
-import React from "react";
-import OwnPlans from "./OwnPlans";
-
-const PlanWrapper = () => {
-
-  let { path } = useRouteMatch();
-
-  return (
-    <>
-      <Switch>
-        <Route exact path={path}>
-          <OwnPlans />
-        </Route>
-        <Route path={`${path}/edit/:planId`}>
-          <OwnPlans />
-        </Route>
-      </Switch>
-    </>
-  );
-}
-
-export default PlanWrapper;

+ 73 - 43
client/src/components/Plans/Plans.jsx

@@ -9,10 +9,13 @@ import { useTranslation } from "react-i18next";
 import { bool, string } from "prop-types";
 import { dateStringOptions, withLoginRequired } from "../util";
 import MissingIngredients from "./MissingIngredients";
-import { getPlansOfUser, getSinglePlan } from "./plans.util";
+import { addPlan, getPlansOfUser, getSinglePlan } from "./plans.util";
 import ShoppingList from "./ShoppingList";
 import MealAvatar from "../Meals/MealAvatar";
-import { useHistory, useParams, useRouteMatch } from "react-router-dom";
+import { Route, Switch, useHistory, useParams, useRouteMatch } from "react-router-dom";
+import useSnackbars from "../util/useSnackbars";
+import AddButton from "../Buttons/AddButton";
+import Navbar from "../Navbar";
 
 const useStyles = makeStyles((theme) => ({
   plansTable: {
@@ -53,18 +56,18 @@ const useStyles = makeStyles((theme) => ({
 const Plans = (props) => {
   const classes = useStyles();
   let history = useHistory();
-  let { path } = useRouteMatch();
   const params = useParams();
   const { t } = useTranslation();
+  let { path, url } = useRouteMatch();
 
   const { own, userId } = props;
 
   const [missingIngredientsDialogOpen, setMissingIngredientsDialogOpen] = useState(false);
   const [plans, setPlans] = useState([]);
-  const [shoppingListOpen, setShoppingListOpen] = useState(false);
   const [itemBeingEdited, setItemBeingEdited] = useState(null);
   const [emptyListFound, setEmptyListFound] = useState(false);
 
+  const [deletedItem, setDeletedItem] = useState(null);
   const [pastPlansOpen, setPastPlansOpen] = useState(false);
 
   const fetchAndUpdatePlans = () => {
@@ -100,7 +103,21 @@ const Plans = (props) => {
   }
 
   const openShoppingList = () => {
-    setShoppingListOpen(true);
+    history.push(`${url}/shoppingList`);
+  }
+
+  const undoDeletion = () => {
+    addPlan(deletedItem, () => {
+      fetchAndUpdatePlans();
+      showReaddedItemMessage();
+    });
+  }
+
+  const { Snackbars, showDeletedItemMessage, showReaddedItemMessage } = useSnackbars('Plan', deletedItem, undoDeletion);
+
+  const onDeletePlan = (planItem) => {
+    setDeletedItem(planItem);
+    showDeletedItemMessage();
   }
 
   function openMissingIngredientDialog(planItem) {
@@ -127,8 +144,8 @@ const Plans = (props) => {
     const pastPlans = [];
     const futurePlans = [];
     plans.forEach((plan, index) => {
-      const planIsInPast = new Date(plan.date).setHours(0, 0, 0, 0) < new Date().setHours(0, 0, 0, 0);
-      if (!plan.hasDate || (plan.hasDate && !(planIsInPast))) {
+      const planIsInPast = plan.hasDate && new Date(plan.date).setHours(0, 0, 0, 0) < new Date().setHours(0, 0, 0, 0);
+      if (!planIsInPast) {
         pastPlansDone = true;
       }
       const currentRow = (
@@ -178,46 +195,59 @@ const Plans = (props) => {
     return rows;
   }
 
-  const planList = (
-    <>
-      {plans.length === 0 ? <Typography className={classes.infoText}>{emptyListFound ? t("Currently nothing planned") : t('Loading') + '...'} </Typography> :
-        <TableContainer className={classes.plansTable}>
-          <Table aria-label="table of all plans" stickyHeader>
-            <TableHead>
-              <TableRow key='planListHeader'>
-                <TableCell className={classes.thCell}>{t('Plan')}</TableCell>
-                <TableCell align="center" className={classes.narrowCell + ' ' + classes.thCell}>{t('Due Date')}</TableCell>
-                <TableCell align="center" className={classes.thCell} onClick={openShoppingList}>
-                <span className="fa-layers fa-fw">
-                  <FontAwesomeIcon icon={faShoppingBasket} transform="grow-6" />
-                  <FontAwesomeIcon icon={faCheck} className={classes.green} transform="down-2" />
-                </span>
-                </TableCell>
-              </TableRow>
-            </TableHead>
-            {getPlanRows()}
-          </Table>
-        </TableContainer>
-      }
+  return (
+    <Switch>
+      <Route path={`${path}/edit/:planId`}>
+        <EditPlanItem planItem={itemBeingEdited} closeDialog={() => {
+          setItemBeingEdited(null);
+          history.push('/plans');
+        }} onDoneEditing={fetchAndUpdatePlans} onDelete={onDeletePlan} />
+      </Route>
+      <Route path={path}>
+        <>
+          {own && <Navbar pageTitle={t('Plans')} rightSideComponent={<AddButton onClick={() => {history.push('/plans/add');}} />} />}
+          <Switch>
 
-      <MissingIngredients planItem={itemBeingEdited} closeDialog={() => {
-        setItemBeingEdited(null);
-        setMissingIngredientsDialogOpen(false);
-      }} onDoneEditing={fetchAndUpdatePlans} open={missingIngredientsDialogOpen} />
+            <Route path={`${path}/shoppingList`}>
+              <ShoppingList userId={userId} plans={plans} onClose={() => {
+                history.goBack();
+                fetchAndUpdatePlans();
+              }} />
+            </Route>
 
-      <EditPlanItem open={path.includes('edit')} planItem={itemBeingEdited} closeDialog={() => {
-        setItemBeingEdited(null);
-        history.push('/plans');
-      }} onDoneEditing={fetchAndUpdatePlans} />
-    </>
-  );
+            <Route path={path}>
+              {plans.length === 0 ? <Typography className={classes.infoText}>{emptyListFound ? t("Currently nothing planned") : t('Loading') + '...'} </Typography> :
+                <TableContainer className={classes.plansTable}>
+                  <Table aria-label="table of all plans" stickyHeader>
+                    <TableHead>
+                      <TableRow key='planListHeader'>
+                        <TableCell className={classes.thCell}>{t('Plan')}</TableCell>
+                        <TableCell align="center" className={classes.narrowCell + ' ' + classes.thCell}>{t('Due Date')}</TableCell>
+                        <TableCell align="center" className={classes.thCell} onClick={openShoppingList}>
+                          <span className="fa-layers fa-fw">
+                            <FontAwesomeIcon icon={faShoppingBasket} transform="grow-6" />
+                            <FontAwesomeIcon icon={faCheck} className={classes.green} transform="down-2" />
+                          </span>
+                        </TableCell>
+                      </TableRow>
+                    </TableHead>
+                    {getPlanRows()}
+                  </Table>
+                </TableContainer>
+              }
 
-  const shoppingList = <ShoppingList userId={userId} plans={plans} onClose={() => {
-    setShoppingListOpen(false);
-    fetchAndUpdatePlans();
-  }} />;
+              <MissingIngredients planItem={itemBeingEdited} closeDialog={() => {
+                setItemBeingEdited(null);
+                setMissingIngredientsDialogOpen(false);
+              }} onDoneEditing={fetchAndUpdatePlans} open={missingIngredientsDialogOpen} />
 
-  return shoppingListOpen ? shoppingList : planList;
+              {Snackbars}
+            </Route>
+          </Switch>
+        </>
+      </Route>
+    </Switch>
+  );
 }
 
 Plans.propTypes = {

+ 1 - 1
client/src/components/Plans/ShoppingList.jsx

@@ -71,7 +71,7 @@ const ShoppingList = (props) => {
     if (plans) {
       dateIngredientMap.clear();
       plans.forEach((plan) => {
-        const planIsInPast = new Date(plan.date).setHours(0, 0, 0, 0) < new Date().setHours(0, 0, 0, 0);
+        const planIsInPast = plan.hasDate && new Date(plan.date).setHours(0, 0, 0, 0) < new Date().setHours(0, 0, 0, 0);
         if (!planIsInPast && plan.missingIngredients && plan.missingIngredients.length > 0) {
           const key = plan.hasDate && plan.date ? new Date(plan.date).setHours(0, 0, 0, 0) : 'noDate';
           let mappedPlans = dateIngredientMap.get(key);

+ 12 - 0
client/src/components/Plans/plans.util.jsx

@@ -34,6 +34,18 @@ export const getSinglePlan = (planId, updatePlan) => {
        });
 }
 
+/**
+ * Add plan to database
+ * @param {object} newPlan plan to be added
+ * @param {function} callback
+ */
+export const addPlan = (newPlan, callback) => {
+  axios.post(serverURL + '/plans/add', newPlan).then((result) => {
+    console.log('add request sent', result.data);
+    if (callback) callback();
+  });
+}
+
 /**
  * Toggles the checked attribute of an ingredient
  * Will also affect the plans gotEverything attribute (will be true if after execution all ingredients of the plan are checked)

+ 17 - 12
client/src/components/Social/ContactsContent.jsx

@@ -5,8 +5,8 @@ import { Box, Dialog, Tab, Tabs } from '@material-ui/core';
 import Meals from "../Meals/Meals";
 import Navbar from "../Navbar";
 import { useAuth0 } from "@auth0/auth0-react";
-import { useHistory, useParams } from "react-router-dom";
-import { getSettingsOfUser, getUserById } from "../Settings/settings.util";
+import { useHistory, useParams, useRouteMatch } from "react-router-dom";
+import { getUserById } from "../Settings/settings.util";
 import BackButton from "../Buttons/BackButton";
 import { useTranslation } from "react-i18next";
 import { LoadingBody } from "../Loading";
@@ -48,8 +48,9 @@ const ContactsContent = () => {
   const theme = useTheme();
   const { t } = useTranslation();
   const { user, isAuthenticated, isLoading } = useAuth0();
-  let { userId } = useParams();
+  let { userId, tab } = useParams();
   let history = useHistory();
+  let { path, url } = useRouteMatch();
 
   const [otherUser, setOtherUser] = React.useState(null);
   const [currentTab, setCurrentTab] = React.useState(1);
@@ -60,13 +61,9 @@ const ContactsContent = () => {
   }, [userId]);
 
   useEffect(() => {
-    if (user) {
-      const userId = user.sub;
-      getSettingsOfUser(userId, (settings) => {
-        setCurrentTab(settings.contactStartPageIndex || 1);
-      });
-    }
-  }, [user]);
+    if (tab === 'meals') setCurrentTab(0);
+    if (tab === 'plans') setCurrentTab(1);
+  }, [tab]);
 
   const leftSideComponent = () => {
     if (!isLoading && isAuthenticated && user) {
@@ -76,10 +73,17 @@ const ContactsContent = () => {
     }
   }
 
+  const switchTab = (newIndex) => {
+    setCurrentTab(newIndex);
+    const newPage = (newIndex === 0) ? 'meals' : 'plans';
+    let basicURL = url.slice(0, url.lastIndexOf('/'));
+    history.replace(basicURL + '/' + newPage);
+  }
+
   const getTabs = () =>
     <Tabs value={currentTab}
           className={classes.tabs}
-          onChange={(event, newValue) => {setCurrentTab(newValue);}}
+          onChange={(event, newValue) => {switchTab(newValue);}}
           indicatorColor="secondary"
           textColor="secondary"
           variant="fullWidth">
@@ -88,6 +92,7 @@ const ContactsContent = () => {
     </Tabs>
   ;
 
+  console.log(url, path);
   return (
     <Box className={classes.contactsContent}>
       {otherUser ?
@@ -102,7 +107,7 @@ const ContactsContent = () => {
           <SwipeableViews style={{
             height: `calc(100% - 2 * ${process.env.REACT_APP_NAV_TOP_HEIGHT}px)`,
             overflowY: 'auto',
-          }} axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} index={currentTab} onChangeIndex={setCurrentTab}>
+          }} axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} index={currentTab} onChangeIndex={switchTab}>
             <Box role="tabpanel" hidden={currentTab !== 0} dir={theme.direction}>
               <Meals own={false} userId={otherUser.user_id} />
             </Box>

+ 17 - 8
client/src/components/Social/Social.jsx

@@ -7,9 +7,10 @@ import { makeStyles } from '@material-ui/styles';
 import UserSearch from "./UserSearch";
 import { useTranslation } from "react-i18next";
 import { fetchContactsOfUser, getContactName, getContactPicture, updateContactsFromAuth0 } from "./social.util";
-import { Route, Switch, useHistory, useRouteMatch } from "react-router-dom";
+import { Route, Switch, Redirect, useHistory, useRouteMatch } from "react-router-dom";
 import ContactsContent from "./ContactsContent";
 import { withLoginRequired } from "../util";
+import { getSettingsOfUser } from "../Settings/settings.util";
 
 const useStyles = makeStyles({
   infoText: {
@@ -34,6 +35,7 @@ const Social = () => {
   const [searchOpen, setSearchOpen] = useState(false);
   const [contacts, setContacts] = useState([]);
   const [noContactsFound, setNoContactsFound] = useState(false);
+  const [contactStartPage, setContactStartPage] = useState('plans');
 
   const fetchContacts = (updateContactsData = false) => {
     if (user) {
@@ -50,14 +52,22 @@ const Social = () => {
   useEffect(() => {
     fetchContacts(true);
     // eslint-disable-next-line
-  }, [])
+  }, []);
+
+  useEffect(() => {
+    if (user) {
+      const userId = user.sub;
+      getSettingsOfUser(userId, (settings) => {
+        console.log(settings, settings.contactStartPageIndex);
+        setContactStartPage(settings.contactStartPageIndex === 0 ? 'meals' : 'plans');
+      });
+    }
+  }, [user]);
 
   const showUser = (userId) => {
-    history.push(`${url}/contact/${userId}`);
+    history.push(`${url}/contact/${userId}/${contactStartPage}`);
   }
 
-  console.log(contacts);
-
   const getListItems = () => {
     return contacts.map(contact => {
       const userId = contact.user_id;
@@ -76,8 +86,6 @@ const Social = () => {
     });
   }
 
-  console.log(url, path);
-
   return (
     <>
       <Switch>
@@ -96,7 +104,8 @@ const Social = () => {
             </List>
           }
         </Route>
-        <Route path={`${path}/contact/:userId`}>
+        <Redirect exact from={`${path}/contact/:userId`} to={`${path}/contact/:userId/${contactStartPage}`} />
+        <Route path={`${path}/contact/:userId/:tab`}>
           <ContactsContent />
         </Route>
       </Switch>

+ 0 - 5
client/src/index.jsx

@@ -20,8 +20,3 @@ ReactDOM.render(
   </Auth0Provider>,
   document.getElementById('root')
 );
-
-// If you want your app to work offline and load faster, you can change
-// unregister() to register() below. Note this comes with some pitfalls.
-// Learn more about service workers: https://cra.link/PWA
-// serviceWorkerRegistration.register();

+ 3 - 0
client/src/translations/_example.translation.json

@@ -89,5 +89,8 @@
   "placeholder tag": "",
   "placeholder category": "",
   "Go to my meals": "",
+  "Import": "",
+  "Import Meal": "",
+  "Successfully imported meal": "",
   "": ""
 }

+ 4 - 1
client/src/translations/de.translation.json

@@ -90,5 +90,8 @@
   "Reload": "Laden",
   "placeholder tag": "z.B. vegetarisch",
   "placeholder category": "z.B. Familienrezepte",
-  "Go to my meals": "Zu meinen Gerichten"
+  "Go to my meals": "Zu meinen Gerichten",
+  "Import": "Importieren",
+  "Import Meal": "Gericht importieren",
+  "Successfully imported meal": "Gericht erfolgreich importiert"
 }

+ 4 - 1
client/src/translations/en-GB.translation.json

@@ -89,5 +89,8 @@
   "Reload": "Reload",
   "placeholder tag": "vegetarian",
   "placeholder category": "family recipes",
-  "Go to my meals": "Go to my meals"
+  "Go to my meals": "Go to my meals",
+  "Import": "Import",
+  "Import Meal": "Import Meal",
+  "Successfully imported meal": "Successfully imported meal"
 }

+ 4 - 1
client/src/translations/en-US.translation.json

@@ -89,5 +89,8 @@
   "Reload": "Reload",
   "placeholder tag": "e.g., vegetarian",
   "placeholder category": "e.g., family recipes",
-  "Go to my meals": "Go to my meals"
+  "Go to my meals": "Go to my meals",
+  "Import": "Import",
+  "Import Meal": "Import Meal",
+  "Successfully imported meal": "Successfully imported meal"
 }

+ 5 - 2
client/src/translations/es.translation.json

@@ -78,7 +78,7 @@
   "Type to add a Category": "Teclear para añadir una categoría",
   "No meals found for filter selection": "No se ha encontrado ninguna comida para la selección de filtros",
   "expand all": "expandir todo",
-  "collapse all": "colapsar todo",
+  "collapse all": "contraer todo",
   "Search for users": "Buscar usuarios",
   "Copy": "Copiar",
   "Copied": "Copiado",
@@ -89,5 +89,8 @@
   "Reload": "Recargar",
   "placeholder tag": "p. ej., vegetariano",
   "placeholder category": "p. ej., recetas internacionales",
-  "Go to my meals": "A mis comidas"
+  "Go to my meals": "A mis comidas",
+  "Import": "Importar",
+  "Import Meal": "Importar Comida",
+  "Successfully imported meal": "Comida importada con éxito"
 }

+ 4 - 1
client/src/translations/fr-FR.translation.json

@@ -89,5 +89,8 @@
   "Reload": "Rechargez",
   "placeholder tag": "par ex. végétarien",
   "placeholder category": "par ex. Recettes de famille",
-  "Go to my meals": "A mes repas"
+  "Go to my meals": "A mes repas",
+  "Import": "Importer",
+  "Import Meal": "Importer un repas",
+  "Successfully imported meal": "Importation réussie du repas"
 }

+ 4 - 1
client/src/translations/it.translation.json

@@ -89,5 +89,8 @@
   "Reload": "Ricarica",
   "placeholder tag": "ad es. vegetariano",
   "placeholder category": "ad es. antipasti",
-  "Go to my meals": "Ai miei pasti"
+  "Go to my meals": "Ai miei pasti",
+  "Import": "Importare",
+  "Import Meal": "Importare pasto",
+  "Successfully imported meal": "Pasto importato con successo"
 }