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-html5-backend": "16.0.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-redux": "9.1.0",
"react-router-dom": "6.22.1",
"react-toastify": "10.0.4",
@ -2680,6 +2681,15 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"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": {
"version": "1.6.7",
"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",
"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": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -5716,6 +5744,23 @@
"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": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",

View File

@ -7,7 +7,7 @@
"scripts": {
"dev": "vite",
"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: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}\"",
@ -47,6 +47,7 @@
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-redux": "9.1.0",
"react-router-dom": "6.22.1",
"react-toastify": "10.0.4",

View File

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

View File

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

View File

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