This is a solution to the Invoice app challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
Users should be able to:
- View the optimal layout for the app depending on their device's screen size
- See hover states for all interactive elements on the page
- create login details
- sign in to proceed
- Create, read, update, and delete invoices
- Receive form validations when trying to create/edit an invoice
- Save draft invoices, and mark pending invoices as paid
- Filter invoices by status (draft/pending/paid)
- Toggle light and dark mode
- Bonus: full-stack app
- Frontend Mentor Solution URL: Frontend Mentor solution url
- Live Site URL: Invoice app
I created the challenge as a Fullstack application, the backend files are in a separate project folder known as invoice backend
The Backend fetches and stores data that is stored in MongoDb. Project created using Vite typescript.
- Semantic HTML5 markup
- CSS custom properties
- Flexbox
- CSS Grid
- Mobile-first workflow
- React - JS library
- vite - Frontend tooling for React
- React Query - Fetching data from the server
- React Router - navigation
- React hook form - forms and form validation
- input type number is used to enter a number and has in-built validation to reject non-numerical entries. in this project, one of the task requires to calculate the total amount of a single project and then sum up to get the total os all the projects.
- The NewInvoice and the EditInvoice design files did not have the Grand Total input field , but to allow the
data to be collected in the form , I created a sr-only input field as shown below and among other things, the
input element had a type
number
.
<div className="sr-only grand-total-wrapper">
<label
htmlFor="grand-total"
className="label"
>
The grand total is
<input
type="number"
{...register("total")}
/>
</label>
</div>
- No issues where recorded when entering a number that has no decimal point, for example, I could make calculations where
the
Quantity = 3
andPrice = 1568
. Issues started when I would enter something likePrice = 1569.95
. The form could not be submitted , but no feedback or error message was provided. In some cases where I had more than 1 project, the Grand total summation was weird as the individual project total was a string concatenation. Eg
- Project 1 Total: 1578.95
- Project 2 Total: 7699.85
- Project 3 Total: 645.55
The above will be displayed as 1578.957699.85645.55
for the total
My final solution was to use the input type text
For issues related to input type number , read the following article Why the GOV.UK Design System team changed the input type for numbers
- used for data management - data fetching , caching , sychronizing and updating the server state
- installation: install using npm by typing
npm i react-query
on the terminal or yarnyarn add react-query
- in the root of the application , I have used the App.tsx as the center for data management. The following steps were taken
- import { QueryClient, QueryClientProvider } from "react-query";
- const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<div className={`app ${theme ? "" : "dark-mode"}`}>
<header className="flex header">
<div className="flex controls">
<div className="logo-container">
<a className="btn btn-logo" href="">
<img src={Logo} alt="" aria-hidden={true} />
<span className="sr-only">preprince investments</span>
</a>
</div>
<Toggle theme={theme} onChange={onChange} />
</div>
<div className="profile">
<a href="#" className="btn btn-profile">
<img className="btn-profile-img" src={Profile} alt="" aria-hidden={true} />
<span className="sr-only">customer profile</span>
</a>
</div>
</header>
<RouterProvider router={router} />
</div>
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
</QueryClientProvider>
-
- Then in the app,
useMutation, useQuery
mainly are used for data fetching, whereuseQuery
is used to get data when there is no need to update the data anduseMutation
is used for updating data
- Then in the app,
- make the div
card
that should be clickable to be ofposition: relative
- then one child element of the
card
div should be an anchor element. In our example React router uses theLink
as the anchor element . See code below
<div key={invoice.id} className="card">
<p>{invoice.id}</p>
<p>{invoice.clientName}</p>
<p>{invoice.paymentDue}</p>
<p>R: {invoice.total}</p>
<Link className="btn-link" to={"/viewInvoice"}>{invoice.status}</Link>
</div>
.card {
position: relative;
background: red;
}
.btn-link {
display: block;
&::before {
position: absolute;
content: "";
width: 100%;
height: 100%;
top: 0;
left: 0;
}
}
The application has multiple pages and the error is encountered during navigation. Here is a brief steps that I performed to notice the bug.
- From the homepage , I navigated to the
ViewInvoice
page - In the
ViewPage
, reload the page.
The error is caused by (in this particular case) , having a hook which was called after a condition
Moving the useMutation hook above the if condition will solve the error
<OverLay
isOverlayOpen={isNewInvoiceOverlayOpen}
toggleOverlay={toggleOverlay}
>
<NewInvoice
toggleOverlay={toggleOverlay}
childInputRef={childInputRef}
/>
</OverLay>
- authentification - log in users by authentification
- A Complete Guide to Mutations in React Query - Part 5 - Using the useMutation Hook to Delete Resources on the Server.
- REST APIs - How To Mutate Data From Your React App Like The Pros
- UseForm hook - watch
- Reusable hook forms text input
- datefns
- Website - Chamu Mutezva
- Frontend Mentor - @ChamuMutezva
- Twitter - @ChamuMutezva
- Focus trap implementation inspired by Tediko from his solution - see link below Tediko Invoice app - Focus trap implementation