diff --git a/React/src/App.css b/React/src/App.css
new file mode 100644
index 000000000..74b5e0534
--- /dev/null
+++ b/React/src/App.css
@@ -0,0 +1,38 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/React/src/App.jsx b/React/src/App.jsx
new file mode 100644
index 000000000..8955573c4
--- /dev/null
+++ b/React/src/App.jsx
@@ -0,0 +1,36 @@
+import { MediaDiv, Main } from './styledComponent';
+import { ThemeProvider } from 'styled-components';
+import { darkTheme, GlobalStyles, lightTheme } from './styles';
+import { useEffect, useState } from 'react';
+import { Route, Routes } from 'react-router-dom';
+import Header from './Header';
+import Slogun from './Slogun';
+import ShowPostList from './ShowPostList';
+import Footer from './Footer';
+import ShowPost from './ShowPost';
+import WritePost from './WritePost';
+
+function App() {
+ const [darkMode, setDarkMode] = useState(true);
+
+ return (
+ <>
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+
+
+
+
+
+ >
+ );
+}
+export default App;
\ No newline at end of file
diff --git a/React/src/App.test.js b/React/src/App.test.js
new file mode 100644
index 000000000..1f03afeec
--- /dev/null
+++ b/React/src/App.test.js
@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+ render();
+ const linkElement = screen.getByText(/learn react/i);
+ expect(linkElement).toBeInTheDocument();
+});
diff --git a/React/src/EachPost.jsx b/React/src/EachPost.jsx
new file mode 100644
index 000000000..7bb996779
--- /dev/null
+++ b/React/src/EachPost.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { EachPostLi, PostLink, PostRepl } from './styledComponent';
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faLocationPin } from '@fortawesome/free-solid-svg-icons';
+function EachPost({ title, postID }) {
+ const navigate = useNavigate();
+
+ const goPost = () => {
+ navigate(`${'/post/' + postID}`);
+ };
+
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/React/src/Footer.jsx b/React/src/Footer.jsx
new file mode 100644
index 000000000..0228619c2
--- /dev/null
+++ b/React/src/Footer.jsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import {
+ FooterDiv,
+ FooterBig,
+ FooterSmall,
+ } from './styledComponent';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faReact } from '@fortawesome/free-brands-svg-icons';
+
+function Footer() {
+ return (
+
+
+ for react study
+ 2022. by chans
+
+ )
+}
+
+export default Footer
\ No newline at end of file
diff --git a/React/src/Header.jsx b/React/src/Header.jsx
new file mode 100644
index 000000000..607fe47b3
--- /dev/null
+++ b/React/src/Header.jsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import {
+ HeaderDiv,
+ TitleLogoDiv,
+ TitleBig,
+ TitleSmall,
+ SubHeaderDiv,
+ CursorDiv,
+} from './styledComponent';
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSun, faMoon } from '@fortawesome/free-solid-svg-icons';
+
+import { useNavigate } from 'react-router-dom';
+function Header({ darkMode, setDarkMode }) {
+ const navigate = useNavigate();
+
+ const goHome = () => {
+ navigate('/');
+ };
+ const toggleDarkMode = () => {
+ setDarkMode((darkMode) => !darkMode);
+ };
+ return (
+
+
+
+ 멋사
+ 익명게시판
+
+
+
+ {darkMode ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+export default Header;
\ No newline at end of file
diff --git a/React/src/ShowPost.jsx b/React/src/ShowPost.jsx
new file mode 100644
index 000000000..791bdafdb
--- /dev/null
+++ b/React/src/ShowPost.jsx
@@ -0,0 +1,121 @@
+
+import React, { useState, useEffect, useMemo } from 'react';
+import { useParams } from 'react-router-dom';
+import {
+ LoadingDiv,
+ LoadingImg,
+ PostReadDiv,
+ PostReplDiv,
+ PostSection,
+ PostTitle,
+ PostTitleDiv,
+ Repl,
+ ReplInput,
+ ReplSubmitDiv,
+ ReplTitleDiv,
+ Replwriter,
+ WritereplDiv,
+} from './styledComponent';
+const postData = {
+ title: '어렵사리 정한 제목입니다.',
+ contents: `내가 그때 그렇게 딱 했는데 보이는 사람은 너라는 것을 알게 되었어, 그게
+ 나에게는 힘들었지만, 쉽내가 그때 그렇게 딱 했는데 보이는 사람은 너라는
+ 것을 알게 되었어, 그게 나에게는 힘들었지만, 내가 그때 그렇게 딱 했는데
+ 보이는 사람은 너라는 것을 알게 되었어, 그게 나에게는 힘들었지만, 지
+ 않았던 만큼 더욱 성장할 수 있다는 것을 뼈저리게 느꼈지`,
+};
+
+const replData = [
+ {
+ id: 2,
+ contents: `내가 생각해도 그건 아니다`,
+ },
+ {
+ id: 5,
+ contents: `그럴수도 있나?? 내가 생각해도 그건 아니다`,
+ },
+];
+const countRepls = (repls) => {
+ console.log('리뷰 개수를 세는 중...');
+ return repls.length;
+};
+const ShowPost = (props) => {
+ const Params = useParams();
+ const [post, setPost] = useState(null);
+ const [repls, setRepls] = useState([]);
+ const [postLoading, setPostLoading] = useState(true);
+ const [replLoading, setReplLoading] = useState(true);
+
+ useEffect(() => {
+ setTimeout(() => {
+ setPost(postData);
+ setPostLoading(false);
+ }, 300);
+ }, []);
+ useEffect(() => {
+ setTimeout(() => {
+ setRepls(replData);
+ setReplLoading(false);
+ }, 1000);
+ }, []);
+
+ const [repl, setRepl] = useState('');
+
+ const onChange = (e) => {
+ setRepl(e.target.value);
+ };
+
+ // for useMemo
+ const replCount = useMemo(() => countRepls(repls), [repls]);
+ // const replCount = countRepls(repls);
+
+ if (!Params.postID) {
+ return 잘못된 접근입니다.;
+ }
+ return (
+
+
+
+
+ {/* title */}
+ {post && post.title}
+
+
+
+ {postLoading ? (
+
+
+
+ ) : (
+ {post && post.contents}
+ )}
+
+ {/* post contents */}
+
+ 댓글 {replCount}
+ {replLoading ? (
+
+
+
+ ) : (
+ repls &&
+ repls.map((element) => (
+
+ 익명
+ {element.contents}
+
+ ))
+ )}
+
+
+
+
+ 입력
+
+
+
+
+ );
+};
+
+export default ShowPost;
\ No newline at end of file
diff --git a/React/src/ShowPostList.jsx b/React/src/ShowPostList.jsx
new file mode 100644
index 000000000..18b314483
--- /dev/null
+++ b/React/src/ShowPostList.jsx
@@ -0,0 +1,102 @@
+import React, { useState, useEffect } from 'react';
+import {
+ PostSection,
+ PostTitleDiv,
+ PostTitle,
+ PostListDiv,
+ PagingSection,
+ LoadingDiv,
+ LoadingImg,
+ PagenumberDiv,
+ CursorDiv,
+} from './styledComponent';
+import { useNavigate } from 'react-router-dom';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {
+ faArrowsRotate,
+ faPenToSquare,
+ faArrowLeft,
+ faArrowRight,
+} from '@fortawesome/free-solid-svg-icons';
+import EachPost from './EachPost';
+
+const initialPostList = [
+ { id: 1, title: '학보, 시사N 대학기자상 취재' },
+ { id: 2, title: '학보, 시사N 대학기자상 취재' },
+ { id: 3, title: '학보, 시사N 대학기자상 취재' },
+];
+
+function ShowPostList() {
+ const [loading, setLoading] = useState(true);
+ const [isPost, setIsPost] = useState(false);
+ const [postList, setPostList] = useState([]);
+
+ const navigate = useNavigate();
+
+ const goWrite = () => {
+ navigate('/write');
+ };
+
+ const addPost = () => {
+ setPostList((postList) => [
+ ...postList,
+ { id: 4, title: '학보, 시사N 대학기자상 취재' },
+ ]);
+ };
+
+ useEffect(() => {
+ setTimeout(() => {
+ setPostList(initialPostList);
+ setLoading(false);
+ }, 600);
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+ 익명게시판
+
+
+
+
+
+ {loading ? (
+
+
+ {/* loading.io */}
+
+ ) : isPost ? (
+ 아직 기록된 글이 없습니다.
+ ) : (
+
+ {postList.map((element) => (
+
+ ))}
+
+ )}
+
+
+ {loading || (
+
+
+
+
+ 2
+
+
+
+
+
+ )}
+ >
+ );
+}
+export default ShowPostList;
\ No newline at end of file
diff --git a/React/src/Slogun.jsx b/React/src/Slogun.jsx
new file mode 100644
index 000000000..da394dd33
--- /dev/null
+++ b/React/src/Slogun.jsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import {
+ SlogunSection,
+ SlogunBig,
+ SlogunSmall,
+ } from './styledComponent';
+
+function Slogan() {
+ return (
+
+ HACK YOUR LIFE
+ 내 아이디어를 내 손으로 실현한다.
+
+ )
+}
+
+export default Slogan
\ No newline at end of file
diff --git a/React/src/WritePost.jsx b/React/src/WritePost.jsx
new file mode 100644
index 000000000..ce587bf25
--- /dev/null
+++ b/React/src/WritePost.jsx
@@ -0,0 +1,56 @@
+
+import React, { useState } from 'react';
+import {
+ ContentsInput,
+ PostSection,
+ PostSubmit,
+ PostSubmitDiv,
+ PostTitle,
+ PostTitleDiv,
+ PostWriteDiv,
+ TitleInput,
+} from './styledComponent';
+
+function WritePost(props) {
+ const [inputs, setInputs] = useState({
+ title: '',
+ contents: '',
+ });
+ const { title, contents } = inputs;
+ const onChange = (e) => {
+ const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
+ setInputs({
+ ...inputs, // 기존의 input 객체를 복사한 뒤
+ [name]: value, // name 키를 가진 값을 value 로 설정
+ });
+ };
+
+ return (
+
+
+ 글쓰기
+
+
+
+
+
+
+ 작성완료
+
+
+ );
+}
+
+export default WritePost;
\ No newline at end of file
diff --git a/React/src/index.css b/React/src/index.css
new file mode 100644
index 000000000..39c185b34
--- /dev/null
+++ b/React/src/index.css
@@ -0,0 +1,23 @@
+@import url('https://fonts.googleapis.com/css2?family=Black+Han+Sans&family=League+Gothic&family=Noto+Sans+KR&display=swap');
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
+ 'Helvetica Neue', sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
+
+textarea {
+ resize: none;
+}
+
+html {
+ width: 100%;
+}
diff --git a/React/src/index.js b/React/src/index.js
new file mode 100644
index 000000000..dd709837b
--- /dev/null
+++ b/React/src/index.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './App.jsx';
+import { BrowserRouter } from 'react-router-dom';
+
+ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById('root'),
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+//reportWebVitals();
diff --git a/React/src/loading.svg b/React/src/loading.svg
new file mode 100644
index 000000000..77bac3ee5
--- /dev/null
+++ b/React/src/loading.svg
@@ -0,0 +1,60 @@
+
+
\ No newline at end of file
diff --git a/React/src/logo.svg b/React/src/logo.svg
new file mode 100644
index 000000000..9dfc1c058
--- /dev/null
+++ b/React/src/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/React/src/reportWebVitals.js b/React/src/reportWebVitals.js
new file mode 100644
index 000000000..5253d3ad9
--- /dev/null
+++ b/React/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/React/src/setupTests.js b/React/src/setupTests.js
new file mode 100644
index 000000000..8f2609b7b
--- /dev/null
+++ b/React/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';
diff --git a/React/src/styledComponent.js b/React/src/styledComponent.js
new file mode 100644
index 000000000..9885ce375
--- /dev/null
+++ b/React/src/styledComponent.js
@@ -0,0 +1,250 @@
+import styled from 'styled-components';
+
+export const MediaDiv = styled.div`
+ margin: 0px auto;
+ min-height: 100vh;
+ width: 768px;
+ color: ${(props) => props.theme.fontColor};
+ background-color: ${(props) => props.theme.bgColor};
+ @media screen and (max-width: 768px) {
+ width: 100%;
+ }
+`;
+export const HeaderDiv = styled.div`
+ width: 768px;
+ height: auto;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ font-family: 'Black Han Sans', sans-serif;
+ position: absolute;
+ @media screen and (max-width: 768px) {
+ width: 100%;
+ }
+`;
+
+export const TitleLogoDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin: 15px;
+ line-height: 1;
+`;
+
+export const TitleBig = styled.span`
+ font-size: 40px;
+`;
+
+export const TitleSmall = styled.span`
+ font-size: 20px;
+`;
+
+export const SubHeaderDiv = styled.div`
+ margin: 15px;
+ font-size: 25px;
+ display: flex;
+ flex-direction: row;
+ cursor: pointer;
+`;
+
+export const Main = styled.div`
+ padding-top: 90px;
+`;
+
+export const SlogunSection = styled.div`
+ margin-top: 10px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+`;
+
+export const SlogunBig = styled.span`
+ font-size: 40px;
+ font-family: 'League Gothic', sans-serif;
+`;
+
+export const SlogunSmall = styled.span`
+ margin: 3px;
+ font-weight: bold;
+ color: #f39926;
+`;
+export const PostSection = styled.div`
+ margin: 0px auto;
+ margin-top: 20px;
+ width: 90%;
+ display: flex;
+ flex-direction: column;
+`;
+export const PostTitleDiv = styled.div`
+ border-top-left-radius: 30px;
+ border-top-right-radius: 30px;
+ color: white;
+ background-color: #f39926;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ align-items: center;
+`;
+export const PostTitle = styled.span`
+ margin-top: 10px;
+ margin-bottom: 5px;
+ font-family: 'Black Han Sans', sans-serif;
+ font-size: 25px;
+`;
+
+export const PostListDiv = styled.div`
+ font-size: 16px;
+ font-family: 'Noto Sans KR', sans-serif;
+`;
+
+export const LoadingDiv = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ margin-top: 15px;
+`;
+
+export const LoadingImg = styled.img`
+ width: 30px;
+`;
+
+export const EachPostLi = styled.li`
+ margin: 14px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ cursor: pointer;
+`;
+
+export const PostLink = styled.span`
+ margin-left: 5px;
+`;
+export const PostRepl = styled.div`
+ font-family: 'Courier New', Courier, monospace;
+`;
+export const PagingSection = styled.section`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ margin: 0px auto;
+ width: 150px;
+ margin-top: 20px;
+`;
+export const PagenumberDiv = styled.div`
+ width: 30px;
+ height: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid #f39926;
+ border-radius: 5px;
+`;
+export const Footerdiv = styled.div`
+ margin-top: 30px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+`;
+export const FooterBig = styled.span`
+ margin: 5px;
+ font-size: 12px;
+`;
+export const FooterSmall = styled.span`
+ margin: 5px;
+ font-size: 5px;
+`;
+
+export const PostReadDiv = styled.div`
+ margin-top: 15px;
+ margin-bottom: 15px;
+ width: 100%;
+ line-height: 24px;
+`;
+export const ReplTitleDiv = styled.div`
+ font-size: 12px;
+ margin-top: 20px;
+ margin-bottom: 10px;
+`;
+export const PostReplDiv = styled.div`
+ border-top: 2px solid white;
+ margin-bottom: 15px;
+ width: 100%;
+ line-height: 24px;
+`;
+export const Replwriter = styled.div`
+ margin-top: 10px;
+ margin-left: 10px;
+ font-weight: bolder;
+`;
+export const Repl = styled.span`
+ font-size: 12px;
+`;
+export const WritereplDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+`;
+export const ReplInput = styled.textarea`
+ margin-top: 10px;
+ width: 100%;
+ padding: 10px;
+ height: 30px;
+ border-radius: 5px;
+ font-family: 'Noto Sans KR', sans-serif;
+`;
+export const ReplSubmitDiv = styled.div`
+ width: 100px;
+ margin-top: 10px;
+ margin-left: 10px;
+ border-radius: 5px;
+ background-color: #f39926;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ align-items: center;
+ cursor: pointer;
+`;
+export const CursorDiv = styled.div`
+ cursor: pointer;
+`;
+
+export const PostWriteDiv = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+`;
+export const TitleInput = styled.input`
+ margin-top: 10px;
+ padding: 10px;
+ border-radius: 5px;
+`;
+export const ContentsInput = styled.textarea`
+ margin-top: 10px;
+ padding: 10px;
+ height: 400px;
+ border-radius: 5px;
+ font-family: 'Noto Sans KR', sans-serif;
+`;
+export const PostSubmitDiv = styled.div`
+ width: 50%;
+ margin: 0px auto;
+ margin-top: 15px;
+ border-radius: 10px;
+ background-color: #f39926;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ align-items: center;
+ cursor: pointer;
+`;
+export const PostSubmit = styled.div`
+ margin-top: 10px;
+ margin-bottom: 5px;
+ font-family: 'Black Han Sans', sans-serif;
+ font-size: 20px;
+ color: white;
+`;
\ No newline at end of file
diff --git a/React/src/styles.js b/React/src/styles.js
new file mode 100644
index 000000000..28c367871
--- /dev/null
+++ b/React/src/styles.js
@@ -0,0 +1,18 @@
+import { createGlobalStyle } from 'styled-components';
+
+//yarn add styled-reset
+import reset from 'styled-reset';
+
+export const lightTheme = {
+ fontColor: '#2c2c2c',
+ bgColor: 'white',
+};
+
+export const darkTheme = {
+ fontColor: 'white',
+ bgColor: '#2c2c2c',
+};
+
+export const GlobalStyles = createGlobalStyle`
+ ${reset}
+`;
diff --git a/reactRouter/src/App.css b/reactRouter/src/App.css
new file mode 100644
index 000000000..74b5e0534
--- /dev/null
+++ b/reactRouter/src/App.css
@@ -0,0 +1,38 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/reactRouter/src/App.js b/reactRouter/src/App.js
new file mode 100644
index 000000000..5e1248f4d
--- /dev/null
+++ b/reactRouter/src/App.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import { Routes, Route } from 'react-router-dom';
+import Home from "./pages/Home";
+import Movies from "./pages/Movies";
+import Menubar from './pages/Menubar';
+import Movie from './pages/Movie';
+
+const App = () => {
+ return (
+
+ }>
+ } />
+ }>
+ } />
+
+ There's nothing here!} />
+
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/reactRouter/src/App.test.js b/reactRouter/src/App.test.js
new file mode 100644
index 000000000..1f03afeec
--- /dev/null
+++ b/reactRouter/src/App.test.js
@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+ render();
+ const linkElement = screen.getByText(/learn react/i);
+ expect(linkElement).toBeInTheDocument();
+});
diff --git a/reactRouter/src/index.css b/reactRouter/src/index.css
new file mode 100644
index 000000000..ec2585e8c
--- /dev/null
+++ b/reactRouter/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/reactRouter/src/index.js b/reactRouter/src/index.js
new file mode 100644
index 000000000..7a6abde66
--- /dev/null
+++ b/reactRouter/src/index.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
+import { BrowserRouter } from 'react-router-dom';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+
+
+
+
+
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/reactRouter/src/logo.svg b/reactRouter/src/logo.svg
new file mode 100644
index 000000000..9dfc1c058
--- /dev/null
+++ b/reactRouter/src/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/reactRouter/src/movie_data.js b/reactRouter/src/movie_data.js
new file mode 100644
index 000000000..d34da397a
--- /dev/null
+++ b/reactRouter/src/movie_data.js
@@ -0,0 +1,75 @@
+let movies = [
+ {
+ id: 1,
+ title: "하울의 움직이는 성",
+ director: "미야자키 하야오",
+ category: "일본 애니메이션",
+ detail:
+ "아버지가 물려준 모자 기계를 지키는 수수한 소녀 소피. 전쟁도, 미녀의 심장을 노리는 마법사의 소문도 먼 세상 이야기일 뿐. 하지만 마녀의 저주로 할머니가 되면서, 소피의 인생이 회전목마처럼 힘차게 움직이기 시작한다.",
+ },
+ {
+ id: 2,
+ title: "보스 베이비2",
+ director: "톰 맥그라스",
+ category: "미국 애니메이션",
+ detail:
+ "각자의 삶을 살아가던 두 형제, 테드와 팀. 가족만 아는 비밀을 안고 이번엔 팀의 딸 티나와 힘을 합친다. 비열한 악당의 음모를 무너뜨리기 위해.",
+ },
+ {
+ id: 3,
+ title: "너의 이름은",
+ director: "신카이 마코토",
+ category: "일본 애니메이션",
+ detail:
+ "도쿄의 잘생긴 남자로 살아볼 순 없을까? 따분한 시골 생활에 질려 도시를 동경하는 여고생. 어느 날, 그 소원이 실제로 이루어진다. 도쿄의 남고생과 이따금 몸이 뒤바뀌는 것. 꿈결 같은 둘의 인연은 또 다른 운명을 부르기 시작한다.",
+ },
+ {
+ id: 4,
+ title: "아이 필 프리티",
+ director: "마크 실버스틴",
+ category: "미국 영화",
+ detail:
+ "난 왜 예쁘지 않은 걸까. 외모가 불만인 그녀, 스피닝 수업에서 아찔한 사고를 당한다. 깨어나보니 확 뒤집힌 그녀의 인생! 커리어도, 연애도 이젠 핑크빛이다!",
+ },
+ {
+ id: 5,
+ title: "트루먼쇼",
+ director: "피터 위어",
+ category: "미국 영화",
+ detail:
+ "한 사람의 일거수일투족이 24시간 생방송 되는 '트루먼 쇼'의 주인공 트루먼 버뱅크. 엄청난 인기를 끌고 있는 이 프로그램의 주인공인 자신만 이 사실을 전혀 모르고 있다. 모든 것이 방송이라는 것이 밝혀지면 그는 과연 어떤 선택을 할까.",
+ },
+ {
+ id: 6,
+ title: "아메리칸셰프",
+ director: "존 패브로",
+ category: "미국 영화",
+ detail:
+ "창의력이 지글지글 끓어오르는 셰프. 똑같은 메뉴만 고집하는 주인과 지지고 볶은 후 허름한 푸드트럭을 차리면서 맛깔나는 좌충우돌 여정에 오른다. 배고플 땐 보지 말 것!",
+ },
+ {
+ id: 7,
+ title: "인턴",
+ director: "낸시 마이어스",
+ category: "미국 영화",
+ detail:
+ "뜨거운 열정으로 단기간에 회사를 키워낸 30대 열혈 여성 CEO. 사별과 은퇴를 겪고 공허한 일상을 보내다가 새내기로 입사한 70세 남성 인턴. 문제없을까, 이 어색한 조합.",
+ },
+ {
+ id: 8,
+ title: "월요일이 사라졌다",
+ director: "토미 비르콜리",
+ category: "미국 영화",
+ detail:
+ "우리는 하나다! 엄격한 산아제한 정책을 시행하는 미래. 정부의 감시를 피해, 일곱 쌍둥이가 한 사람처럼 산다. 들킨 걸까? 사라진 자매 하나를 찾아, 여섯이 힘을 합친다.",
+ },
+ ];
+
+ //movies 전체 데이터 조회
+ export function getMovies() {
+ return movies;
+ }
+
+ export function getMovie(id) {
+ return movies.find((movie) => movie.id === id);
+ }
\ No newline at end of file
diff --git a/reactRouter/src/pages/Home.js b/reactRouter/src/pages/Home.js
new file mode 100644
index 000000000..c814b770e
--- /dev/null
+++ b/reactRouter/src/pages/Home.js
@@ -0,0 +1,12 @@
+import React from 'react';
+
+const Home = () => {
+ return (
+
+
LIKELION React Course
+
리액트 라우터 실습 프로젝트입니다.
+
+ );
+};
+
+export default Home;
\ No newline at end of file
diff --git a/reactRouter/src/pages/Menubar.js b/reactRouter/src/pages/Menubar.js
new file mode 100644
index 000000000..4edf19bee
--- /dev/null
+++ b/reactRouter/src/pages/Menubar.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import {Link, Outlet, useNavigate} from "react-router-dom";
+
+const Menubar = () => {
+ const navigate = useNavigate();
+ const goHome = () => {
+ navigate("/");
+ };
+ return (
+
+
+ -
+ Home
+
+ -
+ Movies
+
+
+
+
+
+
+
+ );
+};
+
+export default Menubar;
\ No newline at end of file
diff --git a/reactRouter/src/pages/Movie.js b/reactRouter/src/pages/Movie.js
new file mode 100644
index 000000000..051be22a6
--- /dev/null
+++ b/reactRouter/src/pages/Movie.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import { useParams, useLocation, useSearchParams } from "react-router-dom";
+import { getMovie } from "../movie_data";
+
+const Movie = () => {
+ //url 파라미터 사용하기
+ const params = useParams();
+ //console.log(params);
+ const movie = getMovie(parseInt(params.movieId));
+ //console.log(movie);
+
+ //쿼리스트링 사용하기
+ const location = useLocation();
+ //console.log(location);
+
+ const [searchParams, setSearchParams] = useSearchParams();
+ const detail = searchParams.get("detail");
+
+ const handleClick = () => {
+ setSearchParams({ detail: detail === "true" ? false : true });
+ console.log(detail);
+ };
+
+ return (
+
+
{movie.title}
+
감독 : {movie.director}
+
카테고리 : {movie.category}
+
+ {detail === "true" ?
{movie.detail}
: " "}
+
+ );
+};
+
+export default Movie;
\ No newline at end of file
diff --git a/reactRouter/src/pages/Movies.js b/reactRouter/src/pages/Movies.js
new file mode 100644
index 000000000..a9f1947f4
--- /dev/null
+++ b/reactRouter/src/pages/Movies.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { getMovies } from "../movie_data";
+import { NavLink, Link, Outlet } from "react-router-dom";
+
+const Movies = () => {
+ const movies = getMovies();
+
+ return (
+
+
넷플릭스 영화 추천 목록
+
+ {movies.map((movie) => (
+
{
+ return {
+ textDecoration : isActive ? "underline" : "",
+ color : isActive ? "#FF9E1B" : "",
+ };
+ }}
+ >
+ {movie.title}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default Movies;
\ No newline at end of file
diff --git a/reactRouter/src/reportWebVitals.js b/reactRouter/src/reportWebVitals.js
new file mode 100644
index 000000000..5253d3ad9
--- /dev/null
+++ b/reactRouter/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/reactRouter/src/setupTests.js b/reactRouter/src/setupTests.js
new file mode 100644
index 000000000..8f2609b7b
--- /dev/null
+++ b/reactRouter/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';