feat(verify-page): new verify page design #151

Merged
enes merged 11 commits from 136-verify-page-after-merges into staging 2024-08-20 11:57:40 +00:00
5 changed files with 142 additions and 59 deletions
Showing only changes of commit 2c586f3c13 - Show all commits

View File

@ -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>
) : ( ) : (

View File

@ -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;
} }

View File

@ -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>
</> </>

View File

@ -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;
}

View File

@ -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 }
} }