chore: use monorepo organization
This commit is contained in:
parent
deaf7d6ecc
commit
a687827f7e
1099 changed files with 8184 additions and 11535 deletions
|
|
@ -0,0 +1,160 @@
|
|||
import { useQuery, useMutation, gql } from '@apollo/client'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import ActionButton from 'src/components/buttons/ActionButton'
|
||||
import { H5 } from 'src/components/typography'
|
||||
import NotificationIconZodiac from 'src/styling/icons/menu/notification-zodiac.svg?react'
|
||||
import ClearAllIconInverse from 'src/styling/icons/stage/spring/empty.svg?react'
|
||||
import ClearAllIcon from 'src/styling/icons/stage/zodiac/empty.svg?react'
|
||||
import ShowUnreadIcon from 'src/styling/icons/stage/zodiac/full.svg?react'
|
||||
|
||||
import NotificationRow from './NotificationRow'
|
||||
import classes from './NotificationCenter.module.css'
|
||||
|
||||
const GET_NOTIFICATIONS = gql`
|
||||
query getNotifications {
|
||||
notifications {
|
||||
id
|
||||
type
|
||||
detail
|
||||
message
|
||||
created
|
||||
read
|
||||
valid
|
||||
}
|
||||
hasUnreadNotifications
|
||||
machines {
|
||||
deviceId
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const TOGGLE_CLEAR_NOTIFICATION = gql`
|
||||
mutation toggleClearNotification($id: ID!, $read: Boolean!) {
|
||||
toggleClearNotification(id: $id, read: $read) {
|
||||
id
|
||||
read
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const CLEAR_ALL_NOTIFICATIONS = gql`
|
||||
mutation clearAllNotifications {
|
||||
clearAllNotifications {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const NotificationCenter = ({
|
||||
close,
|
||||
hasUnreadProp,
|
||||
buttonCoords,
|
||||
popperRef,
|
||||
refetchHasUnreadHeader
|
||||
}) => {
|
||||
const { data, loading } = useQuery(GET_NOTIFICATIONS, {
|
||||
pollInterval: 60000
|
||||
})
|
||||
const [xOffset, setXoffset] = useState(300)
|
||||
|
||||
const [showingUnread, setShowingUnread] = useState(false)
|
||||
const machines = R.compose(
|
||||
R.map(R.prop('name')),
|
||||
R.indexBy(R.prop('deviceId'))
|
||||
)(R.path(['machines'])(data) ?? [])
|
||||
const notifications = R.path(['notifications'])(data) ?? []
|
||||
const [hasUnread, setHasUnread] = useState(hasUnreadProp)
|
||||
|
||||
const [toggleClearNotification] = useMutation(TOGGLE_CLEAR_NOTIFICATION, {
|
||||
onError: () => console.error('Error while clearing notification'),
|
||||
refetchQueries: () => ['getNotifications']
|
||||
})
|
||||
const [clearAllNotifications] = useMutation(CLEAR_ALL_NOTIFICATIONS, {
|
||||
onError: () => console.error('Error while clearing all notifications'),
|
||||
refetchQueries: () => ['getNotifications']
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setXoffset(popperRef.current.getBoundingClientRect().x)
|
||||
if (data && data.hasUnreadNotifications !== hasUnread) {
|
||||
refetchHasUnreadHeader()
|
||||
setHasUnread(!hasUnread)
|
||||
}
|
||||
}, [popperRef, data, hasUnread, refetchHasUnreadHeader])
|
||||
|
||||
const buildNotifications = () => {
|
||||
const notificationsToShow =
|
||||
!showingUnread || !hasUnread
|
||||
? notifications
|
||||
: R.filter(R.propEq('read', false))(notifications)
|
||||
return notificationsToShow.map(n => {
|
||||
return (
|
||||
<NotificationRow
|
||||
key={n.id}
|
||||
id={n.id}
|
||||
type={n.type}
|
||||
detail={n.detail}
|
||||
message={n.message}
|
||||
deviceName={machines[n.detail.deviceId]}
|
||||
created={n.created}
|
||||
read={n.read}
|
||||
valid={n.valid}
|
||||
toggleClear={() =>
|
||||
toggleClearNotification({
|
||||
variables: { id: n.id, read: !n.read }
|
||||
})
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.container}>
|
||||
<div className={classes.header}>
|
||||
<H5 className={classes.headerText}>Notifications</H5>
|
||||
<button
|
||||
onClick={close}
|
||||
className={classes.notificationIcon}
|
||||
style={{
|
||||
top: buttonCoords?.y ?? 0,
|
||||
left: buttonCoords?.x ? buttonCoords.x - xOffset : 0
|
||||
}}>
|
||||
<NotificationIconZodiac />
|
||||
{hasUnread && <div className={classes.hasUnread} />}
|
||||
</button>
|
||||
</div>
|
||||
<div className={classes.actionButtons}>
|
||||
{hasUnread && (
|
||||
<ActionButton
|
||||
color="primary"
|
||||
Icon={ShowUnreadIcon}
|
||||
InverseIcon={ClearAllIconInverse}
|
||||
className={classes.clearAllButton}
|
||||
onClick={() => setShowingUnread(!showingUnread)}>
|
||||
{showingUnread ? 'Show all' : 'Show unread'}
|
||||
</ActionButton>
|
||||
)}
|
||||
{hasUnread && (
|
||||
<ActionButton
|
||||
color="primary"
|
||||
Icon={ClearAllIcon}
|
||||
InverseIcon={ClearAllIconInverse}
|
||||
className={classes.clearAllButton}
|
||||
onClick={clearAllNotifications}>
|
||||
Mark all as read
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.notificationsList}>
|
||||
{!loading && buildNotifications()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationCenter
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
.container {
|
||||
width: 40vw;
|
||||
height: 110vh;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.container @media only screen and (max-width: 1920px) {
|
||||
width: 30vw;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.headerText {
|
||||
margin-top: 20px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
display: flex;
|
||||
margin-left: 16px;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.notificationIcon {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
box-shadow: 0 0 0 transparent;
|
||||
border: 0 solid transparent;
|
||||
text-shadow: 0 0 0 transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.clearAllButton {
|
||||
margin-top: -16px;
|
||||
margin-left: 8px;
|
||||
background-color: var(--zircon);
|
||||
}
|
||||
|
||||
.notificationsList {
|
||||
height: 90vh;
|
||||
max-height: 100vh;
|
||||
margin-top: 24px;
|
||||
margin-left: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: white;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.notificationRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
padding-top: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.notificationRow > *:first-child {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.notificationContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.unread {
|
||||
background-color: var(--spring3)
|
||||
}
|
||||
|
||||
.notificationRowIcon {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.notificationRowIcon > * {
|
||||
margin-left: 24px
|
||||
}
|
||||
|
||||
.readIconWrapper {
|
||||
flex-grow: 1
|
||||
}
|
||||
|
||||
.unreadIcon {
|
||||
margin-top: 5px;
|
||||
margin-left: 8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--spring);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.readIcon {
|
||||
margin-left: 8px;
|
||||
margin-top: 5px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 1px solid var(--comet);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.notificationTitle {
|
||||
margin: 0;
|
||||
color: var(--comet);
|
||||
}
|
||||
|
||||
.notificationBody {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.notificationSubtitle {
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
color: var(--comet);
|
||||
}
|
||||
|
||||
.stripes {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
opacity: 60%;
|
||||
}
|
||||
|
||||
.hasUnread {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 16px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-color: var(--spring);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import classnames from 'classnames'
|
||||
import prettyMs from 'pretty-ms'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
import { Label1, Label2, TL2 } from 'src/components/typography'
|
||||
import Wrench from 'src/styling/icons/action/wrench/zodiac.svg?react'
|
||||
import Transaction from 'src/styling/icons/arrow/transaction.svg?react'
|
||||
import WarningIcon from 'src/styling/icons/warning-icon/tomato.svg?react'
|
||||
|
||||
import classes from './NotificationCenter.module.css'
|
||||
|
||||
const types = {
|
||||
transaction: {
|
||||
display: 'Transactions',
|
||||
icon: <Transaction height={16} width={16} />
|
||||
},
|
||||
highValueTransaction: {
|
||||
display: 'Transactions',
|
||||
icon: <Transaction height={16} width={16} />
|
||||
},
|
||||
fiatBalance: {
|
||||
display: 'Maintenance',
|
||||
icon: <Wrench height={16} width={16} />
|
||||
},
|
||||
cryptoBalance: {
|
||||
display: 'Maintenance',
|
||||
icon: <Wrench height={16} width={16} />
|
||||
},
|
||||
compliance: {
|
||||
display: 'Compliance',
|
||||
icon: <WarningIcon height={16} width={16} />
|
||||
},
|
||||
error: { display: 'Error', icon: <WarningIcon height={16} width={16} /> }
|
||||
}
|
||||
|
||||
const NotificationRow = ({
|
||||
id,
|
||||
type,
|
||||
detail,
|
||||
message,
|
||||
deviceName,
|
||||
created,
|
||||
read,
|
||||
valid,
|
||||
toggleClear
|
||||
}) => {
|
||||
const typeDisplay = R.path([type, 'display'])(types) ?? null
|
||||
const icon = R.path([type, 'icon'])(types) ?? (
|
||||
<Wrench height={16} width={16} />
|
||||
)
|
||||
const age = prettyMs(new Date().getTime() - new Date(created).getTime(), {
|
||||
compact: true,
|
||||
verbose: true
|
||||
})
|
||||
const notificationTitle =
|
||||
typeDisplay && deviceName
|
||||
? `${typeDisplay} - ${deviceName}`
|
||||
: !typeDisplay && deviceName
|
||||
? `${deviceName}`
|
||||
: `${typeDisplay}`
|
||||
|
||||
const iconClass = {
|
||||
[classes.readIcon]: read,
|
||||
[classes.unreadIcon]: !read
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
classes.notificationRow,
|
||||
!read && valid ? classes.unread : ''
|
||||
)}>
|
||||
<div className={classes.notificationRowIcon}>
|
||||
<div>{icon}</div>
|
||||
</div>
|
||||
<div className={classes.notificationContent}>
|
||||
<Label2 className={classes.notificationTitle}>
|
||||
{notificationTitle}
|
||||
</Label2>
|
||||
<TL2 className={classes.notificationBody}>{message}</TL2>
|
||||
<Label1 className={classes.notificationSubtitle}>{age}</Label1>
|
||||
</div>
|
||||
<div className={classes.readIconWrapper}>
|
||||
<div
|
||||
onClick={() => toggleClear(id)}
|
||||
className={classnames(iconClass)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationRow
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import NotificationCenter from './NotificationCenter'
|
||||
export default NotificationCenter
|
||||
Loading…
Add table
Add a link
Reference in a new issue