Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/p-kosinski/invoice-app
Invoice tracking app - frontendmentor.io challenge solution
https://github.com/p-kosinski/invoice-app
framer-motion react react-router-dom redux redux-thunk styled-components typescript
Last synced: 16 days ago
JSON representation
Invoice tracking app - frontendmentor.io challenge solution
- Host: GitHub
- URL: https://github.com/p-kosinski/invoice-app
- Owner: p-kosinski
- License: mit
- Created: 2022-07-10T10:38:58.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2023-02-26T19:18:33.000Z (almost 2 years ago)
- Last Synced: 2024-11-20T16:26:03.429Z (3 months ago)
- Topics: framer-motion, react, react-router-dom, redux, redux-thunk, styled-components, typescript
- Language: TypeScript
- Homepage: https://guarded-beyond-15043.herokuapp.com/
- Size: 536 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Frontend Mentor - Invoice app solution
This is a solution to the [Invoice app challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/invoice-app-i7KaLTQjl). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
## Table of contents
- [Overview](#overview)
- [The challenge](#the-challenge)
- [Screenshot](#screenshot)
- [Links](#links)- [My process](#my-process)
- [Built with](#built-with)
- [What I learned](#what-i-learned)
- [Useful resources](#useful-resources)- [Author](#author)
## Overview
### The challenge
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, 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**: Keep track of any changes, even after refreshing the browser (`localStorage` could be used for this if you're not building out a full-stack app)### Screenshot
![](./screenshot.jpg)
### Links
- Solution URL [here](https://www.frontendmentor.io/solutions/invoice-tracking-app-react-typescript-and-styled-components-bm3Q0_8CCN).
- Live Site URL [here](https://guarded-beyond-15043.herokuapp.com/).## My process
### Built with
- Semantic HTML5 markup
- Flexbox
- CSS Grid
- Mobile-first workflow
- [Vite](https://vitejs.dev/) - Dev environment tooling
- [Typescript](https://www.typescriptlang.org/) - syntactic superset of JavaScript with static typing
- [React](https://reactjs.org/) - JS library
- [Styled Components](https://styled-components.com/) - CSS-in-JS library for styling
- [Redux](https://redux.js.org/) - App state container library for React
- [React Router](https://reactrouter.com/en/main) - Routing library for React
- [Framer Motion](https://www.framer.com/motion/) - React animation library
- [JSON Server](https://github.com/typicode/json-server) - fake REST API for front-end protyping/mocking
- [dayjs](https://day.js.org/) - JS library for working with dates### What I learned
#### - Creating generic types
```ts
type EditItemPropertyPayload = {
index: number;
newValue: T;
};
```#### - Writing type assertions
```ts
export function assertNotUndefined(data: T | undefined): asserts data is T {
if (data === undefined) {
throw new Error(`'undefined' was found unexpectedly`);
}
}
```#### - Typing React components props
```tsx
type Props = {
severity: 'success' | 'error';
closeFunc: () => void;
autoClose?: boolean;
children?: ReactNode;
};const Alert: React.FC = ({
severity,
closeFunc,
autoClose,
children
}) => (
...
);
```#### - Redux usage with TypeScript
```ts
export type RootState = ReturnType;
export type AppDispatch = typeof store.dispatch;
``````ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from './store';export type ThemeMode = 'light' | 'dark';
export interface ThemeState {
mode: ThemeMode;
};const initialState: ThemeState = {
mode: 'light',
};export const themeSlice = createSlice({
name: 'theme',
initialState,
reducers: {
setThemeMode: (state: ThemeState, action: PayloadAction) => {
state.mode = action.payload;
}
}
});export const { setThemeMode } = themeSlice.actions;
export const selectThemeMode = (state: RootState) => state.theme.mode;
export default themeSlice.reducer;
```#### - Creating async redux thunks with TypeScript
```ts
export type ChangeInvoiceDataArgs = {
id: string;
changedData: ChangedInvoiceData;
};export const changeInvoiceData = createAsyncThunk(
'invoices/changeInvoiceData',
async (args: ChangeInvoiceDataArgs) => {
const { id, changedData } = args;return fetch(`${api.url}/${api.endpoints.invoices}/${id}`, {
method: 'PATCH',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(changedData),
}).then((res) => res.json());
}
);
``````ts
export const invoicesSlice = createSlice({
name: 'invoices',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(changeInvoiceData.pending, (state, action) => {
state.dataChanging.active = true;
state.dataChanging.error = false;
state.dataChanging.success = false;
}),
builder.addCase(changeInvoiceData.fulfilled, (state, action) => {
const { id } = action.meta.arg;const changedInvoiceIndex = state.data.findIndex(
(invoice) => invoice.id === id
);state.data[changedInvoiceIndex] = action.payload;
state.dataChanging.active = false;
state.dataChanging.error = false;
state.dataChanging.success = true;
}),
builder.addCase(changeInvoiceData.rejected, (state, action) => {
state.dataChanging.active = false;
state.dataChanging.error = true;
state.dataChanging.success = false;
})
}
```#### - Using styled-components with TypeScript
```ts
import styled, { css, DefaultTheme } from 'styled-components';type SkeletonProps = {
theme: DefaultTheme;
$height?: string;
$width?: string;
};const CardSkeleton = styled.div(
({ theme, $height, $width }) => css`
min-height: ${$height};
width: ${$width};
margin-bottom: 16px;
border-radius: 8px;
box-shadow: 0px 10px 10px -10px ${theme.colors.shadow.lighter};
background-color: ${theme.colors.backgrounds.skeleton};
overflow: hidden;
${pulseAnimation}
transition:
background-color ${theme.transitionDuration} ease-in-out,
box-shadow ${theme.transitionDuration} ease-in-out;@media only screen and (min-width: ${theme.breakpoints.md}) {
height: 80px;
}${pulseKeyframes}
`
);
```### Useful resources
- [Codecademy TypeScript course](https://www.codecademy.com/learn/learn-typescript) - This is a free TypeScript course which helped me understand the basics of TypeScript
- [Stackoverflow](https://stackoverflow.com/) - Of course I searched for solutions for some technical problems here :)## Author
- Github - [p-kosinski](https://github.com/p-kosinski)
- Frontend Mentor - [@p-kosinski](https://www.frontendmentor.io/profile/p-kosinski)