-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6064f3f
commit 2357d46
Showing
12 changed files
with
534 additions
and
17 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use client' | ||
|
||
import { use, useEffect, useState } from 'react' | ||
import { useDispatch, useSelector } from 'react-redux' | ||
import { setNotificationCount } from '../../notificationSlice' | ||
|
||
export default function NotificationCount({ notificationCountP }) { | ||
const { count: initialCount } = use(notificationCountP) | ||
const { count: storeCount } = useSelector((state) => state.notification) | ||
const [count, setCount] = useState(initialCount) | ||
const dispatch = useDispatch() | ||
|
||
useEffect(() => { | ||
if (storeCount === null) { | ||
dispatch(setNotificationCount(initialCount)) | ||
return | ||
} | ||
setCount(storeCount) | ||
}, [storeCount]) | ||
|
||
if (!count) return null | ||
return <span className="badge">{count}</span> | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
'use client' | ||
|
||
import { use, useState } from 'react' | ||
import Table from '../../components/Table' | ||
import NotificationsTable from './NotificationsTable' | ||
import { useDispatch, useSelector } from 'react-redux' | ||
import api from '../../lib/api-client' | ||
import { decrementNotificationCount } from '../../notificationSlice' | ||
|
||
export default function Notifications({ | ||
unviewedMessagesCountsP, | ||
confirmedEmails, | ||
defaultToEmail, | ||
}) { | ||
const [toEmail, setToEmail] = useState(defaultToEmail) | ||
const initialUnviewedCounts = use(unviewedMessagesCountsP) | ||
const [unviewedCounts, setUnviewedCounts] = useState(initialUnviewedCounts) | ||
const { token } = useSelector((state) => state.root) | ||
const dispatch = useDispatch() | ||
|
||
const markAllViewed = async () => { | ||
try { | ||
const apiRes = await api.get( | ||
'/messages', | ||
{ to: toEmail, viewed: false }, | ||
{ accessToken: token } | ||
) | ||
if (!apiRes.messages?.length) return | ||
|
||
const unreadMessageIds = apiRes.messages.map((m) => m.id) | ||
await api.post( | ||
'/messages/viewed', | ||
{ ids: unreadMessageIds, vdate: Date.now() }, | ||
{ accessToken: token } | ||
) | ||
|
||
setUnviewedCounts({ | ||
...unviewedCounts, | ||
[toEmail]: unviewedCounts[toEmail] - unreadMessageIds.length, | ||
}) | ||
dispatch(decrementNotificationCount(unreadMessageIds.length)) | ||
} catch (apiError) { | ||
promptError(apiError.message) | ||
} | ||
} | ||
const markViewed = async (messageId) => { | ||
const now = Date.now() | ||
try { | ||
await api.post( | ||
'/messages/viewed', | ||
{ ids: [messageId], vdate: now }, | ||
{ accessToken: token } | ||
) | ||
setUnviewedCounts({ | ||
...unviewedCounts, | ||
[toEmail]: unviewedCounts[toEmail] - 1, | ||
}) | ||
dispatch(decrementNotificationCount(1)) | ||
} catch (apiError) { | ||
promptError(apiError.message) | ||
} | ||
} | ||
|
||
return ( | ||
<div className="row"> | ||
<div className="filters-col"> | ||
<Table | ||
className="filters-table" | ||
headings={[{ id: 'filters', content: <span>Inboxes</span> }]} | ||
> | ||
<tr> | ||
<td> | ||
<ul className="nav nav-pills nav-stacked"> | ||
{confirmedEmails.map((email) => ( | ||
<li | ||
key={email} | ||
role="presentation" | ||
className={toEmail === email ? 'active' : null} | ||
onClick={(e) => { | ||
setToEmail(email) | ||
}} | ||
> | ||
<a>{email}</a> | ||
{unviewedCounts?.[email] > 0 && ( | ||
<span className="badge badge-light">{unviewedCounts[email]}</span> | ||
)} | ||
</li> | ||
))} | ||
</ul> | ||
</td> | ||
</tr> | ||
</Table> | ||
</div> | ||
<div className="messages-col"> | ||
<NotificationsTable | ||
toEmail={toEmail} | ||
numUnviewed={unviewedCounts?.[toEmail] ?? 0} | ||
markViewed={markViewed} | ||
markAllViewed={markAllViewed} | ||
/> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
@use '../../styles/utils/constants'; | ||
@use '../../styles/utils/mixins'; | ||
|
||
.notifications :global { | ||
header h1 { | ||
margin-bottom: 1.5rem; | ||
} | ||
|
||
.filters-col { | ||
margin-left: 15px; | ||
margin-right: 15px; | ||
} | ||
.messages-col { | ||
margin-left: 15px; | ||
margin-right: 15px; | ||
} | ||
@media #{constants.$desktop} { | ||
.filters-col { | ||
float: left; | ||
width: 245px; | ||
margin-right: 0.5rem; | ||
} | ||
.messages-col { | ||
float: left; | ||
width: calc(100% - 245px - 30px - 1rem); | ||
margin-left: 0.5rem; | ||
} | ||
} | ||
|
||
.table > thead > tr > th { | ||
padding: 0.375rem 0.5rem; | ||
} | ||
table.messages-table { | ||
border-bottom: 0; | ||
|
||
th > span.pull-left { | ||
line-height: 26px; | ||
} | ||
td.viewed { | ||
opacity: 0.6; | ||
background-color: #eee; | ||
&:hover { | ||
opacity: 0.8; | ||
} | ||
.collapse-widget > div { | ||
border-left: 3px solid #fff; | ||
padding-top: 0.5rem; | ||
padding-right: 0.5rem; | ||
padding-bottom: 0.5rem; | ||
background-color: constants.$backgroundWhite; | ||
} | ||
} | ||
.email-title h4 { | ||
font-size: 1rem; | ||
line-height: normal; | ||
color: constants.$darkBlue; | ||
cursor: pointer; | ||
margin: 0; | ||
} | ||
.collapse-widget > div { | ||
margin-top: 0.375rem; | ||
border-left: 3px solid #eee; | ||
padding-left: 0.875rem; | ||
} | ||
} | ||
.markdown-rendered { | ||
@include mixins.markdown-content-styles; | ||
p:last-child { | ||
margin-bottom: 0; | ||
} | ||
} | ||
.filters-table { | ||
th > span { | ||
line-height: 26px; | ||
} | ||
tbody > tr > td { | ||
padding: 0; | ||
padding-top: 0.75rem; | ||
} | ||
} | ||
.nav-stacked { | ||
& > li { | ||
display: flex; | ||
justify-content: space-between; | ||
border-radius: 4px; | ||
padding: 12px 10px; | ||
cursor: pointer; | ||
|
||
&:hover { | ||
background-color: constants.$borderGray; | ||
} | ||
&.active { | ||
background-color: constants.$mediumDarkBlue; | ||
& > a { | ||
background-color: transparent; | ||
} | ||
& > .badge { | ||
background-color: constants.$backgroundWhite; | ||
color: constants.$mediumDarkBlue; | ||
} | ||
} | ||
|
||
& > a { | ||
padding: 0; | ||
font-weight: bold; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
white-space: nowrap; | ||
&:hover { | ||
background-color: transparent; | ||
} | ||
} | ||
} | ||
& > li + li { | ||
margin-top: 6px; | ||
} | ||
.badge { | ||
background-color: constants.$mediumDarkBlue; | ||
padding: 0px 6px; | ||
min-width: 20px; | ||
line-height: 20px; | ||
margin-left: 4px; | ||
flex-shrink: 0; | ||
} | ||
} | ||
} |
Oops, something went wrong.