Building an Election Portal for Student Elections - Part 2

This post is a walkthrough and code analysis of the Student Election Portal that was used by the IIT Madras BS Students. Part 2

15 mins read • Tue Apr 04 2023

Hola! In the first part, I've explained how I've made the data handling and authentication stuff. In this part I'll take you through building pages and components with those states and data. If you haven't had a chance to read part 1, click here

Creating context

What is a Context?

A context in the React app allows us to pass the data to the component tree without having to pass the data manually at every level.

Consider this case, I want to check the authentication status of the user in every page. The direct method to do this is to check the status on every page and render/redirect accordingly. Think of the time it would cost.

Instead of checking on every single page, you can do it one time and then store those information as a context. The data in the context is passed to all the components which share the context as the parent.

Creating and using the context in the app

React provides hooks such as createContext and useContext to create and use the context respectively

// src/contexts/app.js

import { createContext } from "react";
export const AppContext = createContext({});

Now to use the context, you'll have to include the context as the parent of the components where you'll need to check the status. Since in this case, I want to check the status on every page, I added the context as the root element.

// src/App.jsx

import AppContext from "contexts/app.js";
import { useState } from "react";

function App() {
  const [session, setSession] = useState({ data: {}, loading: true });
  return(
    <div className="App">
      <AppContext.Provider value={{ session, setSession }}>
      	{/* code here */}
      </AppContext.Provider>
    </div>
  );

I created session and setSession with useState hook. Now I can use the session data and setSession function from any component

Building Components

Layout

The basic layout of any website would have

  • Header (with or without navigation bar)
  • Content
  • Footer

The header with the navigation bar is the trickier design in this website. Pulling it off required lot of CSS.

/* src/components/nav/Nav.css */

.navbar-brand-img {
  border: 4px solid #d7a74f;
  position: absolute;
  border-radius: 50%;
  height: auto;
  width: 6rem;
}

.navbar-brand {
  font-family: Rationale;
  color: #000;
  width: fit-content;
}

.navbar-rounded {
  border-bottom-left-radius: 3rem;
  border-bottom-right-radius: 3rem;
}

.active {
  text-underline-offset: 3px;
  text-decoration: underline !important;
  text-decoration-color: #79201b !important;
}

@media screen and (max-width: 1000px) {
  .navbar-nav {
    width: 100%;
    padding: 8rem 0 !important;
    text-align: center;
  }
  .bg-secondary {
    margin-top: 1rem;
  }
  .nav-item {
    margin: 1vw
  }
  .navbar-brand-img {
    position: sticky;
    width: 4rem;
  }
  .navbar-brand {
    width: 90%;
    text-align: center;
  }
}

.navbar-toggler {
  border: 0;
  border-radius: 0;
  box-shadow: none !important;
}

.navbar-toggler[aria-expanded="true"] .navbar-toggler-icon {
  background-image: url("~assets/images/icon_close.png");
  opacity: 0.6
}

.navbar-toggler:focus {
  box-shadow: 0 0 0 0
}

.navbar-dark .navbar-toggler {
  border-color: rgba(0,0,0,0)
}

import { NavLink } from "react-router-dom";

import logo from "assets/images/logo.png";

import "./Nav.css";

const LINKS = [
  {
    to: "/candidates",
    label: "Candidates",
  },
  {
    to: "/vote",
    label: "Vote"
  },
  {
    to: "/profile",
    label: "Info",
  }
];

const NavigationDefault = () => {
  return (
    <div className="bg-color-maroon px-lg-5 pb-lg-4">
      <nav className="navbar navbar-dark navbar-expand-lg py-0 mx-lg-5">
        <div className="container-fluid bg-color-gold mx-lg-5 navbar-rounded">
          <img src={logo} className="navbar-brand-img mt-lg-5 m-2 mx-lg-5" alt="" />
          <button
            className="navbar-toggler"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#navbarNav"
            aria-controls="navbarNav"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span className="navbar-toggler-icon"></span>
          </button>
          <div className="collapse navbar-collapse justify-content-end" id="navbarNav">
            <ul className="navbar-nav navbar-dark py-2 pb-5 pb-lg-2 px-5">
              <li className="nav-item mx-3">
                <NavLink
                  to={"/"}
                  exact
                  activeClassName="active"
                  className="nav-link text-uppercase text-white fw-bold"
                  id={"Home"}
                >
                  Home
                </NavLink>
              </li>
              {LINKS.map(({ to, label }) => {
                return (
                  <li className="nav-item mx-3" key={to}>
                    <NavLink
                      to={to}
                      activeClassName="active"
                      className="nav-link text-uppercase text-white fw-bold"
                      id={label}
                    >
                      {label}
                    </NavLink>
                  </li>
                )
              })}
            </ul>
          </div>
        </div>
      </nav>
      <div className="mx-lg-5 px-lg-5">
        <div className="mx-lg-5 px-lg-5 d-flex justify-content-center d-lg-block">
          <div className="navbar-brand bg-color-primary mx-5 mx-lg-3 p-0 px-5 navbar-rounded">
            IIT Madras BS Students
          </div>
        </div>
      </div>
    </div>
  );
};

export default NavigationDefault;

Then the Footer

// src/components/footer/Footer.jsx

import "./boxicons.css";

const Footer = () => {
  return (
    <footer className="bg-color-gold px-lg-5 py-4 font-roboto">
      <div className="mx-lg-5 px-lg-5">
        <a href="https://bit.ly/iitmelections2022" className="d-flex justify-content-center text-white" target={"_blank"} rel="noreferrer">https://bit.ly/iitmelections2022</a>
        <a href="https://forms.gle/Tinh6czKqeu6YSBu7" className="d-flex justify-content-center text-white" target={"_blank"} rel="noreferrer">Support Form Link</a>
        <div className="d-flex justify-content-center">
          <a href="https://twitter.com/iitm_bsc" target={"_blank"} className="social fs-5 bx-border-circle d-flex align-items-center text-decoration-none d-flex align-items-center text-decoration-none" rel="noreferrer">
            <i className="bx m-1 bxl-twitter"></i>
          </a>
          <a href="https://www.facebook.com/iitmadrasbscdegree/" target={"_blank"} className="social bx-border-circle d-flex align-items-center text-decoration-none fs-5" rel="noreferrer">
            <i className="bx m-1 bxl-facebook"></i>
          </a>
          <a href="https://instagram.com/iitmadras_bsc?utm_medium=copy_link" target={"_blank"} className="fs-5 social bx-border-circle d-flex align-items-center text-decoration-none" rel="noreferrer">
            <i className="bx m-1 bxl-instagram"></i>
          </a>
          <a href="https://www.linkedin.com/company/iit-madras-online-degree-programme" target={"_blank"} className="fs-5 social bx-border-circle d-flex align-items-center text-decoration-none" rel="noreferrer">
            <i className="bx m-1 bxl-linkedin"></i>
          </a>
        </div>
        <div className="font-righteous px-2 mt-3 text-white w-100 row">
          <span className="text-start col-6">All rights reserved<br /><a href="mailto:webops@student.onlinedegree.iitm.ac.in" className="text-white" target={"_blank"} rel="noreferrer">WebOps 2022</a></span>
        </div>
      </div>
    </footer>
  );
};

export default Footer;

If you wonder what is in boxicons.css, ask me in the comments 😉

Putting it all together in the Layout

// src/components/Layout.jsx

import NavigationDefault from "components/nav/Nav";
import Footer from "components/footer/Footer";

const Layout = ({ children }) => {
  return (
    <main className="bg-color">
      <NavigationDefault />
      <div style={{minHeight:"70vh"}}>{children}</div>
      <Footer />
    </main>
  );
};

export default Layout;

Loader

No one likes to see a blank screen while the page is loading. Hence this minimal loading animation.

// src/components/loader/Loader.jsx

import './Loader.css';

const Loader = ({ loading, children }) => {
  return loading ? (
    <div className="loading">
      <div className="effect-1 effects"></div>
      <div className="effect-2 effects"></div>
      <div className="effect-3 effects"></div>
    </div>
  ) : (
    children
  );
};

export default Loader;
/* src/components/loader/Loader.css */

.loading {
  position: absolute;
  left: calc(50% - 35px);
  top: 50%;
  width: 55px;
  height: 55px;
  border-radius: 50%;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  border: 3px solid transparent;
}
.loading .effect-1, .loading .effect-2 {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 3px solid transparent;
  border-left: 3px solid #7c251d;
  border-radius: 50%;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.loading .effect-1 {
  animation: rotate 1s ease infinite;
}
.loading .effect-2 {
  animation: rotateOpacity 1s ease infinite 0.1s;
}
.loading .effect-3 {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 3px solid transparent;
  border-left: 3px solid #7c2d5f;
  -webkit-animation: rotateOpacity 1s ease infinite 0.2s;
  animation: rotateOpacity 1s ease infinite 0.2s;
  border-radius: 50%;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.loading .effects {
  transition: all 0.3s ease;
}
@keyframes rotate {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(1turn);
    transform: rotate(1turn);
  }
}
@keyframes rotateOpacity {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
    opacity: 0.1;
  }
  100% {
    -webkit-transform: rotate(1turn);
    transform: rotate(1turn);
    opacity: 1;
  }
}

Authenticate

Yes I build a separate component to check for the auth status and render/redirect accordingly.

// src/components/Auth.jsx

import Layout from "components/Layout";
import { AppContext } from "contexts/app";
import { useContext, useState, useEffect } from "react";
import { Redirect } from "react-router";
import Loader from "./loader/Loader";

const Authenticate = ({ children }) => {
  const { session } = useContext(AppContext);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!session.loading) {
      setLoading(false);
    }
  }, [session]);

  if (loading) {
    return (
      <Layout>
        <Loader loading={true}></Loader>
      </Layout>
    );
  }

  if (session.accessToken) {
    return children;
  } else {
    return <Redirect to={"/login?then="+window.location.href.split("org/")[1]} />;
  }
};

