feat(verify-page): new verify page design #151
@ -56,7 +56,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
typeof usersPubkey !== 'undefined' &&
|
typeof usersPubkey !== 'undefined' &&
|
||||||
signers.includes(hexToNpub(usersPubkey))
|
signers.includes(hexToNpub(usersPubkey))
|
||||||
|
|
||||||
const ext = extractFileExtensions(Object.keys(fileHashes))
|
const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes))
|
||||||
|
|
||||||
return submittedBy ? (
|
return submittedBy ? (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@ -196,14 +196,14 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
<span className={styles.detailsItem}>
|
<span className={styles.detailsItem}>
|
||||||
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
||||||
</span>
|
</span>
|
||||||
{ext.length > 0 ? (
|
{extensions.length > 0 ? (
|
||||||
<span className={styles.detailsItem}>
|
<span className={styles.detailsItem}>
|
||||||
{ext.length > 1 ? (
|
{!isSame ? (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
getExtensionIconLabel(ext[0])
|
getExtensionIconLabel(extensions[0])
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
|
@ -23,7 +23,11 @@
|
|||||||
grid-gap: 15px;
|
grid-gap: 15px;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
max-width: 550px;
|
padding: 10px;
|
||||||
width: 550px;
|
border: 10px solid $overlay-background-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
max-width: 590px;
|
||||||
|
width: 590px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Box, Button, Tooltip, Typography, useTheme } from '@mui/material'
|
import { Box, Button, Divider, Tooltip, Typography } from '@mui/material'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import { MuiFileInput } from 'mui-file-input'
|
import { MuiFileInput } from 'mui-file-input'
|
||||||
import { Event, verifyEvent } from 'nostr-tools'
|
import { Event, verifyEvent } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { NostrController } from '../../controllers'
|
import { NostrController } from '../../controllers'
|
||||||
@ -16,10 +16,10 @@ import {
|
|||||||
parseJson,
|
parseJson,
|
||||||
readContentOfZipEntry,
|
readContentOfZipEntry,
|
||||||
signEventForMetaFile,
|
signEventForMetaFile,
|
||||||
shorten
|
shorten,
|
||||||
|
getCurrentUserFiles
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { Cancel, CheckCircle } from '@mui/icons-material'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { PdfFile } from '../../types/drawing.ts'
|
import { PdfFile } from '../../types/drawing.ts'
|
||||||
@ -27,7 +27,8 @@ import {
|
|||||||
addMarks,
|
addMarks,
|
||||||
convertToPdfBlob,
|
convertToPdfBlob,
|
||||||
convertToPdfFile,
|
convertToPdfFile,
|
||||||
groupMarksByPage
|
groupMarksByPage,
|
||||||
|
inPx
|
||||||
} from '../../utils/pdf.ts'
|
} from '../../utils/pdf.ts'
|
||||||
import { State } from '../../store/rootReducer.ts'
|
import { State } from '../../store/rootReducer.ts'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -40,13 +41,77 @@ import { UsersDetails } from '../../components/UsersDetails.tsx/index.tsx'
|
|||||||
import { UserAvatar } from '../../components/UserAvatar/index.tsx'
|
import { UserAvatar } from '../../components/UserAvatar/index.tsx'
|
||||||
import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx'
|
import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx'
|
||||||
import { TooltipChild } from '../../components/TooltipChild.tsx'
|
import { TooltipChild } from '../../components/TooltipChild.tsx'
|
||||||
|
import FileList from '../../components/FileList'
|
||||||
|
import { CurrentUserFile } from '../../types/file.ts'
|
||||||
|
|
||||||
|
interface PdfViewProps {
|
||||||
|
files: CurrentUserFile[]
|
||||||
|
currentFile: CurrentUserFile | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const SlimPdfView = ({ files, currentFile }: PdfViewProps) => {
|
||||||
|
const pdfRefs = useRef<(HTMLDivElement | null)[]>([])
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentFile !== null && !!pdfRefs.current[currentFile.id]) {
|
||||||
|
pdfRefs.current[currentFile.id]?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'end'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [currentFile])
|
||||||
|
return (
|
||||||
|
<div className={styles.view}>
|
||||||
|
{files.map((currentUserFile, i) => {
|
||||||
|
const { hash, filename, pdfFile, id } = currentUserFile
|
||||||
|
if (!hash) return
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
id={filename}
|
||||||
|
ref={(el) => (pdfRefs.current[id] = el)}
|
||||||
|
key={filename}
|
||||||
|
className={styles.fileWrapper}
|
||||||
|
>
|
||||||
|
{pdfFile.pages.map((page, i) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.imageWrapper} key={i}>
|
||||||
|
<img draggable="false" src={page.image} />
|
||||||
|
{page.drawnFields.map((f, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
border: '1px dotted black',
|
||||||
|
left: inPx(f.left),
|
||||||
|
top: inPx(f.top),
|
||||||
|
width: inPx(f.width),
|
||||||
|
height: inPx(f.height)
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{i < files.length - 1 && (
|
||||||
|
<Divider
|
||||||
|
sx={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'rgba(0,0,0,0.15)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
File Separator
|
||||||
|
</Divider>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const VerifyPage = () => {
|
export const VerifyPage = () => {
|
||||||
const theme = useTheme()
|
|
||||||
const textColor = theme.palette.getContrastText(
|
|
||||||
theme.palette.background.paper
|
|
||||||
)
|
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
/**
|
/**
|
||||||
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
||||||
@ -57,6 +122,8 @@ export const VerifyPage = () => {
|
|||||||
const { submittedBy, zipUrl, encryptionKey, signers, viewers, fileHashes } =
|
const { submittedBy, zipUrl, encryptionKey, signers, viewers, fileHashes } =
|
||||||
useSigitMeta(meta)
|
useSigitMeta(meta)
|
||||||
|
|
||||||
|
console.log('----------', meta)
|
||||||
|
|
||||||
const profiles = useSigitProfiles([
|
const profiles = useSigitProfiles([
|
||||||
...(submittedBy ? [submittedBy] : []),
|
...(submittedBy ? [submittedBy] : []),
|
||||||
...signers,
|
...signers,
|
||||||
@ -72,6 +139,15 @@ export const VerifyPage = () => {
|
|||||||
[key: string]: string | null
|
[key: string]: string | null
|
||||||
}>(fileHashes)
|
}>(fileHashes)
|
||||||
const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({})
|
const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({})
|
||||||
|
const [currentFile, setCurrentFile] = useState<CurrentUserFile | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Object.entries(files).length > 0) {
|
||||||
|
const tmp = getCurrentUserFiles(files, fileHashes)
|
||||||
|
|
||||||
|
setCurrentFile(tmp[0])
|
||||||
|
}
|
||||||
|
}, [fileHashes, files])
|
||||||
|
|
||||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
@ -414,51 +490,24 @@ export const VerifyPage = () => {
|
|||||||
<StickySideColumns
|
<StickySideColumns
|
||||||
left={
|
left={
|
||||||
<>
|
<>
|
||||||
<Box className={styles.filesWrapper}>
|
{currentFile !== null && (
|
||||||
{Object.entries(currentFileHashes).map(
|
<FileList
|
||||||
([filename, hash], index) => {
|
files={getCurrentUserFiles(files, currentFileHashes)}
|
||||||
const isValidHash = fileHashes[filename] === hash
|
currentFile={currentFile}
|
||||||
|
setCurrentFile={setCurrentFile}
|
||||||
return (
|
handleDownload={handleExport}
|
||||||
<Box key={`file-${index}`} className={styles.file}>
|
|
||||||
<Typography
|
|
||||||
component="label"
|
|
||||||
sx={{
|
|
||||||
color: textColor,
|
|
||||||
flexGrow: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{filename}
|
|
||||||
</Typography>
|
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
{displayExportedBy()}
|
{displayExportedBy()}
|
||||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<Button onClick={handleExport} variant="contained">
|
|
||||||
Export Sigit
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
right={<UsersDetails meta={meta} />}
|
right={<UsersDetails meta={meta} />}
|
||||||
|
>
|
||||||
|
<SlimPdfView
|
||||||
|
currentFile={currentFile}
|
||||||
|
files={getCurrentUserFiles(files, currentFileHashes)}
|
||||||
/>
|
/>
|
||||||
|
</StickySideColumns>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
|
@ -50,3 +50,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.view {
|
||||||
|
width: 550px;
|
||||||
|
max-width: 550px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageWrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
@ -142,7 +142,7 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const files = Object.keys(createSignatureContent.fileHashes)
|
const files = Object.keys(createSignatureContent.fileHashes)
|
||||||
const extensions = extractFileExtensions(files)
|
const { extensions } = extractFileExtensions(files)
|
||||||
|
|
||||||
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
||||||
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
||||||
@ -169,6 +169,10 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fileNames - List of filenames to check
|
||||||
|
* @returns List of extensions and if all are same
|
||||||
|
*/
|
||||||
export const extractFileExtensions = (fileNames: string[]) => {
|
export const extractFileExtensions = (fileNames: string[]) => {
|
||||||
const extensions = fileNames.reduce((result: string[], file: string) => {
|
const extensions = fileNames.reduce((result: string[], file: string) => {
|
||||||
const extension = file.split('.').pop()
|
const extension = file.split('.').pop()
|
||||||
@ -178,5 +182,7 @@ export const extractFileExtensions = (fileNames: string[]) => {
|
|||||||
return result
|
return result
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return extensions
|
const isSame = extensions.every((ext) => ext === extensions[0])
|
||||||
|
|
||||||
|
return { extensions, isSame }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user