💻 A Technical Interview Project: Building a Lending-as-a-Service Application 🏦
Tags: webdev, react, typescript, interview
Published: 2023-06-29 17:40:56

I recently completed a technical interview with a lending-as-a-service company. The interview required me to build four pages with TypeScript, React, and SCSS for styling. 

Although, unfortunately I didn't obtain the position, but since it's a large project (which I personally think is gross for the first stage of a technical interview) I'm committed to sharing what I learned. In this article, I'll walk you through the features I implemented and how I implemented them. I hope this article will be helpful to other frontend developers who are preparing for technical interviews.

PS: I'm still open for a frontend developer role. If you're hiring for a frontend role, please reach out to me. I'd love to learn more about the opportunity.

Note that, this article will focus on the features implemented, with little to no discussion of the styling.

# TL;DR

The following is a brief overview of the pages and features implemented in the project:

- Login page: I used Firebase for authentication and implemented protected routes with React Router, even though it wasn't required.


- List of users page: This page pulls data from a mock API with 500 records using Axios and Axios Interceptors.


- User details page: This page was required to use local storage or indexedDB to store and retrieve user details. I chose to use localStorage.

The end product of the pages implemented should look like this:

![Log In Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9gohyiz7ue3wywyfmdwu.png)

![Dashboard Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/962q6erdhrm3zcx7rcru.png)

![User Details Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1i52hadpszrjtbekgnq6.png)

This article shall be a complete overhaul from the ground up, with no stone unturned. I will start with the login page, and discuss how the user is navigated to the dashboard. From there, I will also explore the features implemented in the users page, and finally, we shall reach the user details page, where the user journey ends.

When you are ready, let's dive in.


![Ready GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qebwenmod4e4te7l832d.gif)

## How I Set My Project Up

### I Set Up My React + TypeScript Project With Vite

