Compare commits
2 Commits
5ffedb68d6
...
4b04bdf39e
Author | SHA1 | Date | |
---|---|---|---|
4b04bdf39e | |||
01ca81be2a |
@ -20,6 +20,7 @@ import styles from './style.module.scss'
|
||||
import { TooltipChild } from '../TooltipChild'
|
||||
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
||||
import { useSigitProfiles } from '../../hooks/useSigitProfiles'
|
||||
import { useSigitMeta } from '../../hooks/useSigitMeta'
|
||||
|
||||
type SigitProps = {
|
||||
meta: Meta
|
||||
@ -36,6 +37,8 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
||||
fileExtensions
|
||||
} = parsedMeta
|
||||
|
||||
const { signersStatus } = useSigitMeta(meta)
|
||||
|
||||
const profiles = useSigitProfiles([
|
||||
...(submittedBy ? [submittedBy] : []),
|
||||
...signers
|
||||
@ -94,7 +97,7 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
||||
>
|
||||
<TooltipChild>
|
||||
<DisplaySigner
|
||||
meta={meta}
|
||||
status={signersStatus[signer]}
|
||||
profile={profile}
|
||||
pubkey={pubkey}
|
||||
/>
|
||||
|
@ -1,58 +1,50 @@
|
||||
import { Badge } from '@mui/material'
|
||||
import { Event, verifyEvent } from 'nostr-tools'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Meta, ProfileMetadata } from '../../types'
|
||||
import { hexToNpub, parseJson } from '../../utils'
|
||||
import { ProfileMetadata } from '../../types'
|
||||
import styles from './style.module.scss'
|
||||
import { UserAvatar } from '../UserAvatar'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faCheck, faExclamation } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
enum SignStatus {
|
||||
Signed = 'Signed',
|
||||
Pending = 'Pending',
|
||||
Invalid = 'Invalid Sign'
|
||||
}
|
||||
import {
|
||||
faCheck,
|
||||
faExclamation,
|
||||
faEye,
|
||||
faHourglass,
|
||||
faQuestion
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { SignStatus } from '../../utils'
|
||||
import { Spinner } from '../Spinner'
|
||||
|
||||
type DisplaySignerProps = {
|
||||
meta: Meta
|
||||
profile: ProfileMetadata
|
||||
pubkey: string
|
||||
status: SignStatus
|
||||
}
|
||||
|
||||
export const DisplaySigner = ({
|
||||
meta,
|
||||
status,
|
||||
profile,
|
||||
pubkey
|
||||
}: DisplaySignerProps) => {
|
||||
const [signStatus, setSignedStatus] = useState<SignStatus>()
|
||||
const getStatusIcon = (status: SignStatus) => {
|
||||
switch (status) {
|
||||
case SignStatus.Signed:
|
||||
return <FontAwesomeIcon icon={faCheck} />
|
||||
case SignStatus.Awaiting:
|
||||
return (
|
||||
<Spinner>
|
||||
<FontAwesomeIcon icon={faHourglass} />
|
||||
</Spinner>
|
||||
)
|
||||
case SignStatus.Pending:
|
||||
return <FontAwesomeIcon icon={faHourglass} />
|
||||
case SignStatus.Invalid:
|
||||
return <FontAwesomeIcon icon={faExclamation} />
|
||||
case SignStatus.Viewer:
|
||||
return <FontAwesomeIcon icon={faEye} />
|
||||
|
||||
useEffect(() => {
|
||||
if (!meta) return
|
||||
|
||||
const updateSignStatus = async () => {
|
||||
const npub = hexToNpub(pubkey)
|
||||
if (npub in meta.docSignatures) {
|
||||
parseJson<Event>(meta.docSignatures[npub])
|
||||
.then((event) => {
|
||||
const isValidSignature = verifyEvent(event)
|
||||
if (isValidSignature) {
|
||||
setSignedStatus(SignStatus.Signed)
|
||||
} else {
|
||||
setSignedStatus(SignStatus.Invalid)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`err in parsing the docSignatures for ${npub}:>> `, err)
|
||||
setSignedStatus(SignStatus.Invalid)
|
||||
})
|
||||
} else {
|
||||
setSignedStatus(SignStatus.Pending)
|
||||
}
|
||||
default:
|
||||
return <FontAwesomeIcon icon={faQuestion} />
|
||||
}
|
||||
|
||||
updateSignStatus()
|
||||
}, [meta, pubkey])
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge
|
||||
@ -60,16 +52,7 @@ export const DisplaySigner = ({
|
||||
overlap="circular"
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
badgeContent={
|
||||
signStatus !== SignStatus.Pending && (
|
||||
<div className={styles.statusBadge}>
|
||||
{signStatus === SignStatus.Signed && (
|
||||
<FontAwesomeIcon icon={faCheck} />
|
||||
)}
|
||||
{signStatus === SignStatus.Invalid && (
|
||||
<FontAwesomeIcon icon={faExclamation} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
<div className={styles.statusBadge}>{getStatusIcon(status)}</div>
|
||||
}
|
||||
>
|
||||
<UserAvatar pubkey={pubkey} image={profile?.picture} />
|
||||
|
@ -1,246 +0,0 @@
|
||||
import { CheckCircle, Cancel } from '@mui/icons-material'
|
||||
import { Divider, Tooltip } from '@mui/material'
|
||||
import { verifyEvent } from 'nostr-tools'
|
||||
import { useSigitProfiles } from '../../hooks/useSigitProfiles'
|
||||
import { Meta, SignedEventContent } from '../../types'
|
||||
import {
|
||||
extractFileExtensions,
|
||||
formatTimestamp,
|
||||
fromUnixTimestamp,
|
||||
hexToNpub,
|
||||
npubToHex,
|
||||
shorten
|
||||
} from '../../utils'
|
||||
import { UserAvatar } from '../UserAvatar'
|
||||
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,
|
||||
faEye,
|
||||
faFile,
|
||||
faFileCircleExclamation
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { State } from '../../store/rootReducer'
|
||||
|
||||
interface FileUsersProps {
|
||||
meta: Meta
|
||||
}
|
||||
|
||||
export const FileUsers = ({ meta }: FileUsersProps) => {
|
||||
const { usersPubkey } = useSelector((state: State) => state.auth)
|
||||
const {
|
||||
submittedBy,
|
||||
signers,
|
||||
viewers,
|
||||
fileHashes,
|
||||
sig,
|
||||
docSignatures,
|
||||
parsedSignatureEvents,
|
||||
createdAt,
|
||||
signedStatus,
|
||||
completedAt
|
||||
} = useSigitMeta(meta)
|
||||
const profiles = useSigitProfiles([
|
||||
...(submittedBy ? [submittedBy] : []),
|
||||
...signers,
|
||||
...viewers
|
||||
])
|
||||
const userCanSign =
|
||||
typeof usersPubkey !== 'undefined' &&
|
||||
signers.includes(hexToNpub(usersPubkey))
|
||||
|
||||
const ext = extractFileExtensions(Object.keys(fileHashes))
|
||||
|
||||
const getPrevSignersSig = (npub: string) => {
|
||||
// if user is first signer then use creator's signature
|
||||
if (signers[0] === npub) {
|
||||
return sig
|
||||
}
|
||||
|
||||
// find the index of signer
|
||||
const currentSignerIndex = signers.findIndex((signer) => signer === npub)
|
||||
// return null if could not found user in signer's list
|
||||
if (currentSignerIndex === -1) return null
|
||||
// find prev signer
|
||||
const prevSigner = signers[currentSignerIndex - 1]
|
||||
|
||||
// get the signature of prev signer
|
||||
try {
|
||||
const prevSignersEvent = parsedSignatureEvents[prevSigner]
|
||||
return prevSignersEvent.sig
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const displayUser = (pubkey: string, verifySignature = false) => {
|
||||
const profile = profiles[pubkey]
|
||||
|
||||
let isValidSignature = false
|
||||
|
||||
if (verifySignature) {
|
||||
const npub = hexToNpub(pubkey)
|
||||
const signedEventString = docSignatures[npub]
|
||||
if (signedEventString) {
|
||||
try {
|
||||
const signedEvent = JSON.parse(signedEventString)
|
||||
const isVerifiedEvent = verifyEvent(signedEvent)
|
||||
|
||||
if (isVerifiedEvent) {
|
||||
// get the actual signature of prev signer
|
||||
const prevSignersSig = getPrevSignersSig(npub)
|
||||
|
||||
// get the signature of prev signer from the content of current signers signedEvent
|
||||
|
||||
try {
|
||||
const obj: SignedEventContent = JSON.parse(signedEvent.content)
|
||||
if (
|
||||
obj.prevSig &&
|
||||
prevSignersSig &&
|
||||
obj.prevSig === prevSignersSig
|
||||
) {
|
||||
isValidSignature = true
|
||||
}
|
||||
} catch (error) {
|
||||
isValidSignature = false
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`An error occurred in parsing and verifying the signature event for ${pubkey}`,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserAvatar
|
||||
pubkey={pubkey}
|
||||
name={
|
||||
profile?.display_name || profile?.name || shorten(hexToNpub(pubkey))
|
||||
}
|
||||
image={profile?.picture}
|
||||
/>
|
||||
|
||||
{verifySignature && (
|
||||
<>
|
||||
{isValidSignature && (
|
||||
<Tooltip title="Valid signature">
|
||||
<CheckCircle sx={{ color: 'green' }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{!isValidSignature && (
|
||||
<Tooltip title="Invalid signature">
|
||||
<Cancel sx={{ color: 'red' }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return submittedBy ? (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.section}>
|
||||
<p>Signers</p>
|
||||
{displayUser(submittedBy)}
|
||||
{submittedBy && signers.length ? (
|
||||
<Divider orientation="vertical" flexItem />
|
||||
) : null}
|
||||
<UserAvatarGroup max={7}>
|
||||
{signers.length > 0 &&
|
||||
signers.map((signer) => (
|
||||
<span key={signer}>{displayUser(npubToHex(signer)!, true)}</span>
|
||||
))}
|
||||
{viewers.length > 0 &&
|
||||
viewers.map((viewer) => (
|
||||
<span key={viewer}>{displayUser(npubToHex(viewer)!)}</span>
|
||||
))}
|
||||
</UserAvatarGroup>
|
||||
</div>
|
||||
<div className={styles.section}>
|
||||
<p>Details</p>
|
||||
|
||||
<Tooltip
|
||||
title={'Publication date'}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<span className={styles.detailsItem}>
|
||||
<FontAwesomeIcon icon={faCalendarPlus} />{' '}
|
||||
{createdAt ? formatTimestamp(createdAt) : <>—</>}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
title={'Completion date'}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<span className={styles.detailsItem}>
|
||||
<FontAwesomeIcon icon={faCalendarCheck} />{' '}
|
||||
{completedAt ? formatTimestamp(completedAt) : <>—</>}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/* User signed date */}
|
||||
{userCanSign ? (
|
||||
<Tooltip
|
||||
title={'Your signature date'}
|
||||
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
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<>—</>
|
||||
)
|
||||
) : (
|
||||
<>—</>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<span className={styles.detailsItem}>
|
||||
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
||||
</span>
|
||||
{ext.length > 0 ? (
|
||||
<span className={styles.detailsItem}>
|
||||
{ext.length > 1 ? (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
||||
</>
|
||||
) : (
|
||||
getExtensionIconLabel(ext[0])
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faFileCircleExclamation} /> —
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
6
src/components/Spinner/index.tsx
Normal file
6
src/components/Spinner/index.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styles from './style.module.scss'
|
||||
|
||||
export const Spinner = ({ children }: PropsWithChildren) => (
|
||||
<div className={styles.spin}>{children}</div>
|
||||
)
|
12
src/components/Spinner/style.module.scss
Normal file
12
src/components/Spinner/style.module.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
224
src/components/UsersDetails.tsx/index.tsx
Normal file
224
src/components/UsersDetails.tsx/index.tsx
Normal file
@ -0,0 +1,224 @@
|
||||
import { Divider, Tooltip } from '@mui/material'
|
||||
import { useSigitProfiles } from '../../hooks/useSigitProfiles'
|
||||
import {
|
||||
extractFileExtensions,
|
||||
formatTimestamp,
|
||||
fromUnixTimestamp,
|
||||
hexToNpub,
|
||||
npubToHex,
|
||||
shorten,
|
||||
SignStatus
|
||||
} from '../../utils'
|
||||
import { UserAvatar } from '../UserAvatar'
|
||||
import { FlatMeta } from '../../hooks/useSigitMeta'
|
||||
import { UserAvatarGroup } from '../UserAvatarGroup'
|
||||
|
||||
import styles from './style.module.scss'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCalendar,
|
||||
faCalendarCheck,
|
||||
faCalendarPlus,
|
||||
faEye,
|
||||
faFile,
|
||||
faFileCircleExclamation
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { State } from '../../store/rootReducer'
|
||||
import { TooltipChild } from '../TooltipChild'
|
||||
import { DisplaySigner } from '../DisplaySigner'
|
||||
|
||||
type UsersDetailsProps = Pick<
|
||||
FlatMeta,
|
||||
| 'submittedBy'
|
||||
| 'signers'
|
||||
| 'viewers'
|
||||
| 'fileHashes'
|
||||
| 'parsedSignatureEvents'
|
||||
| 'createdAt'
|
||||
| 'signedStatus'
|
||||
| 'completedAt'
|
||||
| 'signersStatus'
|
||||
>
|
||||
|
||||
export const UsersDetails = ({
|
||||
submittedBy,
|
||||
signers,
|
||||
viewers,
|
||||
fileHashes,
|
||||
parsedSignatureEvents,
|
||||
createdAt,
|
||||
signedStatus,
|
||||
completedAt,
|
||||
signersStatus
|
||||
}: UsersDetailsProps) => {
|
||||
const { usersPubkey } = useSelector((state: State) => state.auth)
|
||||
const profiles = useSigitProfiles([
|
||||
...(submittedBy ? [submittedBy] : []),
|
||||
...signers,
|
||||
...viewers
|
||||
])
|
||||
const userCanSign =
|
||||
typeof usersPubkey !== 'undefined' &&
|
||||
signers.includes(hexToNpub(usersPubkey))
|
||||
|
||||
const ext = extractFileExtensions(Object.keys(fileHashes))
|
||||
|
||||
return submittedBy ? (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.section}>
|
||||
<p>Signers</p>
|
||||
<div className={styles.users}>
|
||||
{submittedBy &&
|
||||
(function () {
|
||||
const profile = profiles[submittedBy]
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
profile?.display_name ||
|
||||
profile?.name ||
|
||||
shorten(hexToNpub(submittedBy))
|
||||
}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<TooltipChild>
|
||||
<UserAvatar pubkey={submittedBy} image={profile?.picture} />
|
||||
</TooltipChild>
|
||||
</Tooltip>
|
||||
)
|
||||
})()}
|
||||
|
||||
{submittedBy && signers.length ? (
|
||||
<Divider orientation="vertical" flexItem />
|
||||
) : null}
|
||||
|
||||
<UserAvatarGroup max={20}>
|
||||
{signers.map((signer) => {
|
||||
const pubkey = npubToHex(signer)!
|
||||
const profile = profiles[pubkey]
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={signer}
|
||||
title={
|
||||
profile?.display_name || profile?.name || shorten(pubkey)
|
||||
}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<TooltipChild>
|
||||
<DisplaySigner
|
||||
status={signersStatus[pubkey as `npub1${string}`]}
|
||||
profile={profile}
|
||||
pubkey={pubkey}
|
||||
/>
|
||||
</TooltipChild>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
{viewers.map((signer) => {
|
||||
const pubkey = npubToHex(signer)!
|
||||
const profile = profiles[pubkey]
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={signer}
|
||||
title={
|
||||
profile?.display_name || profile?.name || shorten(pubkey)
|
||||
}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<TooltipChild>
|
||||
<DisplaySigner
|
||||
status={SignStatus.Viewer}
|
||||
profile={profile}
|
||||
pubkey={pubkey}
|
||||
/>
|
||||
</TooltipChild>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</UserAvatarGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.section}>
|
||||
<p>Details</p>
|
||||
|
||||
<Tooltip
|
||||
title={'Publication date'}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<span className={styles.detailsItem}>
|
||||
<FontAwesomeIcon icon={faCalendarPlus} />{' '}
|
||||
{createdAt ? formatTimestamp(createdAt) : <>—</>}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
title={'Completion date'}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<span className={styles.detailsItem}>
|
||||
<FontAwesomeIcon icon={faCalendarCheck} />{' '}
|
||||
{completedAt ? formatTimestamp(completedAt) : <>—</>}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/* User signed date */}
|
||||
{userCanSign ? (
|
||||
<Tooltip
|
||||
title={'Your signature date'}
|
||||
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
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<>—</>
|
||||
)
|
||||
) : (
|
||||
<>—</>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<span className={styles.detailsItem}>
|
||||
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
||||
</span>
|
||||
{ext.length > 0 ? (
|
||||
<span className={styles.detailsItem}>
|
||||
{ext.length > 1 ? (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
||||
</>
|
||||
) : (
|
||||
getExtensionIconLabel(ext[0])
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faFileCircleExclamation} /> —
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
@ -17,6 +17,11 @@
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.users {
|
||||
display: flex;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.detailsItem {
|
||||
transition: ease 0.2s;
|
||||
color: rgba(0, 0, 0, 0.5);
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { CreateSignatureEventContent, Meta } from '../types'
|
||||
import { CreateSignatureEventContent, Meta, SignedEventContent } from '../types'
|
||||
import { Mark } from '../types/mark'
|
||||
import {
|
||||
fromUnixTimestamp,
|
||||
@ -118,7 +118,6 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
||||
|
||||
if (meta.keys) {
|
||||
const { sender, keys } = meta.keys
|
||||
|
||||
// Retrieve the user's public key from the state
|
||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
||||
const usersNpub = hexToNpub(usersPubkey)
|
||||
@ -141,8 +140,28 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
||||
}
|
||||
}
|
||||
|
||||
// Temp. map to hold events
|
||||
// Temp. map to hold events and signers
|
||||
const parsedSignatureEventsMap = new Map<`npub1${string}`, Event>()
|
||||
const signerStatusMap = new Map<`npub1${string}`, SignStatus>()
|
||||
|
||||
const getPrevSignerSig = (npub: `npub1${string}`) => {
|
||||
if (signers[0] === npub) {
|
||||
return sig
|
||||
}
|
||||
|
||||
// find the index of signer
|
||||
const currentSignerIndex = signers.findIndex(
|
||||
(signer) => signer === npub
|
||||
)
|
||||
// return if could not found user in signer's list
|
||||
if (currentSignerIndex === -1) return
|
||||
// find prev signer
|
||||
const prevSigner = signers[currentSignerIndex - 1]
|
||||
|
||||
// get the signature of prev signer
|
||||
return parsedSignatureEventsMap.get(prevSigner)?.sig
|
||||
}
|
||||
|
||||
for (const npub in meta.docSignatures) {
|
||||
try {
|
||||
// Parse each signature event
|
||||
@ -150,34 +169,49 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
||||
meta.docSignatures[npub as `npub1${string}`]
|
||||
)
|
||||
|
||||
const isValidSignature = verifyEvent(event)
|
||||
|
||||
// Save events to a map, to save all at once outside loop
|
||||
// We need the object to find completedAt
|
||||
// Avoided using parsedSignatureEvents due to useEffect deps
|
||||
parsedSignatureEventsMap.set(npub as `npub1${string}`, event)
|
||||
|
||||
setSignersStatus((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[npub]: isValidSignature
|
||||
? SignStatus.Signed
|
||||
: SignStatus.Invalid
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
setSignersStatus((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[npub]: SignStatus.Invalid
|
||||
}
|
||||
})
|
||||
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Invalid)
|
||||
}
|
||||
}
|
||||
|
||||
setParsedSignatureEvents(
|
||||
Object.fromEntries(parsedSignatureEventsMap.entries())
|
||||
)
|
||||
parsedSignatureEventsMap.forEach((event, npub) => {
|
||||
const isValidSignature = verifyEvent(event)
|
||||
if (isValidSignature) {
|
||||
// get the signature of prev signer from the content of current signers signedEvent
|
||||
const prevSignersSig = getPrevSignerSig(npub)
|
||||
|
||||
try {
|
||||
const obj: SignedEventContent = JSON.parse(event.content)
|
||||
if (
|
||||
obj.prevSig &&
|
||||
prevSignersSig &&
|
||||
obj.prevSig === prevSignersSig
|
||||
) {
|
||||
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Signed)
|
||||
}
|
||||
} catch (error) {
|
||||
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Invalid)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const nextSigner = signers.find((s) => !parsedSignatureEventsMap.has(s))
|
||||
if (nextSigner) {
|
||||
signerStatusMap.set(nextSigner, SignStatus.Awaiting)
|
||||
}
|
||||
|
||||
signers
|
||||
.filter((s) => !(s in meta.docSignatures))
|
||||
.forEach((s) =>
|
||||
signerStatusMap.set(s as `npub1${string}`, SignStatus.Pending)
|
||||
)
|
||||
|
||||
setSignersStatus(Object.fromEntries(signerStatusMap))
|
||||
setParsedSignatureEvents(Object.fromEntries(parsedSignatureEventsMap))
|
||||
|
||||
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
||||
const isCompletelySigned = signers.every((signer) =>
|
||||
@ -187,7 +221,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
||||
isCompletelySigned ? SigitStatus.Complete : SigitStatus.Partial
|
||||
)
|
||||
|
||||
// Check if all signers signed and are valid
|
||||
// Check if all signers signed
|
||||
if (isCompletelySigned) {
|
||||
setCompletedAt(
|
||||
fromUnixTimestamp(
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
unixNow,
|
||||
parseJson,
|
||||
readContentOfZipEntry,
|
||||
signEventForMetaFile
|
||||
signEventForMetaFile,
|
||||
shorten
|
||||
} from '../../utils'
|
||||
import styles from './style.module.scss'
|
||||
import { Cancel, CheckCircle } from '@mui/icons-material'
|
||||
@ -34,8 +35,11 @@ import { getLastSignersSig } from '../../utils/sign.ts'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { Container } from '../../components/Container'
|
||||
import { useSigitMeta } from '../../hooks/useSigitMeta.tsx'
|
||||
import { Files } from '../../layouts/Files.tsx'
|
||||
import { FileUsers } from '../../components/FilesUsers.tsx/index.tsx'
|
||||
import { StickySideColumns } from '../../layouts/StickySideColumns.tsx'
|
||||
import { UsersDetails } from '../../components/UsersDetails.tsx/index.tsx'
|
||||
import { UserAvatar } from '../../components/UserAvatar/index.tsx'
|
||||
import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx'
|
||||
import { TooltipChild } from '../../components/TooltipChild.tsx'
|
||||
|
||||
export const VerifyPage = () => {
|
||||
const theme = useTheme()
|
||||
@ -50,8 +54,25 @@ export const VerifyPage = () => {
|
||||
*/
|
||||
const { uploadedZip, meta } = location.state || {}
|
||||
|
||||
const { submittedBy, zipUrl, encryptionKey, signers, viewers, fileHashes } =
|
||||
useSigitMeta(meta)
|
||||
const {
|
||||
submittedBy,
|
||||
zipUrl,
|
||||
encryptionKey,
|
||||
signers,
|
||||
viewers,
|
||||
fileHashes,
|
||||
parsedSignatureEvents,
|
||||
createdAt,
|
||||
signedStatus,
|
||||
completedAt,
|
||||
signersStatus
|
||||
} = useSigitMeta(meta)
|
||||
|
||||
const profiles = useSigitProfiles([
|
||||
...(submittedBy ? [submittedBy] : []),
|
||||
...signers,
|
||||
...viewers
|
||||
])
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||
@ -339,7 +360,24 @@ export const VerifyPage = () => {
|
||||
const exportSignatureEvent = JSON.parse(exportSignatureString) as Event
|
||||
|
||||
if (verifyEvent(exportSignatureEvent)) {
|
||||
// return displayUser(exportSignatureEvent.pubkey)
|
||||
const exportedBy = exportSignatureEvent.pubkey
|
||||
const profile = profiles[exportedBy]
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
profile?.display_name ||
|
||||
profile?.name ||
|
||||
shorten(hexToNpub(exportedBy))
|
||||
}
|
||||
placement="top"
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<TooltipChild>
|
||||
<UserAvatar pubkey={exportedBy} image={profile?.picture} />
|
||||
</TooltipChild>
|
||||
</Tooltip>
|
||||
)
|
||||
} else {
|
||||
toast.error(`Invalid export signature!`)
|
||||
return (
|
||||
@ -386,7 +424,7 @@ export const VerifyPage = () => {
|
||||
)}
|
||||
|
||||
{meta && (
|
||||
<Files
|
||||
<StickySideColumns
|
||||
left={
|
||||
<>
|
||||
<Box className={styles.filesWrapper}>
|
||||
@ -432,8 +470,19 @@ export const VerifyPage = () => {
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
right={<FileUsers meta={meta} />}
|
||||
content={<div style={{ height: '300vh' }}></div>}
|
||||
right={
|
||||
<UsersDetails
|
||||
submittedBy={submittedBy}
|
||||
signers={signers}
|
||||
viewers={viewers}
|
||||
fileHashes={fileHashes}
|
||||
parsedSignatureEvents={parsedSignatureEvents}
|
||||
createdAt={createdAt}
|
||||
signedStatus={signedStatus}
|
||||
completedAt={completedAt}
|
||||
signersStatus={signersStatus}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
|
@ -5,8 +5,10 @@ import { toast } from 'react-toastify'
|
||||
|
||||
export enum SignStatus {
|
||||
Signed = 'Signed',
|
||||
Awaiting = 'Awaiting',
|
||||
Pending = 'Pending',
|
||||
Invalid = 'Invalid Sign'
|
||||
Invalid = 'Invalid',
|
||||
Viewer = 'Viewer'
|
||||
}
|
||||
|
||||
export enum SigitStatus {
|
||||
|
Loading…
Reference in New Issue
Block a user