diff --git a/frontend/src/store/features/common/commonService.ts b/frontend/src/store/features/common/commonService.ts new file mode 100644 index 000000000..e8820ab45 --- /dev/null +++ b/frontend/src/store/features/common/commonService.ts @@ -0,0 +1,23 @@ +'use client'; + +import Axios, { AxiosResponse } from 'axios'; +import { cleanURL } from '../../../utils/util'; + +const BASE_URL = process.env.BACKEND_URI; + +const fetchPriceInfo = (denom: string): Promise => { + const uri = `${cleanURL(BASE_URL)}/tokens-info/${denom}`; + return Axios.get(uri); +}; + +const fetchAllTokensPriceInfo = (): Promise => { + const uri = `${cleanURL(BASE_URL)}/tokens-info`; + return Axios.get(uri); +}; + +const result = { + tokenInfo: fetchPriceInfo, + allTokensInfo: fetchAllTokensPriceInfo, +}; + +export default result; diff --git a/frontend/src/store/features/common/commonSlice.ts b/frontend/src/store/features/common/commonSlice.ts new file mode 100644 index 000000000..f20b91bf4 --- /dev/null +++ b/frontend/src/store/features/common/commonSlice.ts @@ -0,0 +1,154 @@ +'use client'; + +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; +import commonService from './commonService'; +import { AxiosError } from 'axios'; +import { ERR_UNKNOWN } from '../../../utils/errors'; + +const initialState: CommonState = { + errState: { + message: '', + type: '', + }, + txSuccess: { + hash: '', + }, + txLoadRes: { load: false }, + tokensInfoState: { + error: '', + info: { + denom: '', + coingecko_name: '', + enabled: false, + last_updated: '', + info: { usd: NaN, usd_24h_change: NaN }, + }, + status: 'idle', + }, + allTokensInfoState: { + error: '', + info: {}, + status: 'idle', + }, + selectedNetwork: { + chainName: '', + }, +}; + +export const getTokenPrice = createAsyncThunk( + 'common/getTokenPrice', + async (data: string, { rejectWithValue }) => { + try { + const response = await commonService.tokenInfo(data); + return response.data; + } catch (error) { + if (error instanceof AxiosError) return rejectWithValue(error.message); + return rejectWithValue(ERR_UNKNOWN); + } + } +); + +export const getAllTokensPrice = createAsyncThunk( + 'common/getAllTokensPrice', + async (data, { rejectWithValue }) => { + try { + const response = await commonService.allTokensInfo(); + return response.data; + } catch (error) { + if (error instanceof AxiosError) return rejectWithValue(error.message); + return rejectWithValue(ERR_UNKNOWN); + } + } +); + +export const commonSlice = createSlice({ + name: 'common', + initialState, + reducers: { + setError: (state, action: PayloadAction) => { + state.errState = { + message: action.payload.message, + type: action.payload.type, + }; + }, + setTxHash: (state, action: PayloadAction) => { + state.txSuccess = { + hash: action.payload.hash, + }; + }, + setTxLoad: (state) => { + state.txLoadRes = { load: true }; + }, + resetTxLoad: (state) => { + state.txLoadRes = { load: false }; + }, + resetTxHash: (state) => { + state.txSuccess = { + hash: '', + }; + }, + resetError: (state) => { + state.errState = { + message: '', + type: '', + }; + }, + setSelectedNetwork: (state, action: PayloadAction) => { + state.selectedNetwork.chainName = action.payload.chainName; + }, + }, + extraReducers: (builder) => { + builder + .addCase(getTokenPrice.pending, (state) => { + state.tokensInfoState.status = 'pending'; + state.tokensInfoState.error = ''; + }) + .addCase(getTokenPrice.fulfilled, (state, action) => { + state.tokensInfoState.status = 'idle'; + state.tokensInfoState.error = ''; + state.tokensInfoState.info = + action.payload.data || initialState.tokensInfoState.info; + }) + .addCase(getTokenPrice.rejected, (state, action) => { + state.tokensInfoState.status = 'rejected'; + state.tokensInfoState.error = JSON.stringify(action.payload) || ''; + state.tokensInfoState.info = initialState.tokensInfoState.info; + }); + + builder + .addCase(getAllTokensPrice.pending, (state) => { + state.allTokensInfoState.status = 'pending'; + state.allTokensInfoState.error = ''; + }) + .addCase(getAllTokensPrice.fulfilled, (state, action) => { + const data = action.payload.data || []; + const tokensPriceInfo = data.reduce( + (result: Record, tokenInfo: InfoState) => { + result[tokenInfo.denom] = tokenInfo; + return result; + }, + {} + ); + state.allTokensInfoState.status = 'idle'; + state.allTokensInfoState.error = ''; + state.allTokensInfoState.info = tokensPriceInfo; + }) + .addCase(getAllTokensPrice.rejected, (state, action) => { + state.allTokensInfoState.status = 'rejected'; + state.allTokensInfoState.error = JSON.stringify(action.payload) || ''; + state.allTokensInfoState.info = {}; + }); + }, +}); + +export const { + setError, + resetError, + setTxLoad, + resetTxLoad, + setTxHash, + resetTxHash, + setSelectedNetwork, +} = commonSlice.actions; + +export default commonSlice.reducer; diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts index 7abad5ed6..fd5ffe688 100644 --- a/frontend/src/store/store.ts +++ b/frontend/src/store/store.ts @@ -3,6 +3,7 @@ import { configureStore } from '@reduxjs/toolkit'; import multisigSlice from './features/multisig/multisigSlice'; import walletSlice from './features/wallet/walletSlice'; +import commonSlice from './features/common/commonSlice'; import stakeSlice from './features/staking/stakeSlice'; import bankSlice from './features/bank/bankSlice'; @@ -10,6 +11,7 @@ export const store = configureStore({ reducer: { wallet: walletSlice, multisig: multisigSlice, + common: commonSlice, staking: stakeSlice, bank: bankSlice, }, diff --git a/frontend/src/types/common.d.ts b/frontend/src/types/common.d.ts new file mode 100644 index 000000000..7484bccc9 --- /dev/null +++ b/frontend/src/types/common.d.ts @@ -0,0 +1,50 @@ +interface ErrorState { + message: string; + type: string; +} + +interface TxSuccess { + hash: string; +} + +interface TxLoadRes { + load: boolean; +} + +interface TokenInfo { + usd: number; + usd_24h_change: number; +} + +interface InfoState { + denom: string; + coingecko_name: string; + enabled: boolean; + last_updated: string; + info: TokenInfo; +} + +interface TokensInfoState { + error: string; + info: InfoState; + status: string; +} + +interface SelectedNetwork { + chainName: string; +} + +interface AllTokensInfoState { + error: string; + info: Record; + status: string; +} + +interface CommonState { + errState: ErrorState; + txSuccess: TxSuccess; + txLoadRes: TxLoadRes; + tokensInfoState: TokensInfoState; + selectedNetwork: SelectedNetwork; + allTokensInfoState: AllTokensInfoState; +}