From ad1eec7d47e54a31dcd7585798ff57846d07c426 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 5 Jan 2025 23:15:45 +0800 Subject: [PATCH] migrate exchange rates store to composition API and typescript --- src/DesktopApp.vue | 2 +- src/MobileApp.vue | 2 +- src/lib/numeral.ts | 4 +- src/stores/account.js | 2 +- src/stores/exchangeRates.js | 136 --------------- src/stores/exchangeRates.ts | 155 ++++++++++++++++++ src/stores/index.js | 2 +- src/stores/overview.js | 2 +- src/stores/statistics.js | 2 +- src/stores/transaction.js | 2 +- src/views/desktop/AboutPage.vue | 2 +- src/views/desktop/ExchangeRatesPage.vue | 2 +- src/views/desktop/LoginPage.vue | 2 +- src/views/desktop/SignupPage.vue | 2 +- src/views/desktop/UnlockPage.vue | 2 +- src/views/desktop/accounts/ListPage.vue | 2 +- .../app/settings/tabs/AppBasicSettingTab.vue | 2 +- .../list/dialogs/BatchReplaceDialog.vue | 2 +- .../transactions/list/dialogs/EditDialog.vue | 2 +- .../list/dialogs/ImportDialog.vue | 2 +- src/views/mobile/AboutPage.vue | 2 +- src/views/mobile/ExchangeRatesPage.vue | 2 +- src/views/mobile/LoginPage.vue | 2 +- src/views/mobile/SettingsPage.vue | 2 +- src/views/mobile/SignupPage.vue | 2 +- src/views/mobile/UnlockPage.vue | 2 +- src/views/mobile/accounts/ListPage.vue | 2 +- src/views/mobile/transactions/EditPage.vue | 2 +- 28 files changed, 182 insertions(+), 163 deletions(-) delete mode 100644 src/stores/exchangeRates.js create mode 100644 src/stores/exchangeRates.ts diff --git a/src/DesktopApp.vue b/src/DesktopApp.vue index d511db6c..1a8d4ccc 100644 --- a/src/DesktopApp.vue +++ b/src/DesktopApp.vue @@ -24,7 +24,7 @@ import { useRootStore } from '@/stores/index.js'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useTokensStore } from '@/stores/token.ts'; -import { useExchangeRatesStore } from '@/stores/exchangeRates.js'; +import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts'; import { ThemeType } from '@/core/theme.ts'; diff --git a/src/MobileApp.vue b/src/MobileApp.vue index 998dfa32..30e34167 100644 --- a/src/MobileApp.vue +++ b/src/MobileApp.vue @@ -13,7 +13,7 @@ import { useRootStore } from '@/stores/index.js'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useTokensStore } from '@/stores/token.ts'; -import { useExchangeRatesStore } from '@/stores/exchangeRates.js'; +import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts'; import { ThemeType } from '@/core/theme.ts'; diff --git a/src/lib/numeral.ts b/src/lib/numeral.ts index 6dad21fb..d6638c50 100644 --- a/src/lib/numeral.ts +++ b/src/lib/numeral.ts @@ -276,7 +276,7 @@ export function getAdaptiveDisplayAmountRate(amount1: number, amount2: number, f } } -export function getExchangedAmount(amount: number, fromRate: string, toRate: string): number | null { +export function getExchangedAmountByRate(amount: number, fromRate: string, toRate: string): number | null { const exchangeRate = parseFloat(toRate) / parseFloat(fromRate); if (!isNumber(exchangeRate)) { @@ -295,5 +295,5 @@ export function getConvertedAmount(baseAmount: number | '', fromExchangeRate: { return 0; } - return getExchangedAmount(baseAmount as number, fromExchangeRate.rate, toExchangeRate.rate); + return getExchangedAmountByRate(baseAmount as number, fromExchangeRate.rate, toExchangeRate.rate); } diff --git a/src/stores/account.js b/src/stores/account.js index e8453841..2c0f832b 100644 --- a/src/stores/account.js +++ b/src/stores/account.js @@ -1,7 +1,7 @@ import { defineStore } from 'pinia'; import { useUserStore } from './user.ts'; -import { useExchangeRatesStore } from './exchangeRates.js'; +import { useExchangeRatesStore } from './exchangeRates.ts'; import { AccountType, AccountCategory } from '@/core/account.ts'; import { PARENT_ACCOUNT_CURRENCY_PLACEHOLDER } from '@/consts/currency.ts'; diff --git a/src/stores/exchangeRates.js b/src/stores/exchangeRates.js deleted file mode 100644 index 82e66d6c..00000000 --- a/src/stores/exchangeRates.js +++ /dev/null @@ -1,136 +0,0 @@ -import { defineStore } from 'pinia'; - -import services from '@/lib/services.ts'; -import logger from '@/lib/logger.ts'; -import { isEquals } from '@/lib/common.ts'; -import { getCurrentUnixTime, formatUnixTime } from '@/lib/datetime.ts'; -import { getExchangedAmount } from '@/lib/numeral.ts'; - -const exchangeRatesLocalStorageKey = 'ebk_app_exchange_rates'; - -function getExchangeRatesFromLocalStorage() { - const storageData = localStorage.getItem(exchangeRatesLocalStorageKey) || '{}'; - return JSON.parse(storageData); -} - -function setExchangeRatesToLocalStorage(value) { - const storageData = JSON.stringify(value); - localStorage.setItem(exchangeRatesLocalStorageKey, storageData); -} - -function clearExchangeRatesFromLocalStorage() { - localStorage.removeItem(exchangeRatesLocalStorageKey); -} - -export const useExchangeRatesStore = defineStore('exchangeRates', { - state: () => ({ - latestExchangeRates: getExchangeRatesFromLocalStorage() - }), - getters: { - exchangeRatesLastUpdateTime(state) { - const exchangeRates = state.latestExchangeRates || {}; - return exchangeRates && exchangeRates.data ? exchangeRates.data.updateTime : null; - }, - latestExchangeRateMap(state) { - const exchangeRateMap = {}; - - if (!state.latestExchangeRates || !state.latestExchangeRates.data || !state.latestExchangeRates.data.exchangeRates) { - return exchangeRateMap; - } - - for (let i = 0; i < state.latestExchangeRates.data.exchangeRates.length; i++) { - const exchangeRate = state.latestExchangeRates.data.exchangeRates[i]; - exchangeRateMap[exchangeRate.currency] = exchangeRate; - } - - return exchangeRateMap; - } - }, - actions: { - resetLatestExchangeRates() { - this.latestExchangeRates = {}; - clearExchangeRatesFromLocalStorage(); - }, - getLatestExchangeRates({ silent, force }) { - const self = this; - const currentExchangeRateData = self.latestExchangeRates; - const now = getCurrentUnixTime(); - - if (!force) { - if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data && - formatUnixTime(currentExchangeRateData.data.updateTime, 'YYYY-MM-DD') === formatUnixTime(now, 'YYYY-MM-DD')) { - return Promise.resolve(currentExchangeRateData.data); - } - - if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data && - formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === formatUnixTime(now, 'YYYY-MM-DD HH')) { - return Promise.resolve(currentExchangeRateData.data); - } - } - - return new Promise((resolve, reject) => { - services.getLatestExchangeRates({ - ignoreError: silent - }).then(response => { - const data = response.data; - - if (!data || !data.success || !data.result) { - reject({ message: 'Unable to retrieve exchange rates data' }); - return; - } - - const currentData = getExchangeRatesFromLocalStorage(); - - if (force && currentData && currentData.data && isEquals(currentData.data, data.result)) { - reject({ message: 'Exchange rates data is up to date' }); - return; - } - - this.latestExchangeRates = { - time: now, - data: data.result - }; - setExchangeRatesToLocalStorage(this.latestExchangeRates); - - resolve(data.result); - }).catch(error => { - logger.error('failed to retrieve latest exchange rates data', error); - - if (error && error.processed) { - reject(error); - } else if (error.response && error.response.data && error.response.data.errorMessage) { - reject({ error: error.response.data }); - } else { - reject({ message: 'Unable to retrieve exchange rates data' }); - } - }); - }); - }, - getExchangedAmount(amount, fromCurrency, toCurrency) { - if (amount === 0) { - return 0; - } - - if (!this.latestExchangeRates || !this.latestExchangeRates.data || !this.latestExchangeRates.data.exchangeRates) { - return null; - } - - const exchangeRates = this.latestExchangeRates.data.exchangeRates; - const exchangeRateMap = {}; - - for (let i = 0; i < exchangeRates.length; i++) { - const exchangeRate = exchangeRates[i]; - exchangeRateMap[exchangeRate.currency] = exchangeRate; - } - - const fromCurrencyExchangeRate = exchangeRateMap[fromCurrency]; - const toCurrencyExchangeRate = exchangeRateMap[toCurrency]; - - if (!fromCurrencyExchangeRate || !toCurrencyExchangeRate) { - return null; - } - - return getExchangedAmount(amount, fromCurrencyExchangeRate.rate, toCurrencyExchangeRate.rate); - } - } -}); diff --git a/src/stores/exchangeRates.ts b/src/stores/exchangeRates.ts new file mode 100644 index 00000000..0395bded --- /dev/null +++ b/src/stores/exchangeRates.ts @@ -0,0 +1,155 @@ +import { type Ref, ref, computed } from 'vue'; +import { defineStore } from 'pinia'; + +import type {LatestExchangeRate, LatestExchangeRateResponse} from '@/models/exchange_rate.ts'; + +import { isEquals } from '@/lib/common.ts'; +import { getCurrentUnixTime, formatUnixTime } from '@/lib/datetime.ts'; +import { getExchangedAmountByRate } from '@/lib/numeral.ts'; + +import logger from '@/lib/logger.ts'; +import services from '@/lib/services.ts'; + +const exchangeRatesLocalStorageKey = 'ebk_app_exchange_rates'; + +interface LatestExchangeRates { + time?: number; + data?: LatestExchangeRateResponse; +} + +function getExchangeRatesFromLocalStorage(): LatestExchangeRates { + const storageData = localStorage.getItem(exchangeRatesLocalStorageKey) || '{}'; + return JSON.parse(storageData) as LatestExchangeRates; +} + +function setExchangeRatesToLocalStorage(value: LatestExchangeRates): void { + const storageData = JSON.stringify(value); + localStorage.setItem(exchangeRatesLocalStorageKey, storageData); +} + +function clearExchangeRatesFromLocalStorage(): void { + localStorage.removeItem(exchangeRatesLocalStorageKey); +} + +export const useExchangeRatesStore = defineStore('exchangeRates', () => { + const latestExchangeRates: Ref = ref(getExchangeRatesFromLocalStorage()); + + const exchangeRatesLastUpdateTime = computed(() => { + const exchangeRates = latestExchangeRates.value || {}; + return exchangeRates && exchangeRates.data ? exchangeRates.data.updateTime : null; + }); + + const latestExchangeRateMap = computed>(() => { + const exchangeRateMap: Record = {}; + + if (!latestExchangeRates.value || !latestExchangeRates.value.data || !latestExchangeRates.value.data.exchangeRates) { + return exchangeRateMap; + } + + for (let i = 0; i < latestExchangeRates.value.data.exchangeRates.length; i++) { + const exchangeRate = latestExchangeRates.value.data.exchangeRates[i]; + exchangeRateMap[exchangeRate.currency] = exchangeRate; + } + + return exchangeRateMap; + }); + + function resetLatestExchangeRates(): void { + latestExchangeRates.value = {}; + clearExchangeRatesFromLocalStorage(); + } + + function getLatestExchangeRates(req: { silent: boolean, force: boolean }): Promise { + const currentExchangeRateData = latestExchangeRates.value; + const now = getCurrentUnixTime(); + + if (!req.force) { + if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data && + formatUnixTime(currentExchangeRateData.data.updateTime, 'YYYY-MM-DD') === formatUnixTime(now, 'YYYY-MM-DD')) { + return Promise.resolve(currentExchangeRateData.data); + } + + if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data && + formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === formatUnixTime(now, 'YYYY-MM-DD HH')) { + return Promise.resolve(currentExchangeRateData.data); + } + } + + return new Promise((resolve, reject) => { + services.getLatestExchangeRates({ + ignoreError: req.silent + }).then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + reject({ message: 'Unable to retrieve exchange rates data' }); + return; + } + + const currentData = getExchangeRatesFromLocalStorage(); + + if (req.force && currentData && currentData.data && isEquals(currentData.data, data.result)) { + reject({ message: 'Exchange rates data is up to date' }); + return; + } + + latestExchangeRates.value = { + time: now, + data: data.result + }; + setExchangeRatesToLocalStorage(latestExchangeRates.value); + + resolve(data.result); + }).catch(error => { + logger.error('failed to retrieve latest exchange rates data', error); + + if (error && error.processed) { + reject(error); + } else if (error.response && error.response.data && error.response.data.errorMessage) { + reject({ error: error.response.data }); + } else { + reject({ message: 'Unable to retrieve exchange rates data' }); + } + }); + }); + } + + function getExchangedAmount(amount: number, fromCurrency: string, toCurrency: string): number | null { + if (amount === 0) { + return 0; + } + + if (!latestExchangeRates.value || !latestExchangeRates.value.data || !latestExchangeRates.value.data.exchangeRates) { + return null; + } + + const exchangeRates = latestExchangeRates.value.data.exchangeRates; + const exchangeRateMap: Record = {}; + + for (let i = 0; i < exchangeRates.length; i++) { + const exchangeRate = exchangeRates[i]; + exchangeRateMap[exchangeRate.currency] = exchangeRate; + } + + const fromCurrencyExchangeRate = exchangeRateMap[fromCurrency]; + const toCurrencyExchangeRate = exchangeRateMap[toCurrency]; + + if (!fromCurrencyExchangeRate || !toCurrencyExchangeRate) { + return null; + } + + return getExchangedAmountByRate(amount, fromCurrencyExchangeRate.rate, toCurrencyExchangeRate.rate); + } + + return { + // state + latestExchangeRates, + // computed state + exchangeRatesLastUpdateTime, + latestExchangeRateMap, + // functions + resetLatestExchangeRates, + getLatestExchangeRates, + getExchangedAmount + }; +}); diff --git a/src/stores/index.js b/src/stores/index.js index 2ae9e394..d9264a17 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -9,7 +9,7 @@ import { useTransactionTemplatesStore } from './transactionTemplate.js'; import { useTransactionsStore } from './transaction.js'; import { useOverviewStore } from './overview.js'; import { useStatisticsStore } from './statistics.js'; -import { useExchangeRatesStore } from './exchangeRates.js'; +import { useExchangeRatesStore } from './exchangeRates.ts'; import { hasUserAppLockState, diff --git a/src/stores/overview.js b/src/stores/overview.js index 5d893a1b..e831addd 100644 --- a/src/stores/overview.js +++ b/src/stores/overview.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from './user.ts'; -import { useExchangeRatesStore } from './exchangeRates.js'; +import { useExchangeRatesStore } from './exchangeRates.ts'; import { isNumber, isEquals } from '@/lib/common.ts'; import { diff --git a/src/stores/statistics.js b/src/stores/statistics.js index 04c1dff7..dbf526b8 100644 --- a/src/stores/statistics.js +++ b/src/stores/statistics.js @@ -4,7 +4,7 @@ import { useSettingsStore } from './setting.ts'; import { useUserStore } from './user.ts'; import { useAccountsStore } from './account.js'; import { useTransactionCategoriesStore } from './transactionCategory.js'; -import { useExchangeRatesStore } from './exchangeRates.js'; +import { useExchangeRatesStore } from './exchangeRates.ts'; import { DateRangeScene, DateRange } from '@/core/datetime'; import { CategoryType } from '@/core/category.ts'; diff --git a/src/stores/transaction.js b/src/stores/transaction.js index 4a6c7ec6..89ea2ae5 100644 --- a/src/stores/transaction.js +++ b/src/stores/transaction.js @@ -6,7 +6,7 @@ import { useAccountsStore } from './account.js'; import { useTransactionCategoriesStore } from './transactionCategory.js'; import { useOverviewStore } from './overview.js'; import { useStatisticsStore } from './statistics.js'; -import { useExchangeRatesStore } from './exchangeRates.js'; +import { useExchangeRatesStore } from './exchangeRates.ts'; import { DateRange } from '@/core/datetime.ts'; import { CategoryType } from '@/core/category.ts'; diff --git a/src/views/desktop/AboutPage.vue b/src/views/desktop/AboutPage.vue index cc9edaf9..9524c5fb 100644 --- a/src/views/desktop/AboutPage.vue +++ b/src/views/desktop/AboutPage.vue @@ -122,7 +122,7 @@