Last active
September 2, 2021 15:21
-
-
Save Chrisa0418/d14e9d8bd28e81c13d31612e0e7f5abe to your computer and use it in GitHub Desktop.
React Functional Component - NotificationsDropdown (using styled components)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from "react"; | |
import styled from "styled-components"; | |
import NavDropdown from "react-bootstrap/NavDropdown"; | |
import { | |
listNotifications, | |
partialUpdateNotifications, | |
} from "features/notifications/thunks"; | |
import bellIcon from "images/bell.svg"; | |
import { useDispatch, useSelector } from "react-redux"; | |
import { useToasts } from "react-toast-notifications"; | |
import { useNavigate } from "@reach/router"; | |
import { setDisplayCommentFor } from "features/notifications/slice"; | |
import { getUserImage } from "utils/users"; | |
import Moment from "react-moment"; | |
import FireIcon from "images/fire.svg"; | |
const StyledNavDropdown = styled(NavDropdown)` | |
.dropdown-toggle::before { | |
content: ${(props) => | |
props.totalnots ? '"' + props.totalnots + '"' : null}; | |
display: inline-block; | |
width: 18px; | |
height: 18px; | |
font-size: 12px; | |
text-align: center; | |
-webkit-border-radius: 7.5px; | |
border-radius: 7.5px; | |
background-color: ${(props) => props.theme.black}; | |
position: absolute; | |
top: 5px; | |
right: 5px; | |
color: ${(props) => props.theme.yellow}; | |
} | |
a::after { | |
content: none; | |
} | |
.dropdown-menu { | |
overflow-y: auto; | |
padding: 20px 10px; | |
width: 400px; | |
margin: 0px; | |
position: fixed !important; | |
top: 0px; | |
bottom: 0px; | |
max-height: 100%; | |
background-color: ${(props) => props.theme.lime}; | |
&::-webkit-scrollbar { | |
width: 0.5em; | |
height: 0.5em; | |
} | |
&::-webkit-scrollbar-track { | |
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); | |
border-radius: 10px; | |
background-color: white; | |
} | |
&::-webkit-scrollbar-thumb { | |
background-color: #777777; | |
border-radius: 3px; | |
&:hover { | |
background: #777777; | |
} | |
} | |
@media (max-width: ${(props) => props.theme.smBreakpoint}) { | |
width: 320px; | |
} | |
} | |
@media (max-width: ${(props) => props.theme.tabletBreakpoint}) { | |
position: fixed; | |
top: 4px; | |
right: 32px; | |
} | |
`; | |
const StyledDropDownItem = styled(NavDropdown.Item)` | |
display: flex; | |
margin-bottom: 2px; | |
margin-top: 2px; | |
padding: 15px 30px 15px 15px; | |
border-radius: 5px; | |
position: relative; | |
white-space: normal; | |
font-size: 14px; | |
align-items: center; | |
border-bottom: ${(props) => | |
props.endunseen ? "2px dashed rgba(124, 103, 93, 0.3)" : "none"}; | |
&:hover, | |
&:active, | |
&:focus { | |
background-color: transparent !important; | |
color: black !important; | |
} | |
&.active { | |
color: black; | |
} | |
`; | |
const StyledNotLink = styled.div` | |
border-radius: 50%; | |
padding-right: 6px !important; | |
padding-left: 6px !important; | |
background-color: ${(props) => props.theme.neonPink}; | |
padding: 3px 6px 6px 6px; | |
&:hover { | |
background-color: ${(props) => props.theme.black}; | |
} | |
@media (max-width: ${(props) => props.theme.smBreakpoint}) { | |
.show & { | |
z-index: 10000; | |
position: fixed; | |
right: 282px; | |
top: 22px; | |
} | |
} | |
`; | |
const NotificationHeader = styled.h3` | |
font-family: SuisseIntl; | |
font-style: normal; | |
font-weight: 900; | |
font-size: 34px; | |
line-height: 34px; | |
letter-spacing: 0.06em; | |
text-align: center; | |
margin-bottom: 1rem; | |
color: ${(props) => props.theme.neonPink}; | |
transform: matrix(1, -0.03, 0.04, 1, 0, 0); | |
@media (max-width: ${(props) => props.theme.smBreakpoint}) { | |
font-size: 26px; | |
} | |
`; | |
const LikeFireIcon = styled.img` | |
position: absolute; | |
width: 25px; | |
bottom: -7px; | |
right: -3px; | |
`; | |
const ImageWrapper = styled.div` | |
width: 45px; | |
border-radius: 50%; | |
margin-right: 10px; | |
position: relative; | |
`; | |
const StyledProfileIcon = styled.img` | |
width: 45px; | |
border-radius: 50%; | |
box-shadow: 2px 2px 1px ${(props) => props.theme.gray300}; | |
`; | |
const StyledNotIcon = styled.img` | |
width: 20px; | |
`; | |
const NotContent = styled.div` | |
opacity: ${(props) => (!props.seen ? "1" : ".5")}; | |
color: ${(props) => props.theme.black}; | |
font-size: 15px; | |
line-height: 21px; | |
`; | |
const StyledMoment = styled(Moment)` | |
color: #7c675d; | |
`; | |
function NotificationsDropdown() { | |
// Provides the a dropdown for the list of notifcations related to the logged in user. | |
const notifcationsState = useSelector((state) => state.notifications); | |
const { isLoading, entities } = notifcationsState; | |
const { addToast } = useToasts(); | |
const dispatch = useDispatch(); | |
const navigate = useNavigate(); | |
const triggerFetchNotifications = async () => { | |
try { | |
const action = await dispatch(listNotifications({ params: {} })); | |
if (action.type === "LIST_NOTIFICATIONS/rejected") { | |
addToast("Error occured while fetching notifications", { | |
appearance: "error", | |
autoDismiss: true, | |
}); | |
} | |
} catch (err) { | |
addToast("Error occured while fetching notifications", { | |
appearance: "error", | |
autoDismiss: true, | |
}); | |
} | |
}; | |
const renderNotificationContent = (notification) => { | |
const { target, verb, actor } = notification.action; | |
const actorUsername = actor ? actor.username : ""; | |
let content = ""; | |
switch (notification.action.verb) { | |
case "made a comment": | |
content = `<strong> ${actorUsername} </strong> ${verb} on ${ | |
target.bucket | |
? target.bucket.title | |
: target.title | |
? target.title | |
: "your post" | |
}`; | |
return { __html: content }; | |
case "mentioned": | |
content = `<strong> ${actorUsername} </strong> has ${verb} you`; | |
return { __html: content }; | |
case "liked": | |
content = `<strong> ${actorUsername} </strong> has Liked your post ${ | |
target.text ? target.text : "" | |
}.`; | |
return { __html: content }; | |
case "replied to a comment": | |
content = `<strong> ${actorUsername} </strong> replied to your comment.`; | |
return { __html: content }; | |
default: | |
content = ""; | |
return { __html: content }; | |
} | |
}; | |
const markNotificationAsSeen = async (notification) => { | |
try { | |
const action = await dispatch( | |
partialUpdateNotifications({ | |
objectId: notification.id, | |
data: { seen: true }, | |
}) | |
); | |
if (action.type === "PATCH_NOTIFICATIONS/rejected") { | |
addToast("Error occured while marking notification seen", { | |
appearance: "error", | |
autoDismiss: true, | |
}); | |
} | |
} catch (e) { | |
addToast("Error occured while marking notification seen", { | |
appearance: "error", | |
autoDismiss: true, | |
}); | |
} | |
}; | |
const redirectToNotificationUrl = (notification) => { | |
const { target, actionObject } = notification.action; | |
var url = "/feed"; | |
switch (notification.action.verb) { | |
case "made a comment": | |
url = `/feed?object_id=${actionObject.objectId}&content_type=${actionObject.contentType}`; | |
break; | |
case "mentioned": | |
url = `/feed?object_id=${target.objectId}&content_type=${target.contentType}`; | |
break; | |
case "liked": | |
url = `/feed?object_id=${target.objectId}&content_type=${target.contentType}`; | |
break; | |
case "replied to a comment": | |
url = `/feed?object_id=${target.objectId}&content_type=${target.contentType}`; | |
dispatch(setDisplayCommentFor({ commentId: target.id })); | |
break; | |
default: | |
url = "/feed"; | |
} | |
navigate(url); | |
}; | |
useEffect(() => { | |
triggerFetchNotifications(); | |
setInterval(triggerFetchNotifications, 30000); | |
}, []); | |
const notificationsCount = entities | |
? entities.results | |
? entities.results.filter((entity) => !entity.seen).length | |
: 0 | |
: 0; | |
return ( | |
<StyledNavDropdown | |
totalnots={notificationsCount} | |
title={ | |
<StyledNotLink> | |
<StyledNotIcon src={bellIcon} alt="Rad how to school notification" /> | |
</StyledNotLink> | |
} | |
id="notifications-navbar-dropdown" | |
alignRight | |
> | |
<> | |
<NotificationHeader> notifications </NotificationHeader> | |
{isLoading | |
? "Loading" | |
: entities.results && entities.results.length | |
? entities.results.map((notification, index) => { | |
const isEndUnSeen = | |
index < entities.results.length - 1 && | |
!entities.results[index].seen & | |
entities.results[index + 1].seen; | |
return ( | |
<StyledDropDownItem | |
seen={notification.seen ? "seen" : null} | |
key={notification.id} | |
endunseen={isEndUnSeen ? "end" : null} | |
onClick={() => { | |
markNotificationAsSeen(notification); | |
redirectToNotificationUrl(notification); | |
}} | |
> | |
<ImageWrapper> | |
{notification.action.verb === "liked" ? ( | |
<LikeFireIcon src={FireIcon} /> | |
) : ( | |
"" | |
)} | |
<StyledProfileIcon | |
src={getUserImage(notification.action.actor)} | |
/> | |
</ImageWrapper> | |
<div> | |
<NotContent | |
dangerouslySetInnerHTML={renderNotificationContent( | |
notification | |
)} | |
seen={notification.seen ? "seen" : null} | |
/> | |
<StyledMoment fromNow> | |
{notification.action.timestamp} | |
</StyledMoment> | |
</div> | |
</StyledDropDownItem> | |
); | |
}) | |
: ""} | |
</> | |
</StyledNavDropdown> | |
); | |
} | |
export default NotificationsDropdown; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment