<|start_filename|>client/src/components/Search.js<|end_filename|>
import React from 'react';
import { Input } from 'semantic-ui-react';

const Search = ({ search, setSearch, isDarkMode }) => {
  const handleSearch = (e) => {
    setSearch(e.target.value);
  };

  return (
    <div className="search-card">
      <Input
        className={isDarkMode ? 'dark-mode-input' : ''}
        fluid
        size="large"
        type="text"
        value={search}
        onChange={handleSearch}
        placeholder="Search for contact name or profile link"
        icon={{ name: 'search', color: 'teal' }}
        iconPosition="left"
        action={
          search !== '' && {
            color: 'teal',
            icon: 'close',
            onClick: () => setSearch(''),
          }
        }
      />
    </div>
  );
};

export default Search;


<|start_filename|>client/src/components/LoginForm.js<|end_filename|>
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import FormError from './FormError';
import DemoCredsInfo from './DemoCredsInfo';
import contactService from '../services/contacts';
import authService from '../services/auth';
import storageService from '../utils/localStorageHelpers';
import { useMediaQuery } from 'react-responsive';
import { Segment, Form, Button, Icon, Header } from 'semantic-ui-react';

const LoginForm = ({ setUser, notify, isDarkMode }) => {
  const [credentials, setCredentials] = useState({
    email: '',
    password: '',
  });
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [showPass, setShowPass] = useState(false);
  const history = useHistory();
  const isMobile = useMediaQuery({ maxWidth: 767 });
  const { email, password } = credentials;

  const handleOnChange = (e) => {
    setCredentials({ ...credentials, [e.target.name]: e.target.value });
  };

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      setIsLoading(true);
      const user = await authService.login(credentials);
      setUser(user);
      contactService.setToken(user.token);
      storageService.saveUser(user);
      setIsLoading(false);
      setError(null);

      notify(`Welcome ${user.displayName}, you're logged in!`, {
        appearance: 'success',
      });
      history.push('/');
    } catch (err) {
      setIsLoading(false);
      const errRes = err?.response?.data;

      if (errRes?.error) {
        return setError({ message: errRes.error });
      } else {
        return setError({ message: err.message });
      }
    }
  };

  return (
    <Segment
      className={
        isDarkMode ? 'login-reg-card dark-mode-segment' : 'login-reg-card'
      }
      inverted={isDarkMode}
    >
      <Header as={isMobile ? 'h3' : 'h2'} textAlign="center">
        <Icon name="sign-in" />
        Login to your account
      </Header>
      <Form
        onSubmit={handleLogin}
        className={isDarkMode ? 'dark-mode-auth-form auth-form' : 'auth-form'}
      >
        <Form.Input
          required
          placeholder="For ex. <EMAIL>"
          label="Email"
          type="email"
          name="email"
          value={email}
          onChange={handleOnChange}
          icon="at"
          iconPosition="left"
        />
        <Form.Input
          required
          placeholder="Password must have minimum characters of 6."
          label="Password"
          type={showPass ? 'text' : 'password'}
          name="password"
          value={password}
          onChange={handleOnChange}
          icon="lock"
          iconPosition="left"
          action={
            password !== '' && {
              icon: showPass ? 'eye slash' : 'eye',
              onClick: () => setShowPass(!showPass),
            }
          }
        />

        <Button
          animated="vertical"
          color="teal"
          icon
          labelPosition="left"
          type="submit"
          floated="right"
          loading={isLoading}
          size={isMobile ? 'small' : 'large'}
          fluid={isMobile}
        >
          <Icon name="sign-in" />
          Login
        </Button>
        <Header
          as="h4"
          textAlign={isMobile ? 'center' : 'left'}
          className="login-reg-bottom-text"
        >
          Don't have an account? <Link to="/register">Register.</Link>
        </Header>
      </Form>
      {error && (
        <FormError
          message={error.message}
          title={error.title}
          positive={error.positive}
          setError={setError}
        />
      )}
      <DemoCredsInfo />
    </Segment>
  );
};

export default LoginForm;


<|start_filename|>server/models/contact.js<|end_filename|>
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');

const urlSchema = new mongoose.Schema({
  url: {
    type: String,
    required: true,
    trim: true,
  },
  site: {
    type: String,
    required: true,
    trim: true,
  },
});

const contactSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,
  },
  contacts: [urlSchema],
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
  },
  displayPicture: {
    exists: {
      type: Boolean,
      required: true,
      default: 'false',
    },
    link: {
      type: String,
      required: true,
      default: 'null',
    },
    public_id: {
      type: String,
      required: true,
      default: 'null',
    },
  },
});

contactSchema.plugin(uniqueValidator);

// replaces _id with id, convert id to string from ObjectID and deletes __v
contactSchema.set('toJSON', {
  transform: (_document, returnedObject) => {
    returnedObject.id = returnedObject._id.toString();
    delete returnedObject._id;
    delete returnedObject.__v;
  },
});

urlSchema.set('toJSON', {
  transform: (_document, returnedObject) => {
    returnedObject.id = returnedObject._id.toString();
    delete returnedObject._id;
  },
});

module.exports = mongoose.model('Contact', contactSchema);


<|start_filename|>client/src/components/ContactsDisplay.js<|end_filename|>
import React from 'react';
import ContactCard from './ContactCard';
import ContactsLoader from './ContactsLoader';
import { Header, Icon } from 'semantic-ui-react';

const ContactsDisplay = ({
  contacts,
  setContacts,
  search,
  options,
  handleOptionAddition,
  notify,
  isLoading,
  isDarkMode,
}) => {
  const filterByName = (contact, search) => {
    return contact.name.toLowerCase().includes(search.toLowerCase());
  };

  const filterByProfileLinks = (contact, search) => {
    const urlsArray = contact.contacts.map((c) => c.url);

    return urlsArray.find((u) =>
      u.toLowerCase().includes(search.toLowerCase())
    );
  };

  const contactsToDisplay = contacts.filter(
    (c) => filterByName(c, search) || filterByProfileLinks(c, search)
  );

  return (
    <div className="contacts-display">
      {search !== '' && contactsToDisplay.length !== 0 && (
        <Header className={isDarkMode ? 'dark-mode-info-text' : ''}>
          <Icon name="searchengin" />
          Showing search results for query "{search}"
        </Header>
      )}
      {search !== '' && contactsToDisplay.length === 0 && (
        <Header
          textAlign="center"
          as="h2"
          className={isDarkMode ? 'dark-mode-info-text main-text' : 'main-text'}
        >
          <Icon name="searchengin" />
          Search: No matches found for "{search}"
        </Header>
      )}
      {!isLoading && search === '' && contactsToDisplay.length === 0 && (
        <Header
          textAlign="center"
          as="h2"
          className={isDarkMode ? 'dark-mode-info-text main-text' : 'main-text'}
        >
          <Icon name="info" />
          No contacts added yet.
        </Header>
      )}
      {isLoading ? (
        <ContactsLoader isDarkMode={isDarkMode} />
      ) : (
        contactsToDisplay.map((contact) => (
          <ContactCard
            contact={contact}
            contacts={contacts}
            setContacts={setContacts}
            options={options}
            handleOptionAddition={handleOptionAddition}
            key={contact.id}
            notify={notify}
            isDarkMode={isDarkMode}
          />
        ))
      )}
    </div>
  );
};

export default ContactsDisplay;


<|start_filename|>client/src/components/Routes.js<|end_filename|>
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import Search from './Search';
import AddContactModal from '../components/AddContactModal';
import ContactsDisplay from '../components/ContactsDisplay';
import RegisterForm from '../components/RegisterForm';
import LoginForm from '../components/LoginForm';
import storageService from '../utils/localStorageHelpers';

const Routes = ({
  contacts,
  setContacts,
  user,
  setUser,
  search,
  setSearch,
  options,
  handleOptionAddition,
  notify,
  isLoading,
  isDarkMode,
}) => {
  return (
    <Switch>
      <Route exact path="/">
        {storageService.loadUser() || user ? (
          <>
            <Search
              setSearch={setSearch}
              search={search}
              isDarkMode={isDarkMode}
            />
            <AddContactModal
              setContacts={setContacts}
              options={options}
              handleOptionAddition={handleOptionAddition}
              notify={notify}
              isDarkMode={isDarkMode}
            />
            <ContactsDisplay
              contacts={contacts}
              setContacts={setContacts}
              search={search}
              options={options}
              handleOptionAddition={handleOptionAddition}
              notify={notify}
              isLoading={isLoading}
              isDarkMode={isDarkMode}
            />
          </>
        ) : (
          <Redirect to="login" />
        )}
      </Route>
      <Route exact path="/register">
        <RegisterForm
          setUser={setUser}
          notify={notify}
          isDarkMode={isDarkMode}
        />
      </Route>
      <Route exact path="/login">
        <LoginForm setUser={setUser} notify={notify} isDarkMode={isDarkMode} />
      </Route>
    </Switch>
  );
};

