-
Notifications
You must be signed in to change notification settings - Fork 45
C22 - Phoenix - Kristina Nguyen #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f12bf7a
19a9341
b71053d
8c766cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,41 @@ | ||
import './App.css'; | ||
import CHATS from './data/messages.json'; | ||
import ChatLog from './components/ChatLog'; | ||
import { useState } from 'react'; | ||
import DarkMode from './dark-mode/DarkMode'; | ||
|
||
|
||
const App = () => { | ||
const [chatData, setChatData] = useState(CHATS); | ||
const [theme, setTheme] = useState('App'); | ||
|
||
const handleLikedMessages = (id) => { | ||
setChatData(chatData => chatData.map(chat => { | ||
if (chat.id === id) { | ||
return { ...chat, liked: !chat.liked }; | ||
} else { | ||
return chat; | ||
} | ||
})); | ||
}; | ||
|
||
const toggleDarkMode = (mode) => { | ||
setTheme(mode); | ||
}; | ||
Comment on lines
+22
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we aren't taking any other actions with the |
||
|
||
const likedCount = chatData.filter((chat) => chat.liked).length; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great work calculating the hearts count from the chatsData! Since we don't need the contents of the array we get from // This could be returned from a helper function
// totalLikes is a variable that accumulates a value as we loop over each entry in chatEntries
const likedCount = chatEntries.reduce((totalLikes, currentMessage) => {
// If currentMessage.liked is true add 1 to totalLikes, else add 0
return (totalLikes += currentMessage.liked ? 1 : 0);
}, 0); // The 0 here sets the initial value of totalLikes to 0
Comment on lines
+9
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be a little easier to read and understand code when we co-locate variables rather than placing them in-between functions. One possible re-organization could look like: const [chatData, setChatData] = useState(CHATS);
const [theme, setTheme] = useState('App');
const likedCount = chatData.filter((chat) => chat.liked).length;
const handleLikedMessages = (id) => {
setChatData(chatData => chatData.map(chat => {
if (chat.id === id) {
return { ...chat, liked: !chat.liked };
} else {
return chat;
}
}));
};
const toggleDarkMode = (mode) => {
setTheme(mode);
}; |
||
|
||
return ( | ||
<div id="App"> | ||
<div id={theme}> | ||
<header> | ||
<h1>Application title</h1> | ||
<h1>Chatroom: {chatData[0].sender} and {chatData[1].sender}</h1> | ||
<section id='heartWidget'> | ||
<span className='widget'>{likedCount} ❤️s</span> | ||
<DarkMode currentTheme={theme} toggleTheme={toggleDarkMode}/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love the theme toggle! |
||
</section> | ||
</header> | ||
<main> | ||
{/* Wave 01: Render one ChatEntry component | ||
Wave 02: Render ChatLog component */} | ||
<ChatLog entries={chatData} onLiked={handleLikedMessages} /> | ||
</main> | ||
</div> | ||
); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,20 +1,39 @@ | ||||||
import './ChatEntry.css'; | ||||||
import PropTypes from 'prop-types'; | ||||||
import TimeStamp from './TimeStamp'; | ||||||
|
||||||
const ChatEntry = ({ id, sender, body, timeStamp, liked, onLiked }) => { | ||||||
const onHeartClick = () => { | ||||||
onLiked(id); | ||||||
}; | ||||||
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this pattern of sending just the |
||||||
|
||||||
const filledHeart = liked ? '❤️' : '🤍'; | ||||||
|
||||||
const setMessageBubble = id%2 ? 'local' : 'remote'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
const ChatEntry = () => { | ||||||
return ( | ||||||
<div className="chat-entry local"> | ||||||
<h2 className="entry-name">Replace with name of sender</h2> | ||||||
<section className="entry-bubble"> | ||||||
<p>Replace with body of ChatEntry</p> | ||||||
<p className="entry-time">Replace with TimeStamp component</p> | ||||||
<button className="like">🤍</button> | ||||||
</section> | ||||||
</div> | ||||||
<> | ||||||
<div className={`chat-entry ${setMessageBubble}`}> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great use of a variable and interpolated string to form the |
||||||
<h2 className="entry-name">{sender}</h2> | ||||||
<section className="entry-bubble"> | ||||||
<p>{body}</p> | ||||||
<p className="entry-time"> | ||||||
<TimeStamp time={timeStamp}></TimeStamp> | ||||||
</p> | ||||||
<button className="like" onClick={onHeartClick}>{filledHeart}</button> | ||||||
</section> | ||||||
</div> | ||||||
</> | ||||||
); | ||||||
}; | ||||||
|
||||||
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, | ||||||
onLiked: PropTypes.func.isRequired, | ||||||
Comment on lines
+31
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of |
||||||
}; | ||||||
|
||||||
export default ChatEntry; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import PropTypes from 'prop-types'; | ||
import ChatEntry from './ChatEntry'; | ||
|
||
const ChatLog = ({ entries, onLiked }) => { | ||
const chatEntryComponents = entries.map((entry) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice mapping! 🙌🏻 |
||
return ( | ||
<ChatEntry | ||
id={entry.id} | ||
sender={entry.sender} | ||
body={entry.body} | ||
timeStamp={entry.timeStamp} | ||
liked={entry.liked} | ||
onLiked={onLiked} | ||
key={entry.id} | ||
></ChatEntry> | ||
); | ||
}); | ||
|
||
return ( | ||
<ul> | ||
{chatEntryComponents} | ||
</ul> | ||
); | ||
}; | ||
|
||
ChatLog.propTypes = { | ||
entries: PropTypes.arrayOf(PropTypes.shape({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of |
||
id: PropTypes.number.isRequired, | ||
sender: PropTypes.string.isRequired, | ||
body: PropTypes.string.isRequired, | ||
timeStamp: PropTypes.string.isRequired, | ||
liked: PropTypes.bool.isRequired, | ||
})).isRequired, | ||
onLiked: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default ChatLog; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
.dark_mode { | ||
margin-top: -20px; | ||
margin-left: 10px; | ||
} | ||
|
||
.dark_mode_label { | ||
width: 55px; | ||
height: 30px; | ||
position: relative; | ||
display: block; | ||
background: #ebebeb; | ||
border-radius: 200px; | ||
box-shadow: inset 0px 5px 15px rgba(0, 0, 0, 0.4), | ||
inset 0px -5px 15px rgba(255, 255, 255, 0.4); | ||
cursor: pointer; | ||
transition: 0.3s; | ||
} | ||
.dark_mode_label:after { | ||
content: ""; | ||
width: 25px; | ||
height: 25px; | ||
position: absolute; | ||
top: 3px; | ||
left: 5px; | ||
background: linear-gradient(180deg, #f8f9fa, #e9ecef); | ||
border-radius: 180px; | ||
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.2); | ||
transition: 0.3s; | ||
} | ||
.dark_mode_input { | ||
width: 0; | ||
height: 0; | ||
visibility: hidden; | ||
} | ||
.dark_mode_input:checked + .dark_mode_label { | ||
background: #242424; | ||
} | ||
.dark_mode_input:checked + .dark_mode_label:after { | ||
left: 50px; | ||
transform: translateX(-100%); | ||
} | ||
.dark_mode_label:active:after { | ||
width: 30px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import './DarkMode.css'; | ||
import PropTypes from 'prop-types'; | ||
|
||
const DarkMode = ({ currentTheme, toggleTheme }) => { | ||
let mode = currentTheme === 'App' ? 'AppDark' : 'App'; | ||
|
||
return ( | ||
<div className='dark_mode'> | ||
<input | ||
className='dark_mode_input' | ||
type='checkbox' | ||
id='darkmode-toggle' | ||
/> | ||
<label className='dark_mode_label' | ||
htmlFor='darkmode-toggle' | ||
onClick={()=>{ | ||
toggleTheme(mode); | ||
}}> | ||
</label> | ||
</div> | ||
); | ||
}; | ||
|
||
DarkMode.propTypes = { | ||
currentTheme: PropTypes.string.isRequired, | ||
toggleTheme: PropTypes.func.isRequired | ||
}; | ||
|
||
export default DarkMode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice managing of data & making sure we return a new object when we need to alter a message in our list.