Challenge

Project 1 - Expense tracker

(1)Balance.js:

import React, {useContext} from 'react'
import { GlobalContext } from '../context/GlobalState';
export const Balance = () => {
const { transactions } = useContext(GlobalContext);
const amounts = transactions.map(transaction => transaction.amount);

const total = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2);
return (
<>
<h4 className='balance'>Your Balance</h4>
<h1 className='Total'>₹{total}</h1>
</>
)
}

(2)Header.js:

import React from 'react'

export const Header = () => {
return (
<h2 style={{color:"darkblue",textAlign:"center",fontSize:"48px"}}>
Expense Tracker
</h2>
)
}

(3)AddTransaction.js:

import React, {useState, useContext} from 'react'
import { GlobalContext } from '../context/GlobalState';

export const AddTransaction = () => {
const [text, setText] = useState('');
const [amount, setAmount] = useState(0);

const { addTransaction } = useContext(GlobalContext);

const onSubmit = e => {
e.preventDefault();

const newTransaction = {
id: Math.floor(Math.random() * 1000000),
text,
amount: +amount
}

addTransaction(newTransaction);
}

return (
<>
<h3 className='add'>Add New Transaction</h3>
<form onSubmit={onSubmit}>
<div className="form-control">
<label htmlFor="text">Text</label>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} placeholder="Enter text..." />
</div>
<div className="form-control">
<label htmlFor="amount" >Amount<br />
(negative - expense, positive - income)</label>
<input type="number" value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="Enter amount..." />
</div>
<button className="btn">Add Transaction</button>
</form>
</>
)
}

(4)IncomeExpenses.js:

import React, { useContext } from 'react';
import { GlobalContext } from '../context/GlobalState';

export const IncomeExpenses = () => {
const { transactions } = useContext(GlobalContext);

const amounts = transactions.map(transaction => transaction.amount);

const income = amounts
.filter(item => item > 0)
.reduce((acc, item) => (acc += item), 0)
.toFixed(2);

const expense = (
amounts.filter(item => item < 0).reduce((acc, item) => (acc += item), 0) *
-1
).toFixed(2);

return (
<div className="inc-exp-container">
<div>
<h4 className='income'>Income</h4>
<p className="money plus">{income}</p>
</div>
<div>
<h4 className='expense'>Expense</h4>
<p className="money minus">{expense}</p>
</div>
</div>
)
}

(5)Transaction.js:

import React, {useContext} from 'react';
import { GlobalContext } from '../context/GlobalState';

export const Transaction = ({ transaction }) => {
const { deleteTransaction } = useContext(GlobalContext);

const sign = transaction.amount < 0 ? '-' : '+';

return (
<li className={transaction.amount < 0 ? 'minus' : 'plus'}>
{transaction.text} <span>{sign}₹{Math.abs(transaction.amount)}</span><button onClick={() => deleteTransaction(transaction.id)} className="delete-btn">x</button>
</li>
)
}

(6)TransactionList.js:


import React, { useContext } from 'react';
import { Transaction } from './Transaction';

import { GlobalContext } from '../context/GlobalState';

export const TransactionList = () => {
const { transactions } = useContext(GlobalContext);

return (
<>
<h3 className='history'>History</h3>
<ul className="list">
{transactions.map(transaction => (<Transaction key={transaction.id} transaction={transaction} />))}
</ul>
</>
)
}

(7)AppReducer.js:

export default(state, action) => {
switch(action.type) {
case 'DELETE_TRANSACTION':
return {
...state,
transactions: state.transactions.filter(transaction => transaction.id !== action.payload)
}
case 'ADD_TRANSACTION':
return {
...state,
transactions: [action.payload, ...state.transactions]
}
default:
return state;
}
}

(8)GlobalState.js:

import React, { createContext, useReducer } from 'react';
import AppReducer from './AppReducer';

// Initial state
const initialState = {
transactions: []
}

// Create context
export const GlobalContext = createContext(initialState);

// Provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);

// Actions
function deleteTransaction(id) {
dispatch({
type: 'DELETE_TRANSACTION',
payload: id
});
}

function addTransaction(transaction) {
dispatch({
type: 'ADD_TRANSACTION',
payload: transaction
});
}

