Use source loader for email sprite icons
[ProtonMail-WebClient.git] / packages / wallet / store / slices / exchangeRate.ts
blob39c595dc2ca85f6782add656e720c782f12ea618
1 import { createSlice } from '@reduxjs/toolkit';
3 import type { ModelState } from '@proton/account';
4 import { getInitialModelState } from '@proton/account';
5 import type { WasmApiExchangeRate, WasmFiatCurrencySymbol } from '@proton/andromeda';
6 import { createAsyncModelThunk, handleAsyncModel } from '@proton/redux-utilities';
7 import { SECOND } from '@proton/shared/lib/constants';
8 import { type SimpleMap } from '@proton/shared/lib/interfaces';
10 import type { WalletThunkArguments } from '../thunk';
12 const name = 'exchange_rate' as const;
14 type WasmApiExchangeRateByFiat = SimpleMap<WasmApiExchangeRate>;
16 export interface ExchangeRateState {
17     [name]: ModelState<WasmApiExchangeRateByFiat>;
20 type SliceState = ExchangeRateState[typeof name];
21 type Model = NonNullable<SliceState['value']>;
23 export const selectExchangeRate = (state: ExchangeRateState) => {
24     return state[name];
27 const SEPARATOR = '-';
29 export const serialiseKey = (fiat: WasmFiatCurrencySymbol, timestamp?: number) => {
30     return timestamp ? `${fiat}${SEPARATOR}${Number(timestamp)}` : fiat;
32 export const parseKey = (key: string) => {
33     const [fiat, timestamp] = key.split(SEPARATOR) as [WasmFiatCurrencySymbol, string?];
35     return {
36         fiat,
37         timestamp: timestamp ? Number(timestamp) : undefined,
38     };
41 export const getKeyAndTs = (fiat: WasmFiatCurrencySymbol, date?: Date) => {
42     const ts = date?.getTime();
43     const key = serialiseKey(fiat, ts);
45     // API expects ts in seconds
46     const tsInSeconds = ts && Math.floor(ts / SECOND);
48     return [key, tsInSeconds] as const;
51 const modelThunk = createAsyncModelThunk<
52     Model,
53     ExchangeRateState,
54     WalletThunkArguments,
55     [WasmFiatCurrencySymbol, Date?]
56 >(`${name}/fetch`, {
57     miss: async ({ extraArgument, options, getState }) => {
58         const stateValue = getState()[name].value;
59         if (!options?.thunkArg) {
60             return stateValue ?? {};
61         }
63         const fiat = options?.thunkArg?.[0] ?? 'USD';
64         const date = options?.thunkArg?.[1];
66         const [key, ts] = getKeyAndTs(fiat, date);
68         try {
69             const exchangeRate = await extraArgument.walletApi
70                 .clients()
71                 .exchange_rate.getExchangeRate(fiat, ts ? BigInt(ts) : undefined)
72                 .then((data) => {
73                     return data.Data;
74                 });
76             return {
77                 ...stateValue,
78                 [key]: exchangeRate,
79             };
80         } catch {
81             return stateValue ?? {};
82         }
83     },
84     previous: ({ getState, options }) => {
85         const state = getState()[name];
87         if (!options?.thunkArg || !state.value) {
88             return undefined;
89         }
91         const [fiat, date] = options?.thunkArg;
92         const [key] = getKeyAndTs(fiat, date);
94         if (state.value[key]) {
95             return state;
96         }
98         return undefined;
99     },
102 const initialState = getInitialModelState<Model>();
104 const slice = createSlice({
105     name,
106     initialState,
107     reducers: {},
108     extraReducers: (builder) => {
109         handleAsyncModel(builder, modelThunk);
110     },
113 export const exchangeRateReducer = { [name]: slice.reducer };
114 export const exchangeRateThunk = modelThunk.thunk;