From 161e7adb8e5c2b0b15cf8fb841f4cd642b4ae721 Mon Sep 17 00:00:00 2001 From: w-arantes Date: Wed, 25 Mar 2020 21:32:38 -0300 Subject: [PATCH] feature: frontend notifications --- package.json | 6 +- public/index.html | 2 +- src/assets/logo-purple.svg | 19 +++++ src/components/Header/index.js | 35 +++++++++ src/components/Header/styles.js | 64 +++++++++++++++++ src/components/Notifications/index.js | 84 ++++++++++++++++++++++ src/components/Notifications/styles.js | 98 ++++++++++++++++++++++++++ src/pages/_layouts/default/index.js | 9 ++- src/pages/_layouts/default/styles.js | 2 +- src/styles/global.js | 1 + yarn.lock | 12 ++-- 11 files changed, 320 insertions(+), 12 deletions(-) create mode 100644 src/assets/logo-purple.svg create mode 100644 src/components/Header/index.js create mode 100644 src/components/Header/styles.js create mode 100644 src/components/Notifications/index.js create mode 100644 src/components/Notifications/styles.js diff --git a/package.json b/package.json index 681d1d4..5c4cac1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "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", @@ -14,8 +14,8 @@ "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", diff --git a/public/index.html b/public/index.html index a742b50..e60ce1d 100644 --- a/public/index.html +++ b/public/index.html @@ -13,7 +13,7 @@ - React App + Gobarber - Web diff --git a/src/assets/logo-purple.svg b/src/assets/logo-purple.svg new file mode 100644 index 0000000..fd67c30 --- /dev/null +++ b/src/assets/logo-purple.svg @@ -0,0 +1,19 @@ + + + + logo + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Header/index.js b/src/components/Header/index.js new file mode 100644 index 0000000..91694bc --- /dev/null +++ b/src/components/Header/index.js @@ -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 ( + + + + + + + + ); +} diff --git a/src/components/Header/styles.js b/src/components/Header/styles.js new file mode 100644 index 0000000..81c281e --- /dev/null +++ b/src/components/Header/styles.js @@ -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%; + } +`; diff --git a/src/components/Notifications/index.js b/src/components/Notifications/index.js new file mode 100644 index 0000000..1e6c36c --- /dev/null +++ b/src/components/Notifications/index.js @@ -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 ( + + + + + + + + {notifications.map(notification => ( + +

{notification.content}

+ + {!notification.read && ( + + )} +
+ ))} +
+
+
+ ); +} diff --git a/src/components/Notifications/styles.js b/src/components/Notifications/styles.js new file mode 100644 index 0000000..26df877 --- /dev/null +++ b/src/components/Notifications/styles.js @@ -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; + } + `} +`; diff --git a/src/pages/_layouts/default/index.js b/src/pages/_layouts/default/index.js index 18a2ce2..980dd64 100644 --- a/src/pages/_layouts/default/index.js +++ b/src/pages/_layouts/default/index.js @@ -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 {children}; + return ( + +
+ {children} + + ); } DefaultLayout.propTypes = { diff --git a/src/pages/_layouts/default/styles.js b/src/pages/_layouts/default/styles.js index 243b860..43e8cd9 100644 --- a/src/pages/_layouts/default/styles.js +++ b/src/pages/_layouts/default/styles.js @@ -2,5 +2,5 @@ import styled from 'styled-components'; export const Wrapper = styled.div` height: 100%; - background: #333; + background: linear-gradient(-90deg, #7159c1, #ab59c1); `; diff --git a/src/styles/global.js b/src/styles/global.js index ea760c2..109f17c 100644 --- a/src/styles/global.js +++ b/src/styles/global.js @@ -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` diff --git a/yarn.lock b/yarn.lock index d3d35c5..6d7f85b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3473,10 +3473,10 @@ date-fns-tz@^1.0.7: resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.10.tgz#30fef0038f80534fddd8e133a6b8ca55ba313748" integrity sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg== -date-fns@^2.0.0-beta.2: - version "2.11.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.11.0.tgz#ec2b44977465b9dcb370021d5e6c019b19f36d06" - integrity sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA== +date-fns@^2.0.0-beta.5: + version "2.0.0-beta.5" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-beta.5.tgz#90885db3772802d55519cd12acd49de56aca1059" + integrity sha512-GS5yi964NDFNoja9yOdWFj9T97T67yLrUeJZgddHaVfc/6tHWtX7RXocuubmZkNzrZUZ9BqBOW7jTR5OoWjJ1w== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" @@ -8697,7 +8697,7 @@ react-error-overlay@^6.0.3: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== -react-icons@^3.7.0: +react-icons@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.9.0.tgz#89a00f20a0e02e6bfd899977eaf46eb4624239d5" integrity sha512-gKbYKR+4QsD3PmIHLAM9TDDpnaTsr3XZeK1NTAb6WQQ+gxEdJ0xuCgLq0pxXdS7Utg2AIpcVhM1ut/jlDhcyNg== @@ -8714,7 +8714,7 @@ react-is@^16.8.1, react-is@^16.8.4: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== -react-perfect-scrollbar@^1.5.3: +react-perfect-scrollbar@^1.5.8: version "1.5.8" resolved "https://registry.yarnpkg.com/react-perfect-scrollbar/-/react-perfect-scrollbar-1.5.8.tgz#380959387a325c5c9d0268afc08b3f73ed5b3078" integrity sha512-bQ46m70gp/HJtiBOF3gRzBISSZn8FFGNxznTdmTG8AAwpxG1bJCyn7shrgjEvGSQ5FJEafVEiosY+ccER11OSA==