export default Authenticate;

This Authenticate component is passed with the JSX component as an argument. If the session exists, it renders the component. Else it redirects to /login

Auth Button

// src/components/AuthButton.jsx

import { signInFirebase } from "apis/firebase";

const AuthButton = ({ onAuthSuccess, onAuthFailure }) => {
    
  const handleClick = async () => {
    const response = await signInFirebase();
    const { status } = response;
    if (status === "success") {
      let data = {};
      if (response.user) {
        const { uid, email, displayName, accessToken } = response.user;
        data = { uid, email, displayName, accessToken }
      }
      return onAuthSuccess(data);
    }
    return onAuthFailure(response);
  };

  return (
    <button
      className="btn px-5 text-white auth-btn"
      onClick={handleClick}
    >
      Sign in
    </button>
  );
};

export default AuthButton;

The AuthButton component takes in 2 functions as parameters: onAuthSuccess and onAuthFailure. These 2 functions should be defined and passed when using this component. If you can't understand what is that import { signInFirebase } from "api/firebase", then you might need to check the first part of this post

Profile

// src/components/Profile.jsx

const Profile = ({ userName = "", email ="", house="" }) => {
  return (
    <div className="user-profile text-center">
      <h2 className="text-uppercase">{userName}</h2>
      <p className="text-lowercase">{email}</p>
      <h5 className="">{house}</h5>
    </div>
  )
}
export default Profile;

