sigit.io/src/pages/home/index.tsx

249 lines
7.4 KiB
TypeScript
Raw Normal View History

import { Button, TextField } from '@mui/material'
2024-06-28 14:24:14 +05:00
import JSZip from 'jszip'
import { useEffect, useRef, useState } from 'react'
2024-05-14 14:27:05 +05:00
import { useNavigate } 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'
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
import { Meta, ProfileMetadata } from '../../types'
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 { Container } from '../../components/Container'
import styles from './style.module.scss'
import {
extractSigitInfo,
SigitInfo,
SignedStatus
} from '../../hooks/useSigitMeta'
// Unsupported Filter options are commented
const FILTERS = [
'Show all',
// 'Drafts',
'In-progress',
'Completed',
'Archived'
] 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']
export const HomePage = () => {
2024-05-14 14:27:05 +05:00
const navigate = useNavigate()
const fileInputRef = useRef<HTMLInputElement>(null)
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) {
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])
const handleUploadClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click()
}
}
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 }
})
}
// 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 } })
}
}
const [search, setSearch] = useState('')
const [filter, setFilter] = useState<Filter>('Show all')
const [sort, setSort] = useState<Sort>('desc')
return (
<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
}
})}
/>
<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
setSearch(searchInput.value)
}}
>
<TextField
name="q"
placeholder="Search"
size="small"
type="search"
onChange={(e) => {
// Handle the case when users click native search input's clear or x
if (e.currentTarget.value === '') {
setSearch(e.currentTarget.value)
}
}}
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'}
>
<FontAwesomeIcon icon={faSearch} />
</Button>
</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>
<div className={styles.submissions}>
{Object.keys(parsedSigits)
.filter((s) => {
const { title, signedStatus } = parsedSigits[s]
const isMatch = title?.toLowerCase().includes(search.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>
2024-06-07 16:13:32 +05:00
)
}