2024-08-07 14:15:20 +02:00
|
|
|
import { Button, TextField } from '@mui/material'
|
2024-06-28 14:24:14 +05:00
|
|
|
import JSZip from 'jszip'
|
2024-08-08 17:30:49 +02:00
|
|
|
import { useCallback, useEffect, useState } from 'react'
|
2024-08-09 10:58:30 +02:00
|
|
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
2024-06-28 14:24:14 +05:00
|
|
|
import { toast } from 'react-toastify'
|
2024-07-05 13:38:04 +05:00
|
|
|
import { useAppSelector } from '../../hooks'
|
2024-06-13 11:47:28 +05:00
|
|
|
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
|
2024-08-06 12:42:21 +02:00
|
|
|
import { Meta, ProfileMetadata } from '../../types'
|
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
2024-08-07 14:15:20 +02:00
|
|
|
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
2024-08-06 12:42:21 +02:00
|
|
|
import { Select } from '../../components/Select'
|
|
|
|
import { DisplaySigit } from '../../components/DisplaySigit'
|
2024-08-08 17:30:49 +02:00
|
|
|
import { useDropzone } from 'react-dropzone'
|
2024-07-30 10:28:57 +02:00
|
|
|
import { Container } from '../../components/Container'
|
2024-08-06 12:42:21 +02:00
|
|
|
import styles from './style.module.scss'
|
2024-08-07 14:15:20 +02:00
|
|
|
import {
|
|
|
|
extractSigitInfo,
|
|
|
|
SigitInfo,
|
|
|
|
SignedStatus
|
|
|
|
} from '../../hooks/useSigitMeta'
|
2024-08-06 12:42:21 +02:00
|
|
|
|
|
|
|
// Unsupported Filter options are commented
|
|
|
|
const FILTERS = [
|
|
|
|
'Show all',
|
|
|
|
// 'Drafts',
|
|
|
|
'In-progress',
|
2024-08-08 17:30:49 +02:00
|
|
|
'Completed'
|
|
|
|
// 'Archived'
|
2024-08-06 12:42:21 +02:00
|
|
|
] as const
|
|
|
|
type Filter = (typeof FILTERS)[number]
|
|
|
|
|
|
|
|
const SORT_BY = [
|
|
|
|
{
|
|
|
|
label: 'Newest',
|
|
|
|
value: 'desc'
|
|
|
|
},
|
|
|
|
{ label: 'Oldest', value: 'asc' }
|
|
|
|
] as const
|
|
|
|
type Sort = (typeof SORT_BY)[number]['value']
|
2024-04-08 17:45:51 +05:00
|
|
|
|
|
|
|
export const HomePage = () => {
|
2024-05-14 14:27:05 +05:00
|
|
|
const navigate = useNavigate()
|
2024-08-09 10:58:30 +02:00
|
|
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
|
|
const q = searchParams.get('q') ?? ''
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const searchInput = document.getElementById('q') as HTMLInputElement | null
|
|
|
|
if (searchInput) {
|
|
|
|
searchInput.value = q
|
|
|
|
}
|
|
|
|
}, [q])
|
2024-08-07 14:15:20 +02:00
|
|
|
|
|
|
|
const [sigits, setSigits] = useState<{ [key: string]: Meta }>({})
|
|
|
|
const [parsedSigits, setParsedSigits] = useState<{
|
|
|
|
[key: string]: SigitInfo
|
|
|
|
}>({})
|
2024-06-28 14:24:14 +05:00
|
|
|
const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>(
|
|
|
|
{}
|
|
|
|
)
|
2024-07-05 13:38:04 +05:00
|
|
|
const usersAppData = useAppSelector((state) => state.userAppData)
|
2024-06-28 14:24:14 +05:00
|
|
|
|
|
|
|
useEffect(() => {
|
2024-07-05 13:38:04 +05:00
|
|
|
if (usersAppData) {
|
2024-08-07 14:15:20 +02:00
|
|
|
const getSigitInfo = async () => {
|
|
|
|
for (const key in usersAppData.sigits) {
|
|
|
|
if (Object.prototype.hasOwnProperty.call(usersAppData.sigits, key)) {
|
|
|
|
const sigitInfo = await extractSigitInfo(usersAppData.sigits[key])
|
|
|
|
if (sigitInfo) {
|
|
|
|
setParsedSigits((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
[key]: sigitInfo
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setSigits(usersAppData.sigits)
|
|
|
|
getSigitInfo()
|
2024-07-05 13:38:04 +05:00
|
|
|
}
|
|
|
|
}, [usersAppData])
|
2024-06-13 11:47:28 +05:00
|
|
|
|
2024-08-08 17:30:49 +02:00
|
|
|
const onDrop = useCallback(
|
|
|
|
async (acceptedFiles: File[]) => {
|
|
|
|
// When uploading single file check if it's .sigit.zip
|
|
|
|
if (acceptedFiles.length === 1) {
|
|
|
|
const file = acceptedFiles[0]
|
|
|
|
|
|
|
|
// 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
|
2024-06-13 11:47:28 +05:00
|
|
|
})
|
|
|
|
|
2024-08-08 17:30:49 +02:00
|
|
|
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 }
|
|
|
|
})
|
|
|
|
}
|
2024-06-13 11:47:28 +05:00
|
|
|
|
2024-08-08 17:30:49 +02:00
|
|
|
// 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
|
|
|
|
}
|
2024-06-13 11:47:28 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
// navigate to create page
|
2024-08-08 17:30:49 +02:00
|
|
|
navigate(appPrivateRoutes.create, {
|
|
|
|
state: { uploadedFiles: acceptedFiles }
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[navigate]
|
|
|
|
)
|
|
|
|
|
2024-08-09 11:29:00 +02:00
|
|
|
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
|
|
|
|
onDrop,
|
|
|
|
noClick: true
|
|
|
|
})
|
2024-04-08 17:45:51 +05:00
|
|
|
|
2024-08-06 12:42:21 +02:00
|
|
|
const [filter, setFilter] = useState<Filter>('Show all')
|
2024-08-07 14:15:20 +02:00
|
|
|
const [sort, setSort] = useState<Sort>('desc')
|
2024-08-06 12:42:21 +02:00
|
|
|
|
2024-04-08 17:45:51 +05:00
|
|
|
return (
|
2024-08-09 12:00:36 +02:00
|
|
|
<div {...getRootProps()} tabIndex={-1}>
|
2024-08-09 11:29:00 +02:00
|
|
|
<Container className={styles.container}>
|
|
|
|
<div className={styles.header}>
|
|
|
|
<div className={styles.filters}>
|
|
|
|
<Select
|
|
|
|
name={'filter-select'}
|
|
|
|
value={filter}
|
|
|
|
setValue={setFilter}
|
|
|
|
options={FILTERS.map((f) => {
|
|
|
|
return {
|
|
|
|
label: f,
|
|
|
|
value: f
|
2024-08-06 12:42:21 +02:00
|
|
|
}
|
2024-08-09 11:29:00 +02:00
|
|
|
})}
|
2024-08-06 12:42:21 +02:00
|
|
|
/>
|
2024-08-09 11:29:00 +02:00
|
|
|
<Select
|
|
|
|
name={'sort-select'}
|
|
|
|
value={sort}
|
|
|
|
setValue={setSort}
|
|
|
|
options={SORT_BY.map((s) => {
|
|
|
|
return { ...s }
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className={styles.actionButtons}>
|
|
|
|
<form
|
|
|
|
className={styles.search}
|
|
|
|
onSubmit={(e) => {
|
|
|
|
e.preventDefault()
|
|
|
|
const searchInput = e.currentTarget.elements.namedItem(
|
|
|
|
'q'
|
|
|
|
) as HTMLInputElement
|
|
|
|
searchParams.set('q', searchInput.value)
|
|
|
|
setSearchParams(searchParams)
|
2024-08-06 12:42:21 +02:00
|
|
|
}}
|
|
|
|
>
|
2024-08-09 11:29:00 +02:00
|
|
|
<TextField
|
|
|
|
id="q"
|
|
|
|
name="q"
|
|
|
|
placeholder="Search"
|
|
|
|
size="small"
|
|
|
|
type="search"
|
|
|
|
defaultValue={q}
|
|
|
|
onChange={(e) => {
|
|
|
|
// Handle the case when users click native search input's clear or x
|
|
|
|
if (e.currentTarget.value === '') {
|
|
|
|
searchParams.delete('q')
|
|
|
|
setSearchParams(searchParams)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
sx={{
|
|
|
|
width: '100%',
|
|
|
|
fontSize: '16px',
|
|
|
|
borderTopLeftRadius: 'var(----mui-shape-borderRadius)',
|
|
|
|
borderBottomLeftRadius: 'var(----mui-shape-borderRadius)',
|
|
|
|
'& .MuiInputBase-root': {
|
|
|
|
borderTopRightRadius: 0,
|
|
|
|
borderBottomRightRadius: 0
|
|
|
|
},
|
|
|
|
'& .MuiInputBase-input': {
|
|
|
|
padding: '7px 14px'
|
|
|
|
},
|
|
|
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
|
|
display: 'none'
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Button
|
|
|
|
type="submit"
|
|
|
|
sx={{
|
|
|
|
minWidth: '44px',
|
|
|
|
padding: '11.5px 12px',
|
|
|
|
borderTopLeftRadius: 0,
|
|
|
|
borderBottomLeftRadius: 0
|
|
|
|
}}
|
|
|
|
variant={'contained'}
|
2024-08-09 12:00:36 +02:00
|
|
|
aria-label="submit search"
|
2024-08-09 11:29:00 +02:00
|
|
|
>
|
|
|
|
<FontAwesomeIcon icon={faSearch} />
|
|
|
|
</Button>
|
|
|
|
</form>
|
|
|
|
</div>
|
2024-08-06 12:42:21 +02:00
|
|
|
</div>
|
2024-08-09 12:00:36 +02:00
|
|
|
<button
|
2024-08-09 11:29:00 +02:00
|
|
|
className={`${styles.dropzone} ${isDragActive ? styles.isDragActive : ''}`}
|
2024-08-09 12:00:36 +02:00
|
|
|
tabIndex={0}
|
2024-08-09 11:29:00 +02:00
|
|
|
onClick={open}
|
2024-08-09 12:00:36 +02:00
|
|
|
type="button"
|
|
|
|
aria-label="upload files"
|
2024-08-09 11:29:00 +02:00
|
|
|
>
|
2024-08-09 12:00:36 +02:00
|
|
|
<input {...getInputProps()} />
|
|
|
|
{isDragActive ? (
|
|
|
|
<p>Drop the files here ...</p>
|
|
|
|
) : (
|
|
|
|
<p>Click or drag files to upload!</p>
|
|
|
|
)}
|
|
|
|
</button>
|
2024-08-09 11:29:00 +02:00
|
|
|
<div className={styles.submissions}>
|
|
|
|
{Object.keys(parsedSigits)
|
|
|
|
.filter((s) => {
|
|
|
|
const { title, signedStatus } = parsedSigits[s]
|
|
|
|
const isMatch = title?.toLowerCase().includes(q.toLowerCase())
|
|
|
|
switch (filter) {
|
|
|
|
case 'Completed':
|
|
|
|
return signedStatus === SignedStatus.Complete && isMatch
|
|
|
|
case 'In-progress':
|
|
|
|
return signedStatus === SignedStatus.Partial && isMatch
|
|
|
|
case 'Show all':
|
|
|
|
return isMatch
|
|
|
|
default:
|
|
|
|
console.error('Filter case not handled.')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.sort((a, b) => {
|
|
|
|
const x = parsedSigits[a].createdAt ?? 0
|
|
|
|
const y = parsedSigits[b].createdAt ?? 0
|
|
|
|
return sort === 'desc' ? y - x : x - y
|
|
|
|
})
|
|
|
|
.map((key) => (
|
|
|
|
<DisplaySigit
|
|
|
|
key={`sigit-${key}`}
|
|
|
|
parsedMeta={parsedSigits[key]}
|
|
|
|
meta={sigits[key]}
|
|
|
|
profiles={profiles}
|
|
|
|
setProfiles={setProfiles}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</Container>
|
|
|
|
</div>
|
2024-06-07 16:13:32 +05:00
|
|
|
)
|
|
|
|
}
|