Skip to content

Commit

Permalink
feature: frontend notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
w-arantes committed Mar 26, 2020
1 parent a5e50bf commit 161e7ad
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 12 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"dependencies": {
"@rocketseat/unform": "^1.6.2",
"axios": "^0.19.2",
"date-fns": "^2.0.0-beta.2",
"date-fns": "^2.0.0-beta.5",
"date-fns-tz": "^1.0.7",
"history": "^4.9.0",
"immer": "^6.0.2",
"polished": "^3.5.1",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-icons": "^3.7.0",
"react-perfect-scrollbar": "^1.5.3",
"react-icons": "^3.9.0",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
Expand Down
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

<title>React App</title>
<title>Gobarber - Web</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
19 changes: 19 additions & 0 deletions src/assets/logo-purple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions src/components/Header/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { Link } from 'react-router-dom';

import Notifications from '~/components/Notifications';

import logo from '~/assets/logo-purple.svg';
import { Container, Content, Profile } from './styles';

export default function Header() {
return (
<Container>
<Content>
<nav>
<img src={logo} alt="Gobarber" />
<Link to="/dashboard">DASHBOARD</Link>
</nav>

<aside>
<Notifications />

<Profile>
<div>
<strong>Wellington Arantes</strong>
<Link to="/profile">Meu Perfil</Link>
</div>
<img
src="https://api.adorable.io/avatars/50/[email protected]"
alt="Wellington Arantes"
/>
</Profile>
</aside>
</Content>
</Container>
);
}
64 changes: 64 additions & 0 deletions src/components/Header/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import styled from 'styled-components';

export const Container = styled.div`
background: #fff;
padding: 0 30px;
`;

export const Content = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
max-width: 900px;
margin: 0 auto;
nav {
display: flex;
align-items: center;
img {
margin-right: 20px;
padding-right: 20px;
border-right: 1px solid #eee;
}
a {
font-weight: bold;
color: #7159c1;
}
}
aside {
display: flex;
align-items: center;
}
`;

export const Profile = styled.div`
display: flex;
margin-left: 20px;
padding-left: 20px;
border-left: 1px solid #eee;
div {
text-align: right;
margin-right: 10px;
strong {
display: block;
color: #333;
}
a {
display: block;
margin-top: 2px;
font-size: 12px;
color: #999;
}
}
img {
height: 32px;
border-radius: 50%;
}
`;
84 changes: 84 additions & 0 deletions src/components/Notifications/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState, useEffect, useMemo } from 'react';
import { MdNotifications } from 'react-icons/md';
import { parseISO, formatDistance } from 'date-fns';
import pt from 'date-fns/locale/pt';

import api from '~/services/api';

import {
Container,
Badge,
NotificationList,
Scroll,
Notification,
} from './styles';

export default function Notifications() {
const [visible, setVisible] = useState(false);
const [notifications, setNotifications] = useState([]);

const hasUnread = useMemo(
() => !!notifications.find(notification => notification.read === false),
[notifications]
);

useEffect(() => {
async function loadNotifications() {
const response = await api.get('notifications');

const data = response.data.map(notification => ({
...notification,
timeDistance: formatDistance(
parseISO(notification.createdAt),
new Date(),
{ addSuffix: true, locale: pt }
),
}));

setNotifications(data);
}

loadNotifications();
}, []);

function handleToggleVisible() {
setVisible(!visible);
}

async function handleMarkAsRead(id) {
await api.put(`notifications/${id}`);

setNotifications(
notifications.map(notification =>
notification._id == id ? { ...notification, read: true } : notification
)
);
}

return (
<Container>
<Badge onClick={handleToggleVisible} hasUnread={hasUnread}>
<MdNotifications color="#7159c1" size={20} />
</Badge>

<NotificationList visible={visible}>
<Scroll>
{notifications.map(notification => (
<Notification key={notification._id} unread={!notification.read}>
<p>{notification.content}</p>
<time>{notification.timeDistance}</time>
{!notification.read && (
<button
type="button"
onClick={() => handleMarkAsRead(notification._id)}
>
Marcar como lida
</button>
)}
</Notification>
))}
</Scroll>
</NotificationList>
</Container>
);
}
98 changes: 98 additions & 0 deletions src/components/Notifications/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import styled, { css } from 'styled-components';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { lighten } from 'polished';

export const Container = styled.div`
position: relative;
`;

export const Badge = styled.button`
background: none;
border: 0;
position: relative;
${props =>
props.hasUnread &&
css`
&::after {
position: absolute;
right: 0;
top: 0;
width: 8px;
height: 8px;
background: #ff892e;
content: '';
border-radius: 50%;
}
`}
`;

export const NotificationList = styled.div`
position: absolute;
width: 260px;
left: calc(50% - 130px);
top: calc(100% + 30px);
background: rgba(0, 0, 0, 0.6);
border-radius: 4px;
padding: 15px 5px;
display: ${props => (props.visible ? 'block' : 'none')};
&::before {
content: '';
position: absolute;
left: calc(50% - 20px);
top: -20px;
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 20px solid rgba(0, 0, 0, 0.6);
}
`;

export const Scroll = styled(PerfectScrollbar)`
max-height: 260px;
padding: 5px 15px;
`;

export const Notification = styled.div`
color: #fff;
& + div {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
p {
font-size: 13px;
line-height: 18px;
}
time {
font-size: 12px;
opacity: 0.6;
margin-bottom: 5px;
}
button {
font-size: 12px;
border: 0;
background: none;
color: ${lighten(0.2, '#7159c1')};
}
${props =>
props.unread &&
css`
&::after {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background: #ff892e;
border-radius: 50%;
margin-left: 10px;
}
`}
`;
9 changes: 8 additions & 1 deletion src/pages/_layouts/default/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';

import Header from '~/components/Header';

import { Wrapper } from './styles';

export default function DefaultLayout({ children }) {
return <Wrapper>{children}</Wrapper>;
return (
<Wrapper>
<Header />
{children}
</Wrapper>
);
}

DefaultLayout.propTypes = {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/_layouts/default/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import styled from 'styled-components';

export const Wrapper = styled.div`
height: 100%;
background: #333;
background: linear-gradient(-90deg, #7159c1, #ab59c1);
`;
1 change: 1 addition & 0 deletions src/styles/global.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createGlobalStyle } from 'styled-components';

import 'react-perfect-scrollbar/dist/css/styles.css';
import 'react-toastify/dist/ReactToastify.css';

export default createGlobalStyle`
Expand Down
Loading

0 comments on commit 161e7ad

Please sign in to comment.