Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,496 changes: 1,375 additions & 121 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,29 @@
"preview": "vite preview"
},
"dependencies": {
"@types/styled-components": "^5.1.26",
"@types/youtube-player": "^5.5.6",
"axios": "^1.3.2",
"bootstrap": "^5.2.3",
"date-and-time": "^2.4.2",
"immer": "^9.0.17",
"p-min-delay": "^4.0.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-bootstrap": "^2.7.0",
"react-dom": "^18.2.0",
"react-router": "^6.8.0",
"react-router-dom": "^6.8.0",
"react-spinners": "^0.13.8",
"react-youtube": "^10.1.0",
"styled-components": "^5.3.6"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0",
"typescript": "^4.9.3",
"vite": "^4.0.0"
"vite": "^4.0.0",
"vite-plugin-webpackchunkname": "^0.2.4"
}
}
1 change: 1 addition & 0 deletions public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* /index.html 200
Binary file added public/photos/danielle.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/haerin.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/hanni.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/hyein.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji6.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji7.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji8.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/photos/minji9.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 0 additions & 41 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,41 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
41 changes: 41 additions & 0 deletions src/Jaeheon/Ch_10_react-router-todolist/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Layout from "./components/Layout";
import Home from "./pages/Home";
import About from "./pages/About";
import TodoList from "./pages/TodoList";
import AddTodo from "./pages/AddTodo";
import EditTodo from "./pages/EditTodo";
import NotFound from "./pages/NotFound";
import { CallbacksType, StatesType } from "./AppContainer";
import TodoDetail from "./pages/TodoDetail";

type AppPropsType = {
states: StatesType;
callbacks: CallbacksType;
};

const App = ({ states, callbacks }: AppPropsType) => {
return (
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route
path="todos"
element={<TodoList states={states} callbacks={callbacks} />}
/>
<Route path="todos/:id" element={<TodoDetail states={states} />} />
<Route path="todos/add" element={<AddTodo callbacks={callbacks} />} />
<Route
path="todos/edit/:id"
element={<EditTodo states={states} callbacks={callbacks} />}
/>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</Router>
);
};

export default App;
146 changes: 146 additions & 0 deletions src/Jaeheon/Ch_10_react-router-todolist/AppContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { useEffect, useState } from "react";
import App from "./App";
import produce from "immer";
import DateAndTime from "date-and-time";

export type TodoItemType = {
id: number;
todo: string;
desc: string;
done: boolean;
date: Date;
};

export type StatesType = { todoList: Array<TodoItemType> };

export type CallbacksType = {
addTodo: (todo: string, desc: string) => void;
deleteTodo: (id: number) => void;
toggleDone: (id: number) => void;
updateTodo: (id: number, todo: string, desc: string, done: boolean) => void;
filterTodoList: (filter: string) => TodoItemType[];
sortTodoList: (todoList: TodoItemType[], sort: string) => TodoItemType[];
};

const AppContainer = () => {
const [todoList, setTodoList] = useState<Array<TodoItemType>>([]);

useEffect(() => {
const localData = localStorage.getItem("todoList");
if (localData) setTodoList(JSON.parse(localData) || []);
}, []);

useEffect(() => {
localStorage.setItem("todoList", JSON.stringify(todoList));
}, [todoList]);

const addTodo = (todo: string, desc: string) => {
const newTodoList = produce(todoList, (draft) => {
draft.push({
id: new Date().getTime(),
todo,
desc,
done: false,
date: new Date(),
});
});
setTodoList(newTodoList);
};

const deleteTodo = (id: number) => {
const index = todoList.findIndex((todo) => todo.id === id);
const newTodoList = produce(todoList, (draft) => {
draft.splice(index, 1);
});
setTodoList(newTodoList);
};

const toggleDone = (id: number) => {
const index = todoList.findIndex((todo) => todo.id === id);
const newTodoList = produce(todoList, (draft) => {
draft[index].done = !draft[index].done;
});
setTodoList(newTodoList);
};

const updateTodo = (
id: number,
todo: string,
desc: string,
done: boolean
) => {
const index = todoList.findIndex((todo) => todo.id === id);
const newTodoList = produce(todoList, (draft) => {
draft[index] = { ...draft[index], todo, desc, done };
});
setTodoList(newTodoList);
};

const filterTodoList = (filter: string) => {
switch (filter) {
case "done":
return todoList.filter((item) => item.done);
case "not-done":
return todoList.filter((item) => !item.done);
default:
return [...todoList];
}
};

const sortTodoList = (todoList: TodoItemType[] | [], sort: string) => {
switch (sort) {
case "oldest":
todoList.sort((a, b) => a.id - b.id);
return todoList;
case "latest":
todoList.sort((a, b) => b.id - a.id);
return todoList;
default:
return todoList;
}
};

const callbacks: CallbacksType = {
addTodo,
deleteTodo,
updateTodo,
toggleDone,
filterTodoList,
sortTodoList,
};
const states: StatesType = { todoList };

return <App callbacks={callbacks} states={states} />;
};

