Skip to content

Commit

Permalink
Search improvements (#43)
Browse files Browse the repository at this point in the history
* Delete organizationId filter

See #41 (comment)

* Search only valid partialId string

* Hotfix debounced input search

It has an error because it was setting the value after debounce, so is wrong behaviour on user side.

Now is using internal state to store the value

* Improve organization search

* Prettierify
  • Loading branch information
selankon committed Jul 5, 2024
1 parent f8ad02a commit 0e0c180
Show file tree
Hide file tree
Showing 8 changed files with 32 additions and 13 deletions.
14 changes: 10 additions & 4 deletions src/components/Layout/Inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
PopoverContent,
PopoverTrigger,
} from '@chakra-ui/react'
import React, { ChangeEvent } from 'react'
import React, { ChangeEvent, useState } from 'react'
import { Trans } from 'react-i18next'
import { BiSearchAlt } from 'react-icons/bi'
import { debounce } from '~utils/debounce'
import { Trans } from 'react-i18next'

export const PopoverInputSearch = ({ input, button }: { input?: InputSearchProps; button?: ButtonProps }) => {
return (
Expand Down Expand Up @@ -48,11 +48,15 @@ export const PopoverInputSearch = ({ input, button }: { input?: InputSearchProps
}

type InputSearchProps = {
initialValue?: string
debounceTime?: number
onChange?: (event: string) => void
} & Omit<InputProps, 'onChange'>
} & Omit<InputProps, 'onChange' | 'value'>

export const InputSearch = ({ initialValue, debounceTime = 0, onChange, ...props }: InputSearchProps) => {
// Inner state to enable initial value
const [value, setValue] = useState(initialValue ?? '')

export const InputSearch = ({ debounceTime = 0, onChange, ...props }: InputSearchProps) => {
const debouncedSearch = debounce((value: string) => {
if (onChange) onChange(value)
}, debounceTime)
Expand All @@ -64,7 +68,9 @@ export const InputSearch = ({ debounceTime = 0, onChange, ...props }: InputSearc
</InputLeftElement>
<Input
{...props}
value={value}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)
debouncedSearch(event.target.value)
}}
/>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Organizations/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { retryUnlessNotFound } from '~utils/queries'
export const OrganizationsFilter = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const { query } = useParams<{ query?: string }>()

return (
<InputSearch
Expand All @@ -23,6 +24,7 @@ export const OrganizationsFilter = () => {
navigate(generatePath(RoutePath.OrganizationsList, { page: '0', query: value as string }))
}}
debounceTime={500}
initialValue={query}
/>
)
}
Expand Down
7 changes: 3 additions & 4 deletions src/components/Process/ProcessList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { PaginationItemsPerPage, RoutePath } from '~constants'
import { useProcessesCount, useProcessList } from '~queries/processes'
import useQueryParams from '~src/router/use-query-params'
import { isEmpty } from '~utils/objects'
import { ElectionCard } from './Card'
import { retryUnlessNotFound } from '~utils/queries'
import { ElectionCard } from './Card'

type FilterQueryParams = {
[K in keyof Omit<IElectionListFilter, 'organizationId'>]: string
Expand All @@ -38,7 +38,7 @@ export const ProcessSearchBox = () => {
onChange={(value: string) => {
setQueryParams({ ...queryParams, electionId: value })
}}
value={queryParams.electionId}
initialValue={queryParams.electionId}
debounceTime={500}
/>
</Flex>
Expand Down Expand Up @@ -108,8 +108,7 @@ export const PaginatedProcessList = () => {
} = useProcessList({
page: Number(page || 0),
filters: {
electionId: processFilters.electionId,
// organizationId: processFilters.electionId,
electionId: processFilters.electionId, // electionId contains also the organizationId, so this is enough to find by partial orgId or electionId
status: processFilters.status as IElectionListFilter['status'],
withResults: processFilters.withResults ? processFilters.withResults === 'true' : undefined,
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/Verify/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import addVote from '/images/add-vote.svg'
import { Box, Button, Flex, Image, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { generatePath, useNavigate, useParams } from 'react-router-dom'
import { InputSearch } from '~components/Layout/Inputs'
import { RoutePath } from '~constants'
import addVote from '/images/add-vote.svg'

const SearchVote = ({ compact }: { compact?: boolean }) => {
const { verifier: urlVerifier }: { verifier?: string } = useParams()
Expand All @@ -21,7 +21,7 @@ const SearchVote = ({ compact }: { compact?: boolean }) => {
onChange={(value: string) => {
setVerifier(value)
}}
value={verifier}
initialValue={urlVerifier}
/>
<Box>
<Button
Expand Down
5 changes: 4 additions & 1 deletion src/queries/processes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { useClient } from '@vocdoni/react-providers'
import { ExtendedSDKClient } from '@vocdoni/extended-sdk'
import { useClient } from '@vocdoni/react-providers'
import {
Census,
IElectionKeysResponse,
Expand All @@ -10,6 +10,7 @@ import {
PublishedElection,
} from '@vocdoni/sdk'
import { useChainInfo, useChainInfoOptions } from '~queries/stats'
import { isValidPartialProcessId } from '~utils/strings'

export const useProcessList = ({
page,
Expand All @@ -20,6 +21,7 @@ export const useProcessList = ({
filters?: IElectionListFilter
} & Omit<UseQueryOptions<IElectionListResponse, Error, { elections: PublishedElection[] }>, 'queryKey'>) => {
const { client } = useClient<ExtendedSDKClient>()

return useQuery({
queryKey: ['process', 'list', page, filters],
queryFn: () => client.electionList(page, { ...filters }),
Expand All @@ -37,6 +39,7 @@ export const useProcessList = ({
})
return { elections }
},
enabled: !filters?.electionId || (!!filters?.electionId && isValidPartialProcessId(filters?.electionId)),
...options,
})
}
Expand Down
Empty file removed src/strings.ts
Empty file.
5 changes: 3 additions & 2 deletions src/utils/queries.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { QueryOptions } from '@tanstack/react-query'
import { ErrAccountNotFound, ErrElectionNotFound, ErrTransactionNotFound } from '@vocdoni/sdk'
import { ErrAccountNotFound, ErrAPI, ErrElectionNotFound, ErrTransactionNotFound } from '@vocdoni/sdk'

// Retry up to 3 times for errors other than 404
export const retryUnlessNotFound: QueryOptions['retry'] = (failureCount: number, error: any) => {
if (
error instanceof ErrElectionNotFound ||
error instanceof ErrAccountNotFound ||
error instanceof ErrTransactionNotFound
error instanceof ErrTransactionNotFound ||
(error instanceof ErrAPI && error.raw?.response?.status === 404)
) {
return false // Do not retry if the error is a 404
}
Expand Down
8 changes: 8 additions & 0 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ export const shortHex = (hex: string) => hex.substring(0, 6) + '...' + hex.subst
* ucfirst makes the first letter of a string uppercase
*/
export const ucfirst = (str: string, lng?: string | undefined) => str.charAt(0).toLocaleUpperCase(lng) + str.slice(1)

/**
* Checks if a string is a valid hexadecimal with the correct length. Useful to check if processIds or orgsId are valid.
*/
export const isValidPartialProcessId = (str: string) => {
const hexRegex = /^[0-9A-Fa-f]+$/
return hexRegex.test(str) && str.length % 2 === 0
}

2 comments on commit 0e0c180

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.