Creating Pages

Login Page

// src/pages/login.jsx

import { useContext } from "react";
import { Redirect } from "react-router";

import AuthButton from "components/AuthButton";
import Footer from "components/footer/Footer";

import { AppContext } from "contexts/app";

import logo from "assets/images/logo.png";

const LoginPage = () => {
  const { session, setSession } = useContext(AppContext);

  const handleAuthFailure = (response) => {
    const { errorCode, errorMessage } = response;
    alert(`${errorCode}: ${errorMessage}`);
  };

  const handleAuthSuccess = (response) => {
    setSession(response);
  };

  if(session.accessToken) {
    return <Redirect to={window.location.href.split("=")[1]} />;
  }
  
  return (
    <main className="bg-color-maroon">
      <div style={{ paddingTop: "1rem", minHeight: "75vh" }}>
        <div className="container event-pass-page">
          <div className="m-4 text-center">
          	<img src={logo} style={{ width:"20%", paddingBottom: "2rem" }} alt="treehouse banner" />
            <h1 className="text-center text-white mb-4 heading text-uppercase">
            	Welcome to IITM BS Students Portal!!
            </h1>
            <p className="text-center text-white mb-5">
              Please sign in with your IIT Madras Student Email ID to get started!
            </p>
            <AuthButton onAuthSuccess={handleAuthSuccess} onAuthFailure={handleAuthFailure} />
          </div>
          <h6 className="text-center my-5 mx-5" style={{ color: "rgba(255,255,255,0.6)" }}>
            If you face any issues signing in with your student mail id, please let us know: <br />
            <a href="mailto:webops@student.onlinedegree.iitm.ac.in" className="text-white">
              Web Team
            </a>
          </h6>
        </div>
      </div>
      <Footer />
    </main>
  );
};

