Compare commits
No commits in common. "9470adbb8a4f0b55623bc530a53be9fcae547e12" and "8f2cc49f9b9ba94e6a41fe5293be7438f41cb838" have entirely different histories.
9470adbb8a
...
8f2cc49f9b
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<g transform="matrix(1,0,0,1,0,-0.113382)">
|
|
||||||
<g transform="matrix(0.725404,0,0,0.725404,3.539221,11.473342)">
|
|
||||||
<path d="M43.789,56.563L11.031,56.562C3.922,56.562 0,52.672 0,45.594L0,11C0,3.922 3.906,0.031 10.516,0.031L67.422,0.031C74.547,0.031 78.469,3.922 78.469,11L78.469,39.642C76.475,37.218 73.921,35.265 71.016,33.991L71.016,11.879L54.327,26.905L60.012,32.59C57.573,32.984 55.276,33.831 53.211,35.039L49.46,31.288L45.719,34.656C43.594,36.578 41.609,37.391 39.219,37.391C36.844,37.391 34.844,36.578 32.734,34.656L28.993,31.29L11.188,49.108L11.328,49.109L43.533,49.109C43.374,50.11 43.292,51.133 43.292,52.174C43.292,53.679 43.464,55.148 43.789,56.563ZM7.453,43.594L24.132,26.915L7.453,11.906L7.453,43.594ZM12.279,7.484L36.734,29.547C37.547,30.266 38.359,30.641 39.219,30.641C40.094,30.641 40.906,30.266 41.719,29.547L66.174,7.484L12.279,7.484Z"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.692828,0,0,0.692828,-0,0.822733)">
|
|
||||||
<path d="M87.672,70C87.672,78.984 80.188,86.469 71.219,86.469C62.203,86.469 54.766,79.016 54.766,70C54.766,61 62.203,53.562 71.219,53.562C80.234,53.562 87.672,61 87.672,70ZM75.922,62.812L68.984,72.391L65.766,68.766C65.281,68.219 64.609,67.938 63.797,67.938C62.391,67.938 61.062,68.906 61.062,70.641C61.062,71.328 61.391,72.031 61.891,72.578L67.047,78.156C67.594,78.766 68.453,79 69.188,79C70.078,79 70.938,78.625 71.391,78.016L80.375,65.828C80.719,65.344 80.875,64.781 80.875,64.297C80.875,62.812 79.672,61.625 78.172,61.625C77.266,61.625 76.453,62.078 75.922,62.812Z" style="fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB |
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<g transform="matrix(0.725404,0,0,0.725404,3.539221,11.473342)">
|
|
||||||
<path d="M11.031,56.562C3.922,56.562 0,52.672 0,45.594L0,11C0,3.922 3.906,0.031 10.516,0.031L67.422,0.031C74.547,0.031 78.469,3.922 78.469,11L78.469,45.594C78.469,52.672 74.562,56.562 67.953,56.562L11.031,56.562ZM7.453,43.594L24.132,26.915L7.453,11.906L7.453,43.594ZM67.279,49.107L49.46,31.288L45.719,34.656C43.594,36.578 41.609,37.391 39.219,37.391C36.844,37.391 34.844,36.578 32.734,34.656L28.993,31.29L11.188,49.108C11.234,49.109 11.281,49.109 11.328,49.109L67.109,49.109C67.167,49.109 67.223,49.109 67.279,49.107ZM71.016,11.879L54.327,26.905L71.016,43.594L71.016,11.879ZM12.279,7.484L36.734,29.547C37.547,30.266 38.359,30.641 39.219,30.641C40.094,30.641 40.906,30.266 41.719,29.547L66.174,7.484L12.279,7.484Z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -28,7 +28,6 @@ import { NotificationProvider } from './components/Dashboard/context/Notificatio
|
|||||||
import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx'
|
import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx'
|
||||||
import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx'
|
import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx'
|
||||||
import AuthCallback from './components/App/AuthCallback.jsx'
|
import AuthCallback from './components/App/AuthCallback.jsx'
|
||||||
import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProductionRoutes,
|
ProductionRoutes,
|
||||||
@ -93,10 +92,6 @@ const AppContent = () => {
|
|||||||
path='/auth/callback'
|
path='/auth/callback'
|
||||||
element={<AuthCallback />}
|
element={<AuthCallback />}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path='/email/notification'
|
|
||||||
element={<EmailNotificationTemplate />}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
path='/dashboard'
|
path='/dashboard'
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -259,12 +259,7 @@ const DashboardNavigation = () => {
|
|||||||
onClick={() => showSpotlight()}
|
onClick={() => showSpotlight()}
|
||||||
/>
|
/>
|
||||||
</KeyboardShortcut>
|
</KeyboardShortcut>
|
||||||
<Badge
|
<Badge count={unreadCount} size='small' offset={[-4, 5]}>
|
||||||
count={unreadCount}
|
|
||||||
size='small'
|
|
||||||
offset={[-5, 8]}
|
|
||||||
style={{ padding: 0, fontWeight: 600 }}
|
|
||||||
>
|
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
shortcut='alt+n'
|
shortcut='alt+n'
|
||||||
hint='ALT N'
|
hint='ALT N'
|
||||||
|
|||||||
@ -22,22 +22,13 @@ import MissingPlaceholder from './MissingPlaceholder'
|
|||||||
import NewNote from '../Management/Notes/NewNote'
|
import NewNote from '../Management/Notes/NewNote'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
||||||
import UserNotifierToggle from './UserNotifierToggle'
|
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const NoteItem = ({
|
const NoteItem = ({ note }) => {
|
||||||
note,
|
|
||||||
showCard = true,
|
|
||||||
showCreatedAt = true,
|
|
||||||
showChildNotes = true,
|
|
||||||
showActions = true,
|
|
||||||
showInfo = true,
|
|
||||||
largeSpacing = false
|
|
||||||
}) => {
|
|
||||||
const [childNotes, setChildNotes] = useState([])
|
const [childNotes, setChildNotes] = useState([])
|
||||||
const noteModel = getModelByName('note')
|
const noteModel = getModelByName('note')
|
||||||
const infoAction = noteModel.actions.filter(
|
const infoAction = noteModel.actions.filter(
|
||||||
@ -160,8 +151,15 @@ const NoteItem = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteItem = (
|
return (
|
||||||
<>
|
<Card
|
||||||
|
key={note._id}
|
||||||
|
size='small'
|
||||||
|
style={{
|
||||||
|
backgroundColor: note.noteType.color + '26',
|
||||||
|
textAlign: 'left'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Flex vertical gap={'small'}>
|
<Flex vertical gap={'small'}>
|
||||||
<Flex gap={'middle'} align='start'>
|
<Flex gap={'middle'} align='start'>
|
||||||
<Space>
|
<Space>
|
||||||
@ -172,10 +170,8 @@ const NoteItem = ({
|
|||||||
<MarkdownDisplay content={note.content} />
|
<MarkdownDisplay content={note.content} />
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider style={{ margin: largeSpacing ? '10px 0' : 0 }} />
|
<Divider style={{ margin: 0 }} />
|
||||||
<Flex wrap gap={'small'}>
|
<Flex wrap gap={'small'}>
|
||||||
{showActions && (
|
|
||||||
<>
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{ items: dropdownItems }}
|
menu={{ items: dropdownItems }}
|
||||||
trigger={['hover']}
|
trigger={['hover']}
|
||||||
@ -183,14 +179,6 @@ const NoteItem = ({
|
|||||||
>
|
>
|
||||||
<Button size='small'>Actions</Button>
|
<Button size='small'>Actions</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<UserNotifierToggle
|
|
||||||
type='note'
|
|
||||||
size='small'
|
|
||||||
objectData={note}
|
|
||||||
disabled={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Space size={'small'} style={{ marginRight: 8 }}>
|
<Space size={'small'} style={{ marginRight: 8 }}>
|
||||||
<Text type='secondary'>Type:</Text>
|
<Text type='secondary'>Type:</Text>
|
||||||
<Tag color={note.noteType.color} style={{ margin: 0 }}>
|
<Tag color={note.noteType.color} style={{ margin: 0 }}>
|
||||||
@ -206,15 +194,12 @@ const NoteItem = ({
|
|||||||
showHyperlink={true}
|
showHyperlink={true}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
{showCreatedAt && (
|
|
||||||
<Space size={'small'} style={{ marginRight: 8 }}>
|
<Space size={'small'} style={{ marginRight: 8 }}>
|
||||||
<Text type='secondary'>Created At:</Text>
|
<Text type='secondary'>Created At:</Text>
|
||||||
<TimeDisplay dateTime={note.createdAt} showSince={true} />
|
<TimeDisplay dateTime={note.createdAt} showSince={true} />
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
|
||||||
<Flex style={{ flexGrow: 1 }} justify='end'>
|
<Flex style={{ flexGrow: 1 }} justify='end'>
|
||||||
<Space size={'small'}>
|
<Space size={'small'}>
|
||||||
{showInfo && (
|
|
||||||
<Button
|
<Button
|
||||||
icon={<InfoIcon />}
|
icon={<InfoIcon />}
|
||||||
type='text'
|
type='text'
|
||||||
@ -223,15 +208,9 @@ const NoteItem = ({
|
|||||||
navigate(infoAction.url(note._id))
|
navigate(infoAction.url(note._id))
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{showChildNotes && (
|
|
||||||
<Button
|
<Button
|
||||||
icon={
|
icon={
|
||||||
childNotesLoading ? (
|
childNotesLoading ? <LoadingOutlined /> : <CaretLeftFilled />
|
||||||
<LoadingOutlined />
|
|
||||||
) : (
|
|
||||||
<CaretLeftFilled />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
size='small'
|
size='small'
|
||||||
type='text'
|
type='text'
|
||||||
@ -243,7 +222,6 @@ const NoteItem = ({
|
|||||||
}}
|
}}
|
||||||
onClick={toggleExpand}
|
onClick={toggleExpand}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -318,33 +296,12 @@ const NoteItem = ({
|
|||||||
>
|
>
|
||||||
<Text>Are you sure you want to delete this note?</Text>
|
<Text>Are you sure you want to delete this note?</Text>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
return showCard ? (
|
|
||||||
<Card
|
|
||||||
key={note._id}
|
|
||||||
size='small'
|
|
||||||
style={{
|
|
||||||
backgroundColor: note.noteType.color + '26',
|
|
||||||
textAlign: 'left'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{noteItem}
|
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
|
||||||
noteItem
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteItem.propTypes = {
|
NoteItem.propTypes = {
|
||||||
note: PropTypes.object.isRequired,
|
note: PropTypes.object.isRequired
|
||||||
showCard: PropTypes.bool,
|
|
||||||
showCreatedAt: PropTypes.bool,
|
|
||||||
showChildNotes: PropTypes.bool,
|
|
||||||
showActions: PropTypes.bool,
|
|
||||||
largeSpacing: PropTypes.bool,
|
|
||||||
showInfo: PropTypes.bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NoteItem
|
export default NoteItem
|
||||||
|
|||||||
@ -13,8 +13,6 @@ import PropertyChanges from './PropertyChanges'
|
|||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import ObjectDisplay from './ObjectDisplay'
|
import ObjectDisplay from './ObjectDisplay'
|
||||||
import BinIcon from '../../Icons/BinIcon'
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
import NoteItem from './NoteItem'
|
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography
|
const { Text, Paragraph } = Typography
|
||||||
|
|
||||||
@ -25,9 +23,7 @@ const Notification = ({
|
|||||||
showCard = true,
|
showCard = true,
|
||||||
showDelete = true,
|
showDelete = true,
|
||||||
showExtraInfo = true,
|
showExtraInfo = true,
|
||||||
inlineIcon = false,
|
inlineIcon = false
|
||||||
largeSpacing = false,
|
|
||||||
showSince = true
|
|
||||||
}) => {
|
}) => {
|
||||||
const [deleting, setDeleting] = useState(false)
|
const [deleting, setDeleting] = useState(false)
|
||||||
|
|
||||||
@ -37,8 +33,6 @@ const Notification = ({
|
|||||||
return <InfoCircleOutlined />
|
return <InfoCircleOutlined />
|
||||||
case 'editObject':
|
case 'editObject':
|
||||||
return <EditIcon />
|
return <EditIcon />
|
||||||
case 'newNote':
|
|
||||||
return <PlusIcon />
|
|
||||||
case 'deleteObject':
|
case 'deleteObject':
|
||||||
return <BinIcon />
|
return <BinIcon />
|
||||||
case 'error':
|
case 'error':
|
||||||
@ -71,12 +65,6 @@ const Notification = ({
|
|||||||
Delete
|
Delete
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
case 'newNote':
|
|
||||||
return (
|
|
||||||
<Tag color='green' icon={icon} style={{ margin: 0 }}>
|
|
||||||
Note
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
case 'error':
|
case 'error':
|
||||||
return (
|
return (
|
||||||
<Tag color='red' icon={icon} style={{ margin: 0 }}>
|
<Tag color='red' icon={icon} style={{ margin: 0 }}>
|
||||||
@ -120,7 +108,7 @@ const Notification = ({
|
|||||||
expandable: true,
|
expandable: true,
|
||||||
symbol: 'Show more'
|
symbol: 'Show more'
|
||||||
},
|
},
|
||||||
style: { margin: largeSpacing ? '10px 0 0 0' : 0 }
|
style: { margin: 0 }
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'editObject': {
|
case 'editObject': {
|
||||||
@ -128,7 +116,7 @@ const Notification = ({
|
|||||||
<Paragraph {...paragraph}>
|
<Paragraph {...paragraph}>
|
||||||
Object:
|
Object:
|
||||||
<ObjectDisplay
|
<ObjectDisplay
|
||||||
object={{ ...metadata?.object, _id: metadata?.object?._id }}
|
object={metadata.object}
|
||||||
objectType={metadata.objectType}
|
objectType={metadata.objectType}
|
||||||
showHyperlink={true}
|
showHyperlink={true}
|
||||||
showSpotlight={true}
|
showSpotlight={true}
|
||||||
@ -143,21 +131,6 @@ const Notification = ({
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case 'newNote': {
|
|
||||||
return (
|
|
||||||
<Paragraph {...paragraph}>
|
|
||||||
<NoteItem
|
|
||||||
note={metadata.note}
|
|
||||||
showCard={false}
|
|
||||||
showCreatedAt={false}
|
|
||||||
showChildNotes={false}
|
|
||||||
showActions={false}
|
|
||||||
showInfo={false}
|
|
||||||
largeSpacing={largeSpacing}
|
|
||||||
/>
|
|
||||||
</Paragraph>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return <Paragraph {...paragraph}>{notification.message}</Paragraph>
|
return <Paragraph {...paragraph}>{notification.message}</Paragraph>
|
||||||
}
|
}
|
||||||
@ -219,13 +192,10 @@ const Notification = ({
|
|||||||
getMetadataDisplay(notification.metadata, notification.type)}
|
getMetadataDisplay(notification.metadata, notification.type)}
|
||||||
{showExtraInfo && (
|
{showExtraInfo && (
|
||||||
<>
|
<>
|
||||||
<Divider style={{ margin: largeSpacing ? '10px 0' : 0 }} />
|
<Divider style={{ margin: 0 }} />
|
||||||
<Flex justify='space-between' align='center'>
|
<Flex justify='space-between' align='center'>
|
||||||
{getNotificationTag(notification.type)}
|
{getNotificationTag(notification.type)}
|
||||||
<TimeDisplay
|
<TimeDisplay dateTime={notification.createdAt} showSince={true} />
|
||||||
dateTime={notification.createdAt}
|
|
||||||
showSince={showSince}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -266,9 +236,7 @@ Notification.propTypes = {
|
|||||||
showCard: PropTypes.bool,
|
showCard: PropTypes.bool,
|
||||||
showDelete: PropTypes.bool,
|
showDelete: PropTypes.bool,
|
||||||
showExtraInfo: PropTypes.bool,
|
showExtraInfo: PropTypes.bool,
|
||||||
inlineIcon: PropTypes.bool,
|
inlineIcon: PropTypes.bool
|
||||||
largeSpacing: PropTypes.bool,
|
|
||||||
showSince: PropTypes.bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Notification
|
export default Notification
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Typography, Flex, Badge, Tag, Popover } from 'antd'
|
import { Typography, Flex, Badge, Tag, Popover } from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
|
||||||
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
|
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
@ -19,7 +18,6 @@ const ObjectDisplay = ({
|
|||||||
showSpotlight = true
|
showSpotlight = true
|
||||||
}) => {
|
}) => {
|
||||||
const [objectData, setObjectData] = useState(object)
|
const [objectData, setObjectData] = useState(object)
|
||||||
const [isHydrating, setIsHydrating] = useState(false)
|
|
||||||
const { subscribeToObjectUpdates, connected, fetchSpotlightData } =
|
const { subscribeToObjectUpdates, connected, fetchSpotlightData } =
|
||||||
useContext(ApiServerContext)
|
useContext(ApiServerContext)
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
@ -46,12 +44,7 @@ const ObjectDisplay = ({
|
|||||||
const model = getModelByName(objectType)
|
const model = getModelByName(objectType)
|
||||||
const spotlightQuery = `${model.prefix}:${obj._id}`
|
const spotlightQuery = `${model.prefix}:${obj._id}`
|
||||||
const spotlightResult = await fetchSpotlightData(spotlightQuery)
|
const spotlightResult = await fetchSpotlightData(spotlightQuery)
|
||||||
// Spotlight returns [] when not found; only use single-object results
|
if (spotlightResult && typeof spotlightResult === 'object') {
|
||||||
if (
|
|
||||||
spotlightResult &&
|
|
||||||
typeof spotlightResult === 'object' &&
|
|
||||||
!Array.isArray(spotlightResult)
|
|
||||||
) {
|
|
||||||
return spotlightResult
|
return spotlightResult
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -64,7 +57,7 @@ const ObjectDisplay = ({
|
|||||||
|
|
||||||
// Subscribe to object updates when component mounts
|
// Subscribe to object updates when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (object?._id && objectType && connected && token != null) {
|
if (object?._id && objectType && connected && token) {
|
||||||
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
||||||
object._id,
|
object._id,
|
||||||
objectType,
|
objectType,
|
||||||
@ -86,49 +79,22 @@ const ObjectDisplay = ({
|
|||||||
|
|
||||||
// Update local state when object prop changes
|
// Update local state when object prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token == null) return
|
if (idRef.current == object?._id) return
|
||||||
const isMinimal = isMinimalObject(object)
|
idRef.current = object?._id
|
||||||
// Only skip re-fetch when we have a minimal object and already hydrated this id
|
|
||||||
if (isMinimal && idRef.current === object?._id) return
|
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
if (isMinimal) setIsHydrating(true)
|
|
||||||
const hydrateObject = async () => {
|
const hydrateObject = async () => {
|
||||||
const fullObject = await fetchFullObjectIfNeeded(object)
|
const fullObject = await fetchFullObjectIfNeeded(object)
|
||||||
if (!cancelled) {
|
if (!cancelled) setObjectData(fullObject)
|
||||||
setObjectData((prev) => merge({}, prev, fullObject))
|
|
||||||
if (isMinimal) idRef.current = object?._id
|
|
||||||
setIsHydrating(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hydrateObject()
|
hydrateObject()
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
setIsHydrating(false)
|
|
||||||
}
|
}
|
||||||
}, [object, fetchFullObjectIfNeeded, isMinimalObject, token])
|
}, [object, fetchFullObjectIfNeeded])
|
||||||
if (!objectData) {
|
if (!objectData) {
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHydrating) {
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
style={{
|
|
||||||
margin: 0,
|
|
||||||
border: 'none',
|
|
||||||
minWidth: 0,
|
|
||||||
maxWidth: '100%'
|
|
||||||
}}
|
|
||||||
className='object-display-tag'
|
|
||||||
>
|
|
||||||
<Flex gap='4px' align='center' style={{ minWidth: 0, height: '24px' }}>
|
|
||||||
<LoadingOutlined spin />
|
|
||||||
<Text type='secondary'>Loading...</Text>
|
|
||||||
</Flex>
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = getModelByName(objectType)
|
const model = getModelByName(objectType)
|
||||||
const Icon = model.icon
|
const Icon = model.icon
|
||||||
const prefix = model.prefix
|
const prefix = model.prefix
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useState, useEffect, useContext, useRef, useCallback, useMemo, memo } from 'react'
|
import { useState, useEffect, useContext } from 'react'
|
||||||
import { Button, message, Popover, Typography, Space, Flex } from 'antd'
|
import { Button, message, Popover, Typography, Space, Flex } from 'antd'
|
||||||
import { UserOutlined } from '@ant-design/icons'
|
import { UserOutlined } from '@ant-design/icons'
|
||||||
import BellIcon from '../../Icons/BellIcon'
|
import BellIcon from '../../Icons/BellIcon'
|
||||||
import MailCheckIcon from '../../Icons/MailCheckIcon'
|
import NewMailIcon from '../../Icons/NewMailIcon'
|
||||||
import MailIcon from '../../Icons/MailIcon'
|
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
@ -12,10 +11,9 @@ import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
|||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const UserNotifierToggle = memo(({
|
const UserNotifierToggle = ({
|
||||||
type,
|
type,
|
||||||
objectData,
|
objectData,
|
||||||
size = 'middle',
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
...buttonProps
|
...buttonProps
|
||||||
}) => {
|
}) => {
|
||||||
@ -25,7 +23,7 @@ const UserNotifierToggle = memo(({
|
|||||||
fetchUserNotifiersForObject,
|
fetchUserNotifiersForObject,
|
||||||
fetchAllUserNotifiersForObject
|
fetchAllUserNotifiersForObject
|
||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const { userProfile, token, authInitialized } = useContext(AuthContext)
|
const { userProfile } = useContext(AuthContext)
|
||||||
const [isNotifying, setIsNotifying] = useState(false)
|
const [isNotifying, setIsNotifying] = useState(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [initialLoad, setInitialLoad] = useState(true)
|
const [initialLoad, setInitialLoad] = useState(true)
|
||||||
@ -35,24 +33,14 @@ const UserNotifierToggle = memo(({
|
|||||||
const [emailTogglingId, setEmailTogglingId] = useState(null)
|
const [emailTogglingId, setEmailTogglingId] = useState(null)
|
||||||
|
|
||||||
const objectId = objectData?._id
|
const objectId = objectData?._id
|
||||||
const authReady = Boolean(token && authInitialized)
|
|
||||||
|
|
||||||
const apiRef = useRef({
|
|
||||||
fetchUserNotifiersForObject,
|
|
||||||
fetchAllUserNotifiersForObject
|
|
||||||
})
|
|
||||||
apiRef.current = {
|
|
||||||
fetchUserNotifiersForObject,
|
|
||||||
fetchAllUserNotifiersForObject
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authReady || !objectId || !type) return
|
|
||||||
|
|
||||||
const loadNotifierState = async () => {
|
const loadNotifierState = async () => {
|
||||||
|
if (!objectId || !type) return
|
||||||
|
|
||||||
setInitialLoad(true)
|
setInitialLoad(true)
|
||||||
try {
|
try {
|
||||||
const { data } = await apiRef.current.fetchUserNotifiersForObject(objectId, type)
|
const { data } = await fetchUserNotifiersForObject(objectId, type)
|
||||||
setIsNotifying(data?.length > 0)
|
setIsNotifying(data?.length > 0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching user notifier state:', error)
|
console.error('Error fetching user notifier state:', error)
|
||||||
@ -62,15 +50,15 @@ const UserNotifierToggle = memo(({
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadNotifierState()
|
loadNotifierState()
|
||||||
}, [authReady, objectId, type])
|
}, [objectId, type, fetchUserNotifiersForObject])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authReady || !objectId || !type || !popoverOpen) return
|
|
||||||
|
|
||||||
const loadAllNotifiers = async () => {
|
const loadAllNotifiers = async () => {
|
||||||
|
if (!objectId || !type || !popoverOpen) return
|
||||||
|
|
||||||
setPopoverLoading(true)
|
setPopoverLoading(true)
|
||||||
try {
|
try {
|
||||||
const { data } = await apiRef.current.fetchAllUserNotifiersForObject(objectId, type)
|
const { data } = await fetchAllUserNotifiersForObject(objectId, type)
|
||||||
setAllNotifiers(data || [])
|
setAllNotifiers(data || [])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching all user notifiers:', error)
|
console.error('Error fetching all user notifiers:', error)
|
||||||
@ -81,10 +69,10 @@ const UserNotifierToggle = memo(({
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadAllNotifiers()
|
loadAllNotifiers()
|
||||||
}, [authReady, objectId, type, popoverOpen])
|
}, [objectId, type, popoverOpen, fetchAllUserNotifiersForObject])
|
||||||
|
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = async () => {
|
||||||
if (!authReady || !objectId || !type || loading) return
|
if (!objectId || !type || loading) return
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
@ -105,9 +93,9 @@ const UserNotifierToggle = memo(({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [authReady, objectId, type, loading, popoverOpen, toggleUserNotifier, fetchAllUserNotifiersForObject])
|
}
|
||||||
|
|
||||||
const getUserDisplayName = useCallback((user) => {
|
const getUserDisplayName = (user) => {
|
||||||
if (!user) return 'Unknown'
|
if (!user) return 'Unknown'
|
||||||
return (
|
return (
|
||||||
user.name ||
|
user.name ||
|
||||||
@ -116,11 +104,11 @@ const UserNotifierToggle = memo(({
|
|||||||
user.email ||
|
user.email ||
|
||||||
'Unknown'
|
'Unknown'
|
||||||
)
|
)
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
const isCurrentUser = useCallback((user) => user?._id === userProfile?._id, [userProfile?._id])
|
const isCurrentUser = (user) => user?._id === userProfile?._id
|
||||||
|
|
||||||
const handleEmailToggle = useCallback(async (item) => {
|
const handleEmailToggle = async (item) => {
|
||||||
if (!isCurrentUser(item.user) || emailTogglingId) return
|
if (!isCurrentUser(item.user) || emailTogglingId) return
|
||||||
setEmailTogglingId(item._id)
|
setEmailTogglingId(item._id)
|
||||||
const newEmail = !item.email
|
const newEmail = !item.email
|
||||||
@ -144,10 +132,9 @@ const UserNotifierToggle = memo(({
|
|||||||
} finally {
|
} finally {
|
||||||
setEmailTogglingId(null)
|
setEmailTogglingId(null)
|
||||||
}
|
}
|
||||||
}, [isCurrentUser, emailTogglingId, editUserNotifier])
|
}
|
||||||
|
|
||||||
const popoverContent = useMemo(
|
const popoverContent = (
|
||||||
() => (
|
|
||||||
<Flex
|
<Flex
|
||||||
vertical
|
vertical
|
||||||
justify='center'
|
justify='center'
|
||||||
@ -189,15 +176,11 @@ const UserNotifierToggle = memo(({
|
|||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
icon={
|
icon={
|
||||||
item.email ? (
|
<NewMailIcon
|
||||||
<MailCheckIcon
|
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-primary)'
|
color: item.email ? 'var(--color-primary)' : undefined
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<MailIcon />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
size='small'
|
size='small'
|
||||||
disabled={!isCurrentUser(item.user)}
|
disabled={!isCurrentUser(item.user)}
|
||||||
@ -210,8 +193,6 @@ const UserNotifierToggle = memo(({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
),
|
|
||||||
[popoverLoading, allNotifiers, emailTogglingId, isCurrentUser, getUserDisplayName, handleEmailToggle]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -223,7 +204,7 @@ const UserNotifierToggle = memo(({
|
|||||||
arrow={false}
|
arrow={false}
|
||||||
open={popoverOpen}
|
open={popoverOpen}
|
||||||
onOpenChange={setPopoverOpen}
|
onOpenChange={setPopoverOpen}
|
||||||
styles={{ body: { padding: '10px 12.5px 10px 15px' } }}
|
styles={{ body: { padding: '10px 15px' } }}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
@ -234,23 +215,18 @@ const UserNotifierToggle = memo(({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disabled={disabled || loading || initialLoad || !authReady}
|
disabled={disabled || loading || initialLoad}
|
||||||
loading={loading || !authReady || !objectId || !type}
|
loading={loading || !objectId || !type}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
size={size}
|
|
||||||
style={{ minWidth: size === 'small' ? 24 : undefined }}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
UserNotifierToggle.displayName = 'UserNotifierToggle'
|
|
||||||
|
|
||||||
UserNotifierToggle.propTypes = {
|
UserNotifierToggle.propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
objectData: PropTypes.object.isRequired,
|
objectData: PropTypes.object.isRequired,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool
|
||||||
size: PropTypes.string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserNotifierToggle
|
export default UserNotifierToggle
|
||||||
|
|||||||
@ -20,11 +20,6 @@ import loglevel from 'loglevel'
|
|||||||
const logger = loglevel.getLogger('ApiServerContext')
|
const logger = loglevel.getLogger('ApiServerContext')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
|
|
||||||
const SPOTLIGHT_CACHE_TTL_MS = 10_000
|
|
||||||
|
|
||||||
const spotlightCache = new Map()
|
|
||||||
const runningSpotlightFetches = new Map()
|
|
||||||
|
|
||||||
const ApiServerContext = createContext()
|
const ApiServerContext = createContext()
|
||||||
|
|
||||||
const ApiServerProvider = ({ children }) => {
|
const ApiServerProvider = ({ children }) => {
|
||||||
@ -979,20 +974,8 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchSpotlightData = async (query) => {
|
const fetchSpotlightData = async (query) => {
|
||||||
const cached = spotlightCache.get(query)
|
|
||||||
if (cached && Date.now() - cached.timestamp < SPOTLIGHT_CACHE_TTL_MS) {
|
|
||||||
logger.debug('Returning cached spotlight data for query:', query)
|
|
||||||
return cached.data
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = runningSpotlightFetches.get(query)
|
|
||||||
if (existing) {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchPromise = (async () => {
|
|
||||||
try {
|
|
||||||
logger.debug('Fetching spotlight data with query:', query)
|
logger.debug('Fetching spotlight data with query:', query)
|
||||||
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${config.backendUrl}/spotlight/${query}`,
|
`${config.backendUrl}/spotlight/${query}`,
|
||||||
{
|
{
|
||||||
@ -1002,20 +985,13 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
spotlightCache.set(query, { data: response.data, timestamp: Date.now() })
|
|
||||||
return response.data
|
return response.data
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
showError(err, () => {
|
showError(err, () => {
|
||||||
fetchSpotlightData(query)
|
fetchSpotlightData(query)
|
||||||
})
|
})
|
||||||
} finally {
|
|
||||||
runningSpotlightFetches.delete(query)
|
|
||||||
}
|
}
|
||||||
})()
|
|
||||||
|
|
||||||
runningSpotlightFetches.set(query, fetchPromise)
|
|
||||||
return fetchPromise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getModelStats = async (objectType) => {
|
const getModelStats = async (objectType) => {
|
||||||
|
|||||||
@ -297,7 +297,6 @@ const AuthProvider = ({ children }) => {
|
|||||||
setToken(nextToken)
|
setToken(nextToken)
|
||||||
setExpiresAt(nextExpiresAt)
|
setExpiresAt(nextExpiresAt)
|
||||||
setUserProfile(nextUser)
|
setUserProfile(nextUser)
|
||||||
setAuthenticated(true)
|
|
||||||
|
|
||||||
// Persist session (cookies on web, keytar on electron)
|
// Persist session (cookies on web, keytar on electron)
|
||||||
const persisted = await persistSession({
|
const persisted = await persistSession({
|
||||||
@ -620,7 +619,6 @@ const AuthProvider = ({ children }) => {
|
|||||||
<AuthContext.Provider
|
<AuthContext.Provider
|
||||||
value={{
|
value={{
|
||||||
authenticated,
|
authenticated,
|
||||||
authInitialized: retreivedTokenFromCookies,
|
|
||||||
setUnauthenticated,
|
setUnauthenticated,
|
||||||
loginWithSSO,
|
loginWithSSO,
|
||||||
getLoginToken,
|
getLoginToken,
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useEffect
|
useEffect
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
import { notification, Drawer } from 'antd'
|
import { notification, Drawer } from 'antd'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { AuthContext } from './AuthContext'
|
import { AuthContext } from './AuthContext'
|
||||||
@ -17,7 +16,6 @@ const NotificationContext = createContext()
|
|||||||
|
|
||||||
const NotificationProvider = ({ children }) => {
|
const NotificationProvider = ({ children }) => {
|
||||||
const [api, contextHolder] = notification.useNotification()
|
const [api, contextHolder] = notification.useNotification()
|
||||||
const location = useLocation()
|
|
||||||
const { authenticated } = useContext(AuthContext)
|
const { authenticated } = useContext(AuthContext)
|
||||||
const {
|
const {
|
||||||
showError,
|
showError,
|
||||||
@ -121,10 +119,6 @@ const NotificationProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [authenticated, fetchNotifications])
|
}, [authenticated, fetchNotifications])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setNotificationCenterVisible(false)
|
|
||||||
}, [location.pathname])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authenticated || !registerNotificationListener) return
|
if (!authenticated || !registerNotificationListener) return
|
||||||
const handleNotification = (notif) => {
|
const handleNotification = (notif) => {
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
#email-notification-root {
|
|
||||||
padding: 10% 20px;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-notification-card {
|
|
||||||
border-radius: 25px;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-notification-card-actions {
|
|
||||||
margin-top: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-notification-card-footer {
|
|
||||||
margin-top: 75px;
|
|
||||||
}
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
import { useSearchParams } from 'react-router-dom'
|
|
||||||
import './EmailNotificationTemplate.css'
|
|
||||||
import { Button, Card, ConfigProvider, Flex, theme, Typography } from 'antd'
|
|
||||||
import FarmControlLogo from '../Logos/FarmControlLogo'
|
|
||||||
import Notification from '../Dashboard/common/Notification'
|
|
||||||
import { useThemeContext } from '../Dashboard/context/ThemeContext'
|
|
||||||
import { getModelByName } from '../../database/ObjectModels'
|
|
||||||
|
|
||||||
const { Text } = Typography
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Email notification template - renders notification data for server-side HTML capture.
|
|
||||||
* Used by the API's sendEmailNotification with Puppeteer.
|
|
||||||
* Params: title, message, type, metadata (JSON string)
|
|
||||||
*/
|
|
||||||
const EmailNotificationTemplate = () => {
|
|
||||||
const { themeConfig } = useThemeContext()
|
|
||||||
const [searchParams] = useSearchParams()
|
|
||||||
const title = searchParams.get('title') || 'Notification'
|
|
||||||
const message = searchParams.get('message') || ''
|
|
||||||
const type = searchParams.get('type') || 'info'
|
|
||||||
const read = searchParams.get('read') || false
|
|
||||||
const email = searchParams.get('email') || ''
|
|
||||||
const createdAt = searchParams.get('createdAt') || new Date()
|
|
||||||
const updatedAt = searchParams.get('updatedAt') || new Date()
|
|
||||||
const origin = window.location.origin
|
|
||||||
|
|
||||||
let metadata = {}
|
|
||||||
try {
|
|
||||||
const metaStr = searchParams.get('metadata')
|
|
||||||
if (metaStr) metadata = JSON.parse(metaStr)
|
|
||||||
} catch {
|
|
||||||
// ignore parse errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const lightThemeConfig = {
|
|
||||||
...themeConfig,
|
|
||||||
algorithm: theme.defaultAlgorithm,
|
|
||||||
components: {
|
|
||||||
...themeConfig.components,
|
|
||||||
Layout: { headerBg: '#ffffff' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getNotifictionActions = () => {
|
|
||||||
switch (type) {
|
|
||||||
case 'editObject': {
|
|
||||||
const model = getModelByName(metadata?.objectType)
|
|
||||||
const infoAction = model.actions?.find(
|
|
||||||
(action) => action.name === 'info'
|
|
||||||
)
|
|
||||||
const url = infoAction ? infoAction.url(metadata?.object?._id) : '#'
|
|
||||||
return (
|
|
||||||
<Button href={`${origin}${url}`} type='primary'>
|
|
||||||
View in Dashboard
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'newNote': {
|
|
||||||
console.log('newNote metadata', JSON.stringify(metadata, null, 2))
|
|
||||||
const model = getModelByName(metadata?.note?.parentType)
|
|
||||||
const infoAction =
|
|
||||||
model.actions?.find((action) => action.name === 'info') ?? null
|
|
||||||
const url = infoAction
|
|
||||||
? infoAction.url(metadata?.note?.parent?._id)
|
|
||||||
: null
|
|
||||||
return url ? (
|
|
||||||
<Button href={`${origin}${url}`} type='primary'>
|
|
||||||
View in Dashboard
|
|
||||||
</Button>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConfigProvider theme={lightThemeConfig}>
|
|
||||||
<div id='email-notification-root' data-rendered='true'>
|
|
||||||
<FarmControlLogo
|
|
||||||
style={{
|
|
||||||
fontSize: '500px',
|
|
||||||
height: '40px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto 60px 0'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Card className='email-notification-card'>
|
|
||||||
<Notification
|
|
||||||
showCard={false}
|
|
||||||
showDelete={false}
|
|
||||||
inlineIcon={false}
|
|
||||||
largeSpacing={true}
|
|
||||||
showSince={false}
|
|
||||||
notification={{
|
|
||||||
title: title,
|
|
||||||
message: message,
|
|
||||||
type: type,
|
|
||||||
metadata: metadata,
|
|
||||||
read: read,
|
|
||||||
createdAt: createdAt,
|
|
||||||
updatedAt: updatedAt
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Flex justify='center' className='email-notification-card-actions'>
|
|
||||||
{getNotifictionActions()}
|
|
||||||
</Flex>
|
|
||||||
{email && (
|
|
||||||
<Flex
|
|
||||||
align='center'
|
|
||||||
className='email-notification-card-footer'
|
|
||||||
vertical
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
type='secondary'
|
|
||||||
style={{
|
|
||||||
fontSize: '12px',
|
|
||||||
maxWidth: '300px',
|
|
||||||
textAlign: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
This email was sent to {email}. Please do not reply to this email.
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ConfigProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EmailNotificationTemplate
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import Icon from '@ant-design/icons'
|
|
||||||
import CustomIconSvg from '../../../assets/icons/mailcheckicon.svg?react'
|
|
||||||
|
|
||||||
const MailCheckIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
|
||||||
|
|
||||||
export default MailCheckIcon
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import Icon from '@ant-design/icons'
|
|
||||||
import CustomIconSvg from '../../../assets/icons/mailicon.svg?react'
|
|
||||||
|
|
||||||
const MailIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
|
||||||
|
|
||||||
export default MailIcon
|
|
||||||
Loading…
x
Reference in New Issue
Block a user