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 ( + +
+ + {title} +
+
+ ); +} \ 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';