export default Routes;


<|start_filename|>server/app.js<|end_filename|>
const express = require('express');
require('express-async-errors');
const cors = require('cors');
const middleware = require('./utils/middleware');
const authRoutes = require('./routes/auth');
const contactRoutes = require('./routes/contact');

const app = express();

app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ limit: '10mb', extended: true }));

app.use('/api', authRoutes);
app.use('/api/contacts', contactRoutes);

app.use(middleware.unknownEndpointHandler);
app.use(middleware.errorHandler);

module.exports = app;


<|start_filename|>client/src/components/DemoCredsInfo.js<|end_filename|>
import React from 'react';
import { Message } from 'semantic-ui-react';
import demoCredentials from '../data/demoCreds';

const DemoCredsInfo = () => {
  return (
    <Message header="Demo Account Credentials" content={demoCredentials} />
  );
};

export default DemoCredsInfo;


<|start_filename|>client/src/index.css<|end_filename|>
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  width: calc(100vw - 34px);
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.container {
  margin-bottom: 2em;
}

.main-segment {
  width: 100vw;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 100vh;
  padding: 0 !important;
}

a {
  color: #009c95;
}

a:hover {
  color: #06746e;
  filter: brightness(60%);
}

.nav-bar {
  height: 4em !important;
  position: sticky;
  top: 1px;
  z-index: 10;
}

.nav-bar .menu .item {
  font-size: 1.2em !important;
}

.nav-bar .menu a:hover {
  color: rgb(7, 63, 63) !important;
}

.nav-title {
  display: flex;
  flex-direction: column;
}

.nav-logo {
  height: 25px !important;
  font-size: 1.3em;
  margin-bottom: 0.2em;
  align-self: flex-start;
}

.nav-link {
  color: rgb(9, 85, 85);
}

.nav-link:hover {
  color: rgba(9, 85, 85, 50);
}

.name-header {
  margin-left: 8px;
}

.search-card {
  margin-bottom: 1em !important;
}

.contacts-display {
  margin-top: 20px;
}

.modal {
  padding: 10px;
}

.avatar-label {
  margin-top: 3px !important;
  margin-left: 0px !important;
}

.avatar-image:hover {
  cursor: pointer;
}

.avatar-preview {
  margin: 0 auto;
}

.upload-preview {
  display: block;
  margin: 1.5em auto;
}

.clear-preview-btn {
  margin-top: 0.3em !important;
}

.card-header {
  display: flex !important;
  justify-content: space-between !important;
  align-items: center !important;
}

.contact-edit-btn {
  margin-right: 1em !important;
}

.edit-btn .icon {
  color: #49769c !important;
}

.delete-btn .icon {
  color: #db2828 !important;
}

.login-reg-card {
  padding-left: 14em !important;
  padding-right: 14em !important;
  padding-bottom: 2em !important;
}

.main-text {
  margin-top: 3em !important;
}

.auth-form {
  margin-bottom: 2em;
}

.dark-mode-input input {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
  border: 1px solid rgb(233, 230, 230) !important;
}

.dark-mode-card .content {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
}