export default LoginPage;

Login page, if you remember well, this page shows up when the user is not authenticated. There is the AuthButton which is passed with 2 functions handleAuthSuccess and handleAuthFailure. The context data is parsed here, both the session and setSession and the setSession is used to set the response from sign in to the session.

Home Page

Home page is rather a simple one!

// src/pages/home.jsx

import Layout from "components/Layout";
import Container from "components/Container";
import electionsImg from "assets/images/elections.webp";
import { Link } from "react-router-dom";

const HomePage = () => {
  return (
    <Layout>
      <Container bgColor="bg-color-maroon">
        <img src={electionsImg} className="w-100" alt="header img" />
      </Container>
      <Container>
        <p>It is an honor and a privilege for the IITM BS Degree Student Affairs and the Election Committee to organize the House Council elections. We hope to conduct fair and equal voting access that matches the best person to each of the House Council positions.</p>
        <p>The IITM BS Degree program is structured in a dynamic manner with a plethora of activities around the curriculum. The cohort of our learners brings the best of the diversity of India and abroad. Even more, they are distributed across various age groups {"&"} subjects, adding more flavors to the mix.  Within such a vibrant community, leadership is surely an opportunity and a challenge. </p>
      </Container>
      <Container bgColor="bg-color-maroon">
        <p>The right leadership of Group Leaders, Secretaries, Deputy Secretaries and Web Administrators can ensure better activities and opportunities for all our students.</p>
        <p>In this phase of the elections, we will be voting for the Secretary, Deputy Secretary and Web Admin for each of our twelve houses.</p>
      </Container>
      <Container>
        <p>This website has been developed to provide you candidate information and a voting form that allows you to vote for the candidates of your house.</p>
        <p>Please do share your suggestions and feedback as it helps us make our processes and systems even better.</p>
        <div className="d-flex justify-content-center w-100">
          <Link to="/candidates" className="btn auth-btn px-4 text-white">Know your Candidates</Link>
        </div>
      </Container>
    </Layout>
  );
};

export default HomePage;

Candidates Page

Candidates page shows the list of candidates from the student's house who are standing for the election. We already have functions created to fetch candidates from the database. All we need to do here is call those functions

// src/pages/candidates.jsx 

const [sec, setSec] = useState([]);
const [dySec, setDySec] = useState([]);
const [webAd, setWebAd] = useState([]);

useEffect(() => {
  getElectionCandidates().then((r={}) => {
    setHouse(r.house);
    setSec(r.sec);
    setDySec(r.dysec);
    setWebAd(r.webad);
  }).then(() => {
    setLoading(false)
  })
}, [])

Displaying details is the next headache. The descriptions were both short and long. Hence I contained it in a Model

const [modal, showModal] = useState(false);
const closeModal = () => showModal(false)
const openModal = (name, email, photo, intro, doc) => {
  showModal(true);
  setTimeout(() => {
    document.querySelector('.btn-close').classList.add('bg-light')
    document.querySelector(".candidate-name").innerText = name;
    document.querySelector(".candidate-desc").innerText = intro;
    document.querySelector(".candidate-mail").innerText = email
    document.querySelector(".candidate-doc").href = "https://"+doc.split("https://")[1];
    document.querySelector(".candidate-pic").src = photo;
  }, 1);
}

