@@ -78,25 +105,43 @@ const MarkFormField = ({
-
Add {markLabel}
+ {!complete && (
+
Add {markLabel}
+ )}
+ {complete &&
Finish
}
-
+ )}
+
+ {complete && (
-
-
+ )}
+
{currentUserMarks.map((mark, index) => {
@@ -104,7 +149,7 @@ const MarkFormField = ({
handleCurrentUserMarkChange(mark)}
+ onClick={() => handleCurrentUserMarkClick(mark)}
>
{mark.id}
@@ -114,6 +159,20 @@ const MarkFormField = ({
)
})}
+
+
+
+
+ {complete && (
+
+ )}
+
diff --git a/src/components/MarkFormField/style.module.scss b/src/components/MarkFormField/style.module.scss
index 686595f..3825146 100644
--- a/src/components/MarkFormField/style.module.scss
+++ b/src/components/MarkFormField/style.module.scss
@@ -216,3 +216,7 @@
flex-direction: column;
grid-gap: 5px;
}
+
+.finishPage {
+ padding: 1px 0;
+}
diff --git a/src/components/PDFView/PdfMarking.tsx b/src/components/PDFView/PdfMarking.tsx
index 222c393..f0d4f60 100644
--- a/src/components/PDFView/PdfMarking.tsx
+++ b/src/components/PDFView/PdfMarking.tsx
@@ -24,11 +24,12 @@ import {
interface PdfMarkingProps {
currentUserMarks: CurrentUserMark[]
files: CurrentUserFile[]
- handleDownload: () => void
+ handleExport: () => void
+ handleEncryptedExport: () => void
+ handleSign: () => void
meta: Meta | null
otherUserMarks: Mark[]
setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void
- setIsMarksCompleted: (isMarksCompleted: boolean) => void
setUpdatedMarks: (markToUpdate: Mark) => void
}
@@ -42,10 +43,11 @@ const PdfMarking = (props: PdfMarkingProps) => {
const {
files,
currentUserMarks,
- setIsMarksCompleted,
setCurrentUserMarks,
setUpdatedMarks,
- handleDownload,
+ handleExport,
+ handleEncryptedExport,
+ handleSign,
meta,
otherUserMarks
} = props
@@ -86,11 +88,18 @@ const PdfMarking = (props: PdfMarkingProps) => {
updatedSelectedMark
)
setCurrentUserMarks(updatedCurrentUserMarks)
- setSelectedMarkValue(mark.currentValue ?? EMPTY)
- setSelectedMark(mark)
+
+ // If clicking on the same mark, don't update the value, otherwise do update
+ if (mark.id !== selectedMark.id) {
+ setSelectedMarkValue(mark.currentValue ?? EMPTY)
+ setSelectedMark(mark)
+ }
}
- const handleSubmit = (event: React.FormEvent
) => {
+ /**
+ * Sign and Complete
+ */
+ const handleSubmit = (event: React.MouseEvent) => {
event.preventDefault()
if (!selectedMarkValue || !selectedMark) return
@@ -106,8 +115,8 @@ const PdfMarking = (props: PdfMarkingProps) => {
)
setCurrentUserMarks(updatedCurrentUserMarks)
setSelectedMark(null)
- setIsMarksCompleted(true)
setUpdatedMarks(updatedMark.mark)
+ handleSign()
}
// const updateCurrentUserMarkValues = () => {
@@ -132,7 +141,8 @@ const PdfMarking = (props: PdfMarkingProps) => {
files={files}
currentFile={currentFile}
setCurrentFile={setCurrentFile}
- handleDownload={handleDownload}
+ handleExport={handleExport}
+ handleEncryptedExport={handleEncryptedExport}
/>
)}
diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx
index f30ecdd..7636e61 100644
--- a/src/pages/sign/index.tsx
+++ b/src/pages/sign/index.tsx
@@ -1,17 +1,15 @@
-import { Box, Button, Typography } from '@mui/material'
import axios from 'axios'
import saveAs from 'file-saver'
import JSZip from 'jszip'
import _ from 'lodash'
-import { MuiFileInput } from 'mui-file-input'
import { Event, verifyEvent } from 'nostr-tools'
import { useCallback, useEffect, useState } from 'react'
-import { useAppSelector } from '../../hooks/store'
+import { useAppSelector } from '../../hooks'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { NostrController } from '../../controllers'
-import { appPublicRoutes } from '../../routes'
+import { appPrivateRoutes, appPublicRoutes } from '../../routes'
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types'
import {
decryptArrayBuffer,
@@ -36,15 +34,10 @@ import {
timeout,
processMarks
} from '../../utils'
-import { Container } from '../../components/Container'
-import { DisplayMeta } from './internal/displayMeta'
-import styles from './style.module.scss'
import { CurrentUserMark, Mark } from '../../types/mark.ts'
-import { getLastSignersSig, isFullySigned } from '../../utils/sign.ts'
import {
filterMarksByPubkey,
getCurrentUserMarks,
- isCurrentUserMarksComplete,
updateMarks
} from '../../utils'
import PdfMarking from '../../components/PDFView/PdfMarking.tsx'
@@ -53,16 +46,10 @@ import {
getZipWithFiles,
SigitFile
} from '../../utils/file.ts'
-import { ARRAY_BUFFER, DEFLATE } from '../../utils/const.ts'
+import { ARRAY_BUFFER, DEFLATE } from '../../utils'
import { generateTimestamp } from '../../utils/opentimestamps.ts'
import { MARK_TYPE_CONFIG } from '../../components/MarkTypeStrategy/MarkStrategy.tsx'
-enum SignedStatus {
- Fully_Signed,
- User_Is_Next_Signer,
- User_Is_Not_Next_Signer
-}
-
export const SignPage = () => {
const navigate = useNavigate()
const location = useLocation()
@@ -100,17 +87,12 @@ export const SignPage = () => {
}
}
- const [displayInput, setDisplayInput] = useState(false)
-
- const [selectedFile, setSelectedFile] = useState
(null)
-
const [files, setFiles] = useState<{ [filename: string]: SigitFile }>({})
const [isLoading, setIsLoading] = useState(true)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
const [meta, setMeta] = useState(null)
- const [signedStatus, setSignedStatus] = useState()
const [submittedBy, setSubmittedBy] = useState()
@@ -124,66 +106,14 @@ export const SignPage = () => {
[key: string]: string | null
}>({})
- const [signedBy, setSignedBy] = useState<`npub1${string}`[]>([])
-
- const [nextSinger, setNextSinger] = useState()
-
- // This state variable indicates whether the logged-in user is a signer, a creator, or neither.
- const [isSignerOrCreator, setIsSignerOrCreator] = useState(false)
-
const usersPubkey = useAppSelector((state) => state.auth.usersPubkey)
const nostrController = NostrController.getInstance()
const [currentUserMarks, setCurrentUserMarks] = useState(
[]
)
- const [isMarksCompleted, setIsMarksCompleted] = useState(false)
const [otherUserMarks, setOtherUserMarks] = useState([])
- useEffect(() => {
- if (signers.length > 0) {
- // check if all signers have signed then its fully signed
- if (isFullySigned(signers, signedBy)) {
- setSignedStatus(SignedStatus.Fully_Signed)
- } else {
- for (const signer of signers) {
- if (!signedBy.includes(signer)) {
- // signers in meta.json are in npub1 format
- // so, convert it to hex before setting to nextSigner
- setNextSinger(npubToHex(signer)!)
-
- const usersNpub = hexToNpub(usersPubkey!)
-
- if (signer === usersNpub) {
- // logged in user is the next signer
- setSignedStatus(SignedStatus.User_Is_Next_Signer)
- } else {
- setSignedStatus(SignedStatus.User_Is_Not_Next_Signer)
- }
-
- break
- }
- }
- }
- } else {
- // there's no signer just viewers. So its fully signed
- setSignedStatus(SignedStatus.Fully_Signed)
- }
-
- // Determine and set the status of the user
- if (submittedBy && usersPubkey && submittedBy === usersPubkey) {
- // If the submission was made by the user, set the status to true
- setIsSignerOrCreator(true)
- } else if (usersPubkey) {
- // Convert the user's public key from hex to npub format
- const usersNpub = hexToNpub(usersPubkey)
- if (signers.includes(usersNpub)) {
- // If the user's npub is in the list of signers, set the status to true
- setIsSignerOrCreator(true)
- }
- }
- }, [signers, signedBy, usersPubkey, submittedBy])
-
useEffect(() => {
const handleUpdatedMeta = async (meta: Meta) => {
const createSignatureEvent = await parseJson(
@@ -278,10 +208,7 @@ export const SignPage = () => {
setOtherUserMarks(otherUserMarks)
setCurrentUserMarks(currentUserMarks)
- setIsMarksCompleted(isCurrentUserMarksComplete(currentUserMarks))
}
-
- setSignedBy(Object.keys(meta.docSignatures) as `npub1${string}`[])
}
if (meta) {
@@ -290,29 +217,6 @@ export const SignPage = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [meta, usersPubkey])
- const handleDownload = async () => {
- if (Object.entries(files).length === 0 || !meta || !usersPubkey) return
- setLoadingSpinnerDesc('Generating file')
- try {
- const zip = await getZipWithFiles(meta, files)
- const arrayBuffer = await zip.generateAsync({
- type: ARRAY_BUFFER,
- compression: DEFLATE,
- compressionOptions: {
- level: 6
- }
- })
- if (!arrayBuffer) return
- const blob = new Blob([arrayBuffer])
- saveAs(blob, `exported-${unixNow()}.sigit.zip`)
- } catch (error) {
- console.log('error in zip:>> ', error)
- if (error instanceof Error) {
- toast.error(error.message || 'Error occurred in generating zip file')
- }
- }
- }
-
const decrypt = useCallback(
async (file: File) => {
setLoadingSpinnerDesc('Decrypting file')
@@ -424,7 +328,6 @@ export const SignPage = () => {
})
} else {
setIsLoading(false)
- setDisplayInput(true)
}
}, [decryptedArrayBuffer, uploadedZip, metaInNavState, decrypt])
@@ -541,9 +444,6 @@ export const SignPage = () => {
setFiles(files)
setCurrentFileHashes(fileHashes)
-
- setDisplayInput(false)
-
setLoadingSpinnerDesc('Parsing meta.json')
const metaFileContent = await readContentOfZipEntry(
@@ -571,21 +471,6 @@ export const SignPage = () => {
setMeta(parsedMetaJson)
}
- const handleDecrypt = async () => {
- if (!selectedFile) return
-
- setIsLoading(true)
- const arrayBuffer = await decrypt(selectedFile)
-
- if (!arrayBuffer) {
- setIsLoading(false)
- toast.error('Error decrypting file')
- return
- }
-
- handleDecryptedArrayBuffer(arrayBuffer)
- }
-
const handleSign = async () => {
if (Object.entries(files).length === 0 || !meta) return
@@ -640,6 +525,13 @@ export const SignPage = () => {
setMeta(updatedMeta)
setIsLoading(false)
}
+
+ if (metaInNavState) {
+ const createSignature = JSON.parse(metaInNavState.createSignature)
+ navigate(`${appPublicRoutes.verify}/${createSignature.id}`)
+ } else {
+ navigate(appPrivateRoutes.homePage)
+ }
}
// Sign the event for the meta file
@@ -730,6 +622,14 @@ export const SignPage = () => {
})
}
+ // Check if the current user is the last signer
+ const checkIsLastSigner = (signers: string[]): boolean => {
+ const usersNpub = hexToNpub(usersPubkey!)
+ const lastSignerIndex = signers.length - 1
+ const signerIndex = signers.indexOf(usersNpub)
+ return signerIndex === lastSignerIndex
+ }
+
// Handle errors during zip file generation
const handleZipError = (err: unknown) => {
console.log('Error in zip:>> ', err)
@@ -740,7 +640,7 @@ export const SignPage = () => {
return null
}
- // Handle the online flow: update users app data and send notifications
+ // Handle the online flow: update users' app data and send notifications
const handleOnlineFlow = async (meta: Meta) => {
setLoadingSpinnerDesc('Updating users app data')
const updatedEvent = await updateUsersAppData(meta)
@@ -795,82 +695,102 @@ export const SignPage = () => {
setIsLoading(false)
}
- // Check if the current user is the last signer
- const checkIsLastSigner = (signers: string[]): boolean => {
- const usersNpub = hexToNpub(usersPubkey!)
- const lastSignerIndex = signers.length - 1
- const signerIndex = signers.indexOf(usersNpub)
- return signerIndex === lastSignerIndex
- }
-
const handleExport = async () => {
if (Object.entries(files).length === 0 || !meta || !usersPubkey) return
-
- const usersNpub = hexToNpub(usersPubkey)
- if (
- !signers.includes(usersNpub) &&
- !viewers.includes(usersNpub) &&
- submittedBy !== usersNpub
- )
- return
-
- setIsLoading(true)
- setLoadingSpinnerDesc('Signing nostr event')
-
- if (!meta) return
-
- const prevSig = getLastSignersSig(meta, signers)
- if (!prevSig) return
-
- const signedEvent = await signEventForMetaFile(
- JSON.stringify({
- prevSig
- }),
- nostrController,
- setIsLoading
- )
-
- if (!signedEvent) return
-
- const exportSignature = JSON.stringify(signedEvent, null, 2)
-
- const stringifiedMeta = JSON.stringify(
- {
- ...meta,
- exportSignature
- },
- null,
- 2
- )
-
- const zip = await getZipWithFiles(meta, files)
- zip.file('meta.json', stringifiedMeta)
-
- const arrayBuffer = await zip
- .generateAsync({
- type: 'arraybuffer',
- compression: 'DEFLATE',
+ setLoadingSpinnerDesc('Generating file')
+ try {
+ const zip = await getZipWithFiles(meta, files)
+ const arrayBuffer = await zip.generateAsync({
+ type: ARRAY_BUFFER,
+ compression: DEFLATE,
compressionOptions: {
level: 6
}
})
- .catch((err) => {
- console.log('err in zip:>> ', err)
- setIsLoading(false)
- toast.error(err.message || 'Error occurred in generating zip file')
- return null
- })
-
- if (!arrayBuffer) return
-
- const blob = new Blob([arrayBuffer])
- saveAs(blob, `exported-${unixNow()}.sigit.zip`)
-
- setIsLoading(false)
-
- navigate(appPublicRoutes.verify)
+ if (!arrayBuffer) return
+ const blob = new Blob([arrayBuffer])
+ saveAs(blob, `exported-${unixNow()}.sigit.zip`)
+ } catch (error) {
+ console.log('error in zip:>> ', error)
+ if (error instanceof Error) {
+ toast.error(error.message || 'Error occurred in generating zip file')
+ }
+ }
}
+ /**
+ * @Depricated
+ * It's the same as {@link handleExport} but the code is not the same
+ * leaving it for the reference
+ */
+ // const handleExport = async () => {
+ // if (Object.entries(files).length === 0 || !meta || !usersPubkey) return
+ //
+ // const usersNpub = hexToNpub(usersPubkey)
+ // if (
+ // !signers.includes(usersNpub) &&
+ // !viewers.includes(usersNpub) &&
+ // submittedBy !== usersNpub
+ // )
+ // return
+ //
+ // setIsLoading(true)
+ // setLoadingSpinnerDesc('Signing nostr event')
+ //
+ // if (!meta) return
+ //
+ // const prevSig = getLastSignersSig(meta, signers)
+ // if (!prevSig) return
+ //
+ // const signedEvent = await signEventForMetaFile(
+ // JSON.stringify({
+ // prevSig
+ // }),
+ // nostrController,
+ // setIsLoading
+ // )
+ //
+ // if (!signedEvent) return
+ //
+ // const exportSignature = JSON.stringify(signedEvent, null, 2)
+ //
+ // const stringifiedMeta = JSON.stringify(
+ // {
+ // ...meta,
+ // exportSignature
+ // },
+ // null,
+ // 2
+ // )
+ //
+ // const zip = await getZipWithFiles(meta, files)
+ // zip.file('meta.json', stringifiedMeta)
+ //
+ // const arrayBuffer = await zip
+ // .generateAsync({
+ // type: 'arraybuffer',
+ // compression: 'DEFLATE',
+ // compressionOptions: {
+ // level: 6
+ // }
+ // })
+ // .catch((err) => {
+ // console.log('err in zip:>> ', err)
+ // setIsLoading(false)
+ // toast.error(err.message || 'Error occurred in generating zip file')
+ // return null
+ // })
+ //
+ // if (!arrayBuffer) return
+ //
+ // const blob = new Blob([arrayBuffer])
+ // saveAs(blob, `exported-${unixNow()}.sigit.zip`)
+ //
+ // setIsLoading(false)
+ //
+ // navigate(appPublicRoutes.verify)
+ // }
+
const handleEncryptedExport = async () => {
if (Object.entries(files).length === 0 || !meta) return
@@ -944,90 +864,17 @@ export const SignPage = () => {
return
}
- if (!isMarksCompleted && signedStatus === SignedStatus.User_Is_Next_Signer) {
- return (
-
- )
- }
-
return (
- <>
-
- {displayInput && (
- <>
-
- Select sigit file
-
-
-
- setSelectedFile(value)}
- />
-
-
- {selectedFile && (
-
-
- Decrypt
-
-
- )}
- >
- )}
-
- {submittedBy && Object.entries(files).length > 0 && meta && (
- <>
-
-
- {signedStatus === SignedStatus.Fully_Signed && (
-
-
- Export Sigit
-
-
- )}
-
- {signedStatus === SignedStatus.User_Is_Next_Signer && (
-
-
- Sign
-
-
- )}
-
- {isSignerOrCreator && (
-
-
- Export Encrypted Sigit
-
-
- )}
- >
- )}
-
- >
+
)
}
diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx
index 515a257..1d91fa4 100644
--- a/src/pages/verify/index.tsx
+++ b/src/pages/verify/index.tsx
@@ -23,7 +23,10 @@ import {
getCurrentUserFiles,
updateUsersAppData,
npubToHex,
- sendNotification
+ sendNotification,
+ generateEncryptionKey,
+ encryptArrayBuffer,
+ generateKeysFile
} from '../../utils'
import styles from './style.module.scss'
import { useLocation, useParams } from 'react-router-dom'
@@ -597,6 +600,120 @@ export const VerifyPage = () => {
setIsLoading(false)
}
+ // Handle errors during zip file generation
+ const handleZipError = (err: unknown) => {
+ console.log('Error in zip:>> ', err)
+ setIsLoading(false)
+ if (err instanceof Error) {
+ toast.error(err.message || 'Error occurred in generating zip file')
+ }
+ return null
+ }
+
+ // Check if the current user is the last signer
+ const checkIsLastSigner = (signers: string[]): boolean => {
+ const usersNpub = hexToNpub(usersPubkey!)
+ const lastSignerIndex = signers.length - 1
+ const signerIndex = signers.indexOf(usersNpub)
+ return signerIndex === lastSignerIndex
+ }
+
+ // create final zip file
+ const createFinalZipFile = async (
+ encryptedArrayBuffer: ArrayBuffer,
+ encryptionKey: string
+ ): Promise => {
+ // Get the current timestamp in seconds
+ const blob = new Blob([encryptedArrayBuffer])
+ // Create a File object with the Blob data
+ const file = new File([blob], `compressed.sigit`, {
+ type: 'application/sigit'
+ })
+
+ const isLastSigner = checkIsLastSigner(signers)
+
+ const userSet = new Set()
+
+ if (isLastSigner) {
+ if (submittedBy) {
+ userSet.add(submittedBy)
+ }
+
+ signers.forEach((signer) => {
+ userSet.add(npubToHex(signer)!)
+ })
+
+ viewers.forEach((viewer) => {
+ userSet.add(npubToHex(viewer)!)
+ })
+ } else {
+ const usersNpub = hexToNpub(usersPubkey!)
+ const signerIndex = signers.indexOf(usersNpub)
+ const nextSigner = signers[signerIndex + 1]
+ userSet.add(npubToHex(nextSigner)!)
+ }
+
+ const keysFileContent = await generateKeysFile(
+ Array.from(userSet),
+ encryptionKey
+ )
+ if (!keysFileContent) return null
+
+ const zip = new JSZip()
+ zip.file(`compressed.sigit`, file)
+ zip.file('keys.json', keysFileContent)
+
+ const arraybuffer = await zip
+ .generateAsync({
+ type: 'arraybuffer',
+ compression: 'DEFLATE',
+ compressionOptions: { level: 6 }
+ })
+ .catch(handleZipError)
+
+ if (!arraybuffer) return null
+
+ return new File([new Blob([arraybuffer])], `${unixNow()}.sigit.zip`, {
+ type: 'application/zip'
+ })
+ }
+
+ const handleEncryptedExport = async () => {
+ if (Object.entries(files).length === 0 || !meta) return
+
+ const stringifiedMeta = JSON.stringify(meta, null, 2)
+ const zip = await getZipWithFiles(meta, files)
+
+ zip.file('meta.json', stringifiedMeta)
+
+ const arrayBuffer = await zip
+ .generateAsync({
+ type: 'arraybuffer',
+ compression: 'DEFLATE',
+ compressionOptions: {
+ level: 6
+ }
+ })
+ .catch((err) => {
+ console.log('err in zip:>> ', err)
+ setIsLoading(false)
+ toast.error(err.message || 'Error occurred in generating zip file')
+ return null
+ })
+
+ if (!arrayBuffer) return
+
+ const key = await generateEncryptionKey()
+
+ setLoadingSpinnerDesc('Encrypting zip file')
+ const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
+
+ const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
+
+ if (!finalZipFile) return
+ saveAs(finalZipFile, `exported-${unixNow()}.sigit.zip`)
+ }
+
return (
<>
{isLoading && }
@@ -640,8 +757,8 @@ export const VerifyPage = () => {
)}
currentFile={currentFile}
setCurrentFile={setCurrentFile}
- handleDownload={handleMarkedExport}
- downloadLabel="Download Sigit"
+ handleExport={handleMarkedExport}
+ handleEncryptedExport={handleEncryptedExport}
/>
)
}
diff --git a/src/utils/const.ts b/src/utils/const.ts
index 522f242..2b8e822 100644
--- a/src/utils/const.ts
+++ b/src/utils/const.ts
@@ -120,5 +120,5 @@ export const SIGNATURE_PAD_OPTIONS = {
export const SIGNATURE_PAD_SIZE = {
width: 300,
- height: 300
+ height: 150
}