.dark-mode-card .content strong {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal .header {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal .content {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal label {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal input {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
  border: 1px solid white !important;
}

.dark-mode-modal .field .dropdown {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
  border: 1px solid white !important;
}

.dark-mode-modal .field .dropdown i {
  color: rgb(233, 230, 230) !important;
  border-width: 0 !important;
  border-bottom-width: 1px !important;
}

.dark-mode-modal .field .dropdown .menu .item {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal .field .dropdown .menu .selected {
  color: rgb(194, 229, 229) !important;
  background-color: rgb(70, 70, 63) !important;
}

.dark-mode-modal .field .dropdown .menu .item:hover {
  background-color: rgb(95, 95, 91) !important;
}

.dark-mode-modal i {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-modal label i {
  color: black !important;
}

.dark-mode-modal .actions {
  background: #1b1c1d !important;
}

.dark-mode-auth-form label {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-auth-form .header {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-auth-form input {
  background: #1b1c1d !important;
  color: rgb(233, 230, 230) !important;
  border: 1px solid rgb(233, 230, 230) !important;
}

.dark-mode-auth-form .input i {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-auth-form input:autofill {
  color: #1b1c1d !important;
  border: 1px solid rgb(233, 230, 230) !important;
}

.dark-mode-auth-form .input .button i {
  color: black !important;
}

.dark-mode-clear-btn i {
  color: black !important;
}

.dark-mode-info-text {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-menu {
  background: #24282c !important;
}

.dark-mode-menu .item * {
  color: rgb(233, 230, 230) !important;
}

.dark-mode-segment {
  border: 1px solid rgb(233, 230, 230) !important;
}

@media only screen and (max-width: 768px) {
  body {
    width: 100vw;
  }

  .edit-btn {
    padding: 5px !important;
  }

  .delete-btn {
    padding: 5px !important;
  }

  .contact-edit-btn {
    padding: 8px !important;
  }

  .contact-del-btn {
    padding: 8px !important;
  }

  .login-reg-card {
    padding-left: 1em !important;
    padding-right: 1em !important;
    padding-bottom: 1em !important;
  }

  .login-reg-bottom-text {
    margin-top: 4em !important;
  }
}


<|start_filename|>client/src/utils/arraysAndFuncs.js<|end_filename|>
export const optionsArray = [
  { key: 'fb', text: 'Facebook', value: 'Facebook', icon: 'facebook' },
  { key: 'ig', text: 'Instagram', value: 'Instagram', icon: 'instagram' },
  { key: 'tw', text: 'Twitter', value: 'Twitter', icon: 'twitter' },
  { key: 'gh', text: 'Github', value: 'Github', icon: 'github' },
  { key: 'yt', text: 'Youtube', value: 'Youtube', icon: 'youtube' },
];

export const siteIconsArray = [
  'facebook',
  'github',
  'youtube',
  'twitter',
  'instagram',
  'blogger',
  'linkedin',
  'reddit',
  'medium',
  'telegram',
  'stack overflow',
  'spotify',
];

const labelColors = [
  'red',
  'orange',
  'yellow',
  'olive',
  'green',
  'teal',
  'blue',
  'violet',
  'purple',
  'pink',
  'brown',
  'grey',
  'black',
];

export const randomColor = () => {
  return labelColors[Math.floor(Math.random() * labelColors.length)];
};

export const getCircularAvatar = (imageLink) => {
  const firstPart = imageLink.split('image/upload/')[0];
  const secondPart = imageLink.split('image/upload/')[1];
  const transformApi = 'w_200,h_200,c_fill,r_max/e_trim/';

  return [firstPart, transformApi, secondPart].join('');
};

export const generateBase64Encode = (file, setState) => {
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onloadend = () => {
    setState(reader.result);
  };
};


<|start_filename|>client/src/components/RegisterForm.js<|end_filename|>
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import FormError from './FormError';
import DemoCredsInfo from './DemoCredsInfo';
import contactService from '../services/contacts';
import authService from '../services/auth';
import storageService from '../utils/localStorageHelpers';
import { useMediaQuery } from 'react-responsive';
import { Segment, Form, Button, Icon, Header } from 'semantic-ui-react';

const RegisterForm = ({ setUser, notify, isDarkMode }) => {
  const [userDetails, setUserDetails] = useState({
    displayName: '',
    email: '',
    password: '',
  });
  const [confirmPassword, setConfirmPassword] = useState('');
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [showPass, setShowPass] = useState(false);
  const [showConfirmPass, setShowConfirmPass] = useState(false);
  const history = useHistory();
  const isMobile = useMediaQuery({ maxWidth: 767 });
  const { displayName, email, password } = userDetails;

  const handleOnChange = (e) => {
    setUserDetails({ ...userDetails, [e.target.name]: e.target.value });
  };

  const handleRegister = async (e) => {
    if (password !== confirmPassword)
      return setError(`Confirm password failed. Both passwords need to match.`);
    e.preventDefault();
    try {
      setIsLoading(true);
      const user = await authService.register(userDetails);
      setUser(user);
      contactService.setToken(user.token);
      storageService.saveUser(user);
      setIsLoading(false);
      setError(null);

      notify(`Welcome ${user.displayName}, you have successfully registered!`, {
        appearance: 'success',
      });
      history.push('/');
    } catch (err) {
      setIsLoading(false);
      const errRes = err?.response?.data;

      if (errRes?.error) {
        return setError(errRes.error);
      } else {
        return setError(err.message);
      }
    }
  };

  return (
    <Segment
      className={
        isDarkMode ? 'login-reg-card dark-mode-segment' : 'login-reg-card'
      }
      inverted={isDarkMode}
    >
      <Header as={isMobile ? 'h3' : 'h2'} textAlign="center">
        <Icon name="signup" />
        Create an account
      </Header>
      <Form
        onSubmit={handleRegister}
        className={isDarkMode ? 'dark-mode-auth-form auth-form' : 'auth-form'}
      >
        <Form.Input
          required
          placeholder="For ex. <NAME>"
          label="<NAME>"
          type="text"
          name="displayName"
          value={displayName}
          onChange={handleOnChange}
          icon="user"
          iconPosition="left"
        />
        <Form.Input
          required
          placeholder="For ex. <EMAIL>"
          label="Email"
          type="email"
          name="email"
          value={email}
          onChange={handleOnChange}
          icon="at"
          iconPosition="left"
        />
        <Form.Input
          required
          placeholder="Password must have minimum characters of 6."
          label="Password"
          type={showPass ? 'text' : 'password'}
          name="password"
          value={password}
          onChange={handleOnChange}
          icon="lock"
          iconPosition="left"
          action={
            password !== '' && {
              icon: showPass ? 'eye slash' : 'eye',
              onClick: () => setShowPass(!showPass),
            }
          }
        />
        <Form.Input
          required
          placeholder="Confirm Password"
          label="Confirm Password"
          type={showConfirmPass ? 'text' : 'password'}
          value={confirmPassword}
          onChange={({ target }) => setConfirmPassword(target.value)}
          icon="lock"
          iconPosition="left"
          action={
            confirmPassword !== '' && {
              icon: showConfirmPass ? 'eye slash' : 'eye',
              onClick: () => setShowConfirmPass(!showConfirmPass),
            }
          }
        />

        <Button
          animated="vertical"
          color="teal"
          icon
          labelPosition="left"
          type="submit"
          floated="right"
          loading={isLoading}
          size={isMobile ? 'small' : 'large'}
          fluid={isMobile}
        >
          <Icon name="signup" />
          Register
        </Button>
        <Header
          as="h4"
          textAlign={isMobile ? 'center' : 'left'}
          className="login-reg-bottom-text"
        >
          Already have an account? <Link to="/login">Login.</Link>
        </Header>
      </Form>
      {error && <FormError message={error} setError={setError} />}
      <DemoCredsInfo />
    </Segment>
  );
};

export default RegisterForm;


<|start_filename|>client/src/components/FormError.js<|end_filename|>
import React from 'react';
import { Message, Button } from 'semantic-ui-react';

const FormError = ({ message, setError, title, positive }) => {
  return (
    <Message negative={positive ? false : true}>
      <Message.Header>
        {title || 'Error'}
        <Button
          icon="close"
          color="red"
          floated="right"
          onClick={() => setError(null)}
          compact
          size="mini"
          circular
        />
      </Message.Header>
      <p>{message}</p>
    </Message>
  );
};

export default FormError;


<|start_filename|>client/src/services/contacts.js<|end_filename|>
import axios from 'axios';
import backendUrl from '../backendUrl';

const baseUrl = `${backendUrl}/api/contacts`;

let token = null;

const setToken = (newToken) => {
  token = newToken;
};

const setConfig = () => {
  return {
    headers: { 'x-auth-token': token },
  };
};

const getAll = async () => {
  const response = await axios.get(baseUrl, setConfig());
  return response.data;
};

const addNew = async (contactObj) => {
  const response = await axios.post(baseUrl, contactObj, setConfig());
  return response.data;
};

const deleteContact = async (id) => {
  const response = await axios.delete(`${baseUrl}/${id}`, setConfig());
  return response.data;
};

const editContact = async (id, contactObj) => {
  const response = await axios.patch(
    `${baseUrl}/${id}/name_dp`,
    contactObj,
    setConfig()
  );
  return response.data;
};

const addLink = async (id, linkObj) => {
  const response = await axios.post(
    `${baseUrl}/${id}/url`,
    linkObj,
    setConfig()
  );
  return response.data;
};

const editLink = async (id, urlId, linkObj) => {
  const response = await axios.patch(
    `${baseUrl}/${id}/url/${urlId}`,
    linkObj,
    setConfig()
  );
  return response.data;
};

const deleteLink = async (id, urlId) => {
  const response = await axios.delete(
    `${baseUrl}/${id}/url/${urlId}`,
    setConfig()
  );
  return response.data;
};

export default {
  setToken,
  getAll,
  addNew,
  deleteContact,
  editContact,
  addLink,
  editLink,
  deleteLink,
};


<|start_filename|>client/src/components/NavBar.js<|end_filename|>
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import storageService from '../utils/localStorageHelpers';
import { useMediaQuery } from 'react-responsive';
import { Menu, Icon, Dropdown } from 'semantic-ui-react';

const NavBar = ({ user, setUser, isDarkMode, setIsDarkMode }) => {
  const [iconLoading, setIconLoading] = useState(false);
  const location = useLocation();

  const isMobile = useMediaQuery({ maxWidth: 767 });

  const handleLogout = () => {
    setUser(null);
    storageService.logoutUser();
  };

  const handleDarkModeToggle = () => {
    setIsDarkMode(!isDarkMode);
    setIconLoading(true);
    storageService.saveDarkMode(!isDarkMode);
    setTimeout(() => setIconLoading(false), 2150);
  };

  const logoutMenu = () => {
    return isMobile ? (
      <Dropdown item icon="sidebar">
        <Dropdown.Menu className={isDarkMode ? 'dark-mode-menu' : ''}>
          <Dropdown.Item>
            <Icon name="user" />
            <span>{`Hi, ${user.displayName}`}</span>
          </Dropdown.Item>
          <Dropdown.Item onClick={handleLogout}>
            <Icon name="sign-out" />
            <span>Logout</span>
          </Dropdown.Item>
          <Dropdown.Item onClick={handleDarkModeToggle}>
            <Icon
              name={isDarkMode ? 'moon' : 'sun'}
              color={isDarkMode ? 'purple' : 'yellow'}
            />
            <span>Dark Mode: {isDarkMode ? 'ON' : 'OFF'}</span>
          </Dropdown.Item>
        </Dropdown.Menu>
      </Dropdown>
    ) : (
      <>
        <Menu.Item>
          <Icon name="user" />
          {`Hi, ${user.displayName}`}
        </Menu.Item>
        <Menu.Item name="Logout" onClick={handleLogout} icon="sign-out" />
      </>
    );
  };

  const loginRegisterMenu = () => {
    return isMobile ? (
      <Dropdown item icon="sidebar">
        <Dropdown.Menu className={isDarkMode ? 'dark-mode-menu' : ''}>
          <Dropdown.Item as={Link} to="/register">
            <Icon name="signup" />
            <span>Register</span>
          </Dropdown.Item>
          <Dropdown.Item as={Link} to="/login">
            <Icon name="sign-in" />
            <span>Login</span>
          </Dropdown.Item>
          <Dropdown.Item onClick={handleDarkModeToggle}>
            <Icon
              name={isDarkMode ? 'moon' : 'sun'}
              color={isDarkMode ? 'purple' : 'yellow'}
            />
            <span>Dark Mode: {isDarkMode ? 'ON' : 'OFF'}</span>
          </Dropdown.Item>
        </Dropdown.Menu>
      </Dropdown>
    ) : (
      <>
        <Menu.Item
          as={Link}
          name="Register"
          to="/register"
          active={location.pathname === '/register'}
          icon="signup"
        />
        <Menu.Item
          as={Link}
          name="Login"
          to="/login"
          active={location.pathname === '/login'}
          icon="sign-in"
        />
      </>
    );
  };

  return (
    <Menu
      borderless={isMobile}
      inverted={isDarkMode}
      size="small"
      color="teal"
      className="nav-bar"
    >
      <Menu.Item header className="nav-title">
        <div className="nav-logo">
          <Icon name="user circle" />
          Profile Store
        </div>
        <small>
          Made with <Icon name="heart" color="pink" /> by{' '}
          <a
            href="https://github.com/amand33p"
            target="_blank"
            rel="noopener noreferrer"
            className="nav-link"
          >
            amand33p
          </a>
        </small>
      </Menu.Item>
      <Menu.Menu position="right">
        {user ? <>{logoutMenu()}</> : <>{loginRegisterMenu()}</>}
        {!isMobile && (
          <Menu.Item>
            <Icon
              name={isDarkMode ? 'moon' : 'sun'}
              size="large"
              color={isDarkMode ? 'purple' : 'yellow'}
              circular
              loading={iconLoading}
              link
              onClick={handleDarkModeToggle}
            />
          </Menu.Item>
        )}
      </Menu.Menu>
    </Menu>
  );
};

export default NavBar;


<|start_filename|>server/routes/contact.js<|end_filename|>
const express = require('express');
const {
  getContacts,
  createNewContact,
  deleteContact,
  updateContactNameDP,
  addProfileUrl,
  deleteProfileUrl,
  updateProfileUrl,
} = require('../controllers/contact');
const { auth } = require('../utils/middleware');

const router = express.Router();

router.get('/', auth, getContacts);
router.post('/', auth, createNewContact);
router.delete('/:id', auth, deleteContact);
router.patch('/:id/name_dp', auth, updateContactNameDP);
router.post('/:id/url', auth, addProfileUrl);
router.patch('/:id/url/:urlId', auth, updateProfileUrl);
router.delete('/:id/url/:urlId', auth, deleteProfileUrl);

module.exports = router;


<|start_filename|>client/src/App.js<|end_filename|>
import React, { useState, useEffect } from 'react';
import NavBar from './components/NavBar';
import Routes from './components/Routes';
import contactService from './services/contacts';
import { optionsArray } from './utils/arraysAndFuncs';
import storageService from './utils/localStorageHelpers';
import { useToasts } from 'react-toast-notifications';
import { Container, Segment } from 'semantic-ui-react';

const App = () => {
  const [contacts, setContacts] = useState([]);
  const [user, setUser] = useState(null);
  const [options, setOptions] = useState(optionsArray);
  const [isLoading, setIsLoading] = useState(false);
  const [search, setSearch] = useState('');
  const [isDarkMode, setIsDarkMode] = useState(false);

  const { addToast: notify } = useToasts();

  useEffect(() => {
    const loggedUser = storageService.loadUser();

    if (loggedUser) {
      setUser(loggedUser);
      contactService.setToken(loggedUser.token);
    }
  }, []);

  useEffect(() => {
    const getAllContacts = async () => {
      try {
        setIsLoading(true);
        const contacts = await contactService.getAll();
        setContacts(contacts);
        setIsLoading(false);
      } catch (err) {
        setIsLoading(false);
        notify(err.message, {
          appearance: 'error',
        });
      }
    };

    if (user) {
      getAllContacts();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user]);

  useEffect(() => {
    const darkMode = storageService.loadDarkMode();
    if (darkMode === 'true') {
      setIsDarkMode(true);
    }
  }, []);

  const handleOptionAddition = (e, data) => {
    setOptions((prevState) => [
      {
        key: data.value,
        text: data.value,
        value: data.value,
        icon: 'linkify',
      },
      ...prevState,
    ]);
  };

  return (
    <Segment className="main-segment" inverted={isDarkMode}>
      <Container className="container">
        <NavBar
          user={user}
          setUser={setUser}
          isDarkMode={isDarkMode}
          setIsDarkMode={setIsDarkMode}
        />
        <Routes
          contacts={contacts}
          setContacts={setContacts}
          user={user}
          setUser={setUser}
          search={search}
          setSearch={setSearch}
          options={options}
          handleOptionAddition={handleOptionAddition}
          notify={notify}
          isLoading={isLoading}
          isDarkMode={isDarkMode}
        />
      </Container>
    </Segment>
  );
};

export default App;


<|start_filename|>client/src/components/DisplayPictureModal.js<|end_filename|>
import React, { useState } from 'react';
import { getCircularAvatar } from '../utils/arraysAndFuncs';

import { Header, Modal, Image } from 'semantic-ui-react';

const DisplayPictureModal = ({ imageLink, contactName, isDarkMode }) => {
  const [open, setOpen] = useState(false);

  return (
    <Modal
      closeIcon
      open={open}
      trigger={
        <Image
          avatar
          style={{ width: '49px', height: '49px' }}
          src={getCircularAvatar(imageLink)}
          className="avatar-image"
        />
      }
      onClose={() => setOpen(false)}
      onOpen={() => setOpen(true)}
      className={isDarkMode ? 'dark-mode-modal' : ''}
    >
      <Header icon="picture" content={`DP Preview: ${contactName}`} />
      <Modal.Content>
        <Image
          src={imageLink}
          size="large"
          rounded
          className="avatar-preview"
        />
      </Modal.Content>
    </Modal>
  );
};

export default DisplayPictureModal;
