Ver Fonte

add contacts and finalize user search

Ramona Plogmann há 4 anos atrás
pai
commit
93a3942d34

+ 1 - 0
client/.env

@@ -1,5 +1,6 @@
 REACT_APP_SERVER_URL=http://localhost:5000
 REACT_APP_NAV_BOTTOM_HEIGHT=55
+REACT_APP_NAV_TOP_HEIGHT=55
 REACT_APP_GRID_LIST_ROW_HEIGHT=140
 REACT_APP_LOGIN_REDIRECT=http://localhost:3000/plans
 REACT_APP_LOGOUT_REDIRECT=http://localhost:3000/

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

@@ -13,7 +13,7 @@ import { deleteAllImagesFromMeal } from "./meals.util";
 import Navbar from "../Navbar";
 import BackButton from "../Buttons/BackButton";
 import FontButton from "../Buttons/FontButton";
-import { SlidingTransition } from "../util";
+import { SlidingTransitionLeft } from "../util";
 import { withAuthenticationRequired } from "@auth0/auth0-react";
 import Loading from "../Loading";
 
@@ -118,7 +118,7 @@ const EditMeal = (props) => {
   return (
     <>
       {meal ?
-        <Dialog open={open} fullScreen onClose={closeDialog} className={classes.contentDialog} TransitionComponent={SlidingTransition}>
+        <Dialog open={open} fullScreen onClose={closeDialog} className={classes.contentDialog} TransitionComponent={SlidingTransitionLeft}>
           <Navbar pageTitle={t('Edit Meal')}
                   leftSideComponent={<BackButton onClick={closeDialog} />}
                   rightSideComponent={meal.title ? <FontButton onClick={editAndClose} label={t('Done')} /> : ''} secondary={inverseColors} />

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

@@ -10,7 +10,7 @@ import BackButton from "../Buttons/BackButton";
 import FontButton from "../Buttons/FontButton";
 import ImageGrid from "../Images/ImageGrid";
 import { useTranslation } from "react-i18next";
-import { SlidingTransition } from "../util";
+import { SlidingTransitionLeft } from "../util";
 import { withAuthenticationRequired } from "@auth0/auth0-react";
 import Loading from "../Loading";
 
@@ -64,7 +64,7 @@ const MealDetailView = (props) => {
   return (
     <>
       {meal ?
-        <Dialog open={open} fullScreen onClose={closeDialog} TransitionComponent={SlidingTransition}>
+        <Dialog open={open} fullScreen onClose={closeDialog} TransitionComponent={SlidingTransitionLeft}>
           <Navbar pageTitle={t('Meal')}
                   rightSideComponent={<FontButton label={t('Edit')} onClick={() => {openEditItemDialog(meal)}} />}
                   leftSideComponent={<BackButton onClick={closeDialog} />} />

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

@@ -53,7 +53,7 @@ const Meals = (props) => {
 
   useEffect(() => {
     fetchAndUpdateOwnMeals();
-  }, [user, fetchAndUpdateOwnMeals]);
+  }, [user]);
 
   const goToAddMeal = () => {history.push('/meals/add');};
   const openMealDetailView = (meal) => {

+ 2 - 2
client/src/components/Plans/EditPlanItem.jsx

@@ -9,7 +9,7 @@ import Navbar from "../Navbar";
 import FontButton from "../Buttons/FontButton";
 import EditPlanItemCore from "./EditPlanItemCore";
 import BackButton from "../Buttons/BackButton";
-import { SlidingTransition } from "../util";
+import { SlidingTransitionLeft } from "../util";
 import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
 import Loading from "../Loading";
 
@@ -126,7 +126,7 @@ const EditPlanItem = (props) => {
 
   return (
     <>{planItem ?
-      <Dialog open={open} fullScreen onClose={closeDialog} TransitionComponent={SlidingTransition}>
+      <Dialog open={open} fullScreen onClose={closeDialog} TransitionComponent={SlidingTransitionLeft}>
         <Navbar pageTitle={t('Edit Plan')}
                 leftSideComponent={<BackButton onClick={closeDialog} />}
                 rightSideComponent={planItem.title ? <FontButton onClick={editAndClose} label={t('Done')} /> : ''}

+ 19 - 30
client/src/components/Settings/Settings.jsx

@@ -6,53 +6,42 @@ import Profile from "./Profile";
 import axios from "axios";
 import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
 import Loading from "../Loading";
+import { createNewSettingsForUser } from "./settings.util";
 
 const serverURL = process.env.REACT_APP_SERVER_URL;
 
 const Settings = () => {
   const { t } = useTranslation();
-  const { isAuthenticated, isLoading, user } = useAuth0();
+  const { user } = useAuth0();
   const [settings, setSettings] = useState();
 
   useEffect(() => {
     getSettings();
-  }, [isAuthenticated, isLoading, user]);
-
-  console.log('AUTH0:', isAuthenticated, isLoading, user);
+  }, [user]);
 
   const getSettings = () => {
-    if (!isLoading && isAuthenticated) {
-      if (user) {
-        console.log('user', user);
-        const userId = user.sub;
-        axios.get(serverURL + '/settings/ofUser/' + userId)
-             .then(res => {
-               const settingsFound = res.data;
-               if (!settingsFound) {
-                 console.log('creating new settings for user', userId);
-                 const newSettings = { userId: userId };
-                 axios.post(serverURL + '/settings/add/', newSettings)
-                      .then(res => {
-                        console.log('result of adding settings for ' + userId, res);
-                        getSettings();
-                      }).catch(err => {console.log(err)});
-               } else {
-                 setSettings(res.data);
-               }
-             })
-             .catch(err => {
-               console.log(err.message);
-             });
-      }
+    if (user) {
+      console.log('user', user);
+      const userId = user.sub;
+      axios.get(serverURL + '/settings/ofUser/' + userId)
+           .then(res => {
+             const settingsFound = res.data;
+             if (!settingsFound) {
+               createNewSettingsForUser(userId, getSettings());
+             } else {
+               setSettings(res.data);
+             }
+           })
+           .catch(err => {
+             console.log(err.message);
+           });
     }
   }
 
   return (
     <>
       <Navbar pageTitle={t('Settings')} rightSideComponent={() => {}} />
-      <Typography variant="h1">
-        {t('Settings')}
-      </Typography>
+
       <br />
       <Profile />
       <br />

+ 13 - 0
client/src/components/Settings/settings.util.jsx

@@ -0,0 +1,13 @@
+import axios from "axios";
+
+const serverURL = process.env.REACT_APP_SERVER_URL;
+
+export const createNewSettingsForUser = (userId, callback) => {
+  console.log('creating new settings for user', userId);
+  const newSettings = { userId: userId };
+  axios.post(serverURL + '/settings/add/', newSettings)
+       .then(res => {
+         console.log('result of adding settings for ' + userId, res);
+         if (callback) callback();
+       }).catch(err => {console.log(err)});
+}

+ 68 - 10
client/src/components/Social/Social.jsx

@@ -1,17 +1,17 @@
 import React, { useEffect, useState } from 'react';
-import { List, TextField, Typography } from '@material-ui/core';
-import axios from 'axios';
+import { Avatar, Divider, IconButton, List, ListItem, ListItemAvatar, ListItemSecondaryAction, ListItemText, Typography } from '@material-ui/core';
 import Navbar from "../Navbar";
 import SearchButton from "../Buttons/SearchButton";
 import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
 import { makeStyles } from '@material-ui/styles';
 import Loading from "../Loading";
-import InnerBoxCloseX from "../Buttons/InnerBoxCloseX";
 import UserSearch from "./UserSearch";
 import { useTranslation } from "react-i18next";
-import BoxCloseX from "../Buttons/BoxCloseX";
-import PhotoCloseX from "../Buttons/PhotoCloseX";
-import SimpleCloseX from "../Buttons/SimpleCloseX";
+import axios from "axios";
+import { createNewSettingsForUser } from "../Settings/settings.util";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faUser } from "@fortawesome/free-solid-svg-icons";
+import {ArrowRight} from "@material-ui/icons";
 
 const useStyles = makeStyles({
   infoText: {
@@ -22,26 +22,84 @@ const useStyles = makeStyles({
   },
 });
 
-const domain = process.env.REACT_APP_AUTH0_DOMAIN;
 const serverURL = process.env.REACT_APP_SERVER_URL;
 
 const Social = () => {
   const classes = useStyles();
-  const { user, getAccessTokenSilently } = useAuth0();
+  const { user } = useAuth0();
   const { t } = useTranslation();
 
   const [searchOpen, setSearchOpen] = useState(false);
   const [contacts, setContacts] = useState([]);
 
+  const updateContacts = (newContacts) => {
+    axios.put(serverURL + '/settings/updateUserContacts/' + user.sub, newContacts).then((result) => {
+      console.log('updated user contacts', result.data);
+      fetchContacts();
+    });
+  }
+
+  const fetchContacts = () => {
+    if (user) {
+      const userId = user.sub;
+      axios.get(serverURL + '/settings/ofUser/' + userId)
+           .then(res => {
+             const settingsFound = res.data;
+             if (!settingsFound) {
+               createNewSettingsForUser(userId, fetchContacts());
+             } else {
+               const sortedContacts = settingsFound.contacts.sort((a,b) => a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1)
+               setContacts(sortedContacts);
+             }
+           })
+           .catch(err => {
+             console.log(err.message);
+           });
+    }
+  }
+
+  useEffect(() => {
+    fetchContacts();
+  }, [])
+
+  const showUser = (userId) => {
+
+  }
+
+  console.log(contacts);
+
+  const getListItems = () => {
+    return contacts.map(contact => {
+      const userId = contact.user_id;
+      let avatar;
+      if (contact.picture) {
+        avatar = <Avatar alt={'profile picture of ' + user.name} src={contact.picture} />;
+      } else {
+        avatar = (<Avatar><FontAwesomeIcon icon={faUser} /></Avatar>);
+      }
+      return (
+        <div key={userId}>
+          <ListItem button onClick={() => {showUser(userId)}}>
+            <ListItemAvatar>
+              {avatar}
+            </ListItemAvatar>
+            <ListItemText primary={contact.name} primaryTypographyProps={{ className: classes.listItemText }} />
+          </ListItem>
+          <Divider />
+        </div>
+      );
+    });
+  }
+
   return (
     <>
 
-      {searchOpen ? <UserSearch closeSearch={() => {setSearchOpen(false)}} />
+      {searchOpen ? <UserSearch open={searchOpen} closeSearch={() => {setSearchOpen(false)}} contacts={contacts} updateContacts={updateContacts} openContact={showUser} />
         : <Navbar pageTitle={t('Social')} rightSideComponent={<SearchButton onClick={() => {setSearchOpen(true)}} />} />}
 
       {contacts.length === 0 ? <Typography className={classes.infoText}>{t("No contacts yet")}.<br />{t("Search for friends in the top right corner")}.</Typography> :
         <List component="nav" className={classes.root} aria-label="meal list">
-
+          {getListItems()}
         </List>
       }
     </>

+ 88 - 30
client/src/components/Social/UserSearch.jsx

@@ -1,5 +1,6 @@
 import React, { useEffect, useState } from 'react';
-import { AppBar, InputBase, Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, TextField, Toolbar, Typography } from '@material-ui/core';
+import { InputBase, Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, ListItemSecondaryAction, IconButton, Collapse, Toolbar, Typography } from '@material-ui/core';
+import { PersonAdd, PersonAddDisabled } from '@material-ui/icons';
 import axios from 'axios';
 import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
 import { makeStyles } from '@material-ui/styles';
@@ -8,7 +9,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faUser } from "@fortawesome/free-solid-svg-icons";
 import { useTranslation } from "react-i18next";
 import SimpleCloseX from "../Buttons/SimpleCloseX";
-import { func } from "prop-types";
+import { array, arrayOf, bool, func, shape } from "prop-types";
+import { SlidingTransitionLeft } from "../util";
 
 const useStyles = makeStyles(theme => ({
   userSearchBox: {
@@ -18,6 +20,7 @@ const useStyles = makeStyles(theme => ({
     top: 0,
     left: 0,
     width: '100%',
+    height: 'calc(100% - ' + process.env.REACT_APP_NAV_BOTTOM_HEIGHT + 'px)',
   },
   navBarTextInput: {
     backgroundColor: theme.palette.primary.main,
@@ -50,9 +53,25 @@ const useStyles = makeStyles(theme => ({
     display: 'flex',
     alignItems: 'center',
   },
+  searchInput: {
+    color: '#ffffff',
+  },
+  listItemText: {
+    overflow: "hidden",
+    textOverflow: "ellipsis",
+    paddingRight: '0.5rem',
+  },
+  addFriend: {
+    color: theme.palette.primary.main,
+  },
+  removeFriend: {
+    color: theme.palette.error.main,
+  },
+  resultList: {
+    height: 'calc(100% - ' + process.env.REACT_APP_NAV_BOTTOM_HEIGHT + 'px)',
+  }
 }));
 
-const domain = process.env.REACT_APP_AUTH0_DOMAIN;
 const serverURL = process.env.REACT_APP_SERVER_URL;
 
 const UserSearch = (props) => {
@@ -60,22 +79,50 @@ const UserSearch = (props) => {
   const { user } = useAuth0();
   const { t } = useTranslation();
 
+  const [, updateState] = React.useState();
+  const forceUpdate = React.useCallback(() => updateState({}), []);
+
   const [userList, setUserList] = useState([]);
   const [query, setQuery] = useState('');
-  const { closeSearch } = props;
+
+  const { closeSearch, open, contacts, updateContacts, openContact } = props;
 
   const getUsers = () => {
-    if(!query) setUserList([]);
-    axios.get(serverURL + '/users/fromQuery/' + query)
-         .then(res => {
-           console.log('result', res);
-           let usersFound = res.data;
-           usersFound = usersFound.filter(u => u.user_id !== user.sub);
-           setUserList(usersFound);
-         })
-         .catch(err => {
-           console.log(err.message);
-         });
+    if (query) {
+      axios.get(serverURL + '/users/fromQuery/' + query)
+           .then(res => {
+             console.log('result', res);
+             let usersFound = res.data;
+             usersFound = usersFound.filter(u => u.user_id !== user.sub);
+             setUserList(usersFound);
+           })
+           .catch(err => {
+             console.log(err.message);
+           });
+    } else {
+      axios.get(serverURL + '/users/all/')
+           .then(res => {
+             console.log('result', res);
+             let usersFound = res.data;
+             usersFound = usersFound.filter(u => u.user_id !== user.sub);
+             setUserList(usersFound);
+           })
+           .catch(err => {
+             console.log(err.message);
+           });
+    }
+  }
+
+  const addFriend = (user) => {
+    const newFriends = contacts;
+    newFriends.push(user);
+    updateContacts(newFriends);
+    // forceUpdate();
+  }
+
+  const removeFriend = (userId) => {
+    const newFriends = contacts.filter(u => u.user_id !== userId);
+    updateContacts(newFriends);
   }
 
   useEffect(() => {
@@ -84,6 +131,8 @@ const UserSearch = (props) => {
 
   const getListItems = () => {
     return userList.map(u => {
+      const userId = u.user_id;
+      const isContact = contacts.some(c => c.user_id === userId);
       let avatar;
       if (u.picture) {
         avatar = <Avatar alt={'profile picture of ' + user.name} src={u.picture} />;
@@ -91,12 +140,19 @@ const UserSearch = (props) => {
         avatar = (<Avatar><FontAwesomeIcon icon={faUser} /></Avatar>);
       }
       return (
-        <div key={u.user_id}>
-          <ListItem button onClick={() => {}}>
+        <div key={userId}>
+          <ListItem button onClick={() => {if (openContact) openContact(userId)}}>
             <ListItemAvatar>
               {avatar}
             </ListItemAvatar>
-            <ListItemText primary={u.name} />
+            <ListItemText primary={u.name} primaryTypographyProps={{ className: classes.listItemText }} />
+            <ListItemSecondaryAction>
+              <IconButton edge="end" onClick={() => isContact ? removeFriend(userId) : addFriend(u)}>
+                {isContact
+                  ? <PersonAddDisabled className={classes.removeFriend} />
+                  : <PersonAdd className={classes.addFriend} />}
+              </IconButton>
+            </ListItemSecondaryAction>
           </ListItem>
           <Divider />
         </div>
@@ -106,31 +162,33 @@ const UserSearch = (props) => {
 
   return (
     <div className={classes.userSearchBox}>
-
       <Toolbar className={classes.topNav}>
         <div className={classes.flexdiv}>
-          <InputBase value={query} name="query" onChange={e => setQuery(e.target.value)} label="Query" autoFocus />
-          {/*<TextField value={query} name="query"  />*/}
+          <InputBase value={query} name="query" className={classes.searchInput} onChange={e => setQuery(e.target.value)} label="Query" autoFocus />
         </div>
         <div className={classes.rightSide}>
           <SimpleCloseX onClick={closeSearch} />
         </div>
       </Toolbar>
 
-
-      {userList.length === 0 ? <Typography className={classes.infoText}>{query ? t("No results") : ''} </Typography> :
-        <List component="nav" className={classes.root} aria-label="u list">
-          {getListItems()}
-          <div />
-        </List>
-      }
+      <Collapse in={open && userList} className={classes.resultList}>
+        {userList.length === 0 ? <Typography className={classes.infoText}>{query ? t("No results") : ''} </Typography> :
+          <List component="nav" className={classes.root} aria-label="u list">
+            {getListItems()}
+            <div />
+          </List>
+        }
+      </Collapse>
     </div>
-  )
-    ;
+  );
 }
 
 UserSearch.propTypes = {
   closeSearch: func.isRequired,
+  open: bool.isRequired,
+  contacts: arrayOf(shape({})),
+  updateContacts: func.isRequired,
+  openContact: func,
 }
 
 export default withAuthenticationRequired(UserSearch, {

+ 1 - 1
client/src/components/util.jsx

@@ -1,6 +1,6 @@
 import React from "react";
 import Slide from "@material-ui/core/Slide";
 
-export const SlidingTransition = React.forwardRef(function Transition(props, ref) {
+export const SlidingTransitionLeft = React.forwardRef(function Transition(props, ref) {
   return <Slide direction="left" ref={ref} {...props} />;
 });

+ 22 - 8
server/controllers/settings.controller.js

@@ -42,25 +42,39 @@ export const updateSettings = async (req, res) => {
   let newSettings = req.body;
   let id = req.params.id;
   try {
-    Settings.findById(id, function (err, Settings) {
-      Settings.set('userId', newSettings.userId);
-      Settings.set('language', newSettings.language);
-      Settings.save().then(Settings => {
-        res.status(201).json({ 'info': 'Settings updated', Settings })
+    Settings.findById(id, function (err, settingFound) {
+      settingFound.set('userId', newSettings.userId);
+      settingFound.set('language', newSettings.language);
+      settingFound.save().then(Settings => {
+        res.status(201).json({ 'info': 'Settings updated', settingFound })
       });
     });
   } catch (error) {
-    res.status(400).json({ 'info': `Update of Settings ${id} failed`, 'message': error.message });
+    res.status(400).json({ 'info': `Update of setting ${id} failed`, 'message': error.message });
   }
 }
 
 export const deleteSettings = async (req, res) => {
   let id = req.params.id;
-  Settings.findByIdAndDelete(id, {}, function (err, Settings) {
+  Settings.findByIdAndDelete(id, {}, function (err, settings) {
     if (err) {
       res.status(400).json({ 'info': `Deletion of Settings ${id} failed`, 'message': err.message });
     } else {
-      res.status(201).json({ 'info': 'Settings deleted, id: ', id, Settings })
+      res.status(201).json({ 'info': 'Settings deleted, id: ', id, settings })
     }
   });
 }
+
+export const updateUserContacts = async (req, res) => {
+  let newContacts = req.body;
+  let id = req.params.id;
+  try {
+    const setting = await Settings.findOne({ userId: id });
+    setting.set('contacts', newContacts);
+    setting.save().then(settingSaved => {
+      res.status(201).json({ 'info': 'Updated user contacts', settingSaved })
+    });
+  } catch (error) {
+    res.status(400).json({ 'info': `Update of contacts for user ${id} failed`, 'message': error.message });
+  }
+}

+ 1 - 0
server/models/settings.model.js

@@ -3,6 +3,7 @@ import mongoose from 'mongoose';
 const settingsSchema = mongoose.Schema({
   userId: String,
   language: String,
+  contacts: [],
 });
 
 const Settings = mongoose.model('Settings', settingsSchema);

+ 2 - 1
server/routes/settings.routes.js

@@ -1,6 +1,6 @@
 import express from 'express';
 // just routes
-import { getSingleSetting, getSettingsOfUser, addSettings, updateSettings, deleteSettings } from "../controllers/settings.controller.js";
+import { getSingleSetting, getSettingsOfUser, addSettings, updateSettings, deleteSettings, updateUserContacts } from "../controllers/settings.controller.js";
 
 const router = express.Router();
 
@@ -9,5 +9,6 @@ router.get('/ofUser/:id', getSettingsOfUser);
 router.post('/add', addSettings);
 router.post('/edit/:id', updateSettings);
 router.post('/delete/:id', deleteSettings);
+router.put('/updateUserContacts/:id', updateUserContacts);
 
 export default router;

BIN
server/uploads/mealImages/1615897090969-IMG_2660.jpg


BIN
server/uploads/mealImages/1615897090984-IMG_2661.jpg


BIN
server/uploads/mealImages/1615897090995-IMG_2662.jpg