diff --git a/src/lib/adapters/utils.ts b/src/lib/adapters/utils.ts index 9f564d2a..732276f2 100644 --- a/src/lib/adapters/utils.ts +++ b/src/lib/adapters/utils.ts @@ -46,6 +46,8 @@ export function getFromLocalStorage( throw new Error(`Error getting from local storage: invalid data. ${parseData.error.issues}`) } + console.log('Retrieved', key, parseData.data) + return parseData.data } diff --git a/src/lib/adapters/waku/index.ts b/src/lib/adapters/waku/index.ts index 216fc82f..d929efc6 100644 --- a/src/lib/adapters/waku/index.ts +++ b/src/lib/adapters/waku/index.ts @@ -41,7 +41,8 @@ import type { import { walletStore } from '$lib/stores/wallet' import { SafeWaku } from './safe-waku' import type { TokenAmount } from '$lib/objects/schemas' -import { DEFAULT_FIAT_SYMBOL, exchangeStore } from '$lib/stores/exchangeRates' +import { exchangeStore } from '$lib/stores/exchangeRates' +import { preferences } from '$lib/stores/preferences' import { balanceStore } from '$lib/stores/balances' import type { ContentTopic } from './waku' import { installedObjectStore } from '$lib/stores/installed-objects' @@ -167,7 +168,7 @@ async function executeOnDataMessage( users: users, profile: myProfile, exchangeRates: get(exchangeStore).exchange, - fiatSymbol: DEFAULT_FIAT_SYMBOL, + fiatSymbol: get(preferences).fiatSymbol, tokens: get(balanceStore).balances, } await descriptor.onMessage(dataMessage, args) diff --git a/src/lib/components/icons/settings-adjust.svelte b/src/lib/components/icons/settings-adjust.svelte new file mode 100644 index 00000000..e6521ae8 --- /dev/null +++ b/src/lib/components/icons/settings-adjust.svelte @@ -0,0 +1,14 @@ + + + + + + diff --git a/src/lib/objects/chat.svelte b/src/lib/objects/chat.svelte index 1a2d5f04..fe41ac66 100644 --- a/src/lib/objects/chat.svelte +++ b/src/lib/objects/chat.svelte @@ -13,9 +13,10 @@ import { goto } from '$app/navigation' import routes from '$lib/routes' import { chats } from '$lib/stores/chat' - import { DEFAULT_FIAT_SYMBOL, exchangeStore } from '$lib/stores/exchangeRates' - import { defaultBlockchainNetwork } from '$lib/adapters/transaction' + import { exchangeStore } from '$lib/stores/exchangeRates' import { errorStore } from '$lib/stores/error' + import { preferences } from '$lib/stores/preferences' + import { defaultBlockchainNetwork } from '$lib/adapters/transaction' export let message: DataMessage export let users: User[] @@ -62,7 +63,7 @@ users, tokens, exchangeRates: $exchangeStore.exchange, - fiatSymbol: DEFAULT_FIAT_SYMBOL, + fiatSymbol: $preferences.fiatSymbol, store, viewParams: [], chatName, diff --git a/src/lib/objects/external/iframe.svelte b/src/lib/objects/external/iframe.svelte index 2daa1d53..ba05b593 100644 --- a/src/lib/objects/external/iframe.svelte +++ b/src/lib/objects/external/iframe.svelte @@ -13,7 +13,8 @@ import { onDestroy } from 'svelte' import adapters from '$lib/adapters' import { walletStore } from '$lib/stores/wallet' - import { DEFAULT_FIAT_SYMBOL, exchangeStore } from '$lib/stores/exchangeRates' + import { exchangeStore } from '$lib/stores/exchangeRates' + import { preferences } from '$lib/stores/preferences' import { defaultBlockchainNetwork } from '$lib/adapters/transaction' // TODO: This needs escaping for the CSP @@ -110,7 +111,7 @@ users, tokens, exchangeRates: $exchangeStore.exchange, - fiatSymbol: DEFAULT_FIAT_SYMBOL, + fiatSymbol: $preferences.fiatSymbol, }, context: { view, diff --git a/src/lib/objects/payggy/views/choose-amount.svelte b/src/lib/objects/payggy/views/choose-amount.svelte index 7e213a43..d0ab173c 100644 --- a/src/lib/objects/payggy/views/choose-amount.svelte +++ b/src/lib/objects/payggy/views/choose-amount.svelte @@ -24,7 +24,7 @@ export let token: TokenAmount export let tokens: TokenAmount[] export let fiatRates: Map - export let fiatSymbol: string | undefined + export let fiatSymbol: string export let onViewChange: (view: string) => void export let exitObject: () => void diff --git a/src/lib/objects/payggy/views/confirm-send.svelte b/src/lib/objects/payggy/views/confirm-send.svelte index 64210bf6..5412643f 100644 --- a/src/lib/objects/payggy/views/confirm-send.svelte +++ b/src/lib/objects/payggy/views/confirm-send.svelte @@ -25,7 +25,7 @@ export let amount: string export let token: TokenAmount export let fiatRates: Map - export let fiatSymbol: string | undefined + export let fiatSymbol: string export let estimateTransaction: (to: string, token: TokenAmount) => Promise export let sendTransaction: (to: string, token: TokenAmount, fee: TokenAmount) => Promise diff --git a/src/lib/objects/payggy/views/details.svelte b/src/lib/objects/payggy/views/details.svelte index d9c4a709..ba5e238d 100644 --- a/src/lib/objects/payggy/views/details.svelte +++ b/src/lib/objects/payggy/views/details.svelte @@ -27,7 +27,7 @@ export let profile: User export let status: string export let fiatRates: Map - export let fiatSymbol: string | undefined + export let fiatSymbol: string export let exitObject: () => void $: sender = users.find((u) => u.address === transaction.from) diff --git a/src/lib/objects/split/views/settle-now.svelte b/src/lib/objects/split/views/settle-now.svelte index 8f3bdab4..a6b27ad9 100644 --- a/src/lib/objects/split/views/settle-now.svelte +++ b/src/lib/objects/split/views/settle-now.svelte @@ -29,7 +29,7 @@ export let tokens: TokenAmount[] export let token: Token export let fiatRates: Map - export let fiatSymbol: string | undefined + export let fiatSymbol: string export let getContract: GetContract export let exitObject: () => void diff --git a/src/lib/objects/ui.svelte b/src/lib/objects/ui.svelte index c064bd9e..86da4cf5 100644 --- a/src/lib/objects/ui.svelte +++ b/src/lib/objects/ui.svelte @@ -12,9 +12,10 @@ import Loading from '$lib/components/loading.svelte' import type { HDNodeWallet } from 'ethers/lib.commonjs' import type { TokenAmount } from './schemas' - import { DEFAULT_FIAT_SYMBOL, exchangeStore } from '$lib/stores/exchangeRates' + import { exchangeStore } from '$lib/stores/exchangeRates' import { defaultBlockchainNetwork } from '$lib/adapters/transaction' import { errorStore } from '$lib/stores/error' + import { preferences } from '$lib/stores/preferences' export let objectId: string export let instanceId: string @@ -84,7 +85,7 @@ view, viewParams, exchangeRates: $exchangeStore.exchange, - fiatSymbol: DEFAULT_FIAT_SYMBOL, + fiatSymbol: $preferences.fiatSymbol, ...wakuObjectAdapter, } } diff --git a/src/lib/routes.ts b/src/lib/routes.ts index 3df903b9..9d684761 100644 --- a/src/lib/routes.ts +++ b/src/lib/routes.ts @@ -7,6 +7,7 @@ export default { IDENTITY_ACCOUNT: '/identity/account', IDENTITY_BACKUP: '/identity/backup', IDENTITY_CHAT: '/identity/chat', + IDENTITY_PREFERENCES: '/identity/preferences', CHAT: (id: string) => `/chat/${id}`, INVITE: (address: string) => `/invite/${address}`, OBJECTS: (id: string) => `/chat/${id}/object/new`, diff --git a/src/lib/stores/exchangeRates.ts b/src/lib/stores/exchangeRates.ts index 5cb98c5e..98534483 100644 --- a/src/lib/stores/exchangeRates.ts +++ b/src/lib/stores/exchangeRates.ts @@ -2,8 +2,9 @@ import { writable, get } from 'svelte/store' import type { Writable } from 'svelte/store' import { z } from 'zod' import { defaultBlockchainNetwork } from '$lib/adapters/transaction' +import { fiatSymbolList } from './preferences' -function createSchema(tokens: string[]) { +function createSchema(tokens: readonly string[]) { const schemas: Record = {} for (const token of tokens) { schemas[token] = z.number() @@ -11,10 +12,7 @@ function createSchema(tokens: string[]) { return z.object(schemas) } -// TODO: with centralised endpoint, we can add more fiat currencies -const fiatList = ['DAI', 'EUR', 'USD', 'CZK'] - -const resSchema = createSchema(fiatList) +const resSchema = createSchema(fiatSymbolList) type ExchangeRates = z.infer export interface ExchangeRateRecord { @@ -34,7 +32,7 @@ interface BalanceRateStore extends Writable { } async function fetchTokenPrice(symbol: string): Promise { - const endpoint = `https://min-api.cryptocompare.com/data/price?fsym=${symbol}&tsyms=${fiatList.join( + const endpoint = `https://min-api.cryptocompare.com/data/price?fsym=${symbol}&tsyms=${fiatSymbolList.join( ',', )}&extraParams=0f8e116726ca17f20d95cd93fb85c7740c3bdb86719a58164ca28ed10df3320c` const response = await fetch(endpoint) @@ -108,4 +106,3 @@ function createExchangeStore(): BalanceRateStore { } export const exchangeStore = createExchangeStore() -export const DEFAULT_FIAT_SYMBOL = fiatList[1] // TODO: we could set this in user preferences as originally designed diff --git a/src/lib/stores/preferences.ts b/src/lib/stores/preferences.ts new file mode 100644 index 00000000..c3f1fa51 --- /dev/null +++ b/src/lib/stores/preferences.ts @@ -0,0 +1,35 @@ +import { getFromLocalStorage, saveToLocalStorage } from '$lib/adapters/utils' +import { writable, type Writable } from 'svelte/store' +import { z } from 'zod' + +export const fiatSymbolList = ['DAI', 'EUR', 'USD', 'CZK'] as const +const fiatSymbolSchema = z.enum(fiatSymbolList) +export type FiatSymbol = z.infer + +interface Preferences { + fiatSymbol: FiatSymbol +} + +interface PreferenceStore extends Writable { + setFiatSymbol: (newFiatSymbol: FiatSymbol) => void +} + +function createPreferenceStore(): PreferenceStore { + let fiatSymbol: FiatSymbol = 'DAI' + try { + fiatSymbol = getFromLocalStorage('fiat', fiatSymbolSchema) ?? fiatSymbol + } catch (error) { + // this is fine + } + const store = writable({ fiatSymbol }) + + return { + ...store, + setFiatSymbol: (newFiatSymbol: FiatSymbol) => { + saveToLocalStorage('fiat', newFiatSymbol) + store.update((theme) => ({ ...theme, fiatSymbol: newFiatSymbol })) + }, + } +} + +export const preferences = createPreferenceStore() diff --git a/src/lib/utils/fiat.ts b/src/lib/utils/fiat.ts index b0eb1d6d..c2fcd751 100644 --- a/src/lib/utils/fiat.ts +++ b/src/lib/utils/fiat.ts @@ -1,28 +1,33 @@ import type { ExchangeRateRecord } from '$lib/stores/exchangeRates' +import { isValidNumber } from '$lib/utils' interface FiatAmount { - symbol?: string - amount?: number + symbol: string + amount: number error?: Error refreshing: boolean } function calculateFiatAmount( fiatRates: Map, - fiatSymbol?: string, - tokenAmount?: string, - tokenSymbol?: string, + fiatSymbol: string, + tokenAmount: number, + tokenSymbol: string, ): FiatAmount { - if (!fiatSymbol || !tokenAmount || !tokenSymbol) return { symbol: fiatSymbol, refreshing: false } const exchangeRate = fiatRates.get(tokenSymbol) const rate = exchangeRate?.rates[fiatSymbol] - let amount: number | undefined = undefined - if (rate !== undefined) amount = Number(tokenAmount) * rate + if (rate === undefined) + return { + symbol: fiatSymbol, + amount: 0, + refreshing: Boolean(exchangeRate?.refreshing), + error: new Error(`No exchange rate for ${tokenSymbol} to ${fiatSymbol}`), + } return { symbol: fiatSymbol, - amount, + amount: tokenAmount * rate, refreshing: Boolean(exchangeRate?.refreshing), error: exchangeRate?.error, } @@ -30,12 +35,23 @@ function calculateFiatAmount( export function getFiatAmountText( fiatRates: Map, - fiatSymbol?: string, - tokenAmount?: string, - tokenSymbol?: string, + fiatSymbol: string, + tokenAmount: string, + tokenSymbol: string, ): string { - const fiatAmount = calculateFiatAmount(fiatRates, fiatSymbol, tokenAmount, tokenSymbol) + if (tokenAmount === '') { + const fiatAmount = calculateFiatAmount(fiatRates, fiatSymbol, 1, tokenSymbol) + if (fiatAmount.amount !== undefined) + return `1 ${tokenSymbol} ≈ ${fiatAmount.amount.toFixed(2)} ${fiatSymbol}` + if (fiatAmount.refreshing) return `Getting exchange rate` + return `Failed to load ${fiatSymbol} amount` + } + + if (!isValidNumber(tokenAmount)) return 'Please enter a valid number' + const amount = Number(tokenAmount) + if (isNaN(amount)) return 'Please enter a valid number' + const fiatAmount = calculateFiatAmount(fiatRates, fiatSymbol, amount, tokenSymbol) if (fiatAmount.amount !== undefined) return `≈${fiatAmount.amount.toFixed(2)} ${fiatSymbol}` - else if (fiatAmount.refreshing) return `Getting exchange rate` + if (fiatAmount.refreshing) return `Getting exchange rate` return `Failed to load ${fiatSymbol} amount` } diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 6ad33760..91aa3a90 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -2,3 +2,9 @@ export function genRandomHex(size: number) { return [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('') } + +const regex = /^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$/ + +export function isValidNumber(input: string): boolean { + return regex.test(input) +} diff --git a/src/routes/identity/+page.svelte b/src/routes/identity/+page.svelte index dae045a9..a4fc8ad4 100644 --- a/src/routes/identity/+page.svelte +++ b/src/routes/identity/+page.svelte @@ -4,6 +4,7 @@ import Renew from '$lib/components/icons/renew.svelte' import Wallet from '$lib/components/icons/wallet.svelte' import SettingsView from '$lib/components/icons/settings-view.svelte' + import SettingsAdjust from '$lib/components/icons/settings-adjust.svelte' import Logout from '$lib/components/icons/logout.svelte' import DocumentSigned from '$lib/components/icons/document-signed.svelte' @@ -132,7 +133,7 @@ - goto(routes.IDENTITY_CHAT)}> + goto(routes.IDENTITY_CHAT)}>
Chat appearance @@ -144,6 +145,18 @@
+ goto(routes.IDENTITY_PREFERENCES)}> + +
+ Preferences +
+
+ +
+
+

