diff --git a/package.json b/package.json index 94ac60247..6749e64d1 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,11 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "luxon": "^2.4.0", + "moment": "^2.29.3", "react": "^17.0.1", "react-dom": "^17.0.1", - "react-scripts": "4.0.1", - "luxon": "^2.4.0" + "react-scripts": "4.0.1" }, "scripts": { "start": "react-scripts start", diff --git a/src/App.css b/src/App.css index d97beb4e6..697e7a881 100644 --- a/src/App.css +++ b/src/App.css @@ -4,7 +4,7 @@ #App header { background-color: #222; - color: #fff; + /* color: #fff; */ padding-bottom: 0.5rem; position: fixed; width: 100%; @@ -24,6 +24,7 @@ font-size: 1.5em; text-align: center; display: inline-block; + color: #fff; } #App header section { @@ -71,4 +72,10 @@ .purple { color: purple -} \ No newline at end of file +} + +.colorChoice { + display: flex; + justify-content: space-around; +} + diff --git a/src/App.js b/src/App.js index c10859093..e285cc903 100644 --- a/src/App.js +++ b/src/App.js @@ -1,16 +1,58 @@ -import React from 'react'; +import React, { useState } from 'react'; import './App.css'; +import ChatLog from './components/ChatLog'; import chatMessages from './data/messages.json'; +import ColorChoice from './components/ColorChoice'; const App = () => { + const [chatData, setChatData] = useState(chatMessages); + const [localTextColor, setLocalTextColor] = useState('black'); + const [remoteTextColor, setRemoteTextColor] = useState('black'); + + const updateChatData = (updatedEntry) => { + const entries = chatData.map((entry) => { + if (entry.id === updatedEntry.id) { + return updatedEntry; + } else { + return entry; + } + }); + setChatData(entries); + }; + + const totalLikes = chatData.reduce((total, entry) => { + if (entry.liked === true) { + total += 1; + } + return total; + }, 0); + return (
-

Application title

+

+
Chat Log
+
{totalLikes} ❤️s
+

+
+
+

Vladimir's color

+ +
+
+

Estragon's color

+ +
+
+
- {/* Wave 01: Render one ChatEntry component - Wave 02: Render ChatLog component */} +
); diff --git a/src/App.test.js b/src/App.test.js index ca75c71dd..878148902 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,53 +1,53 @@ -import React from 'react' -import App from './App' -import { render, screen, fireEvent } from '@testing-library/react' +import React from 'react'; +import App from './App'; +import { render, screen, fireEvent } from '@testing-library/react'; describe('Wave 03: clicking like button and rendering App', () => { test('that the correct number of likes is printed at the top', () => { // Arrange - const { container } = render() - let buttons = container.querySelectorAll('button.like') + const { container } = render(); + let buttons = container.querySelectorAll('button.like'); // Act - fireEvent.click(buttons[0]) - fireEvent.click(buttons[1]) - fireEvent.click(buttons[10]) + fireEvent.click(buttons[0]); + fireEvent.click(buttons[1]); + fireEvent.click(buttons[10]); // Assert - const countScreen = screen.getByText(/3 ❤️s/) - expect(countScreen).not.toBeNull() - }) + const countScreen = screen.getByText(/3 ❤️s/); + expect(countScreen).not.toBeNull(); + }); test('clicking button toggles heart and does not affect other buttons', () => { // Arrange - const { container } = render() - const buttons = container.querySelectorAll('button.like') - const firstButton = buttons[0] - const lastButton = buttons[buttons.length - 1] + const { container } = render(); + const buttons = container.querySelectorAll('button.like'); + const firstButton = buttons[0]; + const lastButton = buttons[buttons.length - 1]; // Act-Assert // click the first button - fireEvent.click(firstButton) - expect(firstButton.innerHTML).toEqual('❤️') + fireEvent.click(firstButton); + expect(firstButton.innerHTML).toEqual('❤️'); // check that all other buttons haven't changed for (let i = 1; i < buttons.length; i++) { - expect(buttons[i].innerHTML).toEqual('🤍') + expect(buttons[i].innerHTML).toEqual('🤍'); } // click the first button a few more times - fireEvent.click(firstButton) - expect(firstButton.innerHTML).toEqual('🤍') - fireEvent.click(firstButton) - expect(firstButton.innerHTML).toEqual('❤️') - fireEvent.click(firstButton) - expect(firstButton.innerHTML).toEqual('🤍') + fireEvent.click(firstButton); + expect(firstButton.innerHTML).toEqual('🤍'); + fireEvent.click(firstButton); + expect(firstButton.innerHTML).toEqual('❤️'); + fireEvent.click(firstButton); + expect(firstButton.innerHTML).toEqual('🤍'); // click the last button a couple times - fireEvent.click(lastButton) - expect(lastButton.innerHTML).toEqual('❤️') - fireEvent.click(lastButton) - expect(lastButton.innerHTML).toEqual('🤍') - }) -}) + fireEvent.click(lastButton); + expect(lastButton.innerHTML).toEqual('❤️'); + fireEvent.click(lastButton); + expect(lastButton.innerHTML).toEqual('🤍'); + }); +}); diff --git a/src/components/ChatEntry.js b/src/components/ChatEntry.js index b92f0b7b2..2a3b6e051 100644 --- a/src/components/ChatEntry.js +++ b/src/components/ChatEntry.js @@ -1,22 +1,50 @@ import React from 'react'; import './ChatEntry.css'; import PropTypes from 'prop-types'; +import TimeStamp from './TimeStamp'; const ChatEntry = (props) => { + const heartStatus = props.liked ? '❤️' : '🤍'; + let entrySide = + props.sender === 'Vladimir' ? 'chat-entry local' : 'chat-entry remote'; + + let color = + props.sender === 'Vladimir' ? props.localTextColor : props.remoteTextColor; + + const handleLike = () => { + const updatedEntry = { + id: props.id, + sender: props.sender, + body: props.body, + liked: !props.liked, + timeStamp: props.timeStamp, + }; + props.onUpdate(updatedEntry); + }; + return ( -
-

Replace with name of sender

+
+

{props.sender}

-

Replace with body of ChatEntry

-

Replace with TimeStamp component

- +

{props.body}

+

+ +

+
); }; ChatEntry.propTypes = { - //Fill with correct proptypes + id: PropTypes.number.isRequired, + sender: PropTypes.string, + body: PropTypes.string, + timestamp: PropTypes.string, + liked: PropTypes.bool, + onUpdate: PropTypes.func.isRequired, }; export default ChatEntry; diff --git a/src/components/ChatEntry.test.js b/src/components/ChatEntry.test.js index b69270c03..4a9a2b09f 100644 --- a/src/components/ChatEntry.test.js +++ b/src/components/ChatEntry.test.js @@ -1,9 +1,9 @@ -import React from "react"; -import "@testing-library/jest-dom/extend-expect"; -import ChatEntry from "./ChatEntry"; -import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import React from 'react'; +import '@testing-library/jest-dom/extend-expect'; +import ChatEntry from './ChatEntry'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -describe("Wave 01: ChatEntry", () => { +describe('Wave 01: ChatEntry', () => { beforeEach(() => { render( { ); }); - test("renders without crashing and shows the sender", () => { + test('renders without crashing and shows the sender', () => { expect(screen.getByText(/Joe Biden/)).toBeInTheDocument(); }); - test("that it will display the body", () => { + test('that it will display the body', () => { expect(screen.getByText(/Get out by 8am/)).toBeInTheDocument(); }); - test("that it will display the time", () => { + test('that it will display the time', () => { expect(screen.getByText(/\d+ years ago/)).toBeInTheDocument(); }); }); diff --git a/src/components/ChatLog.js b/src/components/ChatLog.js new file mode 100644 index 000000000..99c89ff60 --- /dev/null +++ b/src/components/ChatLog.js @@ -0,0 +1,28 @@ +import ChatEntry from './ChatEntry'; +import PropTypes from 'prop-types'; + +const ChatLog = (props) => { + const chatEntries = props.entries.map((entry) => { + return ( + + ); + }); + return
{chatEntries}
; +}; + +ChatLog.propTypes = { + entries: PropTypes.array.isRequired, + onUpdateChat: PropTypes.func.isRequired, +}; + +export default ChatLog; diff --git a/src/components/ColorChoice.js b/src/components/ColorChoice.js new file mode 100644 index 000000000..a5ee47139 --- /dev/null +++ b/src/components/ColorChoice.js @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const ColorChoice = (props) => { + const setColorCallback = (event) => { + if (props.onUpdateLocal) { + props.onUpdateLocal(event.currentTarget.id); + } else { + props.onUpdateRemote(event.currentTarget.id); + } + }; + + const getButtonsMap = () => { + const colors = { + red: '🔴', + yellow: '🟡', + purple: '🟣', + green: '🟢', + orange: '🟠', + blue: '🔵', + }; + return Object.entries(colors).map(([key, value]) => { + return ( + + ); + }); + }; + return
{getButtonsMap()}
; +}; + +ColorChoice.propTypes = { + onUpdateLocal: PropTypes.func, + onUpdateRemote: PropTypes.func, +}; + +export default ColorChoice; diff --git a/yarn.lock b/yarn.lock index d4bf966c0..c9bc977b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7374,6 +7374,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +moment@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"