diff --git a/src/App.jsx b/src/App.jsx index 14a7f684d..48a190c1f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,17 +1,44 @@ import './App.css'; +import { useState } from 'react'; +import messagesData from './data/messages.json'; +import ChatLog from './components/ChatLog'; const App = () => { + const [messageList, setMessageList] = useState(messagesData); + + const likeToggleFunc = (messageId) => { + const messages = messageList.map(message => { + if (message.id === messageId) { + return { ...message, liked: !message.liked }; + } else { + return message; + } + }); + + setMessageList(messages); + }; + + const totalLikes = messageList + .filter((message) => message.liked) + .length; + return (
-

Application title

+

Chat between Vladimir and Estragon

+
+ {totalLikes} ❤️s +
- {/* Wave 01: Render one ChatEntry component - Wave 02: Render ChatLog component */} +
); }; -export default App; +export default App; \ No newline at end of file diff --git a/src/components/ChatEntry.css b/src/components/ChatEntry.css index 05c3baa44..614d336e5 100644 --- a/src/components/ChatEntry.css +++ b/src/components/ChatEntry.css @@ -1,19 +1,49 @@ button { background: none; - color: inherit; - border: none; - padding: 10px; - font: inherit; - cursor: pointer; - outline: inherit; + color: inherit; + border: none; + padding: 10px; + font: inherit; + cursor: pointer; + outline: inherit; } .chat-entry { + display: flex; /* Flex container for horizontal alignment */ margin: 1rem; + flex-direction: column; /* Stack name on top of bubble */ } -.chat-entry:last-child { - margin-bottom: 0; +.chat-entry.local { + justify-content: flex-start; /* Align entire entry left */ + align-items: flex-start; /* Align items to the start horizontally */ +} + +.chat-entry.remote { + justify-content: flex-end; /* Align entire entry right */ + align-items: flex-end; /* Align items to the end horizontally */ +} + +/* Sender name styling with full width and alignment */ +.chat-entry .entry-name { + font-size: medium; + margin-bottom: 0.5rem; + width: 100%; +} + +.chat-entry.local .entry-name { + text-align: left; +} + +.chat-entry.remote .entry-name { + text-align: right; +} + +/* Remove any margin on bubbles for flex alignment */ +.chat-entry.local .entry-bubble, +.chat-entry.remote .entry-bubble { + margin-left: 0; + margin-right: 0; } .chat-entry .entry-bubble { @@ -30,11 +60,6 @@ button { background-color: #fefea2; } -.chat-entry .entry-name { - font-size: medium; - margin-bottom: 0.5rem; -} - .chat-entry .entry-time { color: #bbb; font-size: x-small; @@ -48,18 +73,17 @@ button { height: 22px; width: 44px; clip-path: polygon(100% 0, 0 0, 50% 100%); - position: absolute; top: 0; } -/* "local" messages are shown on the left side */ -.chat-entry.local { - text-align: left; +/* "local" message bubble arrow and hover */ +.chat-entry.local .entry-bubble { + background-color: #ffffe0; } -.chat-entry.local .entry-time { - text-align: right; +.chat-entry.local .entry-bubble:hover { + background-color: #fefea2; } .chat-entry.local .entry-bubble::before { @@ -71,25 +95,15 @@ button { background-color: #fefea2; } -/* "remote" messages are shown on the right side, in blue */ -.chat-entry.remote { - text-align: right; -} - +/* "remote" messages shown on the right side in blue */ .chat-entry.remote .entry-bubble { background-color: #e0ffff; - margin-left: auto; - margin-right: 0; } .chat-entry.remote .entry-bubble:hover { background-color: #a9f6f6; } -.chat-entry.remote .entry-time { - text-align: left; -} - .chat-entry.remote .entry-bubble::before { background-color: #e0ffff; right: -18px; @@ -97,4 +111,4 @@ button { .chat-entry.remote .entry-bubble:hover::before { background-color: #a9f6f6; -} \ No newline at end of file +} diff --git a/src/components/ChatEntry.jsx b/src/components/ChatEntry.jsx index 15c56f96b..c42d654ce 100644 --- a/src/components/ChatEntry.jsx +++ b/src/components/ChatEntry.jsx @@ -1,20 +1,35 @@ +import PropTypes from 'prop-types'; import './ChatEntry.css'; +import TimeStamp from './TimeStamp'; + +const ChatEntry = (props) => { + const likeButtonClicked = () => { + props.likeToggleFunc(props.id); + }; -const ChatEntry = () => { return ( -
-

Replace with name of sender

+
+

{props.sender}

-

Replace with body of ChatEntry

-

Replace with TimeStamp component

- +

{props.body}

+

Time Sent:

+
); }; ChatEntry.propTypes = { - // Fill with correct proptypes + id: PropTypes.number.isRequired, + sender: PropTypes.string.isRequired, + senderType: PropTypes.oneOf(['local', 'remote']).isRequired, + body: PropTypes.string.isRequired, + timeStamp: PropTypes.string.isRequired, + liked: PropTypes.bool.isRequired, + likeToggleFunc: PropTypes.func.isRequired, }; export default ChatEntry; + diff --git a/src/components/ChatLog.css b/src/components/ChatLog.css index 378848d1f..d6f4301b6 100644 --- a/src/components/ChatLog.css +++ b/src/components/ChatLog.css @@ -2,3 +2,6 @@ margin: auto; max-width: 50rem; } +.chat-log ul { + list-style: none; +} \ No newline at end of file diff --git a/src/components/ChatLog.jsx b/src/components/ChatLog.jsx new file mode 100644 index 000000000..cad9965d4 --- /dev/null +++ b/src/components/ChatLog.jsx @@ -0,0 +1,46 @@ +import ChatEntry from './ChatEntry'; +import './ChatLog.css'; +import PropTypes from 'prop-types'; + +const ChatLog = (props) => { + const messageComponents = props.entries.map((message) => { + const senderType = message.sender === props.localSender ? 'local' : 'remote'; + + return ( +
  • + +
  • + ); + }); + + return ( +
    + +
    + ); +}; + +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, + likeToggleFunc: PropTypes.func.isRequired, + localSender: PropTypes.string.isRequired, + +}; + +export default ChatLog;