2024-05-14 09:27:05 +00:00
|
|
|
import {
|
|
|
|
Box,
|
|
|
|
Button,
|
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListSubheader,
|
2024-05-24 06:39:28 +00:00
|
|
|
Tooltip,
|
2024-05-14 09:27:05 +00:00
|
|
|
Typography,
|
|
|
|
useTheme
|
|
|
|
} from '@mui/material'
|
|
|
|
import JSZip from 'jszip'
|
|
|
|
import { MuiFileInput } from 'mui-file-input'
|
2024-05-30 17:28:40 +00:00
|
|
|
import { Event, kinds, verifyEvent } from 'nostr-tools'
|
2024-05-14 09:27:05 +00:00
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { toast } from 'react-toastify'
|
|
|
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
2024-05-16 11:22:05 +00:00
|
|
|
import { UserComponent } from '../../components/username'
|
2024-05-16 05:40:56 +00:00
|
|
|
import { MetadataController } from '../../controllers'
|
2024-05-24 06:39:28 +00:00
|
|
|
import {
|
|
|
|
CreateSignatureEventContent,
|
|
|
|
Meta,
|
|
|
|
ProfileMetadata,
|
|
|
|
SignedEventContent
|
|
|
|
} from '../../types'
|
2024-05-14 09:27:05 +00:00
|
|
|
import {
|
2024-07-05 08:38:04 +00:00
|
|
|
decryptArrayBuffer,
|
|
|
|
extractZipUrlAndEncryptionKey,
|
2024-05-22 06:19:40 +00:00
|
|
|
getHash,
|
2024-05-14 09:27:05 +00:00
|
|
|
hexToNpub,
|
2024-05-17 08:34:56 +00:00
|
|
|
npubToHex,
|
2024-05-14 09:27:05 +00:00
|
|
|
parseJson,
|
|
|
|
readContentOfZipEntry,
|
2024-05-16 05:40:56 +00:00
|
|
|
shorten
|
2024-05-14 09:27:05 +00:00
|
|
|
} from '../../utils'
|
|
|
|
import styles from './style.module.scss'
|
2024-05-24 06:39:28 +00:00
|
|
|
import { Cancel, CheckCircle } from '@mui/icons-material'
|
2024-06-13 06:47:28 +00:00
|
|
|
import { useLocation } from 'react-router-dom'
|
2024-07-05 08:38:04 +00:00
|
|
|
import axios from 'axios'
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
export const VerifyPage = () => {
|
2024-05-16 05:40:56 +00:00
|
|
|
const theme = useTheme()
|
|
|
|
const textColor = theme.palette.getContrastText(
|
|
|
|
theme.palette.background.paper
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-13 06:47:28 +00:00
|
|
|
const location = useLocation()
|
2024-07-08 20:16:47 +00:00
|
|
|
/**
|
|
|
|
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
|
|
|
* meta will be received in navigation from create & home page in online mode
|
|
|
|
*/
|
2024-07-05 08:38:04 +00:00
|
|
|
const { uploadedZip, meta: metaInNavState } = location.state || {}
|
2024-06-13 06:47:28 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
2024-05-14 09:27:05 +00:00
|
|
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
2024-05-14 09:27:05 +00:00
|
|
|
const [meta, setMeta] = useState<Meta | null>(null)
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
const [submittedBy, setSubmittedBy] = useState<string>()
|
|
|
|
|
|
|
|
const [signers, setSigners] = useState<`npub1${string}`[]>([])
|
|
|
|
const [viewers, setViewers] = useState<`npub1${string}`[]>([])
|
|
|
|
const [creatorFileHashes, setCreatorFileHashes] = useState<{
|
|
|
|
[key: string]: string
|
|
|
|
}>({})
|
|
|
|
const [currentFileHashes, setCurrentFileHashes] = useState<{
|
|
|
|
[key: string]: string | null
|
|
|
|
}>({})
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
|
|
|
|
{}
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-13 06:47:28 +00:00
|
|
|
useEffect(() => {
|
|
|
|
if (uploadedZip) {
|
|
|
|
setSelectedFile(uploadedZip)
|
2024-07-05 08:38:04 +00:00
|
|
|
} else if (metaInNavState) {
|
|
|
|
const processSigit = async () => {
|
|
|
|
setIsLoading(true)
|
|
|
|
setLoadingSpinnerDesc('Extracting zipUrl and encryption key from meta')
|
2024-06-13 06:47:28 +00:00
|
|
|
|
2024-07-05 08:38:04 +00:00
|
|
|
const res = await extractZipUrlAndEncryptionKey(metaInNavState)
|
|
|
|
if (!res) {
|
|
|
|
setIsLoading(false)
|
|
|
|
return
|
2024-05-22 06:19:40 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:04 +00:00
|
|
|
const {
|
|
|
|
zipUrl,
|
|
|
|
encryptionKey,
|
|
|
|
createSignatureEvent,
|
|
|
|
createSignatureContent
|
|
|
|
} = res
|
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Fetching file from file server')
|
|
|
|
axios
|
|
|
|
.get(zipUrl, {
|
|
|
|
responseType: 'arraybuffer'
|
|
|
|
})
|
|
|
|
.then(async (res) => {
|
|
|
|
const fileName = zipUrl.split('/').pop()
|
|
|
|
const file = new File([res.data], fileName!)
|
|
|
|
|
|
|
|
const encryptedArrayBuffer = await file.arrayBuffer()
|
|
|
|
const arrayBuffer = await decryptArrayBuffer(
|
|
|
|
encryptedArrayBuffer,
|
|
|
|
encryptionKey
|
|
|
|
).catch((err) => {
|
|
|
|
console.log('err in decryption:>> ', err)
|
|
|
|
toast.error(
|
|
|
|
err.message || 'An error occurred in decrypting file.'
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
if (arrayBuffer) {
|
|
|
|
const zip = await JSZip.loadAsync(arrayBuffer).catch((err) => {
|
|
|
|
console.log('err in loading zip file :>> ', err)
|
|
|
|
toast.error(
|
|
|
|
err.message || 'An error occurred in loading zip file.'
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!zip) return
|
|
|
|
|
|
|
|
const fileHashes: { [key: string]: string | null } = {}
|
|
|
|
const fileNames = Object.values(zip.files).map(
|
|
|
|
(entry) => entry.name
|
|
|
|
)
|
|
|
|
|
|
|
|
// generate hashes for all entries in files folder of zipArchive
|
|
|
|
// these hashes can be used to verify the originality of files
|
|
|
|
for (const fileName of fileNames) {
|
|
|
|
const arrayBuffer = await readContentOfZipEntry(
|
|
|
|
zip,
|
|
|
|
fileName,
|
|
|
|
'arraybuffer'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (arrayBuffer) {
|
|
|
|
const hash = await getHash(arrayBuffer)
|
|
|
|
|
|
|
|
if (hash) {
|
|
|
|
fileHashes[fileName.replace(/^files\//, '')] = hash
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fileHashes[fileName.replace(/^files\//, '')] = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentFileHashes(fileHashes)
|
|
|
|
|
|
|
|
setSigners(createSignatureContent.signers)
|
|
|
|
setViewers(createSignatureContent.viewers)
|
|
|
|
setCreatorFileHashes(createSignatureContent.fileHashes)
|
|
|
|
setSubmittedBy(createSignatureEvent.pubkey)
|
|
|
|
|
|
|
|
setMeta(metaInNavState)
|
|
|
|
setIsLoading(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(`error occurred in getting file from ${zipUrl}`, err)
|
|
|
|
toast.error(
|
|
|
|
err.message || `error occurred in getting file from ${zipUrl}`
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
setIsLoading(false)
|
|
|
|
})
|
2024-05-22 06:19:40 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:38:04 +00:00
|
|
|
processSigit()
|
2024-05-22 06:19:40 +00:00
|
|
|
}
|
2024-07-05 08:38:04 +00:00
|
|
|
}, [uploadedZip, metaInNavState])
|
2024-05-22 06:19:40 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (submittedBy) {
|
2024-05-16 05:40:56 +00:00
|
|
|
const metadataController = new MetadataController()
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
const users = [submittedBy, ...signers, ...viewers]
|
2024-05-16 05:40:56 +00:00
|
|
|
|
|
|
|
users.forEach((user) => {
|
2024-05-17 08:34:56 +00:00
|
|
|
const pubkey = npubToHex(user)!
|
|
|
|
|
|
|
|
if (!(pubkey in metadata)) {
|
2024-05-30 17:28:40 +00:00
|
|
|
const handleMetadataEvent = (event: Event) => {
|
|
|
|
const metadataContent =
|
|
|
|
metadataController.extractProfileMetadataContent(event)
|
|
|
|
if (metadataContent)
|
|
|
|
setMetadata((prev) => ({
|
|
|
|
...prev,
|
|
|
|
[pubkey]: metadataContent
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
metadataController.on(pubkey, (kind: number, event: Event) => {
|
|
|
|
if (kind === kinds.Metadata) {
|
|
|
|
handleMetadataEvent(event)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
metadataController
|
2024-05-17 08:34:56 +00:00
|
|
|
.findMetadata(pubkey)
|
2024-05-16 05:40:56 +00:00
|
|
|
.then((metadataEvent) => {
|
2024-05-30 17:28:40 +00:00
|
|
|
if (metadataEvent) handleMetadataEvent(metadataEvent)
|
2024-05-16 05:40:56 +00:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(
|
|
|
|
`error occurred in finding metadata for: ${user}`,
|
|
|
|
err
|
|
|
|
)
|
|
|
|
})
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
})
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
2024-05-22 06:19:40 +00:00
|
|
|
}, [submittedBy, signers, viewers])
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const handleVerify = async () => {
|
|
|
|
if (!selectedFile) return
|
|
|
|
setIsLoading(true)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const zip = await JSZip.loadAsync(selectedFile).catch((err) => {
|
2024-05-14 09:27:05 +00:00
|
|
|
console.log('err in loading zip file :>> ', err)
|
|
|
|
toast.error(err.message || 'An error occurred in loading zip file.')
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!zip) return
|
2024-07-05 08:38:04 +00:00
|
|
|
|
|
|
|
const fileHashes: { [key: string]: string | null } = {}
|
|
|
|
const fileNames = Object.values(zip.files)
|
|
|
|
.filter((entry) => entry.name.startsWith('files/') && !entry.dir)
|
|
|
|
.map((entry) => entry.name)
|
|
|
|
|
|
|
|
// generate hashes for all entries in files folder of zipArchive
|
|
|
|
// these hashes can be used to verify the originality of files
|
|
|
|
for (const fileName of fileNames) {
|
|
|
|
const arrayBuffer = await readContentOfZipEntry(
|
|
|
|
zip,
|
|
|
|
fileName,
|
|
|
|
'arraybuffer'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (arrayBuffer) {
|
|
|
|
const hash = await getHash(arrayBuffer)
|
|
|
|
|
|
|
|
if (hash) {
|
|
|
|
fileHashes[fileName.replace(/^files\//, '')] = hash
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fileHashes[fileName.replace(/^files\//, '')] = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('fileHashes :>> ', fileHashes)
|
|
|
|
setCurrentFileHashes(fileHashes)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Parsing meta.json')
|
|
|
|
|
|
|
|
const metaFileContent = await readContentOfZipEntry(
|
|
|
|
zip,
|
|
|
|
'meta.json',
|
|
|
|
'string'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!metaFileContent) {
|
|
|
|
setIsLoading(false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const parsedMetaJson = await parseJson<Meta>(metaFileContent).catch(
|
|
|
|
(err) => {
|
|
|
|
console.log('err in parsing the content of meta.json :>> ', err)
|
|
|
|
toast.error(
|
|
|
|
err.message || 'error occurred in parsing the content of meta.json'
|
|
|
|
)
|
|
|
|
setIsLoading(false)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
if (!parsedMetaJson) return
|
|
|
|
|
|
|
|
const createSignatureEvent = await parseJson<Event>(
|
|
|
|
parsedMetaJson.createSignature
|
|
|
|
).catch((err) => {
|
|
|
|
console.log('err in parsing the createSignature event:>> ', err)
|
|
|
|
toast.error(
|
|
|
|
err.message || 'error occurred in parsing the create signature event'
|
|
|
|
)
|
|
|
|
setIsLoading(false)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!createSignatureEvent) return
|
|
|
|
|
|
|
|
const isValidCreateSignature = verifyEvent(createSignatureEvent)
|
|
|
|
|
|
|
|
if (!isValidCreateSignature) {
|
|
|
|
toast.error('Create signature is invalid')
|
|
|
|
setIsLoading(false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const createSignatureContent = await parseJson<CreateSignatureEventContent>(
|
|
|
|
createSignatureEvent.content
|
|
|
|
).catch((err) => {
|
|
|
|
console.log(
|
|
|
|
`err in parsing the createSignature event's content :>> `,
|
|
|
|
err
|
|
|
|
)
|
|
|
|
toast.error(
|
|
|
|
err.message ||
|
|
|
|
`error occurred in parsing the create signature event's content`
|
|
|
|
)
|
|
|
|
setIsLoading(false)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!createSignatureContent) return
|
|
|
|
|
|
|
|
setSigners(createSignatureContent.signers)
|
|
|
|
setViewers(createSignatureContent.viewers)
|
|
|
|
setCreatorFileHashes(createSignatureContent.fileHashes)
|
|
|
|
setSubmittedBy(createSignatureEvent.pubkey)
|
|
|
|
|
2024-05-14 09:27:05 +00:00
|
|
|
setMeta(parsedMetaJson)
|
2024-05-16 05:40:56 +00:00
|
|
|
setIsLoading(false)
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
2024-05-24 06:39:28 +00:00
|
|
|
const getPrevSignersSig = (npub: string) => {
|
|
|
|
if (!meta) return null
|
|
|
|
|
|
|
|
// if user is first signer then use creator's signature
|
|
|
|
if (signers[0] === npub) {
|
|
|
|
try {
|
|
|
|
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
|
|
|
|
return createSignatureEvent.sig
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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: Event = JSON.parse(meta.docSignatures[prevSigner])
|
|
|
|
return prevSignersEvent.sig
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const displayUser = (pubkey: string, verifySignature = false) => {
|
|
|
|
const profile = metadata[pubkey]
|
|
|
|
|
|
|
|
let isValidSignature = false
|
|
|
|
|
|
|
|
if (verifySignature) {
|
2024-05-17 08:34:56 +00:00
|
|
|
const npub = hexToNpub(pubkey)
|
2024-05-22 06:19:40 +00:00
|
|
|
const signedEventString = meta ? meta.docSignatures[npub] : null
|
2024-05-16 05:40:56 +00:00
|
|
|
if (signedEventString) {
|
|
|
|
try {
|
|
|
|
const signedEvent = JSON.parse(signedEventString)
|
2024-05-24 06:39:28 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2024-05-16 05:40:56 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.error(
|
|
|
|
`An error occurred in parsing and verifying the signature event for ${pubkey}`,
|
|
|
|
error
|
|
|
|
)
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
return (
|
2024-05-16 11:22:05 +00:00
|
|
|
<>
|
|
|
|
<UserComponent
|
|
|
|
pubkey={pubkey}
|
|
|
|
name={
|
|
|
|
profile?.display_name || profile?.name || shorten(hexToNpub(pubkey))
|
|
|
|
}
|
|
|
|
image={profile?.picture}
|
2024-05-16 05:40:56 +00:00
|
|
|
/>
|
2024-05-16 11:22:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{verifySignature && (
|
2024-05-24 06:39:28 +00:00
|
|
|
<>
|
|
|
|
{isValidSignature && (
|
|
|
|
<Tooltip title="Valid signature">
|
|
|
|
<CheckCircle sx={{ color: theme.palette.success.light }} />
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{!isValidSignature && (
|
|
|
|
<Tooltip title="Invalid signature">
|
|
|
|
<Cancel sx={{ color: theme.palette.error.main }} />
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
|
|
|
</>
|
2024-05-16 05:40:56 +00:00
|
|
|
)}
|
2024-05-16 11:22:05 +00:00
|
|
|
</>
|
2024-05-14 09:27:05 +00:00
|
|
|
)
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const displayExportedBy = () => {
|
|
|
|
if (!meta || !meta.exportSignature) return null
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const exportSignatureString = meta.exportSignature
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
try {
|
|
|
|
const exportSignatureEvent = JSON.parse(exportSignatureString) as Event
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
if (verifyEvent(exportSignatureEvent)) {
|
|
|
|
return displayUser(exportSignatureEvent.pubkey)
|
|
|
|
} else {
|
|
|
|
toast.error(`Invalid export signature!`)
|
|
|
|
return (
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography component="label" sx={{ color: 'red' }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Invalid export signature
|
|
|
|
</Typography>
|
2024-05-14 09:27:05 +00:00
|
|
|
)
|
|
|
|
}
|
2024-05-16 05:40:56 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.error(`An error occurred wile parsing exportSignature`, error)
|
2024-05-14 09:27:05 +00:00
|
|
|
return null
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
|
|
<Box className={styles.container}>
|
2024-05-16 05:40:56 +00:00
|
|
|
{!meta && (
|
2024-05-14 09:27:05 +00:00
|
|
|
<>
|
2024-05-15 08:50:21 +00:00
|
|
|
<Typography component="label" variant="h6">
|
2024-05-16 05:40:56 +00:00
|
|
|
Select exported zip file
|
2024-05-14 09:27:05 +00:00
|
|
|
</Typography>
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
<MuiFileInput
|
2024-05-16 06:25:30 +00:00
|
|
|
placeholder="Select file"
|
2024-05-16 05:40:56 +00:00
|
|
|
value={selectedFile}
|
|
|
|
onChange={(value) => setSelectedFile(value)}
|
|
|
|
InputProps={{
|
|
|
|
inputProps: {
|
2024-06-13 06:47:28 +00:00
|
|
|
accept: '.sigit.zip'
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{selectedFile && (
|
2024-05-15 06:19:28 +00:00
|
|
|
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Button onClick={handleVerify} variant="contained">
|
2024-05-16 05:40:56 +00:00
|
|
|
Verify
|
2024-05-14 09:27:05 +00:00
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{meta && (
|
2024-05-14 09:27:05 +00:00
|
|
|
<>
|
2024-05-16 05:40:56 +00:00
|
|
|
<List
|
|
|
|
sx={{
|
|
|
|
bgcolor: 'background.paper',
|
|
|
|
marginTop: 2
|
|
|
|
}}
|
|
|
|
subheader={
|
|
|
|
<ListSubheader className={styles.subHeader}>
|
|
|
|
Meta Info
|
|
|
|
</ListSubheader>
|
|
|
|
}
|
|
|
|
>
|
2024-05-22 06:19:40 +00:00
|
|
|
{submittedBy && (
|
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
gap: '15px'
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
|
|
|
Submitted By
|
|
|
|
</Typography>
|
|
|
|
{displayUser(submittedBy)}
|
|
|
|
</ListItem>
|
|
|
|
)}
|
2024-05-16 05:40:56 +00:00
|
|
|
|
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
gap: '15px'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Exported By
|
|
|
|
</Typography>
|
|
|
|
{displayExportedBy()}
|
|
|
|
</ListItem>
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
{signers.length > 0 && (
|
2024-05-16 05:40:56 +00:00
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Signers
|
|
|
|
</Typography>
|
|
|
|
<ul className={styles.usersList}>
|
2024-05-22 06:19:40 +00:00
|
|
|
{signers.map((signer) => (
|
2024-05-16 11:22:05 +00:00
|
|
|
<li
|
|
|
|
key={signer}
|
|
|
|
style={{
|
|
|
|
color: textColor,
|
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
gap: '15px'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-17 08:34:56 +00:00
|
|
|
{displayUser(npubToHex(signer)!, true)}
|
2024-05-16 05:40:56 +00:00
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ListItem>
|
|
|
|
)}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
{viewers.length > 0 && (
|
2024-05-16 05:40:56 +00:00
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Viewers
|
|
|
|
</Typography>
|
|
|
|
<ul className={styles.usersList}>
|
2024-05-22 06:19:40 +00:00
|
|
|
{viewers.map((viewer) => (
|
2024-05-16 05:40:56 +00:00
|
|
|
<li key={viewer} style={{ color: textColor }}>
|
2024-05-17 08:34:56 +00:00
|
|
|
{displayUser(npubToHex(viewer)!)}
|
2024-05-16 05:40:56 +00:00
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ListItem>
|
|
|
|
)}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Files
|
|
|
|
</Typography>
|
2024-05-22 06:19:40 +00:00
|
|
|
<Box className={styles.filesWrapper}>
|
|
|
|
{Object.entries(currentFileHashes).map(
|
|
|
|
([filename, hash], index) => {
|
|
|
|
const isValidHash = creatorFileHashes[filename] === hash
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Box key={`file-${index}`} className={styles.file}>
|
|
|
|
<Typography
|
|
|
|
component="label"
|
|
|
|
sx={{
|
|
|
|
color: textColor,
|
|
|
|
flexGrow: 1
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{filename}
|
|
|
|
</Typography>
|
2024-05-24 06:39:28 +00:00
|
|
|
{isValidHash && (
|
|
|
|
<Tooltip title="File integrity check passed" arrow>
|
|
|
|
<CheckCircle
|
|
|
|
sx={{ color: theme.palette.success.light }}
|
|
|
|
/>
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
|
|
|
{!isValidHash && (
|
|
|
|
<Tooltip title="File integrity check failed" arrow>
|
|
|
|
<Cancel
|
|
|
|
sx={{ color: theme.palette.error.main }}
|
|
|
|
/>
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
2024-05-22 06:19:40 +00:00
|
|
|
</Box>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)}
|
|
|
|
</Box>
|
2024-05-16 05:40:56 +00:00
|
|
|
</ListItem>
|
|
|
|
</List>
|
2024-05-14 09:27:05 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Box>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|