mirror of
https://github.com/joinmarket-webui/jam.git
synced 2026-08-19 13:18:30 +02:00
chore: add jm config store
This commit is contained in:
parent
f5318171bb
commit
a93c313ff7
5 changed files with 177 additions and 39 deletions
19
src/App.tsx
19
src/App.tsx
|
|
@ -27,10 +27,11 @@ import { useApiClient } from '@/hooks/useApiClient'
|
|||
import { token } from '@/lib/jm-api/generated/client'
|
||||
import { sessionOptions } from '@/lib/jm-api/generated/client/@tanstack/react-query.gen'
|
||||
import { queryClient } from '@/lib/queryClient'
|
||||
import { setIntervalDebounced } from '@/lib/utils'
|
||||
import { setIntervalDebounced, type WalletFileName } from '@/lib/utils'
|
||||
import { authStore } from '@/store/authStore'
|
||||
import { jmSessionStore } from '@/store/jmSessionStore'
|
||||
import { isDebugFeatureEnabled } from './constants/debugFeatures'
|
||||
import { useFeeConfigValidation } from './hooks/useFeeConfigValidation'
|
||||
import { jamSettingsStore } from './store/jamSettingsStore'
|
||||
|
||||
const DevSetupPage = lazy(() => import('@/components/dev/DevSetupPage'))
|
||||
|
|
@ -53,6 +54,7 @@ function App() {
|
|||
<QueryClientProvider client={queryClient}>
|
||||
<RefreshApiToken />
|
||||
<RefreshJmSession />
|
||||
{walletFileName && <LoadFeeConfigData walletFileName={walletFileName} />}
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route
|
||||
|
|
@ -305,4 +307,19 @@ function RefreshJmSession() {
|
|||
return <></>
|
||||
}
|
||||
|
||||
function LoadFeeConfigData({ walletFileName }: { walletFileName: WalletFileName }) {
|
||||
const { refetchAll } = useFeeConfigValidation({ walletFileName })
|
||||
|
||||
useEffect(() => {
|
||||
refetchAll().catch(() => {
|
||||
const isDevMode = jamSettingsStore.getState().state.developerMode
|
||||
if (isDevMode) {
|
||||
toast.error(`[DEV] Error while loading fee config data.`)
|
||||
}
|
||||
})
|
||||
}, [refetchAll])
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
|
|||
|
|
@ -9,13 +9,18 @@ type RelOfferType = 'sw0reloffer'
|
|||
type AbsOfferType = 'sw0absoffer'
|
||||
export type OfferType = RelOfferType | AbsOfferType | string
|
||||
|
||||
type SectionKey = string
|
||||
export type SectionKey = string
|
||||
|
||||
interface ConfigKey {
|
||||
export interface ConfigKey {
|
||||
section: SectionKey
|
||||
field: string
|
||||
}
|
||||
|
||||
export interface ConfigValue {
|
||||
key: ConfigKey
|
||||
value: string | null
|
||||
}
|
||||
|
||||
export const FEE_CONFIG_KEYS: Record<string, ConfigKey> = {
|
||||
tx_fees: { section: 'POLICY', field: 'tx_fees' },
|
||||
tx_fees_factor: { section: 'POLICY', field: 'tx_fees_factor' },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useMemo } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { FEE_CONFIG_KEYS } from '@/constants/jm'
|
||||
import { useApiClient } from '@/hooks/useApiClient'
|
||||
|
|
@ -28,28 +28,20 @@ export const useFeeConfigValidation = ({ walletFileName }: UseFeeConfigValidatio
|
|||
const createConfigQuery = (configKey: keyof typeof FEE_CONFIG_KEYS) => ({
|
||||
...configgetMutation({
|
||||
client,
|
||||
path: { walletname: walletFileName || '' },
|
||||
path: { walletname: walletFileName },
|
||||
body: FEE_CONFIG_KEYS[configKey],
|
||||
}),
|
||||
})
|
||||
|
||||
const maxCjFeeAbsQuery = useMutation(createConfigQuery('max_cj_fee_abs'))
|
||||
const maxCjFeeRelQuery = useMutation(createConfigQuery('max_cj_fee_rel'))
|
||||
const txFeesQuery = useMutation(createConfigQuery('tx_fees'))
|
||||
const txFeesFactorQuery = useMutation(createConfigQuery('tx_fees_factor'))
|
||||
const maxSweepFeeChangeQuery = useMutation(createConfigQuery('max_sweep_fee_change'))
|
||||
const { mutateAsync: fetchMaxCjFeeAbs, ...maxCjFeeAbsQuery } = useMutation(createConfigQuery('max_cj_fee_abs'))
|
||||
const { mutateAsync: fetchMaxCjFeeRel, ...maxCjFeeRelQuery } = useMutation(createConfigQuery('max_cj_fee_rel'))
|
||||
const { mutateAsync: fetchTxFees, ...txFeesQuery } = useMutation(createConfigQuery('tx_fees'))
|
||||
const { mutateAsync: fetchTxFeesFactor, ...txFeesFactorQuery } = useMutation(createConfigQuery('tx_fees_factor'))
|
||||
const { mutateAsync: fetchMaxSweepFeeChange, ...maxSweepFeeChangeQuery } = useMutation(
|
||||
createConfigQuery('max_sweep_fee_change'),
|
||||
)
|
||||
|
||||
const feeConfigValues = useMemo<FeeConfigValues | undefined>(() => {
|
||||
if (
|
||||
!maxCjFeeAbsQuery.data &&
|
||||
!maxCjFeeRelQuery.data &&
|
||||
!txFeesQuery.data &&
|
||||
!txFeesFactorQuery.data &&
|
||||
!maxSweepFeeChangeQuery.data
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
max_cj_fee_abs: maxCjFeeAbsQuery.data?.configvalue,
|
||||
max_cj_fee_rel: maxCjFeeRelQuery.data?.configvalue,
|
||||
|
|
@ -65,17 +57,6 @@ export const useFeeConfigValidation = ({ walletFileName }: UseFeeConfigValidatio
|
|||
maxSweepFeeChangeQuery.data,
|
||||
])
|
||||
|
||||
const maxFeesConfigMissing = useMemo(() => {
|
||||
// Debug: Force the error for testing
|
||||
if (forceFeeConfigMissing) {
|
||||
return true
|
||||
}
|
||||
|
||||
return (
|
||||
feeConfigValues && (feeConfigValues.max_cj_fee_abs === undefined || feeConfigValues.max_cj_fee_rel === undefined)
|
||||
)
|
||||
}, [feeConfigValues, forceFeeConfigMissing])
|
||||
|
||||
const isLoading = useMemo(() => {
|
||||
return (
|
||||
maxCjFeeAbsQuery.isPending ||
|
||||
|
|
@ -108,18 +89,29 @@ export const useFeeConfigValidation = ({ walletFileName }: UseFeeConfigValidatio
|
|||
maxSweepFeeChangeQuery.error,
|
||||
])
|
||||
|
||||
const refetchAll = async () => {
|
||||
const refetchAll = useCallback(async () => {
|
||||
const args = {
|
||||
path: { walletname: walletFileName },
|
||||
}
|
||||
await Promise.all([
|
||||
maxCjFeeAbsQuery.mutateAsync(args),
|
||||
maxCjFeeRelQuery.mutateAsync(args),
|
||||
txFeesQuery.mutateAsync(args),
|
||||
txFeesFactorQuery.mutateAsync(args),
|
||||
maxSweepFeeChangeQuery.mutateAsync(args),
|
||||
return await Promise.all([
|
||||
fetchMaxCjFeeAbs(args),
|
||||
fetchMaxCjFeeRel(args),
|
||||
fetchTxFees(args),
|
||||
fetchTxFeesFactor(args),
|
||||
fetchMaxSweepFeeChange(args),
|
||||
])
|
||||
}
|
||||
}, [walletFileName, fetchMaxCjFeeAbs, fetchMaxCjFeeRel, fetchTxFees, fetchTxFeesFactor, fetchMaxSweepFeeChange])
|
||||
|
||||
const maxFeesConfigMissing = useMemo(() => {
|
||||
// Debug: Force the error for testing
|
||||
if (forceFeeConfigMissing) {
|
||||
return true
|
||||
}
|
||||
|
||||
return (
|
||||
feeConfigValues && (feeConfigValues.max_cj_fee_abs === undefined || feeConfigValues.max_cj_fee_rel === undefined)
|
||||
)
|
||||
}, [feeConfigValues, forceFeeConfigMissing])
|
||||
|
||||
return {
|
||||
feeConfigValues,
|
||||
|
|
|
|||
65
src/hooks/useJmConfig.ts
Normal file
65
src/hooks/useJmConfig.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { useCallback } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useStore } from 'zustand'
|
||||
import type { ConfigKey, ConfigValue } from '@/constants/jm'
|
||||
import { useApiClient } from '@/hooks/useApiClient'
|
||||
import { configgetMutation } from '@/lib/jm-api/generated/client/@tanstack/react-query.gen'
|
||||
import type { WalletFileName } from '@/lib/utils'
|
||||
import { jmConfigStore } from '@/store/jmConfigStore'
|
||||
|
||||
interface UseJmConfigProps {
|
||||
walletFileName: WalletFileName
|
||||
}
|
||||
|
||||
export const useJmConfig = ({ walletFileName }: UseJmConfigProps) => {
|
||||
const client = useApiClient()
|
||||
const jmConfigStoreState = useStore(jmConfigStore)
|
||||
|
||||
const { mutateAsync: fetchConfigAsync } = useMutation({
|
||||
...configgetMutation({
|
||||
client,
|
||||
path: { walletname: walletFileName },
|
||||
}),
|
||||
retry: 3,
|
||||
})
|
||||
|
||||
const get = useCallback(
|
||||
(key: ConfigKey): ConfigValue | null => {
|
||||
return jmConfigStoreState.get(key)
|
||||
},
|
||||
[jmConfigStoreState],
|
||||
)
|
||||
|
||||
const refetch = useCallback(
|
||||
async (key: ConfigKey): Promise<ConfigValue> => {
|
||||
const { configvalue } = await fetchConfigAsync({
|
||||
path: { walletname: walletFileName },
|
||||
body: {
|
||||
section: key.section,
|
||||
field: key.field,
|
||||
},
|
||||
})
|
||||
const result: ConfigValue = {
|
||||
key,
|
||||
value: configvalue ?? null,
|
||||
}
|
||||
jmConfigStoreState.set(result)
|
||||
return result
|
||||
},
|
||||
[walletFileName, jmConfigStoreState, fetchConfigAsync],
|
||||
)
|
||||
|
||||
const fetchIfMissing = useCallback(
|
||||
async (key: ConfigKey): Promise<ConfigValue> => {
|
||||
const value = get(key)
|
||||
return value !== null ? value : refetch(key)
|
||||
},
|
||||
[get, refetch],
|
||||
)
|
||||
|
||||
return {
|
||||
get,
|
||||
refetch,
|
||||
fetchIfMissing,
|
||||
}
|
||||
}
|
||||
59
src/store/jmConfigStore.ts
Normal file
59
src/store/jmConfigStore.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { createStore } from 'zustand'
|
||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||
import type { ConfigKey, ConfigValue } from '@/constants/jm'
|
||||
|
||||
export type JmConfigs = {
|
||||
[key: ConfigKey['section']]: Record<ConfigKey['field'], ConfigValue['value']>
|
||||
}
|
||||
|
||||
interface JmConfigStoreState {
|
||||
state: JmConfigs
|
||||
get: (val: ConfigKey) => ConfigValue | null
|
||||
getAll: () => ConfigValue[]
|
||||
set: (val: ConfigValue) => void
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
const initial: JmConfigs = {}
|
||||
|
||||
export const jmConfigStore = createStore<JmConfigStoreState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
state: initial,
|
||||
get: (key) => {
|
||||
const fields = get().state[key.section]
|
||||
if (fields === undefined) return null
|
||||
|
||||
const value = fields[key.field]
|
||||
if (value === undefined) return null
|
||||
|
||||
return {
|
||||
key,
|
||||
value: value || null,
|
||||
}
|
||||
},
|
||||
getAll: () => {
|
||||
return Object.entries(get().state).flatMap(([section, entries]) => {
|
||||
return Object.entries(entries).map(([field, value]) => ({
|
||||
key: {
|
||||
section,
|
||||
field,
|
||||
},
|
||||
value,
|
||||
}))
|
||||
})
|
||||
},
|
||||
set: (val) =>
|
||||
set((state) => {
|
||||
const copy = { state: { ...(state.state || {}) } }
|
||||
copy.state[val.key.section][val.key.field] = val.value
|
||||
return copy
|
||||
}),
|
||||
clear: () => set({ state: initial }),
|
||||
}),
|
||||
{
|
||||
name: 'jm-configs',
|
||||
storage: createJSONStorage(() => sessionStorage),
|
||||
},
|
||||
),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue