2024-08-14 14:27:49 +02:00
|
|
|
import { Divider, Tooltip } from '@mui/material'
|
|
|
|
import {
|
|
|
|
formatTimestamp,
|
|
|
|
fromUnixTimestamp,
|
|
|
|
hexToNpub,
|
|
|
|
npubToHex,
|
2024-10-24 12:42:21 +03:00
|
|
|
SigitStatus,
|
2024-08-14 14:27:49 +02:00
|
|
|
SignStatus
|
|
|
|
} from '../../utils'
|
2024-08-14 14:44:11 +02:00
|
|
|
import { useSigitMeta } from '../../hooks/useSigitMeta'
|
2024-08-14 14:27:49 +02:00
|
|
|
import { UserAvatarGroup } from '../UserAvatarGroup'
|
|
|
|
|
|
|
|
import styles from './style.module.scss'
|
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
|
|
import {
|
|
|
|
faCalendar,
|
|
|
|
faCalendarCheck,
|
|
|
|
faCalendarPlus,
|
2024-10-24 12:42:21 +03:00
|
|
|
faCheck,
|
|
|
|
faClock,
|
2024-08-14 14:27:49 +02:00
|
|
|
faEye,
|
2025-01-06 12:29:30 +01:00
|
|
|
faServer,
|
2024-08-14 14:27:49 +02:00
|
|
|
faFile,
|
|
|
|
faFileCircleExclamation
|
|
|
|
} from '@fortawesome/free-solid-svg-icons'
|
|
|
|
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
2024-10-08 17:08:43 +02:00
|
|
|
import { useAppSelector } from '../../hooks/store'
|
2024-08-14 14:27:49 +02:00
|
|
|
import { DisplaySigner } from '../DisplaySigner'
|
2024-10-24 12:42:21 +03:00
|
|
|
import { Meta, OpenTimestamp } from '../../types'
|
2024-08-22 18:20:54 +02:00
|
|
|
import { extractFileExtensions } from '../../utils/file'
|
2024-09-13 18:13:34 +02:00
|
|
|
import { UserAvatar } from '../UserAvatar'
|
2024-08-14 14:27:49 +02:00
|
|
|
|
2024-08-14 14:44:11 +02:00
|
|
|
interface UsersDetailsProps {
|
|
|
|
meta: Meta
|
|
|
|
}
|
2024-08-14 14:27:49 +02:00
|
|
|
|
2024-08-14 14:44:11 +02:00
|
|
|
export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|
|
|
const {
|
|
|
|
submittedBy,
|
2024-09-13 18:13:34 +02:00
|
|
|
exportedBy,
|
2024-08-14 14:44:11 +02:00
|
|
|
signers,
|
|
|
|
viewers,
|
|
|
|
fileHashes,
|
2025-01-06 12:29:30 +01:00
|
|
|
zipUrls,
|
2024-08-14 14:44:11 +02:00
|
|
|
signersStatus,
|
|
|
|
createdAt,
|
|
|
|
completedAt,
|
|
|
|
parsedSignatureEvents,
|
2024-08-15 17:48:05 +02:00
|
|
|
signedStatus,
|
2024-10-24 12:42:21 +03:00
|
|
|
isValid,
|
|
|
|
id,
|
|
|
|
timestamps
|
2024-08-14 14:44:11 +02:00
|
|
|
} = useSigitMeta(meta)
|
2024-10-08 17:08:43 +02:00
|
|
|
const { usersPubkey } = useAppSelector((state) => state.auth)
|
2024-08-14 14:27:49 +02:00
|
|
|
const userCanSign =
|
|
|
|
typeof usersPubkey !== 'undefined' &&
|
|
|
|
signers.includes(hexToNpub(usersPubkey))
|
|
|
|
|
2024-08-14 18:59:00 +02:00
|
|
|
const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes))
|
2024-08-14 14:27:49 +02:00
|
|
|
|
2024-10-24 12:42:21 +03:00
|
|
|
const isTimestampVerified = (
|
|
|
|
timestamps: OpenTimestamp[],
|
|
|
|
nostrId: string
|
|
|
|
): boolean => {
|
|
|
|
const matched = timestamps.find((t) => t.nostrId === nostrId)
|
|
|
|
return !!(matched && matched.verification)
|
|
|
|
}
|
|
|
|
|
|
|
|
const getOpenTimestampsInfo = (
|
|
|
|
timestamps: OpenTimestamp[],
|
|
|
|
nostrId: string
|
|
|
|
) => {
|
|
|
|
if (isTimestampVerified(timestamps, nostrId)) {
|
2024-10-25 13:13:22 +03:00
|
|
|
return <FontAwesomeIcon className={styles.ticket} icon={faCheck} />
|
2024-10-24 12:42:21 +03:00
|
|
|
} else {
|
2024-10-25 13:13:22 +03:00
|
|
|
return <FontAwesomeIcon className={styles.ticket} icon={faClock} />
|
2024-10-24 12:42:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getCompletedOpenTimestampsInfo = (timestamp: OpenTimestamp) => {
|
|
|
|
if (timestamp.verification) {
|
2024-10-25 13:13:22 +03:00
|
|
|
return <FontAwesomeIcon className={styles.ticket} icon={faCheck} />
|
2024-10-24 12:42:21 +03:00
|
|
|
} else {
|
2024-10-25 13:13:22 +03:00
|
|
|
return <FontAwesomeIcon className={styles.ticket} icon={faClock} />
|
2024-10-24 12:42:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getTimestampTooltipTitle = (label: string, isVerified: boolean) => {
|
|
|
|
return `${label} / Open Timestamp ${isVerified ? 'Verified' : 'Pending'}`
|
|
|
|
}
|
|
|
|
|
|
|
|
const isUserSignatureTimestampVerified = () => {
|
|
|
|
if (
|
|
|
|
userCanSign &&
|
|
|
|
hexToNpub(usersPubkey) in parsedSignatureEvents &&
|
|
|
|
timestamps &&
|
|
|
|
timestamps.length > 0
|
|
|
|
) {
|
|
|
|
const nostrId = parsedSignatureEvents[hexToNpub(usersPubkey)].id
|
|
|
|
return isTimestampVerified(timestamps, nostrId)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2025-01-06 12:29:30 +01:00
|
|
|
/**
|
|
|
|
* Used to parse the base URL from Blossom server full path
|
|
|
|
*/
|
|
|
|
const getBaseUrl = (url: string): string => {
|
|
|
|
try {
|
|
|
|
const parsedUrl = new URL(url)
|
|
|
|
return `${parsedUrl.protocol}//${parsedUrl.host}`
|
|
|
|
} catch (error) {
|
|
|
|
return 'Invalid URL'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 14:27:49 +02:00
|
|
|
return submittedBy ? (
|
|
|
|
<div className={styles.container}>
|
|
|
|
<div className={styles.section}>
|
|
|
|
<p>Signers</p>
|
|
|
|
<div className={styles.users}>
|
2024-09-13 18:13:34 +02:00
|
|
|
{submittedBy && (
|
|
|
|
<DisplaySigner
|
|
|
|
status={isValid ? SignStatus.Signed : SignStatus.Invalid}
|
|
|
|
pubkey={submittedBy}
|
|
|
|
/>
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
|
|
|
|
{submittedBy && signers.length ? (
|
|
|
|
<Divider orientation="vertical" flexItem />
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
<UserAvatarGroup max={20}>
|
|
|
|
{signers.map((signer) => {
|
|
|
|
const pubkey = npubToHex(signer)!
|
|
|
|
return (
|
2024-09-13 18:20:10 +02:00
|
|
|
<DisplaySigner
|
|
|
|
key={pubkey}
|
|
|
|
status={signersStatus[signer]}
|
|
|
|
pubkey={pubkey}
|
|
|
|
/>
|
2024-08-14 14:27:49 +02:00
|
|
|
)
|
|
|
|
})}
|
|
|
|
</UserAvatarGroup>
|
|
|
|
</div>
|
2024-08-30 12:55:04 +02:00
|
|
|
|
|
|
|
{viewers.length > 0 && (
|
|
|
|
<>
|
|
|
|
<p>Viewers</p>
|
|
|
|
<div className={styles.users}>
|
|
|
|
<UserAvatarGroup max={20}>
|
|
|
|
{viewers.map((signer) => {
|
|
|
|
const pubkey = npubToHex(signer)!
|
|
|
|
|
|
|
|
return (
|
2024-09-13 18:20:10 +02:00
|
|
|
<DisplaySigner
|
|
|
|
key={pubkey}
|
|
|
|
status={SignStatus.Viewer}
|
|
|
|
pubkey={pubkey}
|
|
|
|
/>
|
2024-08-30 12:55:04 +02:00
|
|
|
)
|
|
|
|
})}
|
|
|
|
</UserAvatarGroup>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
2024-09-13 18:13:34 +02:00
|
|
|
|
|
|
|
{exportedBy && (
|
|
|
|
<>
|
|
|
|
<p>Exported By</p>
|
|
|
|
<div className={styles.users}>
|
|
|
|
<UserAvatar pubkey={exportedBy} />
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
</div>
|
|
|
|
<div className={styles.section}>
|
|
|
|
<p>Details</p>
|
|
|
|
|
|
|
|
<Tooltip
|
2024-10-24 12:42:21 +03:00
|
|
|
title={getTimestampTooltipTitle(
|
|
|
|
'Publication date',
|
|
|
|
!!(timestamps && id && isTimestampVerified(timestamps, id))
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
placement="top"
|
|
|
|
arrow
|
|
|
|
disableInteractive
|
|
|
|
>
|
|
|
|
<span className={styles.detailsItem}>
|
|
|
|
<FontAwesomeIcon icon={faCalendarPlus} />{' '}
|
2024-10-24 12:42:21 +03:00
|
|
|
{createdAt ? formatTimestamp(createdAt) : <>—</>}{' '}
|
2024-10-25 13:13:22 +03:00
|
|
|
{timestamps &&
|
|
|
|
timestamps.length > 0 &&
|
|
|
|
id &&
|
|
|
|
getOpenTimestampsInfo(timestamps, id)}
|
2024-08-14 14:27:49 +02:00
|
|
|
</span>
|
|
|
|
</Tooltip>
|
|
|
|
|
|
|
|
<Tooltip
|
2024-10-24 12:42:21 +03:00
|
|
|
title={getTimestampTooltipTitle(
|
|
|
|
'Completion date',
|
|
|
|
!!(
|
|
|
|
signedStatus === SigitStatus.Complete &&
|
|
|
|
completedAt &&
|
|
|
|
timestamps &&
|
|
|
|
timestamps.length > 0 &&
|
|
|
|
timestamps[timestamps.length - 1].verification
|
|
|
|
)
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
placement="top"
|
|
|
|
arrow
|
|
|
|
disableInteractive
|
|
|
|
>
|
|
|
|
<span className={styles.detailsItem}>
|
|
|
|
<FontAwesomeIcon icon={faCalendarCheck} />{' '}
|
|
|
|
{completedAt ? formatTimestamp(completedAt) : <>—</>}
|
2024-10-24 12:42:21 +03:00
|
|
|
{signedStatus === SigitStatus.Complete &&
|
|
|
|
completedAt &&
|
|
|
|
timestamps &&
|
|
|
|
timestamps.length > 0 && (
|
|
|
|
<span className={styles.ticket}>
|
|
|
|
{getCompletedOpenTimestampsInfo(
|
|
|
|
timestamps[timestamps.length - 1]
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
</span>
|
|
|
|
</Tooltip>
|
|
|
|
|
|
|
|
{/* User signed date */}
|
|
|
|
{userCanSign ? (
|
|
|
|
<Tooltip
|
2024-10-24 12:42:21 +03:00
|
|
|
title={getTimestampTooltipTitle(
|
|
|
|
'Your signature date',
|
|
|
|
isUserSignatureTimestampVerified()
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
placement="top"
|
|
|
|
arrow
|
|
|
|
disableInteractive
|
|
|
|
>
|
|
|
|
<span className={styles.detailsItem}>
|
|
|
|
<FontAwesomeIcon icon={faCalendar} />{' '}
|
|
|
|
{hexToNpub(usersPubkey) in parsedSignatureEvents ? (
|
|
|
|
parsedSignatureEvents[hexToNpub(usersPubkey)].created_at ? (
|
|
|
|
formatTimestamp(
|
|
|
|
fromUnixTimestamp(
|
|
|
|
parsedSignatureEvents[hexToNpub(usersPubkey)].created_at
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) : (
|
|
|
|
<>—</>
|
|
|
|
)
|
|
|
|
) : (
|
|
|
|
<>—</>
|
|
|
|
)}
|
2024-10-24 12:42:21 +03:00
|
|
|
{hexToNpub(usersPubkey) in parsedSignatureEvents &&
|
|
|
|
timestamps &&
|
|
|
|
timestamps.length > 0 && (
|
|
|
|
<span className={styles.ticket}>
|
|
|
|
{getOpenTimestampsInfo(
|
|
|
|
timestamps,
|
|
|
|
parsedSignatureEvents[hexToNpub(usersPubkey)].id
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
)}
|
2024-08-14 14:27:49 +02:00
|
|
|
</span>
|
|
|
|
</Tooltip>
|
|
|
|
) : null}
|
|
|
|
<span className={styles.detailsItem}>
|
|
|
|
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
|
|
|
</span>
|
2024-08-14 18:59:00 +02:00
|
|
|
{extensions.length > 0 ? (
|
2024-08-14 14:27:49 +02:00
|
|
|
<span className={styles.detailsItem}>
|
2024-08-14 18:59:00 +02:00
|
|
|
{!isSame ? (
|
2024-08-14 14:27:49 +02:00
|
|
|
<>
|
|
|
|
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
|
|
|
</>
|
|
|
|
) : (
|
2024-08-14 18:59:00 +02:00
|
|
|
getExtensionIconLabel(extensions[0])
|
2024-08-14 14:27:49 +02:00
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<FontAwesomeIcon icon={faFileCircleExclamation} /> —
|
|
|
|
</>
|
|
|
|
)}
|
2025-01-06 12:29:30 +01:00
|
|
|
<span className={styles.detailsItem}>
|
|
|
|
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className={styles.section}>
|
|
|
|
<p>File Servers</p>
|
|
|
|
|
2025-01-07 21:26:53 +01:00
|
|
|
{zipUrls &&
|
|
|
|
zipUrls.map((url) => (
|
|
|
|
<span className={styles.detailsItem} key={url}>
|
|
|
|
<FontAwesomeIcon icon={faServer} /> {getBaseUrl(url)}
|
|
|
|
</span>
|
|
|
|
))}
|
2024-08-14 14:27:49 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : undefined
|
|
|
|
}
|