feat: add dropzone and multiple files support
All checks were successful
Open PR on Staging / audit_and_check (pull_request) Successful in 33s

This commit is contained in:
enes 2024-08-08 17:30:49 +02:00
parent 276ad23e2c
commit 83ddc1bbc8
5 changed files with 106 additions and 59 deletions

45
package-lock.json generated
View File

@ -37,6 +37,7 @@
"react-dnd": "16.0.1", "react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1", "react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-redux": "9.1.0", "react-redux": "9.1.0",
"react-router-dom": "6.22.1", "react-router-dom": "6.22.1",
"react-toastify": "10.0.4", "react-toastify": "10.0.4",
@ -2680,6 +2681,15 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/attr-accept": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.7", "version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
@ -3857,6 +3867,24 @@
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
}, },
"node_modules/file-selector": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
"integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
"license": "MIT",
"dependencies": {
"tslib": "^2.4.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/file-selector/node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"license": "0BSD"
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -5716,6 +5744,23 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-dropzone": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
"integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
"license": "MIT",
"dependencies": {
"attr-accept": "^2.2.2",
"file-selector": "^0.6.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">= 10.13"
},
"peerDependencies": {
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",

View File

@ -7,7 +7,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 32", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 29",
"lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
@ -47,6 +47,7 @@
"react-dnd": "16.0.1", "react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1", "react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-redux": "9.1.0", "react-redux": "9.1.0",
"react-router-dom": "6.22.1", "react-router-dom": "6.22.1",
"react-toastify": "10.0.4", "react-toastify": "10.0.4",

View File

@ -24,7 +24,7 @@ import JSZip from 'jszip'
import { MuiFileInput } from 'mui-file-input' import { MuiFileInput } from 'mui-file-input'
import { Event, kinds } from 'nostr-tools' import { Event, kinds } from 'nostr-tools'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { DndProvider, DragSourceMonitor, useDrag, useDrop } from 'react-dnd' import { DndProvider, useDrag, useDrop } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend' import { HTML5Backend } from 'react-dnd-html5-backend'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
@ -68,7 +68,7 @@ import { Mark } from '../../types/mark.ts'
export const CreatePage = () => { export const CreatePage = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const { uploadedFile } = location.state || {} const { uploadedFiles } = location.state || {}
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
@ -134,10 +134,10 @@ export const CreatePage = () => {
}) })
useEffect(() => { useEffect(() => {
if (uploadedFile) { if (uploadedFiles) {
setSelectedFiles([uploadedFile]) setSelectedFiles([...uploadedFiles])
} }
}, [uploadedFile]) }, [uploadedFiles])
useEffect(() => { useEffect(() => {
if (usersPubkey) { if (usersPubkey) {
@ -979,7 +979,7 @@ const SignerRow = ({
item: () => { item: () => {
return { id: user.pubkey, index } return { id: user.pubkey, index }
}, },
collect: (monitor: DragSourceMonitor) => ({ collect: (monitor) => ({
isDragging: monitor.isDragging() isDragging: monitor.isDragging()
}) })
}) })

View File

@ -1,6 +1,6 @@
import { Button, TextField } from '@mui/material' import { Button, TextField } from '@mui/material'
import JSZip from 'jszip' import JSZip from 'jszip'
import { useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useAppSelector } from '../../hooks' import { useAppSelector } from '../../hooks'
@ -10,7 +10,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons' import { faSearch } from '@fortawesome/free-solid-svg-icons'
import { Select } from '../../components/Select' import { Select } from '../../components/Select'
import { DisplaySigit } from '../../components/DisplaySigit' import { DisplaySigit } from '../../components/DisplaySigit'
import { useDropzone } from 'react-dropzone'
import { Container } from '../../components/Container' import { Container } from '../../components/Container'
import styles from './style.module.scss' import styles from './style.module.scss'
import { import {
@ -24,8 +24,8 @@ const FILTERS = [
'Show all', 'Show all',
// 'Drafts', // 'Drafts',
'In-progress', 'In-progress',
'Completed', 'Completed'
'Archived' // 'Archived'
] as const ] as const
type Filter = (typeof FILTERS)[number] type Filter = (typeof FILTERS)[number]
@ -40,7 +40,6 @@ type Sort = (typeof SORT_BY)[number]['value']
export const HomePage = () => { export const HomePage = () => {
const navigate = useNavigate() const navigate = useNavigate()
const fileInputRef = useRef<HTMLInputElement>(null)
const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) const [sigits, setSigits] = useState<{ [key: string]: Meta }>({})
const [parsedSigits, setParsedSigits] = useState<{ const [parsedSigits, setParsedSigits] = useState<{
@ -74,51 +73,52 @@ export const HomePage = () => {
} }
}, [usersAppData]) }, [usersAppData])
const handleUploadClick = () => { const onDrop = useCallback(
if (fileInputRef.current) { async (acceptedFiles: File[]) => {
fileInputRef.current.click() // When uploading single file check if it's .sigit.zip
} if (acceptedFiles.length === 1) {
} const file = acceptedFiles[0]
const handleFileChange = async ( // Check if the file extension is .sigit.zip
event: React.ChangeEvent<HTMLInputElement> const fileName = file.name
) => { const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters
const file = event.target.files?.[0] if (fileExtension === '.sigit.zip') {
if (file) { const zip = await JSZip.loadAsync(file).catch((err) => {
// Check if the file extension is .sigit.zip console.log('err in loading zip file :>> ', err)
const fileName = file.name toast.error(err.message || 'An error occurred in loading zip file.')
const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters return null
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 (!zip) return
if ('meta.json' in zip.files) {
return navigate(appPublicRoutes.verify, {
state: { uploadedZip: file }
})
}
toast.error('Invalid zip file') // navigate to sign page if zip contains keys.json
return 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 SiGit zip file')
return
}
} }
// navigate to create page // navigate to create page
navigate(appPrivateRoutes.create, { state: { uploadedFile: file } }) navigate(appPrivateRoutes.create, {
} state: { uploadedFiles: acceptedFiles }
} })
},
[navigate]
)
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [filter, setFilter] = useState<Filter>('Show all') const [filter, setFilter] = useState<Filter>('Show all')
@ -202,15 +202,15 @@ export const HomePage = () => {
</form> </form>
</div> </div>
</div> </div>
<div className={styles.dropzone} onClick={handleUploadClick}> <div className={styles.dropzone}>
<input <div {...getRootProps()}>
id="fileUpload" <input {...getInputProps()} />
type="file" {isDragActive ? (
hidden <p>Drop the files here ...</p>
ref={fileInputRef} ) : (
onChange={handleFileChange} <p>Click or drag files to upload!</p>
/> )}
<div>Click or drag files to upload!</div> </div>
</div> </div>
<div className={styles.submissions}> <div className={styles.submissions}>
{Object.keys(parsedSigits) {Object.keys(parsedSigits)

View File

@ -120,6 +120,7 @@ export const queryNip05 = async (
if (!match) throw new Error('Invalid nip05') if (!match) throw new Error('Invalid nip05')
// Destructure the match result, assigning default value '_' to name if not provided // Destructure the match result, assigning default value '_' to name if not provided
// First variable from the match destructuring is ignored
const [, name = '_', domain] = match const [, name = '_', domain] = match
// Construct the URL to query the NIP-05 data // Construct the URL to query the NIP-05 data