export default AppContainer;
// const [todoList, setTodoList] = useState<Array<TodoItemType>>([
// {
// id: 1,
// todo: "아침",
// desc: "설명1",
// done: false,
// date: new Date(),
// },
// {
// id: 2,
// todo: "점심",
// desc: "설명2",
// done: false,
// date: new Date(),
// },
// {
// id: 3,
// todo: "저녁",
// desc: "설명3",
// done: true,
// date: new Date(),
// },
// {
// id: 4,
// todo: "롤",
// desc: "설명4",
// done: false,
// date: new Date(),
// },
// ]);
78 changes: 78 additions & 0 deletions src/Jaeheon/Ch_10_react-router-todolist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 10장 리액트 라우터를 이용한 TodoList App

## AppContainer

```tsx

const AppContainer = () => {
// 상태와 상태를 변경하는 로직을 담당하는 함수 작성
.....
const callbacks: CallbacksType = {
addTodo,
deleteTodo,
updateTodo,
toggleDone,
filterTodoList,
sortTodoList,
};
const states: StatesType = { todoList };

return <App callbacks={callbacks} states={states} />;
};
```

상태와 상태를 변경하는 로직을 담당하는 함수가 있는 컴포넌트로 상태는 states, 상태 변경 로직은 callbacks 객체로 묶어서 App컴포넌트로 전달한다.

## App

```tsx
// 전달하는 속성은 생략
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="todos" element={<TodoList />} />
<Route path="todos/:id" element={<TodoDetail />} />
<Route path="todos/add" element={<AddTodo />} />
<Route path="todos/edit/:id" element={<EditTodo />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</Router>
```

라우트 정보를 설정하는 컴포넌트

## Layout

- Header 컴포넌트와 Outlet 컴포넌트를 가지고 있는 컴포넌트

## Home, About

- 아무런 기능이 없는 더미 컴포넌트
- 경로: "/", "/about"

## TodoList

- TodoList를 보여주는 컴포넌트
- 경로: "/todos"

## TodoDetail

- TodoItem의 상세 정보를 보여주는 컴포넌트
- 경로: "/todos/:id"

## AddTodo

- TodoItem을 추가하는 컴포넌트
- 경로: "/todos/add"

## EditTodo

- TodoItem의 상세 정보를 수정하는 컴포넌트
- 경로: "/todos/edit/:id"

## NotFound

- 존재하지 않는 경로로 이동 시 보여주는 컴포넌트
45 changes: 45 additions & 0 deletions src/Jaeheon/Ch_10_react-router-todolist/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from "react";
import { Link } from "react-router-dom";

const Header = () => {
let [isNavShow, setIsNavShow] = useState<boolean>(false);
return (
<nav className="navbar navbar-expand-sm bg-dark navbar-dark">
<span className="navbar-brand ps-2">TodoList App</span>
<button
className="navbar-toggler"
type="button"
onClick={() => setIsNavShow(!isNavShow)}
>
<span className="navbar-toggler-icon"></span>
</button>
<div
className={
isNavShow
? "collapse navbar-collapse show"
: "collapse navbar-collapse"
}
>
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link" to="/">
Home
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/about">
About
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/todos">
TodoList
</Link>
</li>
</ul>
</div>
</nav>
);
};

export default Header;
Loading