Skip to content
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
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ You'll use props to break down our app into smaller components by passing down r
1. Fork this repository
2. Clone the forked repository onto your local machines
3. In the root directory, type `npm ci`, which installs dependencies for the project
4. Finally, type `npm run dev`, which starts a development server that runs your website in the browser. That server
will reload your website whenever you make any changes to source files
4. Finally, type `npm run dev`, which starts a development server that runs your website in the browser. That server will reload your website whenever you make any changes to source files

## Instructions
- Break down `App.jsx` into components
Expand All @@ -26,8 +25,7 @@ You'll use props to break down our app into smaller components by passing down r
- Consider breaking down `App.css` into separate stylesheets for each of the components that you have created.

## Extension 1
- Get the emails to open and display an email using **conditional rendering** and a component similar to what you
did with the [react-gmail-intro exercise](https://github.com/boolean-uk/react-gmail-intro/blob/main/images/gmail-email-view-intro.png)
- Get the emails to open and display an email using **conditional rendering** and a component similar to what you did with the [react-gmail-intro exercise](https://github.com/boolean-uk/react-gmail-intro/blob/main/images/gmail-email-view-intro.png)
- You'll need to use the state to keep track of which email is selected and you'll need a component to display the email
- Add a back button so users can return to their inbox

Expand Down
155 changes: 48 additions & 107 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,64 @@
import { useState } from 'react'
import { useState } from "react";

import initialEmails from './data/emails'
import initialEmails from "./data/emails";

import './styles/App.css'
import "./styles/App.css";
import List from "./components/EmailList/List";
import Header from "./components/EmailHeader/Header";
import LeftMenu from "./components/EmailLeftMenu/LeftMenu";
import EmailViewer from "./components/EmailViewer/EmailViewer";

const getReadEmails = emails => emails.filter(email => !email.read)

const getStarredEmails = emails => emails.filter(email => email.starred)
const getReadEmails = (emails) => emails.filter((email) => !email.read);
const getStarredEmails = (emails) => emails.filter((email) => email.starred);

function App() {
const [emails, setEmails] = useState(initialEmails)
const [hideRead, setHideRead] = useState(false)
const [currentTab, setCurrentTab] = useState('inbox')

const unreadEmails = emails.filter(email => !email.read)
const starredEmails = emails.filter(email => email.starred)
const [emails, setEmails] = useState(initialEmails);
const [emailToView, setEmailToView] = useState(null);
const [hideRead, setHideRead] = useState(false);
const [currentTab, setCurrentTab] = useState("inbox");

const toggleStar = targetEmail => {
const updatedEmails = emails =>
emails.map(email =>
email.id === targetEmail.id
? { ...email, starred: !email.starred }
: email
)
setEmails(updatedEmails)
}
const handleSearch = (q) => {
const query = q.trim().toLowerCase();

const toggleRead = targetEmail => {
const updatedEmails = emails =>
emails.map(email =>
email.id === targetEmail.id ? { ...email, read: !email.read } : email
)
setEmails(updatedEmails)
}
setEmails(
query
? initialEmails.filter((e) => e.title.toLowerCase().includes(query))
: initialEmails
);
};

let filteredEmails = emails
let filteredEmails = emails;

if (hideRead) filteredEmails = getReadEmails(filteredEmails)
if (hideRead) filteredEmails = getReadEmails(filteredEmails);

if (currentTab === 'starred')
filteredEmails = getStarredEmails(filteredEmails)
if (currentTab === "starred")
filteredEmails = getStarredEmails(filteredEmails);

return (
<div className="app">
<header className="header">
<div className="left-menu">
<svg className="menu-icon" focusable="false" viewBox="0 0 24 24">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path>
</svg>

<img
src="https://ssl.gstatic.com/ui/v1/icons/mail/rfr/logo_gmail_lockup_default_1x_r2.png"
alt="gmail logo"
/>
</div>

<div className="search">
<input className="search-bar" placeholder="Search mail" />
</div>
</header>
<nav className="left-menu">
<ul className="inbox-list">
<li
className={`item ${currentTab === 'inbox' ? 'active' : ''}`}
onClick={() => setCurrentTab('inbox')}
>
<span className="label">Inbox</span>
<span className="count">{unreadEmails.length}</span>
</li>
<li
className={`item ${currentTab === 'starred' ? 'active' : ''}`}
onClick={() => setCurrentTab('starred')}
>
<span className="label">Starred</span>
<span className="count">{starredEmails.length}</span>
</li>

<li className="item toggle">
<label htmlFor="hide-read">Hide read</label>
<input
id="hide-read"
type="checkbox"
checked={hideRead}
onChange={e => setHideRead(e.target.checked)}
/>
</li>
</ul>
</nav>
<main className="emails">
<ul>
{filteredEmails.map((email, index) => (
<li
key={index}
className={`email ${email.read ? 'read' : 'unread'}`}
>
<div className="select">
<input
className="select-checkbox"
type="checkbox"
checked={email.read}
onChange={() => toggleRead(email)}
/>
</div>
<div className="star">
<input
className="star-checkbox"
type="checkbox"
checked={email.starred}
onChange={() => toggleStar(email)}
/>
</div>
<div className="sender">{email.sender}</div>
<div className="title">{email.title}</div>
</li>
))}
</ul>
</main>
<Header onSearch={handleSearch} />
<LeftMenu
emails={emails}
currentTab={currentTab}
hideRead={hideRead}
setCurrentTab={setCurrentTab}
setHideRead={setHideRead}
onClick={() => setEmailToView(null)}
/>
{emailToView == null ? (
<List
filteredEmails={filteredEmails}
setEmails={setEmails}
setEmailToView={setEmailToView}
/>
) : (
<EmailViewer
emailToView={emailToView}
setEmailToView={setEmailToView}
/>
)}
</div>
)
);
}

export default App
export default App;
30 changes: 30 additions & 0 deletions src/components/EmailHeader/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "./style.css";

function Header({ onSearch }) {
return (
<>
<header className="header">
<div className="left-menu">
<svg className="menu-icon" focusable="false" viewBox="0 0 24 24">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path>
</svg>

<img
src="https://ssl.gstatic.com/ui/v1/icons/mail/rfr/logo_gmail_lockup_default_1x_r2.png"
alt="gmail logo"
/>
</div>

<div className="search">
<input
className="search-bar"
placeholder="Search mail"
onChange={(e) => onSearch(e.target.value)}
/>
</div>
</header>
</>
);
}

export default Header;
34 changes: 34 additions & 0 deletions src/components/EmailHeader/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.app > .header {
grid-area: header;

display: grid;
grid-template-columns: 180px 1fr;

border-bottom: solid 1px #00000020;
}

.app > .header > .left-menu {
display: grid;
grid-auto-flow: column;
align-items: center;
justify-content: left;
}

.app > .header > .left-menu .menu-icon {
width: 25px;
height: 25px;
margin: 10px;
}

.app > .header > .search {
display: grid;
padding: 6px;
}

.app > .header > .search .search-bar {
border: none;
border-radius: 5px;
background-color: rgb(240, 240, 240);
padding: 0 10px;
font-size: 1rem;
}
47 changes: 47 additions & 0 deletions src/components/EmailLeftMenu/LeftMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import "./style.css";

function LeftMenu({
emails,
currentTab,
hideRead,
setCurrentTab,
setHideRead,
onClick
}) {
const unreadEmails = emails.filter((email) => !email.read);
const starredEmails = emails.filter((email) => email.starred);

return (
<nav className="left-menu" onClick={onClick}>
<ul className="inbox-list">
<li
className={`item ${currentTab === "inbox" ? "active" : ""}`}
onClick={() => setCurrentTab("inbox")}
>
<span className="label">Inbox</span>
<span className="count">{unreadEmails.length}</span>
</li>

<li
className={`item ${currentTab === "starred" ? "active" : ""}`}
onClick={() => setCurrentTab("starred")}
>
<span className="label">Starred</span>
<span className="count">{starredEmails.length}</span>
</li>

<li className="item toggle">
<label htmlFor="hide-read">Hide read</label>
<input
id="hide-read"
type="checkbox"
checked={hideRead}
onChange={(e) => setHideRead(e.target.checked)}
/>
</li>
</ul>
</nav>
);
}

export default LeftMenu;
29 changes: 29 additions & 0 deletions src/components/EmailLeftMenu/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.app > .left-menu {
grid-area: left-menu;
padding: 10px;
padding-left: 0;
}

.app > .left-menu .inbox-list {
display: grid;
grid-gap: 10px;
}

.app > .left-menu .inbox-list .item {
padding: 5px 10px;
display: grid;
grid-auto-flow: column;
justify-content: space-between;
align-items: center;

border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
}

.app > .left-menu .inbox-list .item.active {
background-color: #ff000050;
}

.app > .left-menu .inbox-list .item.toggle {
background-color: transparent;
}
32 changes: 32 additions & 0 deletions src/components/EmailList/List.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import "./style.css";
import ListItem from "./ListItem";

function List({ filteredEmails, setEmails, setEmailToView, emailToView }) {
const handleOpen = (e) => {
if (emailToView == null) {
setEmailToView(e);
} else {
setEmailToView(null);
}

console.log(emailToView);
};

return (
<main className="emails">
<ul>
{filteredEmails.map((email, index) => (
<ListItem
emailToDisplay={email}
key={index}
index={index}
setEmails={setEmails}
onClick={() => handleOpen(email)}
/>
))}
</ul>
</main>
);
}

export default List;
Loading