Skip to content

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 121 additions & 40 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,52 +1,133 @@
#App {
background-color: #87cefa;
}
background-color: white;

#App header {
background-color: #222;
color: #fff;
padding-bottom: 0.5rem;
position: fixed;
width: 100%;
z-index: 100;
text-align: center;
align-items: center;
}
header {
background-color: #e9ecef;
color: black;
position: fixed;
width: 100%;
z-index: 100;
text-align: center;
align-items: center;
}

#App main {
padding-left: 2em;
padding-right: 2em;
padding-bottom: 5rem;
padding-top: 10rem;
}
main {
padding-left: 2em;
padding-right: 2em;
padding-bottom: 5rem;
padding-top: 10rem;
}

#App h1 {
font-size: 1.5em;
text-align: center;
display: inline-block;
}
h1 {
font-size: 1.5em;
text-align: center;
display: inline-block;
padding-top: 0.5em;
}

#App header section {
background-color: #e0ffff;
}
header section {
background-color: #e0ffff;
}

#App .widget {
display: inline-block;
line-height: 0.5em;
border-radius: 10px;
color: black;
font-size:0.8em;
padding-left: 1em;
padding-right: 1em;
}
.widget {
display: inline-block;
line-height: 0.5em;
border-radius: 10px;
color: black;
font-size:0.8em;
padding: 1em;
font-weight: bold;
}

#heartWidget {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5em;
margin: 0.25em;
}

#App #heartWidget {
font-size: 1.5em;
margin: 1em
span {
display: inline-block;
}
}

#App span {
display: inline-block
#AppDark {
background-color: #343a40;

header {
background-color: #222;
color: #fff;
position: fixed;
width: 100%;
z-index: 100;
text-align: center;
align-items: center;
}

main {
padding-left: 2em;
padding-right: 2em;
padding-bottom: 5rem;
padding-top: 10rem;
}

h1 {
font-size: 1.5em;
text-align: center;
display: inline-block;
padding-top: 0.5em;
}

h2 {
color: white;
}

.entry-bubble p {
color: white;
}

.local .entry-bubble {
background-color: #212529;
}

.local .entry-bubble::before {
background-color: #212529;
}

.remote .entry-bubble {
background-color: #2a6f97;
}

.remote .entry-bubble::before {
background-color: #2a6f97;
}

header section {
background-color: #00568a;
}

.widget {
display: inline-block;
line-height: 0.5em;
border-radius: 10px;
color: white;
font-size:0.8em;
padding: 1em;
font-weight: bold;
}

#heartWidget {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5em;
margin: 0.25em;
}

span {
display: inline-block
}
}

.red {
Expand Down
35 changes: 31 additions & 4 deletions src/App.jsx
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;
Comment on lines +14 to +17
Copy link
Collaborator

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.

}
}));
};

const toggleDarkMode = (mode) => {
setTheme(mode);
};
Comment on lines +22 to +24
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't taking any other actions with the mode argument, I think we could factor out the toggleDarkMode function and pass setTheme directly to the DarkMode component.


const likedCount = chatData.filter((chat) => chat.liked).length;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 filter, another option is to use a higher order function like array.reduce to take our list of messages and reduce it down to a single value.

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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}/>
Copy link
Collaborator

Choose a reason for hiding this comment

The 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>
);
Expand Down
39 changes: 29 additions & 10 deletions src/components/ChatEntry.jsx
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this pattern of sending just the id to onLiked since it keeps all the state management and message object creation confined to App.


const filledHeart = liked ? '❤️' : '🤍';

const setMessageBubble = id%2 ? 'local' : 'remote';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const setMessageBubble = id%2 ? 'local' : 'remote';
const setMessageBubble = id % 2 ? 'local' : 'remote';


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}`}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great use of a variable and interpolated string to form the className value.

<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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of Prop-Types and flagging those necessary for render with isRequired!.

};

export default ChatEntry;
37 changes: 37 additions & 0 deletions src/components/ChatLog.jsx
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) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of PropTypes.arrayOf and PropTypes.shape!

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;
44 changes: 44 additions & 0 deletions src/dark-mode/DarkMode.css
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;
}
29 changes: 29 additions & 0 deletions src/dark-mode/DarkMode.jsx
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;