return (<GlobalContext.Provider value={{
transactions: state.transactions,
deleteTransaction,
addTransaction
}}>
{children}
</GlobalContext.Provider>);
}
(9)App.js:
import React from 'react';
import {Header} from './components/Header';
import{Balance} from './components/Balance';
import {IncomeExpenses} from './components/IncomeExpenses';
import {TransactionList} from './components/TransactionList';
import{AddTransaction} from './components/AddTransaction';
import{GlobalProvider} from './context/GlobalState';

import './App.css';

function App() {
return (
<>

<div className='hello'>Hello!! this is my first React-Project</div>
<GlobalProvider>
<Header/>
<div className="container">
<Balance/>
<IncomeExpenses/>
<TransactionList/>
<AddTransaction/>
</div>
</GlobalProvider>

</>
);
}

export default App;
(10)App.css:
/* @import url('https://fonts.googleapis.com/css?family=Lato&display=swap'); */

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@700&display=swap');

:root {
--box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}

* {
box-sizing: border-box;
}

body {
background-color: #b2b9c0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
font-family: 'Lato', sans-serif;
}

.container {
margin: 30px auto;
width: 350px;
}

h1 {
letter-spacing: 1px;
margin: 0;
}

h3 {
border-bottom: 1px solid #bbb;
padding-bottom: 10px;
margin: 40px 0 10px;
}

h4 {
margin: 0;
text-transform: uppercase;
}

.inc-exp-container {
background-color: #ffffffc5;
box-shadow: var(--box-shadow);
padding: 20px;
display: flex;
justify-content: space-between;
margin: 20px 0;
}

.inc-exp-container > div {
flex: 1;
text-align: center;
}

.inc-exp-container > div:first-of-type {
border-right: 1px solid #dedede;
}

.money {
font-size: 20px;
letter-spacing: 1px;
margin: 5px 0;
}

.money.plus {
color: #2ecc71;
}

.money.minus {
color: #c0392b;
}

label {
display: inline-block;
margin: 10px 0;
}

input[type='text'],
input[type='number'] {
border: 1px solid #dedede;
border-radius: 2px;
display: block;
font-size: 16px;
padding: 10px;
width: 100%;
}

.btn {
cursor: pointer;
background-color: #931c48;
box-shadow: var(--box-shadow);
color: #fff;
border: 0;
display: block;
font-size: 26px;
margin: 10px 0 30px;
padding: 10px;
width: 100%;
}

.btn:focus,
.delete-btn:focus {
outline: 0;
}

.list {
list-style-type: none;
padding: 0;
margin-bottom: 40px;
}

.list li {
background-color: #fff;
box-shadow: var(--box-shadow);
color: #333;
display: flex;
justify-content: space-between;
position: relative;
padding: 10px;
margin: 10px 0;
}

.list li.plus {
border-right: 5px solid #2ecc71;
}

.list li.minus {
border-right: 5px solid #c0392b;
}

.delete-btn {
cursor: pointer;
background-color: #e74c3c;
border: 0;
color: #fff;
font-size: 20px;
line-height: 20px;
padding: 2px 5px;
position: absolute;
top: 50%;
left: 0;
transform: translate(-100%, -50%);
opacity: 0;
transition: opacity 0.3s ease;
}

.list li:hover .delete-btn {
opacity: 1;
}

.balance{
color:deeppink;
background-color: #9c88ff;
font-size: 25px;
}
.Total{
color: #2bc0c0;
background-color: #121111;
font-size: 25px;
}

.income{
color: black;
background-color: #e0e1db;
font-size: 25px;
}
.expense{
color:black;
background-color: rgb(240, 231, 231);
font-size: 25px;
}
.history{
color: darkred;
background-color: #2ecc71;
font-size: 30px;
}
.add{
color:rgb(21, 138, 184);
background-color:pink;
font-size: 25px;
}

.form-control{
color:#121111;
font-size: 20px;
background-color: #dedede;
}

.hello{
color:#931c48;
background-color: cadetblue;
text-align: center;
font-size: 40px;
font-family: Arial, Helvetica, sans-serif;

}
Your portfolio, next

Ship work like this
in your own course.

Talk to a career counsellor

Free 20-minute call. We'll map the right course to where you want to work.

By submitting you agree to our Terms and Privacy Policy.