@unisat/wallet-state 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unisat/wallet-state",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Redux state management for UniSat wallet, shared between platforms",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.mjs",
@@ -16,11 +16,24 @@
16
16
  "lib",
17
17
  "src"
18
18
  ],
19
+ "dependencies": {
20
+ "loglevel": "^1.8.0",
21
+ "@unisat/keyring-service": "1.0.0",
22
+ "@unisat/babylon-service": "1.0.1",
23
+ "@unisat/base-utils": "0.1.2",
24
+ "@unisat/wallet-types": "1.0.0",
25
+ "@unisat/wallet-shared": "0.1.1",
26
+ "@unisat/i18n": "1.0.0"
27
+ },
19
28
  "peerDependencies": {
29
+ "redux-localstorage-simple": "^2.5.1",
30
+ "compare-versions": "^4.1.3",
31
+ "react-i18next": "^11.16.9",
32
+ "i18next": "^21.8.1",
33
+ "react-router-dom": "^6.3.0",
20
34
  "@reduxjs/toolkit": "^1.9.0",
21
35
  "react": "^18.0.0",
22
- "react-redux": "^8.0.0",
23
- "@unisat/wallet-types": "1.0.0"
36
+ "react-redux": "^8.0.0"
24
37
  },
25
38
  "devDependencies": {
26
39
  "@types/node": "^20.0.0",
@@ -0,0 +1,5 @@
1
+ import { createAction } from '@reduxjs/toolkit'
2
+
3
+ // fired once when the app reloads but before the app renders
4
+ // allows any updates to be applied to store data loaded from localStorage
5
+ export const updateVersion = createAction<void>('global/updateVersion')
@@ -0,0 +1,191 @@
1
+ import log from 'loglevel'
2
+ import React, { createContext, useEffect, useState } from 'react'
3
+
4
+ import {
5
+ BROWSER_TO_APP_LOCALE_MAP,
6
+ changeLanguage,
7
+ FALLBACK_LOCALE,
8
+ getSupportedLocales,
9
+ initI18n,
10
+ LOCALE_NAMES,
11
+ t as translate,
12
+ } from '@unisat/i18n'
13
+ import { useWallet } from './WalletContext'
14
+
15
+ interface I18nContextType {
16
+ t: (key: string, substitutions?: string | string[]) => string
17
+ locale: string
18
+ supportedLocales: string[]
19
+ localeNames: Record<string, string>
20
+ changeLocale: (locale: string) => Promise<void>
21
+ }
22
+
23
+ // Create context
24
+ export const I18nContext = createContext<I18nContextType>({
25
+ t: key => key,
26
+ locale: FALLBACK_LOCALE,
27
+ supportedLocales: [],
28
+ localeNames: {},
29
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
30
+ changeLocale: async () => {},
31
+ })
32
+
33
+ // Context provider component
34
+ export const I18nProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
35
+ const [locale, setLocale] = useState<string>(FALLBACK_LOCALE)
36
+ const [isInitialized, setIsInitialized] = useState(false)
37
+ const [error, setError] = useState<Error | null>(null)
38
+ const wallet = useWallet()
39
+
40
+ // Initialize i18n
41
+ useEffect(() => {
42
+ const initialize = async () => {
43
+ try {
44
+ let localeToUse = FALLBACK_LOCALE
45
+ // @ts-ignore
46
+ const userSelectedLanguage = localStorage.getItem('userSelectedLanguage') === 'true'
47
+
48
+ if (userSelectedLanguage) {
49
+ // @ts-ignore
50
+ const savedLocale = localStorage.getItem('i18nextLng')
51
+ if (savedLocale && getSupportedLocales().includes(savedLocale)) {
52
+ localeToUse = savedLocale
53
+ log.debug(`Using user selected language: ${savedLocale}`)
54
+ }
55
+ } else {
56
+ try {
57
+ const isFirstOpen = await wallet.getIsFirstOpen()
58
+
59
+ if (isFirstOpen) {
60
+ // @ts-ignore
61
+ const browserLang = navigator.language
62
+ log.debug(`New user - Browser language: ${browserLang}`)
63
+
64
+ const mappedLocale = BROWSER_TO_APP_LOCALE_MAP[browserLang]
65
+ if (mappedLocale && getSupportedLocales().includes(mappedLocale)) {
66
+ localeToUse = mappedLocale
67
+ log.debug(`Using mapped browser language: ${mappedLocale}`)
68
+ } else if (getSupportedLocales().includes(browserLang)) {
69
+ localeToUse = browserLang
70
+ log.debug(`Using browser language: ${browserLang}`)
71
+ } else {
72
+ const mainLang = browserLang.split('-')[0]
73
+ if (getSupportedLocales().includes(mainLang)) {
74
+ localeToUse = mainLang
75
+ log.debug(`Using browser main language: ${mainLang}`)
76
+ } else {
77
+ log.debug(`Browser language not supported, using default: ${FALLBACK_LOCALE}`)
78
+ localeToUse = FALLBACK_LOCALE
79
+ }
80
+ }
81
+ } else {
82
+ log.debug('Existing user - Using default English')
83
+ localeToUse = FALLBACK_LOCALE
84
+ }
85
+ } catch (error) {
86
+ log.error('Failed to get user status, using default language:', error)
87
+ localeToUse = FALLBACK_LOCALE
88
+ }
89
+ }
90
+
91
+ // @ts-ignore
92
+ localStorage.setItem('i18nextLng', localeToUse)
93
+ await initI18n(localeToUse)
94
+
95
+ chrome.storage.local.set({ i18nextLng: localeToUse })
96
+ await initI18n(localeToUse)
97
+ // const currentLocale = await getCurrentLocale();
98
+
99
+ // setLocale(currentLocale);
100
+ setIsInitialized(true)
101
+ } catch (error) {
102
+ log.error('Failed to initialize i18n:', error)
103
+
104
+ setLocale(FALLBACK_LOCALE)
105
+ setIsInitialized(true)
106
+ setError(error instanceof Error ? error : new Error('Unknown error'))
107
+ }
108
+ }
109
+
110
+ initialize()
111
+ }, [wallet])
112
+
113
+ // Change language
114
+ const changeLocale = async (newLocale: string) => {
115
+ try {
116
+ await changeLanguage(newLocale)
117
+ setLocale(newLocale)
118
+ // @ts-ignore
119
+ localStorage.setItem('userSelectedLanguage', 'true')
120
+ // @ts-ignore
121
+ localStorage.setItem('i18nextLng', newLocale)
122
+ chrome.storage.local.set({ i18nextLng: newLocale })
123
+ } catch (error) {
124
+ setError(error instanceof Error ? error : new Error('Unknown error'))
125
+ }
126
+ }
127
+
128
+ // Translation function
129
+ const t = (key: string, substitutions?: string | string[]) => {
130
+ try {
131
+ return translate(key, substitutions)
132
+ } catch (error) {
133
+ log.error(`Translation error for key "${key}":`, error)
134
+ return key
135
+ }
136
+ }
137
+
138
+ // If not yet initialized, show loading
139
+ if (!isInitialized) {
140
+ return (
141
+ <div
142
+ style={{
143
+ display: 'flex',
144
+ flexDirection: 'column',
145
+ width: '100vw',
146
+ height: '100vh',
147
+ overflowY: 'auto',
148
+ overflowX: 'hidden',
149
+ }}
150
+ >
151
+ <div
152
+ style={{
153
+ display: 'flex',
154
+ flex: 1,
155
+ flexDirection: 'column',
156
+ justifyItems: 'center',
157
+ alignItems: 'center',
158
+ }}
159
+ >
160
+ <div />
161
+ </div>
162
+ </div>
163
+ )
164
+ }
165
+
166
+ // If there is an error, show error message in development environment
167
+ // @ts-ignore
168
+ if (error && process.env.NODE_ENV === 'development') {
169
+ return (
170
+ <div style={{ color: 'red', padding: '20px' }}>
171
+ <h2>Error initializing i18n</h2>
172
+ <p>{error.message}</p>
173
+ <pre>{error.stack}</pre>
174
+ </div>
175
+ )
176
+ }
177
+
178
+ return (
179
+ <I18nContext.Provider
180
+ value={{
181
+ t,
182
+ locale,
183
+ supportedLocales: getSupportedLocales(),
184
+ localeNames: LOCALE_NAMES,
185
+ changeLocale,
186
+ }}
187
+ >
188
+ {children}
189
+ </I18nContext.Provider>
190
+ )
191
+ }
@@ -0,0 +1,81 @@
1
+ import { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react'
2
+
3
+ import { CoinPrice } from '@unisat/wallet-shared'
4
+ import { useWallet } from './WalletContext'
5
+
6
+ import { useChain, useChainType } from '../hooks/settings'
7
+
8
+ interface PriceContextType {
9
+ isLoadingCoinPrice: boolean
10
+ coinPrice: CoinPrice
11
+ refreshCoinPrice: () => void
12
+ }
13
+
14
+ const PriceContext = createContext<PriceContextType>({} as PriceContextType)
15
+
16
+ export function usePrice() {
17
+ const context = useContext(PriceContext)
18
+ if (!context) {
19
+ throw Error('Feature flag hooks can only be used by children of BridgeProvider.')
20
+ } else {
21
+ return context
22
+ }
23
+ }
24
+
25
+ let isRequestingCoinPrice = false
26
+ let refreshCoinPriceTime = 0
27
+
28
+ export function PriceProvider({ children }: { children: ReactNode }) {
29
+ const wallet = useWallet()
30
+ const chainType = useChainType()
31
+ const chain = useChain()
32
+ const [isLoadingCoinPrice, setIsLoadingCoinPrice] = useState(false)
33
+ const [coinPrice, setCoinPrice] = useState<CoinPrice>({
34
+ btc: 0,
35
+ fb: 0,
36
+ })
37
+
38
+ const refreshCoinPrice = useCallback(() => {
39
+ if (chain.showPrice === false) {
40
+ return
41
+ }
42
+ if (isRequestingCoinPrice) {
43
+ return
44
+ }
45
+ // 30s cache
46
+ if (Date.now() - refreshCoinPriceTime < 30 * 1000) {
47
+ return
48
+ }
49
+ isRequestingCoinPrice = true
50
+ setIsLoadingCoinPrice(true)
51
+
52
+ wallet
53
+ .getCoinPrice()
54
+ .then(res => {
55
+ refreshCoinPriceTime = Date.now()
56
+ setCoinPrice(res)
57
+ })
58
+ .catch(e => {
59
+ setCoinPrice({
60
+ btc: 0,
61
+ fb: 0,
62
+ })
63
+ })
64
+ .finally(() => {
65
+ setIsLoadingCoinPrice(false)
66
+ isRequestingCoinPrice = false
67
+ })
68
+ }, [chainType, chain])
69
+
70
+ useEffect(() => {
71
+ refreshCoinPrice()
72
+ }, [refreshCoinPrice])
73
+
74
+ const value = {
75
+ isLoadingCoinPrice,
76
+ coinPrice,
77
+ refreshCoinPrice,
78
+ }
79
+
80
+ return <PriceContext.Provider value={value}>{children}</PriceContext.Provider>
81
+ }