To kickstart my project swiftly, I opted for [Vite](https://vitejs.dev/guide/) as my project initializer, and I highly recommend you do the same if you haven't been using Vite. Initializing a TypeScript + React project with Vite is effortless with just one command:

```npm
npm create vite@latest interview-project -- --template react-ts
```

### How I Set Up Firebase For Authentication

I decided to utilize Firebase for authentication for this app although it wasn't a requirement. 

The fact that I also wanted to implement a protected route functionality for all authenticated pages also played a role in my decision. This feature will be discussed in a later section.

In this section, I will briefly guide you through the process of how I set it up.

Generally, to use Firebase, you need to [create and register](https://firebase.google.com/docs/web/setup) your project on the platform, which I did. 

Following this, I installed Firebase in my project and created a `components` folder. Inside this folder, I created a file named `firebaseConfig.ts` to store my configuration details and establish a connection between my app and Firebase like this:

```ts
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  //firebase config
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);// connect firebase
export const auth = getAuth(app)
```

If you are new to Firebase and are interested in learning more about it, check out the official documentation [here](https://firebase.console.com).


### File Structure

I established a `pages` directory to contain the various pages of the application.

Within the previously created `components` directory, I created subdirectories with names corresponding to the folders within the `pages` directory. This organizational structure allows for convenient grouping of relevant components within each page-specific folder.

Furthermore, I added a `styles` folder in the `src` directory to centralize all the styles for my pages. Additionally, an `utils` folder was created within the `src` directory to store files responsible for handling API requests.

I had adopted this file structure during my time at my previous job.

In the end, the folder structure looked like this:

```
- project
  - src
    - components	
      - dashboard
	  - common
	  - user-details
	  - users	  
      - login
	  - firebaseConfig.ts
    - pages
      - Login.tsx
      - Users.tsx
      - User-details.tsx
    - styles
    - utils
      - request-adapter.ts
      - requests.ts
    - main.tsx
  - public
  - package.json
```
I'll now walk you through each page and files if necessary in the next section.

### Page Routing In The `main.tsx` File

The powerful React Router was employed for seamless page routing, not like there are numerous alternatives available lol. If you are new to React Router or uncertain about its benefits, I recommend checking out its documentation [here](https://reactrouter.com/en/main/start/overview) for further insights. 

Once React Router is installed, I implemented the routes in the `main.tsx` file as seen in the code snippet below. 

> I had not used React Router in a while due to the fact that I have been writing a lot of `Nextjs` these days. As a result, I was taken aback by the new syntax when revisiting it. If you're in a similar situation where you haven't kept up with the updates, note that an updated version, `6.4`, has been released with fresh syntax and features. I recommend referring to the documentation to stay updated and informed.

```tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import './App.scss';
import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";
import Login from './pages/Login-Auth.tsx';
import Users from './pages/Users.tsx';
import UserDetails from './pages/User-details.tsx';

const router = createBrowserRouter([
  
  {
    path: "/",
    element: <Login/>
  },

  {
    path: "/dashboard/users",
    element: <Users/>
  },

  {
    path: "/dashboard/users/:id",
    element: <UserDetails/>
  },
]);

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router}/>
  </React.StrictMode>,
)

```

## How I Implemented Features

### Starting Off With Authentication In The Log In Page :rocket: 


![Log In Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkw16fudpac2x8v3ocw7.png)

For the sign-in method, Firebase enables you to use different sign-in providers, ranging from `email` and `password` provider to `Google` provider and so on... For the authentication of this app, I employed the email and password provider since the email and password input fields were provided and required.

Here's how I implemented the feature:

```ts
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from "firebase/auth";
import {  useNavigate } from 'react-router-dom'
import { auth } from '../components/login-auth/firebaseConfig';

export default function Login() {
	
  const navigate = useNavigate()
  const [ email, setEmail ] = useState<string>('');
  const [ password, setPassword ] = useState<string>('')
  const [ signUp, setSignUp ] = useState<boolean>(true)
  const [ helperText, setHelperText ] = useState<string>('')
  const [ hide, setHide ] = useState<boolean>(false)
  
  const handleSignUp = (e: React.FormEvent<HTMLButtonElement> ) => {
    e.preventDefault()
    createUserWithEmailAndPassword(auth, email, password)
     .then(() => {
      setHelperText('Congrats!, you can now Sign In')
      setTimeout(() => setHelperText(''), 2000)
      setSignUp(!signUp)
      setError(false)
     }) 
   .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
    setError(true)
    console.log(errorCode, errorMessage)
   });
 }

  const handleSignIn = (e: React.FormEvent<HTMLButtonElement>) => {
    e.preventDefault()
    signInWithEmailAndPassword(auth, email, password)
    .then(() => {
      setError(false)
      navigate('/dashboard/users')
      sessionStorage.setItem('auth', JSON.stringify(auth))
     })
   .catch((error) => {
   const errorCode = error.code;
   (errorCode === 'auth/user-not-found!') ?
    setHelperText('Email is not registered!') :
    setHelperText('Invalid password')
    setError(true)
    setTimeout(() => setHelperText(''), 2000)
  });
}
}

return (
       <form>
            <div>
            <h1>Welcome!</h1>
            <p>Enter details to {signUp ? "login" : 'sign up'}.</p>   
            <div>
            <p className={`${error ? 'error' : 'success'}`}>{helperText}</p>
              <div>
              <Input type='email' placeholder={'Email'} value={email} onChange={setEmail}/>
              </div>
              <div>
              <Input type={hide ? "password" : 'text'} placeholder={'Password'} value={password} onChange={setPassword}/>
              <span onClick={() => setHide(!hide)}>{hide ? "Show": "Hide"}</span>
              </div>
            </div>
              <p>Forgot Password?</p>
              <p>Don't have an account? <span onClick={() => setSignUp(!signUp)}>Sign Up</span></p>
               <button type='submit' onClick={!signUp ? handleSignUp : handleSignIn}>           
                 signUp ? ' Log In' : ' Sign Up'
               </button>
            </div>
          </form>
)
```

I implemented two functions in the code block above: `handleSignIn` and `handleSignUp`. 

As their names imply, the `handleSignin` function handles the sign in process, while the latter handles that of the sign up. If the requirements of each function are met, the code in their `.then` block runs, if not, the error is caught in the `.catch` block.

> Note that once the code in the `.then` block in the `handleSignIn` function runs, an `auth` object is saved in the `sessionStorage` of the browser. This will be used when implementing the protected routes feature. More details on this in the [authenticated pages](#On-To-The-Authenticated-Pages) section.

I introduced the `signUp` state to regulate which of the functions executes in the proper scenario. When a user hits the `sign up` text in the form beneath the `Forgot Password` text, the `signUp` state is updated. The highlighted code below demonstrates how this is done:

```tsx
 <p>Don't have an account? <span onClick={() => setSignUp(!signUp)}>Sign Up</span></p>
```

As I mentioned in earlier, this state is used to toggle the functions that get invoked when the `button` in the `form` element is clicked. That is, if a user is new and just wants to sign up, the `handleSignUp` function is triggered, otherwise, if it is an already existing user who wants to log in, the log in function is triggered. 

This also controls the text that renders in the `button`. 

You can see how this is implemented in the highlighted code below:

```tsx
   <button type='submit' onClick={!signUp ? handleSignUp : handleSignIn}>           
                 signUp ? ' Log In' : ' Sign Up'
  </button>
```

The purpose of the `signUp` state wasn't limited to just the function and text of the button as it also updates the text below the **`Welcome`** header:

```tsx
<p>Enter details to {signUp ? "login" : 'sign up'}.</p>   
```

See video below for an illustration of this scenario.


![gif illustration of text renders](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6c5z7b76ct4v9ufe1qob.gif)

### On To The Authenticated Pages :walking: 

Before I started off with the authenticated pages - the user and the user-details page; I thought the implementation of the protected routes feature seemed to make sense after the authentication procedure was finished.

As mentioned earlier, upon successful sign-in, an `auth` object is stored in the user's browser `sessionStorage` temporarily. I chose `sessionStorage` for this purpose because it retains data only for the duration of the session, deleting it when the browser is closed.

With the `auth` variable now appropriately stored in `sessionStorage`, I proceeded to implement the protected route feature. To accomplish this, I created a Page.tsx file within the common folder of the dashboard, as indicated in the [file structure](#File-Structure) section, and implemented the feature as follows:

```tsx
import Navbar from "./Navbar";
import Sidebar from "./Sidebar";
import { useState } from 'react';
import { Navigate } from "react-router-dom";

type PageProps = {
   children: React.ReactNode,
}

export default function Page ({children }: PageProps) {
    
    const authFromSessionStorage = sessionStorage.getItem('auth')
    
    return (
        <>
        {  
        (!authFromSessionStorage) ? 
        <Navigate to='/' replace={true}/> :
        (
        <>
        <Navbar/>
        <Sidebar/>
        <div>
            {children}
        </div>
        </>
	)} // navigate to log in page if user isn't authenticated
        </>
    )
}
```

Since the `Page` component will wrap around all authenticated pages, which share the navbar and sidebar components, I decided to include those components within it. This explains their inclusion in the code block. Here's an example of how I wrapped this component around an authenticated page component:

```tsx
// users.tsx

export default function Users() {

    return (
     <Page>
	  <div>
	  // rest of the page content here
      </div>
     </Page>
    )
}

```

Following this implementation, when an unauthenticated user attempts to access an authenticated page, they will be redirected to the sign-in page to authenticate themselves.

### Navigating To The Users' Page :busts_in_silhouette: 


![Users page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxfgfsh3f2cp1jt7of7f.png)

This page requires candidates to pull data from a mock API and populate it in form of a table as seen in the image above. 

After pulling the data, candidates are to also implement a feature to paginate the data gotten from the API just below the table:

![Dashboard footer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhy0qlfoo6ww02qu4kgu.jpg)

Since the number of users gotten from this API is 100, I decided to paginate the data in multiples of 20, 50, and 100. At the end the feature looks like this:

![gif dropdown ilustration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8z9rtn90pul1d2njnila.gif)

Moving on, in the `request-adapter.ts` file created in the `utils` folder ( see folder structure section ),  I created an instance of `Axios` with `Axios.create` and assigned it to a variable `request`. This instance also includes a `base URL` and timeout configuration. I also added a response interceptor to handle successful responses and errors, finally the instance is exported for use in other parts of the application:

```ts
import axios from "axios";

const request = axios.create({
    baseURL: "https://6270020422c706a0ae70b72c.mockapi.io/lendsqr/api/v1/",
    timeout: 30000,
  });
  
  request.interceptors.response.use(
    (response) => {
      return response;
    },
    ({ response }) => {
      return Promise.reject(response);
    }
  );
  
  export default request;
```

Following this, the `request` variable is imported from the `request-adapter` module.

Then, a `getUsers` function which it uses the `request` instance to make a GET request to the `/users` endpoint is defined. 

```ts
import request from "./request-adapter"

export const getUsers = () => request.get('/users')
```

By calling the `getUsers`, the Axios instance will send a GET request to the complete URL formed by combining the base URL from the instance's configuration and the `/users` endpoint.

The `getUsers` function is exported, making it available for other modules to import and use. This function can now be called in the users' page where it will initiate a GET request to retrieve the users' data from the specified endpoint.

Next, I implemented the markup for the table that displays the users. I created a `Table.tsx` file in the users folder situated in the components folder for this purpose.

Here's how the markup looks like:

```tsx

type TableProps<T extends object> = {
    users: T
} // users is a generic of type object just as in he Users component

export default function Table<T extends object>({ users }: TableProps<T[]>) {
    return (

        <section>
        <table>
         <thead>
         <tr>
           <th>
             <div>
            <p>Organization</p> 
             </div>
           </th>
           <th>
           <div>
            <p>Username</p> 
             
            </div>    
           </th>
           <th>
           <div>
            <p>Email</p> 
           </div>
           </th>
           <th>
             <div>
            <p>Phone Number</p> 
                
             </div>
           </th>
           <th>
             <div>
            <p>Date Joined</p> 
             </div>
           </th>
           <th>
             <div>
             <p>Status</p>    
             </div>
           </th>
         </tr>
         </thead>
		 <tbody>
		 // list of users will be here
		 </tbody>
		</table>
		</section>
}
```
Notice the custom type `TableProp` defined. This type is a generic type whose parameter extends an `object` and assigned to the users property, this ensures that the users property accepts any type of `object`. After the users endpoint is called, the body of the table will be implemented and populated. 

After implementing the `Table` component, I created another component in the `users` folder for the pagination feature just below the table and named it `PaginatedItems.tsx`. The pagination feature was not just implemented in this component though, I also made the call to the `users` endpoint and rendered the `Table` component inside this component. I will explain why.

This was done mostly because I used [`react-paginate`](#https://www.npmjs.com/package/react-paginate) library to implement the pagination feature. This library needs to have access with the data you want to paginate directly - the users data gotten from the API, in order to interact with it. 

I'll now walk you through the pagination and Table implementation step-by-step.

Firstly, I defined the component and fetched the data from the API:

```tsx
import { useState, useEffect, useCallback } from "react";
import { getUsers } from "../../../utils/requests";
import { AxiosResponse } from "axios"

export default function PaginatedItems<T extends object>() {

  const [ users, setUsers ] = useState<T[]>([]);
  const fetchData = useCallback(() => {
     getUsers()
    .then(({data} : AxiosResponse<T[]>) => { 
      setUsers(data)  
    })
    .catch(err => console.log(err))
  }, []);
  
    useEffect(() => {
        fetchData()
      }, [])
  return (
     // code markup here
  )
}
```

I then went on to develop the pagination feature after implementing and obtaining the component. The npm website's instruction of how to use the `react-paginate` module was sufficient for its integration. The following code snippet was included in the component:

```tsx 
import { useState, useEffect, useCallback } from "react";
import { getUsers } from "../../../utils/requests";
import { AxiosResponse } from "axios"

type DropdownProps = {  
    setItemsPerPage: React.Dispatch<React.SetStateAction<number>>
}

export function DropdownFilter({setItemsPerPage } : DropdownProps) {

    return (
        <div>
          <p onClick={() => setItemsPerPage(20)}>20</p>   
          <p onClick={() => setItemsPerPage(50)}>50</p>   
          <p onClick={() => setItemsPerPage(100)}>100</p>   
        </div>
    )
}
export default function PaginatedItems<T extends object>() {

  const [ currentItems, setCurrentItems ] = useState<T[]>([]);
  const [ itemsPerPage, setItemsPerPage ] = useState<number>(50);
  const [ pageCount, setPageCount ] = useState<number>(0);
  const [ itemOffset, setItemOffset ] = useState<number>(0);
  
      useEffect(() => {
      if(users){
          const endOffset = itemOffset + itemsPerPage;
          setCurrentItems(users.slice(itemOffset, endOffset));
          setPageCount(Math.ceil(users.length / itemsPerPage));
      }
    }, [users, itemOffset, itemsPerPage]);
  
    // Invoke when user clicks to request another page.
    const handlePageClick = (event: { selected : number}) => { //since we just need the selected property
     if(currentItems) {
         const newOffset = event.selected * itemsPerPage % users.length;
         setItemOffset(newOffset);
     }
    };
return (
      <>
      {
      (currentItems.length > 0) && (
        <>
          <Table users={currentItems} />

          <div>

            <div>
            <p>Showing</p>
            <div 
            onClick={() => setShowDropdown(!showDropdown)}
            >
             <p>{itemsPerPage}</p> 
             <img src={dropdown} alt='dropdown icon'/>
             <DropdownFilter 
             setItemsPerPage={setItemsPerPage}
             className={showDropdown ? 'active' : ''}/>
            </div>
            <p>out of {users.length}</p>
            </div>

           <ReactPaginate
            nextLabel={<Button src={right}/>}
            onPageChange={handlePageClick}
            pageRangeDisplayed={3}
            marginPagesDisplayed={2}
            pageCount={pageCount}
            previousLabel={<Button src={left}/>}
            pageClassName="page-item"
            pageLinkClassName="page-link"
            previousClassName="page-item"
            previousLinkClassName="page-link"
            nextClassName="page-item"
            nextLinkClassName="page-link"
            breakLabel="..."
            breakClassName="page-item"
            breakLinkClassName="page-link"
            containerClassName="pagination"
            activeClassName="active"
            renderOnZeroPageCount={null}
            />
          </div>
        </>
      )}
      </>
    )}
```

The code provided shows the implementation of a paginated table component. The `PaginatedItems` component manages the state for displaying a specific number of items per page and calculating the necessary pagination parameters.

The `DropdownFilter` component renders a dropdown menu with options for setting the number of items per page. When an option is clicked, the `setItemsPerPage` function is called to update the `itemsPerPage` state in the `PaginatedItems` component.

Inside the `PaginatedItems` component, the `useEffect` hook is used to update the `currentItems` state and calculate the `pageCount` whenever the `users`, `itemOffset`, or `itemsPerPage` dependencies change. The `currentItems` state is updated by slicing the users array based on the current `itemOffset` and `itemsPerPage` values.

The `handlePageClick` function is invoked when the user clicks on a page in the pagination component (`ReactPaginate`). It calculates the new `itemOffset` based on the selected page and updates the state accordingly.

The `PaginatedItems` component also renders the `Table` component with the `currentItems` as the `users` prop, showing the table with the paginated data.

Additionally, there are UI elements for displaying the current page's range and the total number of users. The `ReactPaginate` component handles the pagination rendering and interaction based on the provided parameters.

After passing the data to the `Table` component, I populated the body of the table with the data.

```tsx
import { useState } from "react";

type TableProps<T extends object> = {
  users: T;
}; // users is a generic of type object just as in he Users component

type User = {
  [key: string]: any;
}; //use index signature for type checking individual objects in the users array.

export default function Table<T extends object>({ users }: TableProps<T[]>) {
  const [activeId, setActiveId] = useState<string>("");

  return (
    <section>
      <table>
        <thead>
          <tr>
            <th>
              <div>
                <p>Organization</p>
              </div>
            </th>
            <th>
              <div>
                <p>Username</p>
              </div>
            </th>
            <th>
              <div>
                <p>Email</p>
              </div>
            </th>
            <th>
              <div>
                <p>Phone Number</p>
              </div>
            </th>
            <th>
              <div>
                <p>Date Joined</p>
              </div>
            </th>
            <th>
              <div>
                <p>Status</p>
              </div>
            </th>
          </tr>
        </thead>
        <tbody>
          {users.map((user: User) => {
            const { id, orgName, userName, email, phoneNumber, createdAt } =
              user;
            const date = new Date(createdAt);
            const formattedDate = date.toLocaleString("en-NG", {
              month: "short",
              day: "numeric",
              year: "numeric",
              hour: "numeric",
              minute: "numeric",
              hour12: true,
            });

            return (
              <tr key={id} className="tr-body">
                <td>{orgName}</td>
                <td>{userName}</td>
                <td>{email}</td>
                <td>{phoneNumber}</td>
                <td>{formattedDate}</td>
                <td>Inactive</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </section>
  );
}
```
Next, the table needs a dropdown for each user, it will be triggered on clicking the ellipsis on the extreme right of the table. The dropdown should look like:

![Table dropdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etuthz7i73b68un4bgch.png)

Clicking the view details text should navigate to the user details page. I created a `Dropdown.tsx` file in the users folder of the component folder where I implemented the functionality like this:

```tsx
import { Link } from "react-router-dom";

type DropdownProps = {
  id: string;
  toggleDropdown: {
    activeId: string;
    setActiveId: React.Dispatch<React.SetStateAction<string>>;
  };
};

export default function Dropdown({ id, toggleDropdown }: DropdownProps) {
  const { activeId, setActiveId } = toggleDropdown;

  return (
    <ul
      onMouseEnter={() => setActiveId(id)}
      onMouseLeave={() => setActiveId("")}
      className={`${id === activeId ? "show" : ""} dropdown`}
    >
      <Link
        style={{
          textDecoration: "none",
        }}
        to={`/dashboard/users/${id}`}
      >
        <li>
          <img src={viewDetails} alt="view icon" />
          <p>View Details</p>
        </li>
      </Link>
      <li>
        <img src={blacklist} alt="view icon" />
        <p>Blacklist User</p>
      </li>
      <li>
        <img src={activate} alt="view icon" />
        <p>Activate User</p>
      </li>
    </ul>
  );
}
```

Inside the component, an unordered list `(<ul>)` is rendered. The className of the `<ul>` element is conditionally set based on whether the `id` matches the activeId. If they match, the show class ( which makes the dropdown visible ) is added to the element, otherwise it remains empty.

The `<ul>` element has two event handlers: `onMouseEnter` and `onMouseLeave`. When the mouse enters the element, the `setActiveId` function is called with the `id` as the argument, setting it as the `activeId`. When the mouse leaves the element, the `setActiveId` function is called with an empty string, clearing the `activeId`.

Inside the `<ul>` element, there are three list items (`<li>`) representing the dropdown options. The first list item contains a Link component that wraps the content. The `Link` component is used to create a clickable link that navigates to a specific URL. The to prop of the Link component is set to `/dashboard/users/${id}`, which dynamically generates the URL based on the `id` prop. This means that clicking on this option will navigate to the user details page for the corresponding `id`.

This component is then imported to the table component and included it in the last element of the table like this:

```tsx
<td>
  <div>
    <p className="status">Inactive</p>
    <img
      src={ellipsis}
      alt="ellipsis icon"
      onMouseEnter={() => setActiveId(id)}
    />
    <Dropdown id={id} toggleDropdown={{ activeId, setActiveId }} />
  </div>
</td>;
```
The end product of the dropdown looks like this:

![gif illustration of dropdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wbeqcci2eqirlkt2dqy.gif)

Finally, I rendered the `PaginatedItems` component in the users component created in the page folder. Ofcourse, wrapped around the page component [initially discussed.](#On-To-The-Authenticated-Page)

```tsx
import PaginatedItems from "../components/dashboard/users/PaginatedItems"

export default function Users() {

    return (
     <Page>
      <PaginatedItems/>
     </Page>
    )
}
```

If you have been with me up to this point, congratulations on surviving this wild ride through the `User` page. Grab yourself a virtual cookie and take a well-deserved break! But hold on tight because in the next section, we'll be diving into the thrilling world of each user details, where we'll uncover secrets, unravel mysteries, and navigate through the intricacies of individual user profiles.

### Journey's End: The User Details Page

![User Details Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sci29thmpltummkzzkw6.png)

Fortunately, unlike the `users` page, the user page only requires implementing the data fetching and UI populating functions. However, there is an interesting twist. Once a user's information has been accessed, it needs to be saved in either the `indexedDB` or `localStorage`. The purpose of this storage is to avoid unnecessary network queries. If the user details data is already present in the `localStorage`, we can simply retrieve it from there instead of making additional network requests.

To get started, I defined the route to the endpoint that calls a user's details in the `requests.ts` file, located in the `utils` folder, similar to how I did it for the users endpoint:

```ts
export const getUserById = (id: string) => request.get(`/users/${id}`);
```

This endpoint requires including the `id` of each user in its query. Fortunately, with React Router, we can extract this `id` from the URL once we have navigated to the user details page. Stay tuned for more exciting details!

Moving on, I created a file in the `pages` folder and named it `User-details.tsx` where I imported the previously defined endpoint for it to be called with the `id`. This `id` will be gotten from the URL with [`useParams`](https://reactrouter.com/en/main/hooks/use-params) provided by react-router and passed to the API route as seen below:

```tsx
import Page from "../components/dashboard/common/Page";
import { useParams } from "react-router-dom";
import { getUserById } from "../utils/requests";
import { useEffect, useState } from 'react';

export default function UserDetails():JSX.Element {

 const [ user, setUser ] = useState<{[key: string]: any}>();
 
 const { id } = useParams(); //get id from URL
 
  return (
  //markup here
  )
}
```

Following this, I implemented the functionality to fetch and save to `localStorage` in the `fetchUserDetails` function included in the code snippet below:

```tsx
import Page from "../components/dashboard/common/Page";
import { useParams } from "react-router-dom";
import { getUserById } from "../utils/requests";
import { useEffect, useState } from "react";

export default function UserDetails(): JSX.Element {
  const [user, setUser] = useState<{ [key: string]: any }>();
  const { id } = useParams();

  const fetchUserDetails = (): void => {
    const usersFromLocalStorage = localStorage.getItem("users");
    if (usersFromLocalStorage) {
      const users: { [key: string]: any }[] = JSON.parse(usersFromLocalStorage);
      const user = users.find((u: { [key: string]: any }) => u.id === id);
      if (user) {
        setUser(user);
      } else {
        getUserById(id!)
          .then(({ data }: AxiosResponse<{ [key: string]: any }>) => {
            users.push(data);
            localStorage.setItem("users", JSON.stringify(users));
            setUser(data);
          })
          .catch((err) => console.log(err));
      }
    } else {
      getUserById(id!)
        .then(({ data }: AxiosResponse<{ [key: string]: any }>) => {
          const users = [data];
          localStorage.setItem("users", JSON.stringify(users));
          setUser(data);
        })
        .catch((err) => console.log(err));
    }
  };

  useEffect(() => {
    fetchUserDetails();
  }, [id]);

  return (
  <Page>
  // UI population code here
  </Page>
  )
}
```
In the `UserDetails` component, the `fetchUserDetails` function is responsible for fetching and setting the user details. It first checks if the users property exists in the `localStorage`. If it does, it searches for a user with a matching id and sets the user state accordingly.

If no matching user is found, a fresh API call is made using `getUserById` and the retrieved data is added to the users array in the `localStorage`. The user state is then set with the fetched data.

In the case where the users property does not exist in the `localStorage`, a fresh API call is made to retrieve the user details. The retrieved data is stored in the users array in the `localStorage`, and the user state is set with the fetched data.

After the `fetchUserDetails` function is executed, the UI is populated with the relevant user details.

And thus, our adventurous quest through the intricate realm of user details comes to a triumphant end. 

## Conclusion

Well, despite my best efforts, one might think they would at least get a chance to proceed to the next stage, right? :grinning: However, to my surprise, I received a rejection without the interviewer even signing into the app. Imagine finding out this tidbit from my Firebase Console! Life has a way of throwing unexpected twists in our coding journeys.

But fear not, my dear readers, for our shared adventure has not been in vain. I extend my deepest appreciation for your unwavering support and for joining me through the lines of code, the challenges, and the triumphs. :punch: Together, we have explored the intricacies of building an admin dashboard, overcoming obstacles along the way.

If you're curious to see the fruits of our labor, you can find the live site [here](https://lendsqr-admin-dashboard.netlify.app/), ready to be explored. For those interested in diving into the code, it awaits you on GitHub [here](https://github.com/SiR-PENt/lendsqr-admin-dashboard).

Now, as I embark on new opportunities, I want to let you know that I'm still open to exploring frontend developer roles. If you or anyone you know is seeking a dedicated and passionate developer, please reach out to me via email at olasunkanmiibalogun@gmail.com. I'm eager to learn more about exciting opportunities that lie ahead.

Thank you once again for being part of this coding journey. Until we meet again in the realm of knowledge and exploration, may your code be bug-free and your programming endeavors be filled with joy and success. Happy coding!