<Modal show={modal} onHide={closeModal} size="lg" centered>
  <Modal.Header className="bg-color-maroon text-color-gold" closeButton>
    <Modal.Title>
      <h4 className="candidate-name"></h4>
    </Modal.Title>
  </Modal.Header>
  <Modal.Body className="p-0 m-0">
    <Row className="m-0 p-0">
      <Col xs={12} md={4} className="p-0">
        <img src="" className="candidate-pic h-100 w-100" alt="modal img" />
      </Col>
      <Col xs={12} md={8} className="d-flex align-items-center">
        <div className="m-2">
          <p className="candidate-mail font-italic small"></p>
          <p className="candidate-desc mt-1"></p>
        </div>
      </Col>
    </Row>
  </Modal.Body>
  <Modal.Footer>
    <a href="#modal" target={"_blank"} className="candidate-doc btn btn-outlined rounded">
      Know More
    </a>
  </Modal.Footer>
</Modal>

Rendering the data in the page

<Container>
  <img src={candidateImg} width="100%" alt="candidate" />
  <h3 className="text-center mb-4">Secretary</h3>
  {sec.length === 0 ? (
    <h6 className="text-center">No nominations recieved for the post of Secretary</h6>
  ) : (
    <div className="d-flex flex-wrap justify-content-center">
      {sec.map((candidate) => {
        return (
          <div 
            className="card m-1" 
            key={candidate.email} 
            style={{
              width:"300px",
              height:"300px",
              cursor:"pointer", 
              backgroundImage: `url("https://drive.google.com/uc?export=view&id=${candidate.photo.split("=")[1]}")`,
              backgroundSize:"cover",
              backgroundPosition: "center center"
            }} 
            onClick={()=>openModal(
              candidate.name, 
              candidate.email, 
              `https://drive.google.com/uc?export=view&id=${candidate.photo.split("=")[1]}`, 
              candidate.intro, 
              candidate.doc
            )}
          >
            <div className="text-white position-absolute w-100 pt-5 px-2" style={{bottom:'0', background:`linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.8))`}}>
              <h4>{candidate.name}</h4>
              <p className="small">{candidate.email}</p>
            </div>
          </div>
        )
      })}
    </div>
  )}
</Container>

This template is followed for both deputy secretary and web admin too.

Displaying details is the next headache. The descriptions were both short and long. Hence I contained it in a Model

Vote page

This is the important page for this website. A single mistake in this page would make this project useless. (Well there were lots of issues after getting this project to production. I spent an entire night only to find that the issue was because of replacing 'a' with 'e' in Sundarbans)

First, I needed a timer. I can't keep track of time to open and close the election portal. Hence I created a timer along with a clock to be rendered on the website.

// src/pages/vote.jsx

const currDate = new Date();
const startDate = new Date(2022, 8, 2, 0, 0, 0);
const endDate = new Date(2022, 8, 4, 0, 0, 0);

const [time, setTime] = useState(0)

setInterval(() => {
  const endTime = endDate.getTime();
  const currTime = new Date().getTime();
  setTime(endTime-currTime);
}, 1000)

        <h6 className="text-center">Voting Closes in {String(parseInt(time/(1000*60*60))).length===1 ? "0"+String(parseInt(time/(1000*60*60))) : String(parseInt(time/(1000*60*60)))}:{String(parseInt((time%(1000*60*60))/(1000*60))).length===1 ? "0"+String(parseInt((time%(1000*60*60))/(1000*60))) : String(parseInt((time%(1000*60*60))/(1000*60)))}:{String(parseInt((time%(1000*60))/(1000))).length===1 ? "0"+String(parseInt((time%(1000*60))/(1000))) : String(parseInt((time%(1000*60))/(1000)))}</h6>

{startDate<currDate && endDate>currDate ? (} : ()}

I know the timer part is a bit of a mess 😅

Next, the selection of candidates and store them

const [data, setData] = useState({sec: [], dysec: [], webad: [], mentor: []});
const [resp, setResp] = useState({sec: "None Selected",dysec: "None Selected",webad: "None Selected",house: "",email: ""})
{data.sec.length === 0 ? (
  <h6>No nominations recieved for the post of Web Admin</h6>
) : (
  <div className="d-flex flex-wrap justify-content-center">
    {data.sec.map((candidate) => {
      return (
        <div className="card m-1" style={{width:"300px",height:"300px"}} key={candidate.email}>
          <input type="radio" className="btn-check" name="sec" id={candidate.email+"sec"} required  />
          <label className="btn card p-0 h-100 w-100" style={{backgroundImage:`url(${"https://drive.google.com/uc?export=view&id="+candidate.photo.split("=")[1]})`,backgroundSize:"cover",backgroundPosition:"center center"}} htmlFor={candidate.email+"sec"} onClick={()=>setResp({...resp, sec: `${candidate.email} - ${candidate.name}`})}>
            <div className="card-body pb-0 text-start position-absolute w-100 pt-5" style={{bottom:"0",background:`linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.8))`}}>
              <h5 className="card-title">{candidate.name}</h5>
              <p className="small">{candidate.email}</p>
            </div>
          </label>
        </div>
      )
    })}
  </div>
)}

