├── .eslintcache
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.js
├── component
│ ├── CreateDay.js
│ ├── CreateWord.tsx
│ ├── Day.tsx
│ ├── DayList.tsx
│ ├── EmptyPage.js
│ ├── Header.js
│ └── Word.tsx
├── db
│ └── data.json
├── hooks
│ └── useFetch.ts
├── index.css
├── index.js
├── react-app-env.d.ts
└── setupTests.js
└── tsconfig.json
/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"/Users/Shared/Dev_tutorial/react/voca/src/index.js":"1","/Users/Shared/Dev_tutorial/react/voca/src/App.js":"2","/Users/Shared/Dev_tutorial/react/voca/src/component/Header.js":"3","/Users/Shared/Dev_tutorial/react/voca/src/component/EmptyPage.js":"4","/Users/Shared/Dev_tutorial/react/voca/src/component/CreateDay.js":"5","/Users/Shared/Dev_tutorial/react/voca/src/component/Word.tsx":"6","/Users/Shared/Dev_tutorial/react/voca/src/component/Day.tsx":"7","/Users/Shared/Dev_tutorial/react/voca/src/component/CreateWord.tsx":"8","/Users/Shared/Dev_tutorial/react/voca/src/component/DayList.tsx":"9","/Users/Shared/Dev_tutorial/react/voca/src/hooks/useFetch.ts":"10"},{"size":219,"mtime":1612150868479,"results":"11","hashOfConfig":"12"},{"size":899,"mtime":1613835275166,"results":"13","hashOfConfig":"12"},{"size":429,"mtime":1613835275167,"results":"14","hashOfConfig":"12"},{"size":194,"mtime":1612271439596,"results":"15","hashOfConfig":"12"},{"size":715,"mtime":1613835275166,"results":"16","hashOfConfig":"12"},{"size":1606,"mtime":1616254085279,"results":"17","hashOfConfig":"12"},{"size":551,"mtime":1616254155218,"results":"18","hashOfConfig":"12"},{"size":2012,"mtime":1616254836112,"results":"19","hashOfConfig":"12"},{"size":501,"mtime":1616254405109,"results":"20","hashOfConfig":"12"},{"size":313,"mtime":1616254931517,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},"1itjryz",{"filePath":"25","messages":"26","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"27","messages":"28","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"24"},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/Shared/Dev_tutorial/react/voca/src/index.js",[],["43","44"],"/Users/Shared/Dev_tutorial/react/voca/src/App.js",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/Header.js",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/EmptyPage.js",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/CreateDay.js",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/Word.tsx",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/Day.tsx",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/CreateWord.tsx",[],"/Users/Shared/Dev_tutorial/react/voca/src/component/DayList.tsx",[],"/Users/Shared/Dev_tutorial/react/voca/src/hooks/useFetch.ts",[],{"ruleId":"45","replacedBy":"46"},{"ruleId":"47","replacedBy":"48"},"no-native-reassign",["49"],"no-negated-in-lhs",["50"],"no-global-assign","no-unsafe-negation"]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 초보자를 위한 React JS 강좌
2 |
3 | 유튜브 재생목록 :
4 | https://www.youtube.com/playlist?list=PLZKTXPmaJk8J_fHAzPLH8CJ_HO_M33e7-
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "voca",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "5.11.9",
7 | "@testing-library/react": "11.2.3",
8 | "@testing-library/user-event": "12.6.2",
9 | "@types/jest": "26.0.21",
10 | "@types/node": "14.14.35",
11 | "@types/react": "17.0.3",
12 | "@types/react-dom": "17.0.2",
13 | "@types/react-router-dom": "5.1.7",
14 | "react": "17.0.1",
15 | "react-dom": "17.0.1",
16 | "react-router-dom": "5.2.0",
17 | "react-scripts": "4.0.1",
18 | "typescript": "4.2.3",
19 | "web-vitals": "0.2.4"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coding-angma/voca/626ed9f129e01ef86de59c21ec02017654321e97/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coding-angma/voca/626ed9f129e01ef86de59c21ec02017654321e97/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coding-angma/voca/626ed9f129e01ef86de59c21ec02017654321e97/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import Day from "./component/Day";
2 | import DayList from "./component/DayList";
3 | import Header from "./component/Header";
4 | import { BrowserRouter, Route, Switch } from "react-router-dom";
5 | import EmptyPage from "./component/EmptyPage";
6 | import CreateWord from "./component/CreateWord";
7 | import CreateDay from "./component/CreateDay";
8 |
9 | function App() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | export default App;
37 |
--------------------------------------------------------------------------------
/src/component/CreateDay.js:
--------------------------------------------------------------------------------
1 | import { useHistory } from "react-router";
2 | import useFetch from "../hooks/useFetch";
3 |
4 | export default function CreateDay() {
5 | const days = useFetch("http://localhost:3001/days");
6 | const history = useHistory();
7 |
8 | function addDay() {
9 | fetch(`http://localhost:3001/days/`, {
10 | method: "POST",
11 | headers: {
12 | "Content-Type": "application/json",
13 | },
14 | body: JSON.stringify({
15 | day: days.length + 1,
16 | }),
17 | }).then(res => {
18 | if (res.ok) {
19 | alert("생성이 완료 되었습니다");
20 | history.push(`/`);
21 | }
22 | });
23 | }
24 | return (
25 |
26 |
현재 일수 : {days.length}일
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/src/component/CreateWord.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from "react";
2 | import { useHistory } from "react-router";
3 | import useFetch from "../hooks/useFetch";
4 | import { IDay } from "./DayList";
5 |
6 | export default function CreateWord() {
7 | const days: IDay[] = useFetch("http://localhost:3001/days");
8 | const history = useHistory();
9 | const [isLoading, setIsLoading] = useState(false);
10 |
11 | function onSubmit(e: React.FormEvent) {
12 | e.preventDefault();
13 |
14 | if (!isLoading && dayRef.current && engRef.current && korRef.current) {
15 | setIsLoading(true);
16 |
17 | const day = dayRef.current.value;
18 | const eng = engRef.current.value;
19 | const kor = korRef.current.value;
20 |
21 | fetch(`http://localhost:3001/words/`, {
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | },
26 | body: JSON.stringify({
27 | day,
28 | eng,
29 | kor,
30 | isDone: false,
31 | }),
32 | }).then(res => {
33 | if (res.ok) {
34 | alert("생성이 완료 되었습니다");
35 | history.push(`/day/${day}`);
36 | setIsLoading(false);
37 | }
38 | });
39 | }
40 | }
41 |
42 | const engRef = useRef(null);
43 | const korRef = useRef(null);
44 | const dayRef = useRef(null);
45 |
46 | return (
47 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/src/component/Day.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from "react-router-dom";
2 | import useFetch from "../hooks/useFetch";
3 | import Word, { IWord } from "./Word";
4 |
5 | export default function Day() {
6 | const { day } = useParams<{ day: string }>();
7 | const words: IWord[] = useFetch(`http://localhost:3001/words?day=${day}`);
8 |
9 | return (
10 | <>
11 | Day {day}
12 | {words.length === 0 && Loading...}
13 |
14 |
15 | {words.map(word => (
16 |
17 | ))}
18 |
19 |
20 | >
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/component/DayList.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import useFetch from "../hooks/useFetch";
3 |
4 | export interface IDay {
5 | id: number;
6 | day: number;
7 | }
8 |
9 | export default function DayList() {
10 | const days: IDay[] = useFetch("http://localhost:3001/days");
11 |
12 | if (days.length === 0) {
13 | return Loading...;
14 | }
15 |
16 | return (
17 |
18 | {days.map(day => (
19 | -
20 | Day {day.day}
21 |
22 | ))}
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/component/EmptyPage.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | export default function EmptyPage() {
4 | return (
5 | <>
6 | 잘못된 접근입니다.
7 | 돌아가기
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/component/Header.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | export default function Header() {
4 | return (
5 |
6 |
7 | 토익 영단어(고급)
8 |
9 |
10 |
11 | 단어 추가
12 |
13 |
14 | Day 추가
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/component/Word.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | interface IProps {
4 | word: IWord;
5 | }
6 |
7 | export interface IWord {
8 | day: string;
9 | eng: string;
10 | kor: string;
11 | isDone: boolean;
12 | id: number;
13 | }
14 |
15 | export default function Word({ word: w }: IProps) {
16 | const [word, setWord] = useState(w);
17 | const [isShow, setIsShow] = useState(false);
18 | const [isDone, setIsDone] = useState(word.isDone);
19 |
20 | function toggleShow() {
21 | setIsShow(!isShow);
22 | }
23 |
24 | function toggleDone() {
25 | fetch(`http://localhost:3001/words/${word.id}`, {
26 | method: "PUT",
27 | headers: {
28 | "Content-Type": "application/json",
29 | },
30 | body: JSON.stringify({
31 | ...word,
32 | isDone: !isDone,
33 | }),
34 | }).then(res => {
35 | if (res.ok) {
36 | setIsDone(!isDone);
37 | }
38 | });
39 | }
40 |
41 | function del() {
42 | if (window.confirm("삭제 하시겠습니까?")) {
43 | fetch(`http://localhost:3001/words/${word.id}`, {
44 | method: "DELETE",
45 | }).then(res => {
46 | if (res.ok) {
47 | setWord({
48 | ...word,
49 | id: 0,
50 | });
51 | }
52 | });
53 | }
54 | }
55 |
56 | if (word.id === 0) {
57 | return null;
58 | }
59 |
60 | return (
61 |
62 |
63 |
64 | |
65 | {word.eng} |
66 | {isShow && word.kor} |
67 |
68 |
69 |
72 | |
73 |
74 | );
75 | }
76 |
77 | // Create - POST
78 | // Read - GET
79 | // Update - PUT
80 | // Delete - DELETE
81 |
--------------------------------------------------------------------------------
/src/db/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "days": [
3 | {
4 | "id": 1,
5 | "day": 1
6 | },
7 | {
8 | "id": 2,
9 | "day": 2
10 | },
11 | {
12 | "id": 3,
13 | "day": 3
14 | },
15 | {
16 | "day": 4,
17 | "id": 4
18 | }
19 | ],
20 | "words": [
21 | {
22 | "id": 1,
23 | "day": 1,
24 | "eng": "book",
25 | "kor": "책",
26 | "isDone": false
27 | },
28 | {
29 | "id": 3,
30 | "day": 2,
31 | "eng": "car",
32 | "kor": "자동차",
33 | "isDone": false
34 | },
35 | {
36 | "id": 5,
37 | "day": 3,
38 | "eng": "school",
39 | "kor": "학교",
40 | "isDone": false
41 | },
42 | {
43 | "id": 6,
44 | "day": 3,
45 | "eng": "pencil",
46 | "kor": "연필",
47 | "isDone": false
48 | },
49 | {
50 | "day": "3",
51 | "eng": "window",
52 | "kor": "창문",
53 | "isDone": false,
54 | "id": 7
55 | },
56 | {
57 | "day": "3",
58 | "eng": "house",
59 | "kor": "집",
60 | "isDone": false,
61 | "id": 8
62 | },
63 | {
64 | "day": "2",
65 | "eng": "mouse",
66 | "kor": "쥐",
67 | "isDone": false,
68 | "id": 9
69 | },
70 | {
71 | "day": "4",
72 | "eng": "monkey",
73 | "kor": "원숭이",
74 | "isDone": false,
75 | "id": 10
76 | },
77 | {
78 | "day": "4",
79 | "eng": "apple",
80 | "kor": "사과",
81 | "isDone": false,
82 | "id": 11
83 | },
84 | {
85 | "day": "3",
86 | "eng": "apple",
87 | "kor": "사과",
88 | "isDone": false,
89 | "id": 12
90 | }
91 | ]
92 | }
--------------------------------------------------------------------------------
/src/hooks/useFetch.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export default function useFetch(url: string) {
4 | const [data, setData] = useState([]);
5 |
6 | useEffect(() => {
7 | fetch(url)
8 | .then(res => {
9 | return res.json();
10 | })
11 | .then(data => {
12 | setData(data);
13 | });
14 | }, [url]);
15 |
16 | return data;
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | font-size: 20px;
9 | }
10 |
11 | ol,
12 | ul {
13 | margin: 0;
14 | padding: 0;
15 | list-style: none;
16 | }
17 |
18 | code {
19 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
20 | monospace;
21 | }
22 |
23 | a {
24 | text-decoration: none;
25 | color: #333;
26 | }
27 |
28 | .App {
29 | width: 800px;
30 | margin: 0 auto;
31 | }
32 |
33 | .header {
34 | position: relative;
35 | }
36 |
37 | .header .menu {
38 | position: absolute;
39 | top: 10px;
40 | right: 0;
41 | }
42 |
43 | .header .link {
44 | border: 1px solid #333;
45 | padding: 10px;
46 | margin-left: 10px;
47 | background-color: #efefef;
48 | font-weight: bold;
49 | border-radius: 4px;
50 | }
51 |
52 | .list_day {
53 | display: flex;
54 | flex-wrap: wrap;
55 | }
56 |
57 | .list_day li {
58 | flex: 20% 0 0;
59 | box-sizing: border-box;
60 | padding: 10px;
61 | }
62 |
63 | .list_day a {
64 | display: block;
65 | padding: 20px 0;
66 | font-weight: bold;
67 | color: #fff;
68 | text-align: center;
69 | border-radius: 10px;
70 | background-color: dodgerblue;
71 | }
72 |
73 | table {
74 | border-collapse: collapse;
75 | width: 100%;
76 | }
77 | table td {
78 | width: 25%;
79 | height: 70px;
80 | border: 1px solid #ccc;
81 | text-align: center;
82 | font-size: 26px;
83 | }
84 |
85 | table td:first-child {
86 | width: 10%;
87 | }
88 |
89 | .off td {
90 | background: #eee;
91 | color: #ccc;
92 | }
93 |
94 | .btn_del {
95 | margin-left: 10px;
96 | color: #fff;
97 | background-color: firebrick;
98 | }
99 |
100 | button {
101 | padding: 10px;
102 | font-weight: bold;
103 | font-size: 18px;
104 | cursor: pointer;
105 | border: 0 none;
106 | border-radius: 6px;
107 | padding: 10px 20px;
108 | color: #fff;
109 | background-color: dodgerblue;
110 | }
111 |
112 | .input_area {
113 | margin-bottom: 10px;
114 | }
115 |
116 | .input_area label {
117 | display: block;
118 | margin-bottom: 10px;
119 | }
120 |
121 | .input_area input {
122 | width: 400px;
123 | height: 40px;
124 | font-size: 20px;
125 | padding: 0 10px;
126 | }
127 |
128 | .input_area select {
129 | width: 400px;
130 | height: 40px;
131 | font-size: 20px;
132 | }
133 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById("root")
11 | );
12 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------