Skip to content

Commit b129d17

Browse files
committed
Completed exercise jonasschmedtmann#15: The world bank reducer in section 20 of the course
1 parent a9672bb commit b129d17

19 files changed

+519
-184
lines changed

15-redux-intro/starter/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<!doctype html>
1+
<!DOCTYPE html>
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React</title>
7+
<title>The React-Redux Bank</title>
88
</head>
99
<body>
1010
<div id="root"></div>

15-redux-intro/starter/package-lock.json

Lines changed: 29 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

15-redux-intro/starter/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"dependencies": {
1313
"react": "^18.2.0",
1414
"react-dom": "^18.2.0",
15-
"redux": "^5.0.1"
15+
"redux": "^4.0.0",
16+
"redux-devtools-extension": "^2.13.9"
1617
},
1718
"devDependencies": {
1819
"@types/react": "^18.2.66",

15-redux-intro/starter/src/App.jsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
import CreateCustomer from "./CreateCustomer";
2-
import Customer from "./Customer";
3-
import AccountOperations from "./AccountOperations";
4-
import BalanceDisplay from "./BalanceDisplay";
1+
import CreateCustomer from "./features/customers/CreateCustomer";
2+
import Customer from "./features/customers/Customer";
3+
import AccountOperations from "./features/accounts/AccountOperations";
4+
import BalanceDisplay from "./features/accounts/BalanceDisplay";
5+
import { useSelector } from "react-redux";
56

67
function App() {
8+
const { fullName } = useSelector((store) => store.customer);
9+
710
return (
811
<div>
912
<h1>🏦 The React-Redux Bank ⚛️</h1>
10-
<CreateCustomer />
11-
<Customer />
12-
<AccountOperations />
13-
<BalanceDisplay />
13+
{fullName ? (
14+
<>
15+
<Customer />
16+
<AccountOperations />
17+
<BalanceDisplay />
18+
</>
19+
) : (
20+
<CreateCustomer />
21+
)}
1422
</div>
1523
);
1624
}

15-redux-intro/starter/src/BalanceDisplay.jsx

Lines changed: 0 additions & 12 deletions
This file was deleted.

15-redux-intro/starter/src/Customer.jsx

Lines changed: 0 additions & 5 deletions
This file was deleted.

15-redux-intro/starter/src/AccountOperations.jsx renamed to 15-redux-intro/starter/src/features/accounts/AccountOperations.jsx

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { useState } from "react";
2+
import { useDispatch, useSelector } from "react-redux";
3+
import { deposit, payLoan, requestLoan, withdraw } from "./accountSlice";
24

35
function AccountOperations() {
46
const [depositAmount, setDepositAmount] = useState("");
@@ -7,13 +9,36 @@ function AccountOperations() {
79
const [loanPurpose, setLoanPurpose] = useState("");
810
const [currency, setCurrency] = useState("USD");
911

10-
function handleDeposit() {}
12+
const {
13+
loan: currentLoan,
14+
loanPurpose: currentLoanPurpose,
15+
isLoading,
16+
} = useSelector((store) => store.account);
17+
const dispatch = useDispatch();
1118

12-
function handleWithdrawal() {}
19+
function handleDeposit() {
20+
if (depositAmount < 1 || !depositAmount) return;
21+
dispatch(deposit(depositAmount, currency));
22+
setDepositAmount("");
23+
setCurrency("USD");
24+
}
1325

14-
function handleRequestLoan() {}
26+
function handleWithdrawal() {
27+
if (withdrawalAmount < 1 || !withdrawalAmount) return;
28+
dispatch(withdraw(withdrawalAmount));
29+
setWithdrawalAmount("");
30+
}
1531

16-
function handlePayLoan() {}
32+
function handleRequestLoan() {
33+
if (loanAmount < 1 || !loanAmount || !loanPurpose) return;
34+
dispatch(requestLoan(loanAmount, loanPurpose));
35+
setLoanAmount("");
36+
setLoanPurpose("");
37+
}
38+
39+
function handlePayLoan() {
40+
dispatch(payLoan());
41+
}
1742

1843
return (
1944
<div>
@@ -35,7 +60,9 @@ function AccountOperations() {
3560
<option value="GBP">British Pound</option>
3661
</select>
3762

38-
<button onClick={handleDeposit}>Deposit {depositAmount}</button>
63+
<button onClick={handleDeposit} disabled={isLoading}>
64+
{isLoading ? "Converting..." : `Deposit ${depositAmount}`}
65+
</button>
3966
</div>
4067

4168
<div>
@@ -66,10 +93,14 @@ function AccountOperations() {
6693
<button onClick={handleRequestLoan}>Request loan</button>
6794
</div>
6895

69-
<div>
70-
<span>Pay back $X</span>
71-
<button onClick={handlePayLoan}>Pay loan</button>
72-
</div>
96+
{currentLoan > 0 && (
97+
<div>
98+
<span>
99+
Pay back ${currentLoan} ({currentLoanPurpose}){" "}
100+
</span>
101+
<button onClick={handlePayLoan}>Pay loan</button>
102+
</div>
103+
)}
73104
</div>
74105
</div>
75106
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useSelector, connect } from "react-redux";
2+
3+
function formatCurrency(value) {
4+
return new Intl.NumberFormat("en", {
5+
style: "currency",
6+
currency: "USD",
7+
}).format(value);
8+
}
9+
10+
// function BalanceDisplay() {
11+
// const { balance } = useSelector((store) => store.account);
12+
13+
// return <div className="balance">{formatCurrency(balance)}</div>;
14+
// }
15+
16+
function BalanceDisplay({ balance }) {
17+
return <div className="balance">{formatCurrency(balance)}</div>;
18+
}
19+
20+
function mapStateToProps(state) {
21+
return {
22+
balance: state.account.balance,
23+
};
24+
}
25+
26+
// export default BalanceDisplay;
27+
export default connect(mapStateToProps)(BalanceDisplay);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const initialStateAccount = {
2+
balance: 0,
3+
loan: 0,
4+
loanPurpose: "",
5+
isLoading: false,
6+
};
7+
8+
export default function accountReducer(state = initialStateAccount, action) {
9+
switch (action.type) {
10+
case "account/deposit":
11+
return {
12+
...state,
13+
balance: state.balance + action.payload,
14+
isLoading: false,
15+
};
16+
17+
case "account/withdraw":
18+
return {
19+
...state,
20+
balance: state.balance - action.payload,
21+
};
22+
23+
case "account/requestLoan":
24+
if (state.loan > 0) return state;
25+
return {
26+
...state,
27+
loan: action.payload.amount,
28+
loanPurpose: action.payload.purpose,
29+
balance: state.balance + action.payload.amount,
30+
};
31+
32+
case "account/payLoan":
33+
return {
34+
...state,
35+
loan: 0,
36+
loanPurpose: "",
37+
balance: state.balance - state.loan,
38+
};
39+
40+
case "account/convertingCurrency":
41+
return { ...state, isLoading: true };
42+
43+
default:
44+
return state;
45+
}
46+
}
47+
48+
export const deposit = (amount, currency) => {
49+
if (currency === "USD") return { type: "account/deposit", payload: amount };
50+
51+
return async (dispatch, getState) => {
52+
dispatch({ type: "account/convertingCurrency" });
53+
54+
const res = await fetch(
55+
`https://api.frankfurter.app/latest?amount=${amount}&from=${currency}&to=USD`
56+
);
57+
const data = await res.json();
58+
const converted = data.rates.USD;
59+
60+
dispatch({ type: "account/deposit", payload: converted });
61+
};
62+
};
63+
64+
export const withdraw = (amount) => ({
65+
type: "account/withdraw",
66+
payload: amount,
67+
});
68+
69+
export const requestLoan = (amount, purpose) => ({
70+
type: "account/requestLoan",
71+
payload: { amount, purpose },
72+
});
73+
74+
export const payLoan = () => ({
75+
type: "account/payLoan",
76+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { createSlice } from "@reduxjs/toolkit";
2+
3+
const initialState = {
4+
balance: 0,
5+
loan: 0,
6+
loanPurpose: "",
7+
isLoading: false,
8+
};
9+
10+
const accountSlice = createSlice({
11+
name: "account",
12+
initialState,
13+
reducers: {
14+
deposit(state, action) {
15+
state.balance += action.payload;
16+
state.isLoading = false;
17+
},
18+
withdraw(state, action) {
19+
state.balance -= action.payload;
20+
},
21+
requestLoan: {
22+
prepare(amount, purpose) {
23+
return { payload: { amount, purpose } };
24+
},
25+
reducer(state, action) {
26+
if (state.loan > 0) return;
27+
state.loan = action.payload.amount;
28+
state.loanPurpose = action.payload.purpose;
29+
state.balance += action.payload.amount;
30+
},
31+
},
32+
payLoan(state) {
33+
state.balance -= state.loan;
34+
state.loan = 0;
35+
state.loanPurpose = "";
36+
},
37+
convertingCurrency(state) {
38+
state.isLoading = true;
39+
},
40+
},
41+
});
42+
43+
export const { withdraw, requestLoan, payLoan } = accountSlice.actions;
44+
45+
export const deposit = (amount, currency) => {
46+
if (currency === "USD") return { type: "account/deposit", payload: amount };
47+
48+
return async (dispatch) => {
49+
dispatch({ type: "account/convertingCurrency" });
50+
const res = await fetch(
51+
`https://api.frankfurter.app/latest?amount=${amount}&from=${currency}&to=USD`
52+
);
53+
const data = await res.json();
54+
const converted = data.rates.USD;
55+
56+
dispatch({ type: "account/deposit", payload: converted });
57+
};
58+
};
59+
60+
export default accountSlice.reducer;

0 commit comments

Comments
 (0)