Skip to content

Commit 5f24c5b

Browse files
authored
Merge pull request #1957 from streamr-dev/FRONT-1679-table-order-url
[FRONT-1679] Keep view parameters in URL
2 parents f78fd57 + f59c359 commit 5f24c5b

File tree

5 files changed

+167
-31
lines changed

5 files changed

+167
-31
lines changed

src/components/ActionBars/NetworkActionBar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,15 @@ type Props = {
5656
searchEnabled: boolean
5757
placeholder?: string
5858
onSearch?: (term: string) => void
59+
searchValue?: string
5960
leftSideContent?: ReactNode
6061
rightSideContent?: ReactNode
6162
}
6263

6364
export const NetworkActionBar: FunctionComponent<Props> = ({
6465
searchEnabled,
6566
onSearch,
67+
searchValue,
6668
placeholder,
6769
leftSideContent,
6870
rightSideContent,
@@ -72,7 +74,11 @@ export const NetworkActionBar: FunctionComponent<Props> = ({
7274
<div
7375
className={'search-bar-wrap ' + (!searchEnabled ? 'search-disabled' : '')}
7476
>
75-
<SearchBar onChange={onSearch} placeholder={placeholder} />
77+
<SearchBar
78+
onChange={onSearch}
79+
placeholder={placeholder}
80+
value={searchValue}
81+
/>
7682
</div>
7783
{(leftSideContent || rightSideContent) && (
7884
<div className="action-content-wrap">

src/hooks/useUrlParams.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useSearchParams } from 'react-router-dom'
2+
import { useEffect } from 'react'
3+
4+
interface UrlParamConfig<T extends string> {
5+
param: string
6+
value: T | undefined
7+
defaultValue: T
8+
}
9+
10+
export function useUrlParams<T extends string>(params: UrlParamConfig<T>[]) {
11+
const [searchParams, setSearchParams] = useSearchParams()
12+
13+
useEffect(() => {
14+
// Check if current params are different from desired params
15+
let needsUpdate = false
16+
const currentParams = new URLSearchParams(searchParams)
17+
18+
params.forEach(({ param, value, defaultValue }) => {
19+
const currentValue = currentParams.get(param)
20+
const targetValue = value !== defaultValue ? value || defaultValue : null
21+
22+
if (targetValue === null && currentValue !== null) {
23+
needsUpdate = true
24+
} else if (targetValue !== null && targetValue !== currentValue) {
25+
needsUpdate = true
26+
}
27+
})
28+
29+
// Only update if necessary
30+
if (needsUpdate) {
31+
setSearchParams((prevParams) => {
32+
const newParams = new URLSearchParams(prevParams)
33+
34+
params.forEach(({ param, value, defaultValue }) => {
35+
if (value !== defaultValue) {
36+
newParams.set(param, value || defaultValue)
37+
} else {
38+
newParams.delete(param)
39+
}
40+
})
41+
42+
return newParams
43+
})
44+
}
45+
}, [params, searchParams, setSearchParams])
46+
}

src/pages/OperatorsPage.tsx

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useTableOrder } from '~/hooks/useTableOrder'
1919
import { Operator } from '~/parsers/Operator'
2020
import { ScrollTableCore } from '~/shared/components/ScrollTable/ScrollTable'
2121
import Tabs, { Tab } from '~/shared/components/Tabs'
22-
import { useWalletAccount } from '~/shared/stores/wallet'
22+
import { useIsWalletLoading, useWalletAccount } from '~/shared/stores/wallet'
2323
import { OrderDirection } from '~/types'
2424
import { saveOperator } from '~/utils'
2525
import {
@@ -28,34 +28,59 @@ import {
2828
useCurrentChainSymbolicName,
2929
} from '~/utils/chains'
3030
import { Route as R, routeOptions } from '~/utils/routes'
31-
32-
const PAGE_SIZE = 20
31+
import { useUrlParams } from '~/hooks/useUrlParams'
3332

3433
enum TabOption {
3534
AllOperators = 'all',
3635
MyDelegations = 'my',
3736
}
3837

38+
const PAGE_SIZE = 20
39+
const DEFAULT_ORDER_BY = 'totalValue'
40+
const DEFAULT_ORDER_DIRECTION = 'desc'
41+
const DEFAULT_TAB = TabOption.AllOperators
42+
3943
function isTabOption(value: unknown): value is TabOption {
4044
return value === TabOption.AllOperators || value === TabOption.MyDelegations
4145
}
4246

4347
export const OperatorsPage = () => {
44-
const [params] = useSearchParams()
45-
46-
const tab = params.get('tab')
47-
48-
const selectedTab = isTabOption(tab) ? tab : TabOption.AllOperators
49-
50-
const [searchQuery, setSearchQuery] = useState('')
51-
5248
const wallet = useWalletAccount()
49+
const isWalletLoading = useIsWalletLoading()
5350

5451
const { orderBy, orderDirection, setOrder } = useTableOrder<string>({
55-
orderBy: 'totalValue',
56-
orderDirection: 'desc',
52+
orderBy: DEFAULT_ORDER_BY,
53+
orderDirection: DEFAULT_ORDER_DIRECTION,
5754
})
5855

56+
const [params] = useSearchParams()
57+
const tab = params.get('tab')
58+
const selectedTab = isTabOption(tab) ? tab : DEFAULT_TAB
59+
const [searchQuery, setSearchQuery] = useState(params.get('search') || '')
60+
61+
useUrlParams([
62+
{
63+
param: 'tab',
64+
value: selectedTab,
65+
defaultValue: DEFAULT_TAB,
66+
},
67+
{
68+
param: 'orderBy',
69+
value: orderBy,
70+
defaultValue: DEFAULT_ORDER_BY,
71+
},
72+
{
73+
param: 'orderDir',
74+
value: orderDirection,
75+
defaultValue: DEFAULT_ORDER_DIRECTION,
76+
},
77+
{
78+
param: 'search',
79+
value: searchQuery,
80+
defaultValue: '',
81+
},
82+
])
83+
5984
const allOperatorsQuery = useAllOperatorsQuery({
6085
batchSize: PAGE_SIZE,
6186
searchQuery,
@@ -83,7 +108,7 @@ export const OperatorsPage = () => {
83108
const navigate = useNavigate()
84109

85110
useEffect(() => {
86-
if (!wallet) {
111+
if (!wallet && !isWalletLoading) {
87112
navigate(
88113
R.operators(
89114
routeOptions(chainName, {
@@ -92,13 +117,14 @@ export const OperatorsPage = () => {
92117
),
93118
)
94119
}
95-
}, [wallet, navigate, chainName])
120+
}, [wallet, isWalletLoading, navigate, chainName])
96121

97122
return (
98123
<Layout>
99124
<NetworkHelmet title="Operators" />
100125
<NetworkActionBar
101126
searchEnabled={true}
127+
searchValue={searchQuery}
102128
onSearch={setSearchQuery}
103129
leftSideContent={
104130
<Tabs

src/pages/SponsorshipsPage.tsx

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,84 @@ import {
2020
} from '~/hooks/sponsorships'
2121
import { useTableOrder } from '~/hooks/useTableOrder'
2222
import Tabs, { Tab } from '~/shared/components/Tabs'
23-
import { useWalletAccount } from '~/shared/stores/wallet'
23+
import { useWalletAccount, useIsWalletLoading } from '~/shared/stores/wallet'
2424
import {
2525
useCurrentChainFullName,
2626
useCurrentChainId,
2727
useCurrentChainSymbolicName,
2828
} from '~/utils/chains'
2929
import { Route as R, routeOptions } from '~/utils/routes'
30-
31-
const PAGE_SIZE = 20
30+
import { useUrlParams } from '~/hooks/useUrlParams'
3231

3332
enum TabOption {
3433
AllSponsorships = 'all',
3534
MySponsorships = 'my',
3635
}
3736

37+
const PAGE_SIZE = 20
38+
const DEFAULT_ORDER_BY = 'remainingWei'
39+
const DEFAULT_ORDER_DIRECTION = 'desc'
40+
const DEFAULT_TAB = TabOption.AllSponsorships
41+
3842
function isTabOption(value: unknown): value is TabOption {
3943
return value === TabOption.AllSponsorships || value === TabOption.MySponsorships
4044
}
4145

4246
export const SponsorshipsPage = () => {
4347
const [params] = useSearchParams()
44-
45-
const tab = params.get('tab')
46-
47-
const selectedTab = isTabOption(tab) ? tab : TabOption.AllSponsorships
48-
49-
const [searchQuery, setSearchQuery] = useState('')
50-
51-
const [filters, setFilters] = useState<SponsorshipFilters>(defaultFilters)
48+
const initialFilters = {
49+
...defaultFilters,
50+
...Object.keys(defaultFilters).reduce((acc, key) => {
51+
if (params.has(key)) {
52+
return { ...acc, [key]: params.get(key) === 'true' }
53+
}
54+
return acc
55+
}, {}),
56+
}
57+
58+
const [filters, setFilters] = useState<SponsorshipFilters>(initialFilters)
5259

5360
const wallet = useWalletAccount()
61+
const isWalletLoading = useIsWalletLoading()
5462

5563
const { orderBy, orderDirection, setOrder } = useTableOrder<string>({
56-
orderBy: 'remainingWei',
57-
orderDirection: 'desc',
64+
orderBy: DEFAULT_ORDER_BY,
65+
orderDirection: DEFAULT_ORDER_DIRECTION,
5866
})
5967

68+
const tab = params.get('tab')
69+
const selectedTab = isTabOption(tab) ? tab : DEFAULT_TAB
70+
71+
const [searchQuery, setSearchQuery] = useState(params.get('search') || '')
72+
73+
useUrlParams([
74+
{
75+
param: 'tab',
76+
value: selectedTab,
77+
defaultValue: DEFAULT_TAB,
78+
},
79+
{
80+
param: 'orderBy',
81+
value: orderBy,
82+
defaultValue: DEFAULT_ORDER_BY,
83+
},
84+
{
85+
param: 'orderDir',
86+
value: orderDirection,
87+
defaultValue: DEFAULT_ORDER_DIRECTION,
88+
},
89+
{
90+
param: 'search',
91+
value: searchQuery,
92+
defaultValue: '',
93+
},
94+
...Object.entries(defaultFilters).map(([key, value]) => ({
95+
param: key,
96+
value: filters[key as keyof SponsorshipFilters].toString(),
97+
defaultValue: value.toString(),
98+
})),
99+
])
100+
60101
const allSponsorshipsQuery = useAllSponsorshipsQuery({
61102
pageSize: PAGE_SIZE,
62103
searchQuery,
@@ -78,7 +119,7 @@ export const SponsorshipsPage = () => {
78119
const chainName = useCurrentChainSymbolicName()
79120

80121
useEffect(() => {
81-
if (!wallet) {
122+
if (!wallet && !isWalletLoading) {
82123
navigate(
83124
R.sponsorships(
84125
routeOptions(chainName, {
@@ -87,7 +128,7 @@ export const SponsorshipsPage = () => {
87128
),
88129
)
89130
}
90-
}, [wallet, navigate, chainName])
131+
}, [wallet, navigate, chainName, isWalletLoading])
91132

92133
const createSponsorship = useCreateSponsorship()
93134

@@ -102,6 +143,7 @@ export const SponsorshipsPage = () => {
102143
<NetworkHelmet title="Sponsorships" />
103144
<NetworkActionBar
104145
searchEnabled
146+
searchValue={searchQuery}
105147
onSearch={setSearchQuery}
106148
leftSideContent={
107149
<Tabs

src/shared/stores/wallet.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export async function getWalletAccount({
161161
interface WalletStore {
162162
account: string | undefined
163163
ens: Record<string, string | undefined>
164+
isLoading: boolean
164165
}
165166

166167
const useWalletStore = create<WalletStore>((set, get) => {
@@ -219,13 +220,20 @@ const useWalletStore = create<WalletStore>((set, get) => {
219220
onAccountsChange(accounts)
220221
} catch (e) {
221222
console.warn('Provider setup failed', e)
223+
} finally {
224+
set((current) =>
225+
produce(current, (next) => {
226+
next.isLoading = false
227+
}),
228+
)
222229
}
223230
})
224231

225232
return {
226233
account: undefined,
227234

228235
ens: {},
236+
isLoading: true,
229237
}
230238
})
231239

@@ -238,6 +246,14 @@ export function useWalletAccount() {
238246
return useWalletStore().account
239247
}
240248

249+
/**
250+
* A hook that gives you the loading state of the wallet.
251+
* @returns a boolean value indicating whether the wallet is loading.
252+
*/
253+
export function useIsWalletLoading() {
254+
return useWalletStore().isLoading
255+
}
256+
241257
/**
242258
* A hook that gives you an ENS domain for an account address.
243259
* @returns either an ENS domain name, or an empty string.

0 commit comments

Comments
 (0)