Initial Commit
This commit is contained in:
commit
f2c0e4c932
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/react
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=react
|
||||||
|
|
||||||
|
### react ###
|
||||||
|
.DS_*
|
||||||
|
*.log
|
||||||
|
logs
|
||||||
|
**/*.backup.*
|
||||||
|
**/*.back.*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
*.sublime*
|
||||||
|
|
||||||
|
psd
|
||||||
|
thumb
|
||||||
|
sketch
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/react
|
||||||
|
|
1
react-complete-guide/.eslintcache
Normal file
1
react-complete-guide/.eslintcache
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/index.js":"1","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/App.js":"2","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/Expenses.js":"3","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpenseItem.js":"4","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpenseDate.js":"5","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/UI/Card.js":"6","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/NewExpense/NewExpense.js":"7","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/NewExpense/ExpenseForm.js":"8","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpensesFilter.js":"9","/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpensesList.js":"10"},{"size":179,"mtime":1663691555700,"results":"11","hashOfConfig":"12"},{"size":1074,"mtime":1663784301191,"results":"13","hashOfConfig":"12"},{"size":865,"mtime":1663784016200,"results":"14","hashOfConfig":"12"},{"size":554,"mtime":1663784250244,"results":"15","hashOfConfig":"12"},{"size":560,"mtime":1663696520001,"results":"16","hashOfConfig":"12"},{"size":203,"mtime":1663696382418,"results":"17","hashOfConfig":"12"},{"size":870,"mtime":1663785090702,"results":"18","hashOfConfig":"12"},{"size":2284,"mtime":1663784961415,"results":"19","hashOfConfig":"12"},{"size":808,"mtime":1663783729073,"results":"20","hashOfConfig":"12"},{"size":643,"mtime":1663784178162,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},"3vr72g",{"filePath":"25","messages":"26","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"27","messages":"28","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"29","messages":"30","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"31","messages":"32","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"33","messages":"34","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"35","messages":"36","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"39","messages":"40","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"41","messages":"42","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/index.js",[],["43","44"],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/App.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/Expenses.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpenseItem.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpenseDate.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/UI/Card.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/NewExpense/NewExpense.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/NewExpense/ExpenseForm.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpensesFilter.js",[],"/Users/tyrel.souza/code/udemy/react-course/react-complete-guide/src/components/Expenses/ExpensesList.js",[],{"ruleId":"45","replacedBy":"46"},{"ruleId":"47","replacedBy":"48"},"no-native-reassign",["49"],"no-negated-in-lhs",["50"],"no-global-assign","no-unsafe-negation"]
|
6
react-complete-guide/.prettierrc.json
Normal file
6
react-complete-guide/.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
38145
react-complete-guide/package-lock.json
generated
Normal file
38145
react-complete-guide/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
react-complete-guide/package.json
Normal file
38
react-complete-guide/package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "react-complete-guide",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.11.6",
|
||||||
|
"@testing-library/react": "^11.2.2",
|
||||||
|
"@testing-library/user-event": "^12.5.0",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"react-scripts": "4.0.1",
|
||||||
|
"web-vitals": "^0.2.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
BIN
react-complete-guide/public/favicon.ico
Normal file
BIN
react-complete-guide/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
43
react-complete-guide/public/index.html
Normal file
43
react-complete-guide/public/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
react-complete-guide/public/logo192.png
Normal file
BIN
react-complete-guide/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
react-complete-guide/public/logo512.png
Normal file
BIN
react-complete-guide/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
react-complete-guide/public/manifest.json
Normal file
25
react-complete-guide/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
3
react-complete-guide/public/robots.txt
Normal file
3
react-complete-guide/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
49
react-complete-guide/src/App.js
vendored
Normal file
49
react-complete-guide/src/App.js
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import Expenses from "./components/Expenses/Expenses"
|
||||||
|
import NewExpense from "./components/NewExpense/NewExpense"
|
||||||
|
|
||||||
|
const INITIAL_EXPENSES = [
|
||||||
|
{
|
||||||
|
id: "e1",
|
||||||
|
title: "Toilet Paper",
|
||||||
|
amount: 94.12,
|
||||||
|
date: new Date(2020, 7, 14),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "e2",
|
||||||
|
title: "New TV",
|
||||||
|
amount: 799.49,
|
||||||
|
date: new Date(2021, 2, 12),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "e3",
|
||||||
|
title: "Car Insurance",
|
||||||
|
amount: 294.67,
|
||||||
|
date: new Date(2021, 2, 28),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "e4",
|
||||||
|
title: "New Desk (Wooden)",
|
||||||
|
amount: 450,
|
||||||
|
date: new Date(2021, 5, 12),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [expenses, setExpenses] = useState(INITIAL_EXPENSES)
|
||||||
|
|
||||||
|
const addExpenseHandler = (expense) => {
|
||||||
|
setExpenses((prevExpenses) => {
|
||||||
|
return [expense, ...expenses]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<NewExpense onAddExpense={addExpenseHandler} />
|
||||||
|
<Expenses items={expenses} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
26
react-complete-guide/src/components/Expenses/ExpenseDate.css
Normal file
26
react-complete-guide/src/components/Expenses/ExpenseDate.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.expense-date {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 5.5rem;
|
||||||
|
height: 5.5rem;
|
||||||
|
border: 1px solid #ececec;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-date__month {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-date__year {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-date__day {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
19
react-complete-guide/src/components/Expenses/ExpenseDate.js
vendored
Normal file
19
react-complete-guide/src/components/Expenses/ExpenseDate.js
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import "./ExpenseDate.css"
|
||||||
|
|
||||||
|
const ExpenseDate= (props) => {
|
||||||
|
const month = props.date.toLocaleString("en-us", { month: "long" })
|
||||||
|
const day = props.date.toLocaleString("en-us", { day: "2-digit" })
|
||||||
|
const year = props.date.getFullYear()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="expense-date">
|
||||||
|
<div className="expense-date__month">{month}</div>
|
||||||
|
<div className="expense-date__day">{day}</div>
|
||||||
|
<div className="expense-date__year">{year}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpenseDate
|
54
react-complete-guide/src/components/Expenses/ExpenseItem.css
Normal file
54
react-complete-guide/src/components/Expenses/ExpenseItem.css
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
.expense-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
background-color: #4b4b4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-item__description {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-flow: column-reverse;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-item h2 {
|
||||||
|
color: #3a3a3a;
|
||||||
|
font-size: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 1rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-item__price {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
background-color: #40005d;
|
||||||
|
border: 1px solid white;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 580px) {
|
||||||
|
.expense-item__description {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-item__description h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expense-item__price {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
22
react-complete-guide/src/components/Expenses/ExpenseItem.js
vendored
Normal file
22
react-complete-guide/src/components/Expenses/ExpenseItem.js
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react"
|
||||||
|
import ExpenseDate from "./ExpenseDate"
|
||||||
|
import Card from "../UI/Card"
|
||||||
|
|
||||||
|
import "./ExpenseItem.css"
|
||||||
|
|
||||||
|
const ExpenseItem = (props) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<Card className="expense-item">
|
||||||
|
<ExpenseDate date={props.date} />
|
||||||
|
|
||||||
|
<div className="expense-item__description">
|
||||||
|
<h2>{props.title}</h2>
|
||||||
|
<div className="expense-item__price">${props.amount}</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpenseItem
|
@ -0,0 +1,7 @@
|
|||||||
|
.expenses {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: rgb(31, 31, 31);
|
||||||
|
margin: 2rem auto;
|
||||||
|
width: 50rem;
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
34
react-complete-guide/src/components/Expenses/Expenses.js
vendored
Normal file
34
react-complete-guide/src/components/Expenses/Expenses.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import ExpensesList from "./ExpensesList"
|
||||||
|
import ExpensesFilter from "./ExpensesFilter"
|
||||||
|
import Card from "../UI/Card"
|
||||||
|
|
||||||
|
import "./Expenses.css"
|
||||||
|
|
||||||
|
const Expenses = (props) => {
|
||||||
|
const [filteredYear, setFilteredYear] = useState("2021")
|
||||||
|
const filterChangeHandler = (year) => {
|
||||||
|
setFilteredYear(year)
|
||||||
|
console.log(year)
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredExpenses = props.items.filter(
|
||||||
|
(expense) => expense.date.getFullYear().toString() === filteredYear
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card className="expenses">
|
||||||
|
<ExpensesFilter
|
||||||
|
selected={filteredYear}
|
||||||
|
onChangeFilter={filterChangeHandler}
|
||||||
|
/>
|
||||||
|
<ExpensesList items={filteredExpenses} />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Expenses
|
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
.expenses-filter {
|
||||||
|
color: white;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expenses-filter__control {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expenses-filter label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expenses-filter select {
|
||||||
|
font: inherit;
|
||||||
|
padding: 0.5rem 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
26
react-complete-guide/src/components/Expenses/ExpensesFilter.js
vendored
Normal file
26
react-complete-guide/src/components/Expenses/ExpensesFilter.js
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import "./ExpensesFilter.css"
|
||||||
|
|
||||||
|
const ExpensesFilter = (props) => {
|
||||||
|
const dropdownChangeHandler = (event) => {
|
||||||
|
props.onChangeFilter(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="expenses-filter">
|
||||||
|
<div className="expenses-filter__control">
|
||||||
|
<label>Filter by year</label>
|
||||||
|
<select value={props.selected} onChange={dropdownChangeHandler}>
|
||||||
|
<option value="2023">2023</option>
|
||||||
|
<option value="2022">2022</option>
|
||||||
|
<option value="2021">2021</option>
|
||||||
|
<option value="2020">2020</option>
|
||||||
|
<option value="2019">2019</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpensesFilter
|
@ -0,0 +1,9 @@
|
|||||||
|
.expenses-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expenses-list__fallback {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
25
react-complete-guide/src/components/Expenses/ExpensesList.js
vendored
Normal file
25
react-complete-guide/src/components/Expenses/ExpensesList.js
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from "react"
|
||||||
|
import ExpenseItem from "./ExpenseItem"
|
||||||
|
import "./ExpensesList.css"
|
||||||
|
|
||||||
|
const ExpensesList = (props) => {
|
||||||
|
if (props.items.length === 0) {
|
||||||
|
return <h2 className="expenses-list__fallback">Found no expenses.</h2>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="expenses-list">
|
||||||
|
{" "}
|
||||||
|
{props.items.map((expense) => (
|
||||||
|
<ExpenseItem
|
||||||
|
key={expense.id}
|
||||||
|
title={expense.title}
|
||||||
|
amount={expense.amount}
|
||||||
|
date={expense.date}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpensesList
|
@ -0,0 +1,26 @@
|
|||||||
|
.new-expense__controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense__control label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense__control input {
|
||||||
|
font: inherit;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
width: 20rem;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense__actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
71
react-complete-guide/src/components/NewExpense/ExpenseForm.js
vendored
Normal file
71
react-complete-guide/src/components/NewExpense/ExpenseForm.js
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import "./ExpenseForm.css"
|
||||||
|
|
||||||
|
function ExpenseForm(props) {
|
||||||
|
const [enteredTitle, setEnteredTitle] = useState("")
|
||||||
|
const [enteredAmount, setEnteredAmount] = useState("")
|
||||||
|
const [enteredDate, setEnteredDate] = useState("")
|
||||||
|
|
||||||
|
const titleChangeHandler = (event) => {
|
||||||
|
setEnteredTitle(event.target.value)
|
||||||
|
}
|
||||||
|
const amountChangeHandler = (event) => {
|
||||||
|
setEnteredAmount(event.target.value)
|
||||||
|
}
|
||||||
|
const dateChangeHandler = (event) => {
|
||||||
|
setEnteredDate(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitHandler = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const expenseData = {
|
||||||
|
title: enteredTitle,
|
||||||
|
amount: enteredAmount,
|
||||||
|
date: new Date(enteredDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(expenseData)
|
||||||
|
setEnteredTitle("")
|
||||||
|
setEnteredAmount("")
|
||||||
|
setEnteredDate("")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={submitHandler}>
|
||||||
|
<div className="new-expense__controls">
|
||||||
|
<div className="new-expense__control">
|
||||||
|
<label>Title</label>
|
||||||
|
<input type="text" value={enteredTitle} onChange={titleChangeHandler} />
|
||||||
|
</div>
|
||||||
|
<div className="new-expense__control">
|
||||||
|
<label>Amount</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0.01"
|
||||||
|
step="0.01"
|
||||||
|
value={enteredAmount}
|
||||||
|
onChange={amountChangeHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="new-expense__control">
|
||||||
|
<label>Date</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
min="2022-01-01"
|
||||||
|
max="2022-12-31"
|
||||||
|
value={enteredDate}
|
||||||
|
onChange={dateChangeHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="new-expense__actions">
|
||||||
|
<button type="button" onClick={props.onCancel}>Cancel</button>
|
||||||
|
<button type="submit">Add Expense</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpenseForm
|
@ -0,0 +1,38 @@
|
|||||||
|
.new-expense {
|
||||||
|
background-color: #a892ee;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
width: 50rem;
|
||||||
|
max-width: 95%;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense button {
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border: 1px solid #40005d;
|
||||||
|
background-color: #40005d;
|
||||||
|
color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense button:hover,
|
||||||
|
.new-expense button:active {
|
||||||
|
background-color: #510674;
|
||||||
|
border-color: #510674;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense button.alternative {
|
||||||
|
color: #220131;
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-expense button.alternative:hover,
|
||||||
|
.new-expense button.alternative:active {
|
||||||
|
background-color: #ddb3f8;
|
||||||
|
}
|
31
react-complete-guide/src/components/NewExpense/NewExpense.js
vendored
Normal file
31
react-complete-guide/src/components/NewExpense/NewExpense.js
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React, {useState} from 'react'
|
||||||
|
import "./NewExpense.css"
|
||||||
|
|
||||||
|
import ExpenseForm from "./ExpenseForm"
|
||||||
|
|
||||||
|
function NewExpense(props) {
|
||||||
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
|
|
||||||
|
const saveExpenseDataHandler = (enteredExpenseData) => {
|
||||||
|
const expenseData = {
|
||||||
|
...enteredExpenseData,
|
||||||
|
id: Math.random().toString()
|
||||||
|
}
|
||||||
|
props.onAddExpense(expenseData)
|
||||||
|
setIsEditing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const startEditingHandler = () => {
|
||||||
|
setIsEditing(true)
|
||||||
|
}
|
||||||
|
const stopEditingHandler = () => {
|
||||||
|
setIsEditing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="new-expense">
|
||||||
|
{!isEditing && <button onClick={startEditingHandler}>Add New Expense</button>}
|
||||||
|
{isEditing && <ExpenseForm onCancel={stopEditingHandler} onSaveExpenseData={saveExpenseDataHandler} />}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewExpense;
|
4
react-complete-guide/src/components/UI/Card.css
Normal file
4
react-complete-guide/src/components/UI/Card.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
11
react-complete-guide/src/components/UI/Card.js
vendored
Normal file
11
react-complete-guide/src/components/UI/Card.js
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import "./Card.css"
|
||||||
|
|
||||||
|
const Card = (props) => {
|
||||||
|
const classes = "card " + props.className
|
||||||
|
|
||||||
|
return <div className={classes}>{props.children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Card
|
15
react-complete-guide/src/index.css
Normal file
15
react-complete-guide/src/index.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap');
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: 'Noto Sans JP', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #3f3f3f;
|
||||||
|
}
|
||||||
|
|
7
react-complete-guide/src/index.js
vendored
Normal file
7
react-complete-guide/src/index.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
root.render(<App />);
|
Loading…
Reference in New Issue
Block a user