import { CalendarMonth, Description, Upload } from '@mui/icons-material' import { Box, Button, Tooltip, Typography } from '@mui/material' import JSZip from 'jszip' import { Event, kinds, verifyEvent } from 'nostr-tools' import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { UserComponent } from '../../components/username' import { MetadataController } from '../../controllers' import { useAppSelector } from '../../hooks' import { appPrivateRoutes, appPublicRoutes } from '../../routes' import { CreateSignatureEventContent, Meta, ProfileMetadata } from '../../types' import { formatTimestamp, hexToNpub, npubToHex, parseJson, shorten } from '../../utils' import styles from './style.module.scss' export const HomePage = () => { const navigate = useNavigate() const fileInputRef = useRef(null) const [sigits, setSigits] = useState([]) const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>( {} ) const usersAppData = useAppSelector((state) => state.userAppData) useEffect(() => { if (usersAppData) { setSigits(Object.values(usersAppData.sigits)) } }, [usersAppData]) const handleUploadClick = () => { if (fileInputRef.current) { fileInputRef.current.click() } } const handleFileChange = async ( event: React.ChangeEvent ) => { const file = event.target.files?.[0] if (file) { // Check if the file extension is .sigit.zip const fileName = file.name const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters if (fileExtension === '.sigit.zip') { const zip = await JSZip.loadAsync(file).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 // navigate to sign page if zip contains keys.json if ('keys.json' in zip.files) { return navigate(appPrivateRoutes.sign, { state: { uploadedZip: file } }) } // navigate to verify page if zip contains meta.json if ('meta.json' in zip.files) { return navigate(appPublicRoutes.verify, { state: { uploadedZip: file } }) } toast.error('Invalid zip file') return } // navigate to create page navigate(appPrivateRoutes.create, { state: { uploadedFile: file } }) } } return ( <> Sigits {/* This is for desktop view */} {/* This is for mobile view */} {sigits.map((sigit, index) => ( ))} ) } type SigitProps = { meta: Meta profiles: { [key: string]: ProfileMetadata } setProfiles: Dispatch> } enum SignedStatus { Partial = 'Partially Signed', Complete = 'Completely Signed' } const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { const navigate = useNavigate() const [title, setTitle] = useState() const [createdAt, setCreatedAt] = useState('') const [submittedBy, setSubmittedBy] = useState() const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [signedStatus, setSignedStatus] = useState( SignedStatus.Partial ) useEffect(() => { const extractInfo = async () => { const createSignatureEvent = await parseJson( meta.createSignature ).catch((err) => { console.log('err in parsing the createSignature event:>> ', err) toast.error( err.message || 'error occurred in parsing the create signature event' ) return null }) if (!createSignatureEvent) return // created_at in nostr events are stored in seconds // convert it to ms before formatting setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000)) const createSignatureContent = await parseJson( createSignatureEvent.content ).catch((err) => { console.log( `err in parsing the createSignature event's content :>> `, err ) return null }) if (!createSignatureContent) return setTitle(createSignatureContent.title) setSubmittedBy(createSignatureEvent.pubkey) setSigners(createSignatureContent.signers) const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] const isCompletelySigned = createSignatureContent.signers.every( (signer) => signedBy.includes(signer) ) if (isCompletelySigned) { setSignedStatus(SignedStatus.Complete) } } extractInfo() }, [meta]) useEffect(() => { const hexKeys: string[] = [] if (submittedBy) { hexKeys.push(npubToHex(submittedBy)!) } hexKeys.push(...signers.map((signer) => npubToHex(signer)!)) const metadataController = new MetadataController() hexKeys.forEach((key) => { if (!(key in profiles)) { const handleMetadataEvent = (event: Event) => { const metadataContent = metadataController.extractProfileMetadataContent(event) if (metadataContent) setProfiles((prev) => ({ ...prev, [key]: metadataContent })) } metadataController.on(key, (kind: number, event: Event) => { if (kind === kinds.Metadata) { handleMetadataEvent(event) } }) metadataController .findMetadata(key) .then((metadataEvent) => { if (metadataEvent) handleMetadataEvent(metadataEvent) }) .catch((err) => { console.error(`error occurred in finding metadata for: ${key}`, err) }) } }) }, [submittedBy, signers]) const handleNavigation = () => { if (signedStatus === SignedStatus.Complete) { navigate(appPublicRoutes.verify, { state: { meta } }) } else { navigate(appPrivateRoutes.sign, { state: { meta } }) } } return ( {title} {submittedBy && (function () { const profile = profiles[submittedBy] return ( ) })()} {createdAt} {signers.map((signer) => { const pubkey = npubToHex(signer)! const profile = profiles[pubkey] return ( ) })} ) } enum SignStatus { Signed = 'Signed', Pending = 'Pending', Invalid = 'Invalid Sign' } type DisplaySignerProps = { meta: Meta profile: ProfileMetadata pubkey: string } const DisplaySigner = ({ meta, profile, pubkey }: DisplaySignerProps) => { const [signStatus, setSignedStatus] = useState() useEffect(() => { const updateSignStatus = async () => { const npub = hexToNpub(pubkey) if (npub in meta.docSignatures) { parseJson(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) } } updateSignStatus() }, [meta, pubkey]) return ( {signStatus} ) }