feat(design): home page new design and functionality #135
@ -1,10 +1,10 @@
|
|||||||
import { Dispatch, SetStateAction, useEffect } from 'react'
|
import { Dispatch, SetStateAction, useEffect } from 'react'
|
||||||
import { Meta, ProfileMetadata } from '../../types'
|
import { Meta, ProfileMetadata } from '../../types'
|
||||||
import { SignedStatus, useSigitMeta } from '../../hooks/useSigitMeta'
|
import { SigitInfo, SignedStatus } from '../../hooks/useSigitMeta'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { MetadataController } from '../../controllers'
|
import { MetadataController } from '../../controllers'
|
||||||
import { hexToNpub, npubToHex, shorten } from '../../utils'
|
import { formatTimestamp, hexToNpub, npubToHex, shorten } from '../../utils'
|
||||||
import { appPublicRoutes, appPrivateRoutes } from '../../routes'
|
import { appPublicRoutes, appPrivateRoutes } from '../../routes'
|
||||||
import { Button, Divider, Tooltip } from '@mui/material'
|
import { Button, Divider, Tooltip } from '@mui/material'
|
||||||
import { DisplaySigner } from '../DisplaySigner'
|
import { DisplaySigner } from '../DisplaySigner'
|
||||||
@ -25,11 +25,17 @@ import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
|||||||
|
|
||||||
type SigitProps = {
|
type SigitProps = {
|
||||||
meta: Meta
|
meta: Meta
|
||||||
|
parsedMeta: SigitInfo
|
||||||
profiles: { [key: string]: ProfileMetadata }
|
profiles: { [key: string]: ProfileMetadata }
|
||||||
setProfiles: Dispatch<SetStateAction<{ [key: string]: ProfileMetadata }>>
|
setProfiles: Dispatch<SetStateAction<{ [key: string]: ProfileMetadata }>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
|
export const DisplaySigit = ({
|
||||||
|
meta,
|
||||||
|
parsedMeta,
|
||||||
|
profiles,
|
||||||
|
setProfiles
|
||||||
|
}: SigitProps) => {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
createdAt,
|
createdAt,
|
||||||
@ -37,7 +43,7 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
|
|||||||
signers,
|
signers,
|
||||||
signedStatus,
|
signedStatus,
|
||||||
fileExtensions
|
fileExtensions
|
||||||
} = useSigitMeta(meta)
|
} = parsedMeta
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hexKeys: string[] = []
|
const hexKeys: string[] = []
|
||||||
@ -144,7 +150,7 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={`${styles.details} ${styles.date} ${styles.iconLabel}`}>
|
<div className={`${styles.details} ${styles.date} ${styles.iconLabel}`}>
|
||||||
<FontAwesomeIcon icon={faCalendar} />
|
<FontAwesomeIcon icon={faCalendar} />
|
||||||
{createdAt}
|
{createdAt ? formatTimestamp(createdAt) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.details} ${styles.status}`}>
|
<div className={`${styles.details} ${styles.status}`}>
|
||||||
<span className={styles.iconLabel}>
|
<span className={styles.iconLabel}>
|
||||||
|
@ -60,6 +60,7 @@ interface SelectItemProps<T extends string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SelectProps<T extends string> {
|
interface SelectProps<T extends string> {
|
||||||
|
value: T
|
||||||
setValue: React.Dispatch<React.SetStateAction<T>>
|
setValue: React.Dispatch<React.SetStateAction<T>>
|
||||||
options: SelectItemProps<T>[]
|
options: SelectItemProps<T>[]
|
||||||
name?: string
|
name?: string
|
||||||
@ -67,6 +68,7 @@ interface SelectProps<T extends string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Select<T extends string>({
|
export function Select<T extends string>({
|
||||||
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
options,
|
options,
|
||||||
name,
|
name,
|
||||||
@ -83,7 +85,7 @@ export function Select<T extends string>({
|
|||||||
name={name}
|
name={name}
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
defaultValue={options[0].value as string}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
MenuProps={{
|
MenuProps={{
|
||||||
MenuListProps: {
|
MenuListProps: {
|
||||||
|
@ -1,26 +1,34 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { CreateSignatureEventContent, Meta } from '../types'
|
import { CreateSignatureEventContent, Meta } from '../types'
|
||||||
import { parseJson, formatTimestamp } from '../utils'
|
import { parseJson } from '../utils'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
|
|
||||||
|
type npub = `npub1${string}`
|
||||||
|
|
||||||
export enum SignedStatus {
|
export enum SignedStatus {
|
||||||
Partial = 'In-Progress',
|
Partial = 'In-Progress',
|
||||||
Complete = 'Completed'
|
Complete = 'Completed'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSigitMeta = (meta: Meta) => {
|
export interface SigitInfo {
|
||||||
const [title, setTitle] = useState<string>()
|
createdAt?: number
|
||||||
const [createdAt, setCreatedAt] = useState('')
|
title?: string
|
||||||
const [submittedBy, setSubmittedBy] = useState<string>()
|
submittedBy?: string
|
||||||
const [signers, setSigners] = useState<`npub1${string}`[]>([])
|
signers: npub[]
|
||||||
const [signedStatus, setSignedStatus] = useState<SignedStatus>(
|
fileExtensions: string[]
|
||||||
SignedStatus.Partial
|
signedStatus: SignedStatus
|
||||||
)
|
}
|
||||||
const [fileExtensions, setFileExtensions] = useState<string[]>([])
|
|
||||||
|
export const extractSigitInfo = async (meta: Meta) => {
|
||||||
|
if (!meta?.createSignature) return
|
||||||
|
|
||||||
|
const sigitInfo: SigitInfo = {
|
||||||
|
signers: [],
|
||||||
|
fileExtensions: [],
|
||||||
|
signedStatus: SignedStatus.Partial
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const extractInfo = async () => {
|
|
||||||
const createSignatureEvent = await parseJson<Event>(
|
const createSignatureEvent = await parseJson<Event>(
|
||||||
meta.createSignature
|
meta.createSignature
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
@ -28,24 +36,19 @@ export const useSigitMeta = (meta: Meta) => {
|
|||||||
toast.error(
|
toast.error(
|
||||||
err.message || 'error occurred in parsing the create signature event'
|
err.message || 'error occurred in parsing the create signature event'
|
||||||
)
|
)
|
||||||
return null
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!createSignatureEvent) return
|
if (!createSignatureEvent) return
|
||||||
|
|
||||||
// created_at in nostr events are stored in seconds
|
// created_at in nostr events are stored in seconds
|
||||||
// convert it to ms before formatting
|
sigitInfo.createdAt = createSignatureEvent.created_at * 1000
|
||||||
setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000))
|
|
||||||
|
|
||||||
const createSignatureContent =
|
const createSignatureContent = await parseJson<CreateSignatureEventContent>(
|
||||||
await parseJson<CreateSignatureEventContent>(
|
|
||||||
createSignatureEvent.content
|
createSignatureEvent.content
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
console.log(
|
console.log(`err in parsing the createSignature event's content :>> `, err)
|
||||||
`err in parsing the createSignature event's content :>> `,
|
return
|
||||||
err
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!createSignatureContent) return
|
if (!createSignatureContent) return
|
||||||
@ -59,20 +62,48 @@ export const useSigitMeta = (meta: Meta) => {
|
|||||||
return result
|
return result
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
setTitle(createSignatureContent.title)
|
const signedBy = Object.keys(meta.docSignatures) as npub[]
|
||||||
setSubmittedBy(createSignatureEvent.pubkey)
|
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
||||||
setSigners(createSignatureContent.signers)
|
signedBy.includes(signer)
|
||||||
setFileExtensions(extensions)
|
|
||||||
|
|
||||||
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
|
||||||
const isCompletelySigned = createSignatureContent.signers.every(
|
|
||||||
(signer) => signedBy.includes(signer)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sigitInfo.title = createSignatureContent.title
|
||||||
|
sigitInfo.submittedBy = createSignatureEvent.pubkey
|
||||||
|
|||||||
|
sigitInfo.signers = createSignatureContent.signers
|
||||||
|
sigitInfo.fileExtensions = extensions
|
||||||
|
|
||||||
if (isCompletelySigned) {
|
if (isCompletelySigned) {
|
||||||
setSignedStatus(SignedStatus.Complete)
|
sigitInfo.signedStatus = SignedStatus.Complete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sigitInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSigitMeta = (meta: Meta) => {
|
||||||
|
const [title, setTitle] = useState<string>()
|
||||||
|
const [createdAt, setCreatedAt] = useState<number>()
|
||||||
|
const [submittedBy, setSubmittedBy] = useState<string>()
|
||||||
|
const [signers, setSigners] = useState<npub[]>([])
|
||||||
|
const [signedStatus, setSignedStatus] = useState<SignedStatus>(
|
||||||
|
SignedStatus.Partial
|
||||||
|
)
|
||||||
|
const [fileExtensions, setFileExtensions] = useState<string[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getSigitInfo = async () => {
|
||||||
|
const sigitInfo = await extractSigitInfo(meta)
|
||||||
|
|
||||||
|
if (!sigitInfo) return
|
||||||
|
|
||||||
|
setTitle(sigitInfo.title)
|
||||||
|
setCreatedAt(sigitInfo.createdAt)
|
||||||
|
setSubmittedBy(sigitInfo.submittedBy)
|
||||||
|
setSigners(sigitInfo.signers)
|
||||||
|
setSignedStatus(sigitInfo.signedStatus)
|
||||||
|
setFileExtensions(sigitInfo.fileExtensions)
|
||||||
}
|
}
|
||||||
extractInfo()
|
|
||||||
|
getSigitInfo()
|
||||||
}, [meta])
|
}, [meta])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Button, Divider, TextField, Tooltip } from '@mui/material'
|
import { Button, TextField } from '@mui/material'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
@ -7,25 +7,25 @@ import { useAppSelector } from '../../hooks'
|
|||||||
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
|
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
|
||||||
import { Meta, ProfileMetadata } from '../../types'
|
import { Meta, ProfileMetadata } from '../../types'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {
|
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||||
faAdd,
|
|
||||||
faFilter,
|
|
||||||
faFilterCircleXmark,
|
|
||||||
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 { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
import {
|
||||||
|
extractSigitInfo,
|
||||||
|
SigitInfo,
|
||||||
|
SignedStatus
|
||||||
|
} from '../../hooks/useSigitMeta'
|
||||||
|
|
||||||
// Unsupported Filter options are commented
|
// Unsupported Filter options are commented
|
||||||
const FILTERS = [
|
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]
|
||||||
|
|
||||||
@ -41,7 +41,11 @@ 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 fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const [sigits, setSigits] = useState<Meta[]>([])
|
|
||||||
|
const [sigits, setSigits] = useState<{ [key: string]: Meta }>({})
|
||||||
|
const [parsedSigits, setParsedSigits] = useState<{
|
||||||
|
[key: string]: SigitInfo
|
||||||
|
}>({})
|
||||||
const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>(
|
const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>(
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
@ -49,7 +53,24 @@ export const HomePage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (usersAppData) {
|
if (usersAppData) {
|
||||||
setSigits(Object.values(usersAppData.sigits))
|
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()
|
||||||
}
|
}
|
||||||
}, [usersAppData])
|
}, [usersAppData])
|
||||||
|
|
||||||
@ -99,17 +120,17 @@ export const HomePage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
const [filter, setFilter] = useState<Filter>('Show all')
|
const [filter, setFilter] = useState<Filter>('Show all')
|
||||||
const [isFilterVisible, setIsFilterVisible] = useState(true)
|
const [sort, setSort] = useState<Sort>('desc')
|
||||||
const [sort, setSort] = useState<Sort>('asc')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={styles.container}>
|
<Container className={styles.container}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
{isFilterVisible && (
|
<div className={styles.filters}>
|
||||||
<>
|
|
||||||
<Select
|
<Select
|
||||||
name={'filter-select'}
|
name={'filter-select'}
|
||||||
|
value={filter}
|
||||||
setValue={setFilter}
|
setValue={setFilter}
|
||||||
options={FILTERS.map((f) => {
|
options={FILTERS.map((f) => {
|
||||||
return {
|
return {
|
||||||
@ -120,21 +141,25 @@ export const HomePage = () => {
|
|||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
name={'sort-select'}
|
name={'sort-select'}
|
||||||
|
value={sort}
|
||||||
setValue={setSort}
|
setValue={setSort}
|
||||||
options={SORT_BY.map((s) => {
|
options={SORT_BY.map((s) => {
|
||||||
return { ...s }
|
return { ...s }
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
)}
|
|
||||||
<div className={styles.actionButtons}>
|
<div className={styles.actionButtons}>
|
||||||
<div className={styles.search}>
|
<div className={styles.search}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearch(e.currentTarget.value)
|
||||||
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
|
width: '100%',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
height: '34px',
|
|
||||||
borderTopLeftRadius: 'var(----mui-shape-borderRadius)',
|
borderTopLeftRadius: 'var(----mui-shape-borderRadius)',
|
||||||
borderBottomLeftRadius: 'var(----mui-shape-borderRadius)',
|
borderBottomLeftRadius: 'var(----mui-shape-borderRadius)',
|
||||||
'& .MuiInputBase-root': {
|
'& .MuiInputBase-root': {
|
||||||
@ -142,7 +167,7 @@ export const HomePage = () => {
|
|||||||
borderBottomRightRadius: 0
|
borderBottomRightRadius: 0
|
||||||
},
|
},
|
||||||
'& .MuiInputBase-input': {
|
'& .MuiInputBase-input': {
|
||||||
padding: '5.5px 14px'
|
padding: '7px 14px'
|
||||||
},
|
},
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
display: 'none'
|
display: 'none'
|
||||||
@ -152,7 +177,7 @@ export const HomePage = () => {
|
|||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: '44px',
|
minWidth: '44px',
|
||||||
padding: '10px 12px',
|
padding: '11.5px 12px',
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderBottomLeftRadius: 0
|
borderBottomLeftRadius: 0
|
||||||
}}
|
}}
|
||||||
@ -161,37 +186,9 @@ export const HomePage = () => {
|
|||||||
<FontAwesomeIcon icon={faSearch} />
|
<FontAwesomeIcon icon={faSearch} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider orientation="vertical" variant="middle" flexItem />
|
</div>
|
||||||
<Tooltip title="Toggle Filter" arrow>
|
</div>
|
||||||
<Button
|
<div className={styles.dropzone} onClick={handleUploadClick}>
|
||||||
sx={{
|
|
||||||
minWidth: '44px',
|
|
||||||
padding: '10px 12px'
|
|
||||||
}}
|
|
||||||
variant={'contained'}
|
|
||||||
onClick={() => setIsFilterVisible((v) => !v)}
|
|
||||||
>
|
|
||||||
{isFilterVisible ? (
|
|
||||||
<FontAwesomeIcon icon={faFilterCircleXmark} />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faFilter} />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Upload" arrow>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
minWidth: '44px',
|
|
||||||
padding: '10px 12px'
|
|
||||||
}}
|
|
||||||
component={'label'}
|
|
||||||
htmlFor="fileUpload"
|
|
||||||
variant={'contained'}
|
|
||||||
onClick={handleUploadClick}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faAdd} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<input
|
<input
|
||||||
id="fileUpload"
|
id="fileUpload"
|
||||||
type="file"
|
type="file"
|
||||||
@ -199,16 +196,34 @@ export const HomePage = () => {
|
|||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.dropzone}>
|
|
||||||
<div>Click or drag files to upload!</div>
|
<div>Click or drag files to upload!</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.submissions}>
|
<div className={styles.submissions}>
|
||||||
{sigits.map((sigit, index) => (
|
{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
|
<DisplaySigit
|
||||||
key={`sigit-${index}`}
|
key={`sigit-${key}`}
|
||||||
meta={sigit}
|
parsedMeta={parsedSigits[key]}
|
||||||
|
meta={sigits[key]}
|
||||||
profiles={profiles}
|
profiles={profiles}
|
||||||
setProfiles={setProfiles}
|
setProfiles={setProfiles}
|
||||||
/>
|
/>
|
||||||
|
@ -10,13 +10,17 @@
|
|||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@container (width < 610px) {
|
@container (width < 610px) {
|
||||||
flex-wrap: wrap;
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.actionButtons {
|
.actionButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
|
Loading…
Reference in New Issue
Block a user
created_at * 1000
should be made into a utility function and used in other similar places (there is at least one that I could find).Similarly, there is a reverse action,
created_at / 1000
, which should also be a utility.