Skip to content

Commit 4b5272c

Browse files
authored
Merge pull request #269 from beck-8/feat/service-providers-reachable-endorsed
feat(service-providers): default to reachable nodes and add endorsed provider badge
2 parents a8c7a51 + 5b24336 commit 4b5272c

18 files changed

Lines changed: 209 additions & 14 deletions

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.4.7/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
33
"vcs": {
44
"enabled": true,
55
"clientKind": "git",

src/app/service-providers/components/DesktopTableFilters.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { CapacityFilter } from './CapacityFilter'
1414
import { IpniFilter } from './IpniFilter'
1515
import { LocationFilter } from './LocationFilter'
1616
import { ProvingPeriodFilter } from './ProvingPeriodFilter'
17+
import { ReachableFilter } from './ReachableFilter'
1718
import { ServiceTierFilter } from './ServiceTierFilter'
1819
import type { useFilterOptions } from '../hooks/use-filter-options'
1920
import { useFilterQueryState } from '../hooks/use-filter-query-state'
@@ -72,6 +73,7 @@ export function DesktopTableFilters({ options }: DesktopTableFiltersProps) {
7273
provingPeriodMin={provingPeriodMin}
7374
provingPeriodMax={provingPeriodMax}
7475
/>
76+
<ReachableFilter />
7577
{ipniOptions.length > 1 && <IpniFilter options={ipniOptions} />}
7678
{serviceTierOptions.length > 1 && (
7779
<ServiceTierFilter options={serviceTierOptions} />

src/app/service-providers/components/MobileTableFilters.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CapacityFilter } from './CapacityFilter'
1818
import { IpniFilter } from './IpniFilter'
1919
import { LocationFilter } from './LocationFilter'
2020
import { ProvingPeriodFilter } from './ProvingPeriodFilter'
21+
import { ReachableFilter } from './ReachableFilter'
2122
import { ServiceTierFilter } from './ServiceTierFilter'
2223
import type { useFilterOptions } from '../hooks/use-filter-options'
2324
import { useFilterQueryState } from '../hooks/use-filter-query-state'
@@ -74,6 +75,7 @@ export function MobileTableFilters({ options }: MobileTableFiltersProps) {
7475
provingPeriodMin={provingPeriodMin}
7576
provingPeriodMax={provingPeriodMax}
7677
/>
78+
<ReachableFilter />
7779

7880
{ipniOptions.length > 1 && <IpniFilter options={ipniOptions} />}
7981

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Fieldset } from '@headlessui/react'
2+
3+
import { CheckboxesContainer } from '@/components/CheckboxesContainer'
4+
import { CheckboxWithLabel } from '@/components/CheckboxWithLabel'
5+
import { FilterHeading } from '@/components/FilterHeading'
6+
7+
import { useFilterQueryState } from '../hooks/use-filter-query-state'
8+
9+
const REACHABLE_FILTER_OPTIONS = [
10+
{ value: 'true', label: 'Accessible' },
11+
{ value: 'false', label: 'Unavailable' },
12+
] as const
13+
14+
export function ReachableFilter() {
15+
const { filterQueries, toggleFilterQuery } = useFilterQueryState()
16+
17+
return (
18+
<Fieldset>
19+
<FilterHeading>Node Accessibility</FilterHeading>
20+
<CheckboxesContainer>
21+
{REACHABLE_FILTER_OPTIONS.map((option) => (
22+
<CheckboxWithLabel
23+
key={option.value}
24+
checked={filterQueries.reachable.includes(option.value)}
25+
onChange={() => toggleFilterQuery('reachable', option.value)}
26+
label={option.label}
27+
/>
28+
))}
29+
</CheckboxesContainer>
30+
</Fieldset>
31+
)
32+
}

src/app/service-providers/components/ServiceProvidersTable.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export function ServiceProvidersTable({ data }: ServiceProvidersTableProps) {
101101
</div>
102102
</div>
103103

104+
<p className="mb-3 text-xs text-(--color-text-muted)">
105+
Legend: <span aria-hidden>🏅</span> Endorsed = Endorsed (EndorsementSet)
106+
| Warm storage = Approved (FWSS)
107+
</p>
108+
104109
{hasSearchResults ? (
105110
<TanstackTable table={table} maxHeight="100vh" />
106111
) : (

src/app/service-providers/data/column-definition.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
ipniFilterFn,
1818
locationFilterFn,
1919
provingPeriodRangeFilterFn,
20+
reachableFilterFn,
2021
serviceTierFilterFn,
2122
} from '../utils/service-provider-filters'
2223

@@ -40,6 +41,7 @@ export const columns = [
4041
description={row.description}
4142
address={row.serviceProviderAddress}
4243
serviceUrl={row.serviceUrl}
44+
isEndorsed={row.isEndorsed}
4345
/>
4446
)
4547
},
@@ -53,6 +55,7 @@ export const columns = [
5355
},
5456
sortingFn: sortSoftwareVersion,
5557
sortUndefined: 'last',
58+
filterFn: reachableFilterFn,
5659
}),
5760
columnHelper.accessor('isActive', {
5861
id: 'serviceOffered',

src/app/service-providers/hooks/use-filter-query-state.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import type { ServiceTier } from '@/utils/service-tier'
1010
import { toggleValueInArray } from '@/utils/toggle-value-in-array'
1111

1212
import { parseNumericInput } from '../utils/parse-numeric-input'
13+
import { parseAsReachableFilterValue } from '../utils/parse-reachable-filter-value'
1314
import { parseAsServiceTier } from '../utils/parse-service-tier'
1415

16+
export type ReachableFilterValue = 'true' | 'false'
17+
1518
export type FilterState = {
1619
location: Array<string>
1720
capacityMin: number | null
@@ -20,8 +23,11 @@ export type FilterState = {
2023
provingPeriodMax: number | null
2124
ipni: Array<string>
2225
serviceTier: Array<ServiceTier>
26+
reachable: Array<ReachableFilterValue>
2327
}
2428

29+
const DEFAULT_REACHABLE_FILTER: Array<ReachableFilterValue> = ['true']
30+
2531
const filterParsers = {
2632
location: parseAsArrayOf(parseAsString).withDefault([]),
2733
capacityMin: parseAsInteger,
@@ -30,6 +36,9 @@ const filterParsers = {
3036
provingPeriodMax: parseAsInteger,
3137
ipni: parseAsArrayOf(parseAsString).withDefault([]),
3238
serviceTier: parseAsArrayOf(parseAsServiceTier).withDefault([]),
39+
reachable: parseAsArrayOf(parseAsReachableFilterValue).withDefault(
40+
DEFAULT_REACHABLE_FILTER,
41+
),
3342
}
3443

3544
type ArrayKeys<T> = {
@@ -77,7 +86,9 @@ export function useFilterQueryState() {
7786

7887
const activeFilterCount = useMemo(() => {
7988
return Object.entries(filterQueries).reduce((count, [_, value]) => {
80-
if (Array.isArray(value)) return count + (value.length > 0 ? 1 : 0)
89+
if (Array.isArray(value)) {
90+
return count + (value.length > 0 ? 1 : 0)
91+
}
8192
if (value != null) return count + 1
8293
return count
8394
}, 0)

src/app/service-providers/utils/map-filter-state-to-column-filters.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export function mapFilterStateToColumnFilters({
2222
provingPeriodMax,
2323
ipni,
2424
serviceTier,
25+
reachable,
2526
}: FilterState) {
2627
const columnFilters: ServiveProviderColumnFilters = []
2728

@@ -49,6 +50,9 @@ export function mapFilterStateToColumnFilters({
4950
if (serviceTier.length > 0) {
5051
columnFilters.push({ id: 'serviceOffered', value: serviceTier })
5152
}
53+
if (reachable.length > 0) {
54+
columnFilters.push({ id: 'softwareVersion', value: reachable })
55+
}
5256

5357
return columnFilters
5458
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createParser } from 'nuqs'
2+
3+
import type { ReachableFilterValue } from '../hooks/use-filter-query-state'
4+
5+
export const parseAsReachableFilterValue = createParser({
6+
parse: (value) => {
7+
if (value === 'true' || value === 'false') {
8+
return value as ReachableFilterValue
9+
}
10+
return null
11+
},
12+
serialize: (value) => value,
13+
})

src/app/service-providers/utils/service-provider-filters.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ export const ipniFilterFn: FilterFn<ServiceProvider> = (
3232
return ipniArray.includes(ipniValue)
3333
}
3434

35+
export const reachableFilterFn: FilterFn<ServiceProvider> = (
36+
row,
37+
_columnId,
38+
filterValue,
39+
) => {
40+
const reachableArray = filterValue as FilterState['reachable']
41+
if (reachableArray.length === 0) return true
42+
43+
// Reachability proxy: Curio only populates softwareVersion when the node is reachable.
44+
// Treat any defined value (including empty string) as reachable.
45+
const hasSoftwareVersion =
46+
row.original.softwareVersion !== null &&
47+
row.original.softwareVersion !== undefined
48+
const reachableValue: FilterState['reachable'][number] = hasSoftwareVersion
49+
? 'true'
50+
: 'false'
51+
return reachableArray.includes(reachableValue)
52+
}
53+
3554
export const capacityRangeFilterFn: FilterFn<ServiceProvider> = (
3655
row,
3756
_columnId,

0 commit comments

Comments
 (0)