diff --git a/.gitignore b/.gitignore index 4d29575..fec43d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ + # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies diff --git a/package-lock.json b/package-lock.json index c491ee3..51b7ba9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7021,6 +7021,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -9887,6 +9900,15 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, "mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -12310,6 +12332,52 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.0.tgz", @@ -12765,6 +12833,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -14537,6 +14610,16 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -14977,6 +15060,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 16bb883..9930df3 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^12.2.0", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-router-dom": "^5.2.0", "react-scripts": "4.0.0", "styled-components": "^5.2.1", "web-vitals": "^0.2.4" diff --git a/public/ic_chat.png b/public/ic_chat.png new file mode 100644 index 0000000..5bb12ed Binary files /dev/null and b/public/ic_chat.png differ diff --git a/public/ic_person.png b/public/ic_person.png new file mode 100644 index 0000000..ce855e6 Binary files /dev/null and b/public/ic_person.png differ diff --git a/public/ic_setting.png b/public/ic_setting.png new file mode 100644 index 0000000..2a7b3be Binary files /dev/null and b/public/ic_setting.png differ diff --git a/public/index.html b/public/index.html index 6cd9cb5..0e4d6db 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,44 @@ - - - - - - - - - - + + - 카카오톡 따라하기 - - - -
- + 카카오톡 따라하기 + + + +
+ - + To begin the development, run `npm start` or `yarn start`. + To create a production bundle, use `npm run build` or `yarn build`. +--> + diff --git a/src/App.js b/src/App.js index c75b73d..1dcd0a8 100644 --- a/src/App.js +++ b/src/App.js @@ -1,8 +1,11 @@ -import { useState } from 'react'; import styled, { createGlobalStyle } from 'styled-components'; -import Header from './Header'; -import ChatContainer from './ChatContainer'; -import ChatInput from './ChatInput'; +import ChatList from './ChatList/index'; +import User from './User/index'; +import Setting from './Setting/index'; +import Nav from './Nav'; +import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; +import { sampleChatList, sampleUserList } from './SampleData'; +import { useState } from 'react'; const StyledGlobal = createGlobalStyle` body { @@ -13,103 +16,38 @@ const StyledGlobal = createGlobalStyle` const StyledContainer = styled.div` display: flex; - flex-direction: column; - height: auto; - min-height: 100%; - - background: #9bbbd4; + flex-direction: row; `; -const userList = [ - { - id: 0, - img: `${process.env.PUBLIC_URL}/muji.jpg`, - name: '무지', - status: '현재 접속 중', - }, - { - id: 1, - img: `${process.env.PUBLIC_URL}/corn.png`, - name: '콘', - status: '현재 접속 중', - }, -]; - -const sampleChat = [ - { - user: userList[0], - chatList: ['안녕하세요'], - }, - { - user: userList[1], - chatList: [ - '안녕하세요', - '저도 반갑습니다', - `저는 ${userList[1].name}입니다.`, - ], - }, - { - user: userList[0], - chatList: ['안녕하세요', '반갑습니다', `저는 ${userList[0].name}입니다.`], - }, - { - user: userList[1], - chatList: [ - '안녕하세요', - '저도 반갑습니다', - `저는 ${userList[1].name}입니다.`, - ], - }, - { - user: userList[0], - chatList: [ - '안녕하세요', - '반갑습니다', - `테스트 입력입니다테스트 입력입니다테스트 입력입니다테스트 입력입니다`, - ], - }, - { - user: userList[1], - chatList: [ - '안녕하세요', - '저도 반갑습니다', - `저는 ${userList[1].name}입니다.`, - ], - }, -]; - export default () => { - const [currentUser, setCurrentUser] = useState(userList[0]); - const [ownerUser, setOwnerUser] = useState(userList[0]); - const [chatData, setChatData] = useState(sampleChat); - - const sendMessage = (message) => { - if (message === '') { - alert('빈 텍스트 입니다'); - return; - } - - const lastChatItem = chatData[chatData.length - 1]; - if (lastChatItem.user.id === currentUser.id) { - lastChatItem.chatList.push(message); - setChatData([...chatData]); - } else { - const nextChatItem = { user: currentUser, chatList: [message] }; - const newChatData = [...chatData, nextChatItem]; - setChatData(newChatData); - } - }; - - const toggleCurrentUser = () => { - setCurrentUser(currentUser.id === 0 ? userList[1] : userList[0]); - }; + const [chatList, setChatList] = useState(sampleChatList); + const [userList, setUserList] = useState(sampleUserList); return ( - + -
- - - + + + + + + + + } + /> + } + /> + + + + ); }; diff --git a/src/ChatContainer.js b/src/Chat/ChatContainer.js similarity index 91% rename from src/ChatContainer.js rename to src/Chat/ChatContainer.js index 1a15ba6..2a85357 100644 --- a/src/ChatContainer.js +++ b/src/Chat/ChatContainer.js @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { Fragment, useEffect, useRef } from 'react'; import styled from 'styled-components'; import ChatItemContainer from './ChatItemsContainer'; import * as ReactDOM from 'react-dom'; @@ -17,7 +17,7 @@ export default (props) => { const list = props.chatData.map((item, index) => { return ( { + const { id } = useParams(); + + const data = datas[id]; + + const [currentUser, setCurrentUser] = useState(data.userList[0]); + const [ownerUser, setOwnerUser] = useState(data.userList[0]); + const [chatData, setChatData] = useState(data.chatList); + + const sendMessage = (message) => { + if (message === '') { + alert('빈 텍스트 입니다'); + return; + } + + const lastChatItem = chatData[chatData.length - 1]; + if (lastChatItem.user.id === currentUser.id) { + lastChatItem.chat.push(message); + setChatData([...chatData]); + } else { + const nextChatItem = { user: currentUser, chat: [message] }; + const newChatData = [...chatData, nextChatItem]; + setChatData(newChatData); + } + data.chatList = chatData; + }; + + const toggleCurrentUser = () => { + setCurrentUser( + currentUser.id === data.userList[0].id + ? data.userList[1] + : data.userList[0] + ); + }; + + return ( + +
+ + + + ); +}; diff --git a/src/ChatList/ChatListItem.js b/src/ChatList/ChatListItem.js new file mode 100644 index 0000000..177a4af --- /dev/null +++ b/src/ChatList/ChatListItem.js @@ -0,0 +1,36 @@ +import styled from 'styled-components'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: row; + margin: 10px 0; +`; + +const StyledLabelContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const StyledProfileImage = styled.img` + width: 70px; + height: 70px; +`; +const StyledText = styled.p` + margin: 5px 0; +`; + +export default ({ item }) => { + const otherUser = + item.userList[0].id === 0 ? item.userList[1] : item.userList[0]; + const lastChatList = item.chatList[item.chatList.length - 1]; + const lastChatText = lastChatList.chat[lastChatList.chat.length - 1]; + return ( + + + + {otherUser.name} + {lastChatText} + + + ); +}; diff --git a/src/ChatList/index.js b/src/ChatList/index.js new file mode 100644 index 0000000..c024590 --- /dev/null +++ b/src/ChatList/index.js @@ -0,0 +1,95 @@ +import styled from 'styled-components'; +import { Fragment, useEffect, useState } from 'react'; +import { Link, Route, Switch } from 'react-router-dom'; +import Chat from '../Chat/index'; +import ChatListItem from './ChatListItem'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const StyledSearchForm = styled.form` + display: flex; + flex-direction: row; + width: 100%; +`; + +const StyledSearchInput = styled.input.attrs({ + type: 'text', +})` + font-size: 1.5em; + padding: 10px 20px; + flex-grow: 1; +`; + +const List = ({ chatList }) => { + const [searchText, setSearchText] = useState(''); + const [chatItemComponents, setChatItemComponents] = useState([]); + + const myChatList = []; + for (const chatData of chatList) { + for (const userData of chatData.userList) { + if (userData.id === 0) { + myChatList.push(chatData); + break; + } + } + } + + useEffect(() => { + const filteredList = + searchText === '' + ? myChatList + : myChatList.filter((item) => { + if (searchText === '') return true; + for (const userData of item.userList) { + if (userData.name.includes(searchText)) return true; + } + return false; + }); + + setChatItemComponents( + filteredList.map((item, index) => { + return ( + + + + ); + }) + ); + }, [searchText]); + + const onSearchTextChanged = (e) => { + console.log(`_${e.target.value}_`); + setSearchText(e.target.value); + }; + + return ( + + + + + {chatItemComponents} + + ); +}; + +export default ({ chatList }) => { + return ( + + + } + /> + } + exact + /> + + + ); +}; diff --git a/src/Nav.js b/src/Nav.js new file mode 100644 index 0000000..ecdfcb9 --- /dev/null +++ b/src/Nav.js @@ -0,0 +1,56 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + height: 100vh; + background: #ececed; +`; + +const StyledList = styled.ul` + padding: 0 20px; +`; + +const StyledListItem = styled.li` + list-style: none; + margin: 10px 0; +`; + +const StyledListItemImage = styled.img` + width: 40px; + height: 40px; + padding: 10px; +`; + +export default () => { + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/SampleData.js b/src/SampleData.js new file mode 100644 index 0000000..8a46eb0 --- /dev/null +++ b/src/SampleData.js @@ -0,0 +1,77 @@ +export const sampleUserList = [ + { + id: 0, + img: `${process.env.PUBLIC_URL}/muji.jpg`, + name: '무지', + status: '현재 접속 중', + }, + { + id: 1, + img: `${process.env.PUBLIC_URL}/corn.png`, + name: '콘', + status: '현재 접속 중', + }, + { + id: 2, + img: `${process.env.PUBLIC_URL}/tube.jpg`, + name: '튜브', + status: '현재 접속 중', + }, + { + id: 3, + img: `${process.env.PUBLIC_URL}/lion.png`, + name: '라이언', + status: '현재 접속 중', + }, + { + id: 4, + img: `${process.env.PUBLIC_URL}/frodo.png`, + name: '프로도', + status: '현재 접속 중', + }, + { + id: 5, + img: `${process.env.PUBLIC_URL}/apeach.png`, + name: '어피치', + status: '현재 접속 중', + }, +]; + +export const sampleChatList = [ + { + userList: [sampleUserList[0], sampleUserList[1]], + chatList: [ + { + user: sampleUserList[0], + chat: ['안녕하세요', '열심히 하겠습니다.'], + }, + ], + }, + { + userList: [sampleUserList[0], sampleUserList[2]], + chatList: [ + { + user: sampleUserList[0], + chat: ['안녕하세요', '열심히 하겠습니다.'], + }, + ], + }, + { + userList: [sampleUserList[0], sampleUserList[3]], + chatList: [ + { + user: sampleUserList[0], + chat: ['안녕하세요', '열심히 하겠습니다.'], + }, + ], + }, + { + userList: [sampleUserList[0], sampleUserList[3]], + chatList: [ + { + user: sampleUserList[0], + chat: ['안녕하세요', '열심히 하겠습니다.'], + }, + ], + }, +]; diff --git a/src/Setting/index.js b/src/Setting/index.js new file mode 100644 index 0000000..ceb0841 --- /dev/null +++ b/src/Setting/index.js @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +export default () => { + return

Setting

; +}; diff --git a/src/User/UserListItem.js b/src/User/UserListItem.js new file mode 100644 index 0000000..f7c6d07 --- /dev/null +++ b/src/User/UserListItem.js @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: row; + margin: 10px 0; +`; + +const StyledLabelContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const StyledProfileImage = styled.img` + width: 70px; + height: 70px; +`; +const StyledText = styled.p` + margin: 5px 0; +`; + +export default ({ user }) => { + return ( + + + + {user.name} + + + ); +}; diff --git a/src/User/index.js b/src/User/index.js new file mode 100644 index 0000000..99b0b5e --- /dev/null +++ b/src/User/index.js @@ -0,0 +1,66 @@ +import styled from 'styled-components'; +import { Fragment, useEffect, useState } from 'react'; +import UserListItem from './UserListItem'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const StyledSearchForm = styled.form` + display: flex; + flex-direction: row; + width: 100%; +`; + +const StyledSearchInput = styled.input.attrs({ + type: 'text', +})` + font-size: 1.5em; + padding: 10px 20px; + flex-grow: 1; +`; + +export default ({ userList }) => { + const [searchText, setSearchText] = useState(''); + const [componentList, setComponentList] = useState([]); + + const friendList = []; + for (const userData of userList) { + if (userData.id !== 0) { + friendList.push(userData); + } + } + + useEffect(() => { + const filteredList = + searchText === '' + ? friendList + : friendList.filter((item) => { + if (searchText === '') return true; + if (item.name.includes(searchText)) return true; + + return false; + }); + + setComponentList( + filteredList.map((item, index) => { + return ; + }) + ); + }, [searchText]); + + const onSearchTextChanged = (e) => { + setSearchText(e.target.value); + }; + + return ( + + + + + {componentList} + + ); +}; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..48f5326 --- /dev/null +++ b/src/index.css @@ -0,0 +1,4 @@ +a { + text-decoration: none; + color: black; +} diff --git a/src/index.js b/src/index.js index c1f31c5..d2cff4a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; +import './index.css'; ReactDOM.render(