import { Divider, Tooltip } from '@mui/material' import { formatTimestamp, fromUnixTimestamp, hexToNpub, npubToHex, SigitStatus, SignStatus } from '../../utils' import { useSigitMeta } from '../../hooks/useSigitMeta' import { UserAvatarGroup } from '../UserAvatarGroup' import styles from './style.module.scss' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCalendar, faCalendarCheck, faCalendarPlus, faCheck, faClock, faEye, faServer, faFile, faFileCircleExclamation } from '@fortawesome/free-solid-svg-icons' import { getExtensionIconLabel } from '../getExtensionIconLabel' import { useAppSelector } from '../../hooks/store' import { DisplaySigner } from '../DisplaySigner' import { Meta, OpenTimestamp } from '../../types' import { extractFileExtensions } from '../../utils/file' import { UserAvatar } from '../UserAvatar' interface UsersDetailsProps { meta: Meta } export const UsersDetails = ({ meta }: UsersDetailsProps) => { const { submittedBy, exportedBy, signers, viewers, fileHashes, zipUrls, signersStatus, createdAt, completedAt, parsedSignatureEvents, signedStatus, isValid, id, timestamps } = useSigitMeta(meta) const { usersPubkey } = useAppSelector((state) => state.auth) const userCanSign = typeof usersPubkey !== 'undefined' && signers.includes(hexToNpub(usersPubkey)) const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes)) 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)) { return <FontAwesomeIcon className={styles.ticket} icon={faCheck} /> } else { return <FontAwesomeIcon className={styles.ticket} icon={faClock} /> } } const getCompletedOpenTimestampsInfo = (timestamp: OpenTimestamp) => { if (timestamp.verification) { return <FontAwesomeIcon className={styles.ticket} icon={faCheck} /> } else { return <FontAwesomeIcon className={styles.ticket} icon={faClock} /> } } 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 } /** * 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' } } return submittedBy ? ( <div className={styles.container}> <div className={styles.section}> <p>Signers</p> <div className={styles.users}> {submittedBy && ( <DisplaySigner status={isValid ? SignStatus.Signed : SignStatus.Invalid} pubkey={submittedBy} /> )} {submittedBy && signers.length ? ( <Divider orientation="vertical" flexItem /> ) : null} <UserAvatarGroup max={20}> {signers.map((signer) => { const pubkey = npubToHex(signer)! return ( <DisplaySigner key={pubkey} status={signersStatus[signer]} pubkey={pubkey} /> ) })} </UserAvatarGroup> </div> {viewers.length > 0 && ( <> <p>Viewers</p> <div className={styles.users}> <UserAvatarGroup max={20}> {viewers.map((signer) => { const pubkey = npubToHex(signer)! return ( <DisplaySigner key={pubkey} status={SignStatus.Viewer} pubkey={pubkey} /> ) })} </UserAvatarGroup> </div> </> )} {exportedBy && ( <> <p>Exported By</p> <div className={styles.users}> <UserAvatar pubkey={exportedBy} /> </div> </> )} </div> <div className={styles.section}> <p>Details</p> <Tooltip title={getTimestampTooltipTitle( 'Publication date', !!(timestamps && id && isTimestampVerified(timestamps, id)) )} placement="top" arrow disableInteractive > <span className={styles.detailsItem}> <FontAwesomeIcon icon={faCalendarPlus} />{' '} {createdAt ? formatTimestamp(createdAt) : <>—</>}{' '} {timestamps && timestamps.length > 0 && id && getOpenTimestampsInfo(timestamps, id)} </span> </Tooltip> <Tooltip title={getTimestampTooltipTitle( 'Completion date', !!( signedStatus === SigitStatus.Complete && completedAt && timestamps && timestamps.length > 0 && timestamps[timestamps.length - 1].verification ) )} placement="top" arrow disableInteractive > <span className={styles.detailsItem}> <FontAwesomeIcon icon={faCalendarCheck} />{' '} {completedAt ? formatTimestamp(completedAt) : <>—</>} {signedStatus === SigitStatus.Complete && completedAt && timestamps && timestamps.length > 0 && ( <span className={styles.ticket}> {getCompletedOpenTimestampsInfo( timestamps[timestamps.length - 1] )} </span> )} </span> </Tooltip> {/* User signed date */} {userCanSign ? ( <Tooltip title={getTimestampTooltipTitle( 'Your signature date', isUserSignatureTimestampVerified() )} 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 ) ) ) : ( <>—</> ) ) : ( <>—</> )} {hexToNpub(usersPubkey) in parsedSignatureEvents && timestamps && timestamps.length > 0 && ( <span className={styles.ticket}> {getOpenTimestampsInfo( timestamps, parsedSignatureEvents[hexToNpub(usersPubkey)].id )} </span> )} </span> </Tooltip> ) : null} <span className={styles.detailsItem}> <FontAwesomeIcon icon={faEye} /> {signedStatus} </span> {extensions.length > 0 ? ( <span className={styles.detailsItem}> {!isSame ? ( <> <FontAwesomeIcon icon={faFile} /> Multiple File Types </> ) : ( getExtensionIconLabel(extensions[0]) )} </span> ) : ( <> <FontAwesomeIcon icon={faFileCircleExclamation} /> — </> )} <span className={styles.detailsItem}> <FontAwesomeIcon icon={faEye} /> {signedStatus} </span> </div> <div className={styles.section}> <p>File Servers</p> {zipUrls.map((url) => ( <span className={styles.detailsItem} key={url}> <FontAwesomeIcon icon={faServer} /> {getBaseUrl(url)} </span> ))} </div> </div> ) : undefined }