Next the storing of votes 🤯

const handleSubmit = () => {
  resp.house = data.house;
  resp.email = data.email;
  if (window.confirm(`Confirm the Candidates:\nSecretary: ${resp.sec.split("-")[1] || "No Nominations"} - ${resp.sec.split("@")[0]}\nDeputy Secretary: ${resp.dysec.split("-")[1] || "No Nominations"} - ${resp.dysec.split("@")[0]}\nWeb Admin: ${resp.webad.split("-")[1] || "No Nominations"} - ${resp.webad.split("@")[0]}`)) {
    document.getElementById('vote-form').innerHTML='<div class="loading"><div class="effect-1 effects"></div><div class="effect-2 effects"></div><div class="effect-3 effects"></div></div>'
    updateVote(resp).then((r) => {
      document.getElementById('vote-form').innerHTML = `
        <div class="row justify-content-center w-100 align-items-center" style="height:'70vh'">
          <h5 class='text-center mb-3'>Thanks for casting your vote</h5>
          <div>
            <h5>Secretary: </h5>
            <p>${resp.sec || "No nominations"}</p>
            <h5>Deputy Secretary</h5>
            <p>${resp.dysec || "No nominations"}</p>
            <h5>Web Admin</h5>
            <p>${resp.webad || "No nominations"}</p>
          </div>
          <a class="btn auth-btn mt-3 col-6 text-white" href="/">Go to Home</a>
        </div>
      `
    })
  }
}

Creating Routes

Last part 😍

Combining everything

Routing is the main part of any multi page website. I used react-router@5.2.0 when I developed this website. (This version might be deprecateed by now)

import HomePage from "pages/home";
import ElectionsPage from "pages/elections";
import LoginPage from "pages/login";
import ProfilePage from "pages/profile";
import Authenticate from "components/Auth";
import CandidatesPage from "pages/canididates";
import VotePage from "pages/vote";
import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom";
import { getAuth, onAuthStateChanged } from "firebase/auth";

<AppContext.Provider value={{ session, setSession }}>
  <BrowserRouter>
    <Switch>
      <Route
        exact
        path="/elections"
        render={(routeProps) => (
          <Authenticate>
            <ElectionsPage {...routeProps} />
          </Authenticate>
        )}
      />
      <Route exact path="/login" render={(routeProps) => <LoginPage {...routeProps} />} />
      <Route
        exact
        path="/profile"
        render={(routeProps) => (
          <Authenticate>
            <ProfilePage {...routeProps} />
          </Authenticate>
        )}
      />
      <Route
        exact
        path="/candidates"
        render={(routeProps) => (
          <Authenticate>
            <CandidatesPage {...routeProps} />
          </Authenticate>
        )}
      />
      <Route
        exact
        path="/vote"
        render={(routeProps) => (
          <Authenticate>
            <ElectionsPage {...routeProps} />
          </Authenticate>
        )}
      />
      <Route
        exact
        path="/"
        render={(routeProps) => (
          <Authenticate>
            <HomePage {...routeProps} />
          </Authenticate>
        )}
      />
      <Redirect from="*" to="/" />
    </Switch>
  </BrowserRouter>
</AppContext.Provider>

In the routes, I didn't use Authenticate component for /login. Because if the user is not logged in, it'll redirect to /login then again the same process is repeated resulting in an endless loop of multiple renders.

Wrap up

That's all from this project. There are still a few things which you guys can search for. You can implement blockchain technologies for casting vites. There are more obvious technologies out there and I would love to hear them in the comments~

Be the first to know.

© 2024 All rights reserved | v3.0