If you disconnect or need to recover access to your identity you will need your recovery diff --git a/src/routes/identity/account/+page.svelte b/src/routes/identity/account/+page.svelte index 77d0b9fc..47193f04 100644 --- a/src/routes/identity/account/+page.svelte +++ b/src/routes/identity/account/+page.svelte @@ -21,7 +21,8 @@ import { fetchBalances } from '$lib/adapters/balance' import AuthenticatedOnly from '$lib/components/authenticated-only.svelte' import Layout from '$lib/components/layout.svelte' - import { exchangeStore, DEFAULT_FIAT_SYMBOL } from '$lib/stores/exchangeRates' + import { exchangeStore } from '$lib/stores/exchangeRates' + import { preferences } from '$lib/stores/preferences' import Loading from '$lib/components/loading.svelte' let copied = false @@ -85,8 +86,10 @@ amount={balance.amount} decimals={balance.decimals} image={balance.image} - fiatSymbol={DEFAULT_FIAT_SYMBOL} - fiatExchange={$exchangeStore.exchange.get(balance.symbol)?.rates[DEFAULT_FIAT_SYMBOL]} + fiatSymbol={$preferences.fiatSymbol} + fiatExchange={$exchangeStore.exchange.get(balance.symbol)?.rates[ + $preferences.fiatSymbol + ]} /> {/each} diff --git a/src/routes/identity/preferences/+page.svelte b/src/routes/identity/preferences/+page.svelte new file mode 100644 index 00000000..6ee7d51e --- /dev/null +++ b/src/routes/identity/preferences/+page.svelte @@ -0,0 +1,41 @@ + + + + +

+ +
+ + + + + + {#each fiatSymbolList as fiatSymbol} + preferences.setFiatSymbol(fiatSymbol)} + >{fiatSymbol} + {/each} + + + +