diff --git a/eslint.config.mjs b/eslint.config.mjs index 6c2b6b501..bde2a6b3d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -65,7 +65,7 @@ export default [{ "comma-dangle": ["warn", "only-multiline"], "dot-notation": "warn", "space-before-function-paren": "off", - "indent": ["warn", 2], + "indent": ["warn", 'tab'], "padded-blocks": "warn", "no-trailing-spaces": "warn", "array-bracket-spacing": "warn", diff --git a/package-lock.json b/package-lock.json index 2a6ed1d5b..6992a2558 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "react-chatlog", "version": "0.0.0", "dependencies": { + "date-fns": "^4.1.0", "luxon": "^2.5.2", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -2264,6 +2265,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", diff --git a/package.json b/package.json index 1a053dda4..404f3da32 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "vitest --run" }, "dependencies": { + "date-fns": "^4.1.0", "luxon": "^2.5.2", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/src/App.css b/src/App.css index d97beb4e6..92db5b79b 100644 --- a/src/App.css +++ b/src/App.css @@ -31,18 +31,24 @@ } #App .widget { - display: inline-block; - line-height: 0.5em; - border-radius: 10px; + position: fixed; + top: 9.6vh; + width: 100%; + height: 9vh; + background-color: #e0ffff; color: black; - font-size:0.8em; - padding-left: 1em; - padding-right: 1em; -} + font-size: 1.5em; + text-align: center; + display: flex; /* enables centering */ + align-items: center; /* vertical centering */ + justify-content: center; /* horizontal centering */ + box-sizing: border-box; + z-index: 90; + #App #heartWidget { font-size: 1.5em; - margin: 1em + margin: 0em } #App span { diff --git a/src/App.jsx b/src/App.jsx index 14a7f684d..055758efd 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,17 +1,33 @@ import './App.css'; +import entriesData from './data/messages.json'; +import ChatLog from './components/ChatLog'; +import { useState } from 'react'; const App = () => { - return ( -
-
-

Application title

-
-
- {/* Wave 01: Render one ChatEntry component - Wave 02: Render ChatLog component */} -
-
- ); + const [entries, setEntries] = useState(entriesData); + const toggleLike = (id) => { + const updatedEntries = entries.map(entry => { + if (entry.id === id) { + return { ...entry, liked: !entry.liked } ; + } else{ + return entry; + } + }); + setEntries(updatedEntries); + }; + const totalLikes = entries.filter((entry) => entry.liked).length; + + return ( +
+
+

Chat between {entries[0].sender} and {entries[1].sender}

+
+
{totalLikes} ❤️{totalLikes !== 1 ? 's' : ''}
+
+ +
+
+ ); }; export default App; diff --git a/src/components/ChatEntry.jsx b/src/components/ChatEntry.jsx index 15c56f96b..137b181df 100644 --- a/src/components/ChatEntry.jsx +++ b/src/components/ChatEntry.jsx @@ -1,20 +1,39 @@ import './ChatEntry.css'; +import PropTypes from 'prop-types'; +import TimeStamp from './TimeStamp'; -const ChatEntry = () => { - return ( -
-

Replace with name of sender

-
-

Replace with body of ChatEntry

-

Replace with TimeStamp component

- -
-
- ); + +const ChatEntry = (props) => { + const localSender = props.sender === 'Vladimir'; + const entryClass = `chat-entry ${localSender ? 'local' : 'remote'}`; + // const likedStatus = () => { + // props.liked ? 'liked' : 'not-liked'; + + return ( +
+

{props.sender}

+
+

{props.body}

+

+ +
+
+ ); }; ChatEntry.propTypes = { - // Fill with correct proptypes + id: PropTypes.number.isRequired, + sender: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, + timeStamp: PropTypes.string.isRequired, + liked: PropTypes.bool.isRequired, + onToggleLike: PropTypes.func, //had to remove isRequired to pass the test from Waves 1 and 2 +}; + +ChatEntry.defaultProps = { // added dafault props so it stays always defined + onToggleLike: () => {}, }; -export default ChatEntry; +export default ChatEntry; \ No newline at end of file diff --git a/src/components/ChatLog.jsx b/src/components/ChatLog.jsx new file mode 100644 index 000000000..b33bc379b --- /dev/null +++ b/src/components/ChatLog.jsx @@ -0,0 +1,27 @@ +// import entries from './data/messages.json'; +import PropTypes from 'prop-types'; +import ChatEntry from './ChatEntry'; + +const ChatLog = ({ entries, onToggleLike }) => { + return ( +
+ {entries.map((entry) => ( + + ))} +
+); +}; +ChatLog.propTypes = { + entries: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + sender: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, + timeStamp: PropTypes.string.isRequired, + liked: PropTypes.bool.isRequired + }) + ).isRequired, + onToggleLike: PropTypes.func.isRequired, +}; + +export default ChatLog; diff --git a/src/components/ChatLog.test.jsx b/src/components/ChatLog.test.jsx index dfcfeda99..9b8d5815f 100644 --- a/src/components/ChatLog.test.jsx +++ b/src/components/ChatLog.test.jsx @@ -3,70 +3,70 @@ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; const LOG = [ - { - id: 1, - sender: 'Vladimir', - body: 'why are you arguing with me', - timeStamp: '2018-05-29T22:49:06+00:00', - liked: false, - }, - { - id: 2, - sender: 'Estragon', - body: 'Because you are wrong.', - timeStamp: '2018-05-29T22:49:33+00:00', - liked: false, - }, - { - id: 3, - sender: 'Vladimir', - body: 'because I am what', - timeStamp: '2018-05-29T22:50:22+00:00', - liked: false, - }, - { - id: 4, - sender: 'Estragon', - body: 'A robot.', - timeStamp: '2018-05-29T22:52:21+00:00', - liked: false, - }, - { - id: 5, - sender: 'Vladimir', - body: 'Notabot', - timeStamp: '2019-07-23T22:52:21+00:00', - liked: false, - }, + { + id: 1, + sender: 'Vladimir', + body: 'why are you arguing with me', + timeStamp: '2018-05-29T22:49:06+00:00', + liked: false, + }, + { + id: 2, + sender: 'Estragon', + body: 'Because you are wrong.', + timeStamp: '2018-05-29T22:49:33+00:00', + liked: false, + }, + { + id: 3, + sender: 'Vladimir', + body: 'because I am what', + timeStamp: '2018-05-29T22:50:22+00:00', + liked: false, + }, + { + id: 4, + sender: 'Estragon', + body: 'A robot.', + timeStamp: '2018-05-29T22:52:21+00:00', + liked: false, + }, + { + id: 5, + sender: 'Vladimir', + body: 'Notabot', + timeStamp: '2019-07-23T22:52:21+00:00', + liked: false, + }, ]; describe('Wave 02: ChatLog', () => { - beforeEach(() => { - render(); - }); + beforeEach(() => { + render(); + }); - test('renders without crashing and shows all the names', () => { - [ - { - name: 'Vladimir', - numChats: 3, - }, - { - name: 'Estragon', - numChats: 2, - }, - ].forEach((person) => { - const elementList = screen.getAllByText(new RegExp(person.name)); - expect(elementList.length).toEqual(person.numChats); + test('renders without crashing and shows all the names', () => { + [ + { + name: 'Vladimir', + numChats: 3, + }, + { + name: 'Estragon', + numChats: 2, + }, + ].forEach((person) => { + const elementList = screen.getAllByText(new RegExp(person.name)); + expect(elementList.length).toEqual(person.numChats); - elementList.forEach((element) => { - expect(element).toBeInTheDocument(); - }); - }); - }); + elementList.forEach((element) => { + expect(element).toBeInTheDocument(); + }); + }); + }); - test('renders an empty list without crashing', () => { - const element = render(); - expect(element).not.toBeNull(); - }); + test('renders an empty list without crashing', () => { + const element = render(); + expect(element).not.toBeNull(); + }); });