@b3dotfun/sdk 0.0.42-alpha.2 → 0.0.42-alpha.3
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/dist/cjs/global-account/react/components/index.d.ts +8 -7
- package/dist/cjs/global-account/react/components/index.js +29 -23
- package/dist/cjs/global-account/react/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/cjs/global-account/react/components/ui/dropdown-menu.js +100 -0
- package/dist/cjs/shared/constants/currency.d.ts +1 -0
- package/dist/cjs/shared/constants/currency.js +5 -0
- package/dist/cjs/shared/constants/index.d.ts +1 -0
- package/dist/cjs/shared/constants/index.js +15 -0
- package/dist/cjs/shared/react/components/CurrencySelector.d.ts +7 -0
- package/dist/cjs/shared/react/components/CurrencySelector.js +14 -0
- package/dist/cjs/shared/react/components/FormattedCurrency.d.ts +12 -0
- package/dist/cjs/shared/react/components/FormattedCurrency.js +60 -0
- package/dist/cjs/shared/react/components/index.d.ts +2 -0
- package/dist/cjs/shared/react/components/index.js +18 -0
- package/dist/cjs/shared/react/hooks/__tests__/useCurrencyConversion.test.d.ts +1 -0
- package/dist/cjs/shared/react/hooks/__tests__/useCurrencyConversion.test.js +245 -0
- package/dist/cjs/shared/react/hooks/index.d.ts +1 -0
- package/dist/cjs/shared/react/hooks/index.js +1 -0
- package/dist/cjs/shared/react/hooks/useCurrencyConversion.d.ts +35 -0
- package/dist/cjs/shared/react/hooks/useCurrencyConversion.js +200 -0
- package/dist/cjs/shared/react/index.d.ts +2 -0
- package/dist/cjs/shared/react/index.js +2 -0
- package/dist/cjs/shared/react/stores/currencyModalStore.d.ts +7 -0
- package/dist/cjs/shared/react/stores/currencyModalStore.js +9 -0
- package/dist/cjs/shared/react/stores/currencyStore.d.ts +51 -0
- package/dist/cjs/shared/react/stores/currencyStore.js +57 -0
- package/dist/cjs/shared/react/stores/index.d.ts +2 -0
- package/dist/cjs/shared/react/stores/index.js +18 -0
- package/dist/esm/global-account/react/components/index.d.ts +8 -7
- package/dist/esm/global-account/react/components/index.js +8 -7
- package/dist/esm/global-account/react/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/esm/global-account/react/components/ui/dropdown-menu.js +60 -0
- package/dist/esm/shared/constants/currency.d.ts +1 -0
- package/dist/esm/shared/constants/currency.js +2 -0
- package/dist/esm/shared/constants/index.d.ts +1 -0
- package/dist/esm/shared/constants/index.js +1 -0
- package/dist/esm/shared/react/components/CurrencySelector.d.ts +7 -0
- package/dist/esm/shared/react/components/CurrencySelector.js +11 -0
- package/dist/esm/shared/react/components/FormattedCurrency.d.ts +12 -0
- package/dist/esm/shared/react/components/FormattedCurrency.js +57 -0
- package/dist/esm/shared/react/components/index.d.ts +2 -0
- package/dist/esm/shared/react/components/index.js +2 -0
- package/dist/esm/shared/react/hooks/__tests__/useCurrencyConversion.test.d.ts +1 -0
- package/dist/esm/shared/react/hooks/__tests__/useCurrencyConversion.test.js +243 -0
- package/dist/esm/shared/react/hooks/index.d.ts +1 -0
- package/dist/esm/shared/react/hooks/index.js +1 -0
- package/dist/esm/shared/react/hooks/useCurrencyConversion.d.ts +35 -0
- package/dist/esm/shared/react/hooks/useCurrencyConversion.js +197 -0
- package/dist/esm/shared/react/index.d.ts +2 -0
- package/dist/esm/shared/react/index.js +2 -0
- package/dist/esm/shared/react/stores/currencyModalStore.d.ts +7 -0
- package/dist/esm/shared/react/stores/currencyModalStore.js +6 -0
- package/dist/esm/shared/react/stores/currencyStore.d.ts +51 -0
- package/dist/esm/shared/react/stores/currencyStore.js +54 -0
- package/dist/esm/shared/react/stores/index.d.ts +2 -0
- package/dist/esm/shared/react/stores/index.js +2 -0
- package/dist/styles/index.css +1 -1
- package/dist/types/global-account/react/components/index.d.ts +8 -7
- package/dist/types/global-account/react/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/types/shared/constants/currency.d.ts +1 -0
- package/dist/types/shared/constants/index.d.ts +1 -0
- package/dist/types/shared/react/components/CurrencySelector.d.ts +7 -0
- package/dist/types/shared/react/components/FormattedCurrency.d.ts +12 -0
- package/dist/types/shared/react/components/index.d.ts +2 -0
- package/dist/types/shared/react/hooks/__tests__/useCurrencyConversion.test.d.ts +1 -0
- package/dist/types/shared/react/hooks/index.d.ts +1 -0
- package/dist/types/shared/react/hooks/useCurrencyConversion.d.ts +35 -0
- package/dist/types/shared/react/index.d.ts +2 -0
- package/dist/types/shared/react/stores/currencyModalStore.d.ts +7 -0
- package/dist/types/shared/react/stores/currencyStore.d.ts +51 -0
- package/dist/types/shared/react/stores/index.d.ts +2 -0
- package/package.json +29 -3
- package/src/global-account/react/components/index.ts +19 -12
- package/src/global-account/react/components/ui/dropdown-menu.tsx +132 -0
- package/src/shared/constants/currency.ts +2 -0
- package/src/shared/constants/index.ts +2 -0
- package/src/shared/react/components/CurrencySelector.tsx +71 -0
- package/src/shared/react/components/FormattedCurrency.tsx +106 -0
- package/src/shared/react/components/index.ts +2 -0
- package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +308 -0
- package/src/shared/react/hooks/index.ts +1 -0
- package/src/shared/react/hooks/useCurrencyConversion.ts +211 -0
- package/src/shared/react/index.ts +2 -0
- package/src/shared/react/stores/currencyModalStore.ts +13 -0
- package/src/shared/react/stores/currencyStore.ts +82 -0
- package/src/shared/react/stores/index.ts +2 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { renderHook } from "@testing-library/react";
|
|
3
|
+
import { useCurrencyConversion } from "../useCurrencyConversion";
|
|
4
|
+
|
|
5
|
+
// Mock the external dependencies
|
|
6
|
+
// Store mock rates for different quote currencies
|
|
7
|
+
const mockRates: Record<string, number | undefined> = {};
|
|
8
|
+
|
|
9
|
+
// Mock store state
|
|
10
|
+
const mockStoreState: any = {
|
|
11
|
+
selectedCurrency: "B3",
|
|
12
|
+
baseCurrency: "B3",
|
|
13
|
+
setSelectedCurrency: vi.fn(),
|
|
14
|
+
setBaseCurrency: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
vi.mock("@b3dotfun/sdk/global-account/react", () => ({
|
|
18
|
+
useExchangeRate: vi.fn((params: any) => {
|
|
19
|
+
const rate = mockRates[params?.quoteCurrency];
|
|
20
|
+
return { rate };
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("@b3dotfun/sdk/shared/utils/number", () => ({
|
|
25
|
+
formatDisplayNumber: vi.fn((value: any) => {
|
|
26
|
+
const num = Number(value);
|
|
27
|
+
if (isNaN(num)) return "0";
|
|
28
|
+
return num.toLocaleString("en-US", { maximumFractionDigits: 6 });
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock("../../stores/currencyStore", () => ({
|
|
33
|
+
useCurrencyStore: vi.fn((selector: any) => {
|
|
34
|
+
if (selector) {
|
|
35
|
+
return selector(mockStoreState);
|
|
36
|
+
}
|
|
37
|
+
return mockStoreState;
|
|
38
|
+
}),
|
|
39
|
+
CURRENCY_SYMBOLS: {
|
|
40
|
+
B3: "B3",
|
|
41
|
+
USD: "$",
|
|
42
|
+
EUR: "€",
|
|
43
|
+
GBP: "£",
|
|
44
|
+
JPY: "¥",
|
|
45
|
+
CAD: "C$",
|
|
46
|
+
AUD: "A$",
|
|
47
|
+
ETH: "ETH",
|
|
48
|
+
SOL: "SOL",
|
|
49
|
+
KRW: "₩",
|
|
50
|
+
},
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
describe("useCurrencyConversion", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
// Reset mock rates to default
|
|
57
|
+
Object.keys(mockRates).forEach(key => delete mockRates[key]);
|
|
58
|
+
mockRates.USD = 1.0;
|
|
59
|
+
// Reset store state
|
|
60
|
+
mockStoreState.selectedCurrency = "B3";
|
|
61
|
+
mockStoreState.baseCurrency = "B3";
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("formatCurrencyValue", () => {
|
|
65
|
+
it("should format base currency (B3) without conversion", () => {
|
|
66
|
+
mockStoreState.selectedCurrency = "B3";
|
|
67
|
+
mockStoreState.baseCurrency = "B3";
|
|
68
|
+
|
|
69
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
70
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
71
|
+
|
|
72
|
+
expect(formatted).toContain("B3");
|
|
73
|
+
expect(formatted).toContain("100");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should show base currency when exchange rate is unavailable", () => {
|
|
77
|
+
mockRates.USD = undefined;
|
|
78
|
+
mockStoreState.selectedCurrency = "USD";
|
|
79
|
+
mockStoreState.baseCurrency = "B3";
|
|
80
|
+
|
|
81
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
82
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
83
|
+
|
|
84
|
+
expect(formatted).toContain("B3");
|
|
85
|
+
expect(formatted).not.toContain("$");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should format USD with prefix symbol", () => {
|
|
89
|
+
mockRates.USD = 2.0;
|
|
90
|
+
mockStoreState.selectedCurrency = "USD";
|
|
91
|
+
mockStoreState.baseCurrency = "B3";
|
|
92
|
+
|
|
93
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
94
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
95
|
+
|
|
96
|
+
expect(formatted).toMatch(/^\$/);
|
|
97
|
+
expect(formatted).toContain("200");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should format EUR with prefix symbol", () => {
|
|
101
|
+
mockRates.EUR = 1.8;
|
|
102
|
+
mockRates.USD = 2.0;
|
|
103
|
+
mockStoreState.selectedCurrency = "EUR";
|
|
104
|
+
mockStoreState.baseCurrency = "B3";
|
|
105
|
+
|
|
106
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
107
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
108
|
+
|
|
109
|
+
expect(formatted).toMatch(/^€/);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should format JPY without decimals", () => {
|
|
113
|
+
mockRates.JPY = 150;
|
|
114
|
+
mockRates.USD = 2.0;
|
|
115
|
+
mockStoreState.selectedCurrency = "JPY";
|
|
116
|
+
mockStoreState.baseCurrency = "B3";
|
|
117
|
+
|
|
118
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
119
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
120
|
+
|
|
121
|
+
expect(formatted).toContain("¥");
|
|
122
|
+
expect(formatted).not.toContain(".");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should format KRW without decimals", () => {
|
|
126
|
+
mockRates.KRW = 1300;
|
|
127
|
+
mockRates.USD = 2.0;
|
|
128
|
+
mockStoreState.selectedCurrency = "KRW";
|
|
129
|
+
mockStoreState.baseCurrency = "B3";
|
|
130
|
+
|
|
131
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
132
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
133
|
+
|
|
134
|
+
expect(formatted).toContain("₩");
|
|
135
|
+
expect(formatted).not.toContain(".");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should format ETH with suffix symbol", () => {
|
|
139
|
+
mockRates.ETH = 0.0005;
|
|
140
|
+
mockRates.USD = 2.0;
|
|
141
|
+
mockStoreState.selectedCurrency = "ETH";
|
|
142
|
+
mockStoreState.baseCurrency = "B3";
|
|
143
|
+
|
|
144
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
145
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
146
|
+
|
|
147
|
+
expect(formatted).toContain("ETH");
|
|
148
|
+
expect(formatted).not.toMatch(/^ETH/);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should format SOL with suffix symbol", () => {
|
|
152
|
+
mockRates.SOL = 0.05;
|
|
153
|
+
mockRates.USD = 2.0;
|
|
154
|
+
mockStoreState.selectedCurrency = "SOL";
|
|
155
|
+
mockStoreState.baseCurrency = "B3";
|
|
156
|
+
|
|
157
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
158
|
+
const formatted = result.current.formatCurrencyValue(100);
|
|
159
|
+
|
|
160
|
+
expect(formatted).toContain("SOL");
|
|
161
|
+
expect(formatted).not.toMatch(/^SOL/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should handle small USD amounts with proper conversion", () => {
|
|
165
|
+
mockRates.USD = 1.5;
|
|
166
|
+
mockStoreState.selectedCurrency = "USD";
|
|
167
|
+
mockStoreState.baseCurrency = "B3";
|
|
168
|
+
|
|
169
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
170
|
+
const formatted = result.current.formatCurrencyValue(10);
|
|
171
|
+
|
|
172
|
+
// 10 * 1.5 = 15
|
|
173
|
+
expect(formatted).toMatch(/^\$/);
|
|
174
|
+
expect(formatted).toContain("15");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should apply correct exchange rate conversion", () => {
|
|
178
|
+
const testRate = 3.5;
|
|
179
|
+
mockRates.USD = testRate;
|
|
180
|
+
mockStoreState.selectedCurrency = "USD";
|
|
181
|
+
mockStoreState.baseCurrency = "B3";
|
|
182
|
+
|
|
183
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
184
|
+
const inputValue = 100;
|
|
185
|
+
const formatted = result.current.formatCurrencyValue(inputValue);
|
|
186
|
+
|
|
187
|
+
expect(formatted).toContain("350");
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("return values", () => {
|
|
192
|
+
it("should return selected currency", () => {
|
|
193
|
+
mockStoreState.selectedCurrency = "USD";
|
|
194
|
+
mockStoreState.baseCurrency = "B3";
|
|
195
|
+
|
|
196
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
197
|
+
|
|
198
|
+
expect(result.current.selectedCurrency).toBe("USD");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should return base currency", () => {
|
|
202
|
+
mockStoreState.selectedCurrency = "USD";
|
|
203
|
+
mockStoreState.baseCurrency = "B3";
|
|
204
|
+
|
|
205
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
206
|
+
|
|
207
|
+
expect(result.current.baseCurrency).toBe("B3");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should return exchange rate", () => {
|
|
211
|
+
const testRate = 2.5;
|
|
212
|
+
mockRates.USD = testRate;
|
|
213
|
+
mockStoreState.selectedCurrency = "USD";
|
|
214
|
+
mockStoreState.baseCurrency = "B3";
|
|
215
|
+
|
|
216
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
217
|
+
|
|
218
|
+
expect(result.current.exchangeRate).toBe(testRate);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should return correct currency symbols", () => {
|
|
222
|
+
mockStoreState.selectedCurrency = "EUR";
|
|
223
|
+
mockStoreState.baseCurrency = "B3";
|
|
224
|
+
|
|
225
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
226
|
+
|
|
227
|
+
expect(result.current.selectedCurrencySymbol).toBe("€");
|
|
228
|
+
expect(result.current.baseCurrencySymbol).toBe("B3");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("formatTooltipValue", () => {
|
|
233
|
+
it("should show USD equivalent when displaying base currency", () => {
|
|
234
|
+
mockRates.USD = 1.5;
|
|
235
|
+
mockStoreState.selectedCurrency = "B3";
|
|
236
|
+
mockStoreState.baseCurrency = "B3";
|
|
237
|
+
|
|
238
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
239
|
+
const tooltip = result.current.formatTooltipValue(100);
|
|
240
|
+
|
|
241
|
+
expect(tooltip).toContain("USD");
|
|
242
|
+
expect(tooltip).toContain("150");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should show base currency when displaying other currency", () => {
|
|
246
|
+
mockRates.EUR = 0.9;
|
|
247
|
+
mockRates.USD = 1.2;
|
|
248
|
+
mockStoreState.selectedCurrency = "EUR";
|
|
249
|
+
mockStoreState.baseCurrency = "B3";
|
|
250
|
+
|
|
251
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
252
|
+
const tooltip = result.current.formatTooltipValue(100);
|
|
253
|
+
|
|
254
|
+
expect(tooltip).toContain("B3");
|
|
255
|
+
expect(tooltip).toContain("100");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should handle custom currency for base currency", () => {
|
|
259
|
+
mockRates.USD = 2.0;
|
|
260
|
+
mockRates.EUR = 1.8;
|
|
261
|
+
mockStoreState.selectedCurrency = "EUR";
|
|
262
|
+
mockStoreState.baseCurrency = "B3";
|
|
263
|
+
|
|
264
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
265
|
+
const tooltip = result.current.formatTooltipValue(100, "B3");
|
|
266
|
+
|
|
267
|
+
expect(tooltip).toContain("USD");
|
|
268
|
+
expect(tooltip).toContain("200");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("should handle custom currency for non-base currency", () => {
|
|
272
|
+
mockRates.USD = 2.0;
|
|
273
|
+
mockStoreState.selectedCurrency = "USD";
|
|
274
|
+
mockStoreState.baseCurrency = "B3";
|
|
275
|
+
|
|
276
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
277
|
+
const tooltip = result.current.formatTooltipValue(50, "ETH");
|
|
278
|
+
|
|
279
|
+
expect(tooltip).toContain("ETH");
|
|
280
|
+
expect(tooltip).toContain("50");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should handle absolute values for negative amounts", () => {
|
|
284
|
+
mockRates.USD = 1.5;
|
|
285
|
+
mockStoreState.selectedCurrency = "B3";
|
|
286
|
+
mockStoreState.baseCurrency = "B3";
|
|
287
|
+
|
|
288
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
289
|
+
const tooltip = result.current.formatTooltipValue(-100);
|
|
290
|
+
|
|
291
|
+
expect(tooltip).toContain("USD");
|
|
292
|
+
expect(tooltip).toContain("150");
|
|
293
|
+
expect(tooltip).not.toContain("-");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("should handle exchange rate unavailable", () => {
|
|
297
|
+
mockRates.USD = undefined;
|
|
298
|
+
mockStoreState.selectedCurrency = "B3";
|
|
299
|
+
mockStoreState.baseCurrency = "B3";
|
|
300
|
+
|
|
301
|
+
const { result } = renderHook(() => useCurrencyConversion());
|
|
302
|
+
const tooltip = result.current.formatTooltipValue(100);
|
|
303
|
+
|
|
304
|
+
expect(tooltip).toContain("USD");
|
|
305
|
+
expect(tooltip).toContain("100");
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { useExchangeRate } from "@b3dotfun/sdk/global-account/react";
|
|
2
|
+
import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
|
|
3
|
+
import { CURRENCY_SYMBOLS, useCurrencyStore } from "../stores/currencyStore";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook for currency conversion and formatting with real-time exchange rates.
|
|
7
|
+
*
|
|
8
|
+
* This hook provides currency conversion functionality using live exchange rates
|
|
9
|
+
* and formats values according to currency-specific rules (decimals, symbols, etc.).
|
|
10
|
+
*
|
|
11
|
+
* @returns Currency conversion utilities and state
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* function PriceDisplay({ amount }: { amount: number }) {
|
|
16
|
+
* const { formatCurrencyValue, selectedCurrency } = useCurrencyConversion();
|
|
17
|
+
* return <div>{formatCurrencyValue(amount)}</div>;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function useCurrencyConversion() {
|
|
22
|
+
const selectedCurrency = useCurrencyStore(state => state.selectedCurrency);
|
|
23
|
+
const baseCurrency = useCurrencyStore(state => state.baseCurrency);
|
|
24
|
+
|
|
25
|
+
// Get exchange rate for the selected currency
|
|
26
|
+
const { rate: exchangeRate } = useExchangeRate({
|
|
27
|
+
baseCurrency,
|
|
28
|
+
quoteCurrency: selectedCurrency === baseCurrency ? "USD" : selectedCurrency,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Always fetch USD rate for tooltip purposes
|
|
32
|
+
const { rate: usdRate } = useExchangeRate({
|
|
33
|
+
baseCurrency,
|
|
34
|
+
quoteCurrency: "USD",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Formats a numeric value as a currency string with proper conversion and formatting.
|
|
39
|
+
*
|
|
40
|
+
* Behavior:
|
|
41
|
+
* - When exchange rate is unavailable, displays value in base currency
|
|
42
|
+
* - Applies currency-specific formatting rules:
|
|
43
|
+
* - JPY/KRW: No decimal places
|
|
44
|
+
* - ETH/SOL: 6 significant digits with subscript notation for small values
|
|
45
|
+
* - Fiat (USD/EUR/GBP/CAD/AUD): 2 decimal places minimum for values < 1000
|
|
46
|
+
* - Handles symbol positioning (prefix for fiat, suffix for crypto)
|
|
47
|
+
*
|
|
48
|
+
* @param value - The numeric value to format (in base currency)
|
|
49
|
+
* @param options - Optional formatting overrides
|
|
50
|
+
* @param options.decimals - Override number of decimal places
|
|
51
|
+
* @param options.currency - Override currency (bypasses conversion)
|
|
52
|
+
* @returns Formatted currency string with appropriate symbol and decimal places
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* formatCurrencyValue(100) // Returns "$100.00" if USD is selected
|
|
57
|
+
* formatCurrencyValue(0.0001) // Returns "0.0₄1 ETH" if ETH is selected
|
|
58
|
+
* formatCurrencyValue(1500) // Returns "¥1,500" if JPY is selected
|
|
59
|
+
* formatCurrencyValue(100, { decimals: 4, currency: "ETH" }) // Returns "100.0000 ETH"
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
const formatCurrencyValue = (value: number, options?: { decimals?: number; currency?: string }): string => {
|
|
63
|
+
const overrideCurrency = options?.currency;
|
|
64
|
+
const overrideDecimals = options?.decimals;
|
|
65
|
+
|
|
66
|
+
// Custom currency provided - bypass conversion and use simple formatting
|
|
67
|
+
if (overrideCurrency) {
|
|
68
|
+
const decimalsToUse = overrideDecimals !== undefined ? overrideDecimals : overrideCurrency === "B3" ? 0 : 2;
|
|
69
|
+
|
|
70
|
+
const formatted = formatDisplayNumber(value, {
|
|
71
|
+
fractionDigits: decimalsToUse,
|
|
72
|
+
showSubscripts: false,
|
|
73
|
+
});
|
|
74
|
+
return `${formatted} ${overrideCurrency}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Custom decimals for base currency without conversion
|
|
78
|
+
if (overrideDecimals !== undefined && selectedCurrency === baseCurrency) {
|
|
79
|
+
const formatted = formatDisplayNumber(value, {
|
|
80
|
+
fractionDigits: overrideDecimals,
|
|
81
|
+
showSubscripts: false,
|
|
82
|
+
});
|
|
83
|
+
return `${formatted} ${baseCurrency}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If no exchange rate available, show base currency to prevent showing
|
|
87
|
+
// incorrect values with wrong currency symbols during rate fetching
|
|
88
|
+
if (selectedCurrency === baseCurrency || !exchangeRate) {
|
|
89
|
+
const formatted = formatDisplayNumber(value, {
|
|
90
|
+
significantDigits: baseCurrency === "B3" ? 6 : 8,
|
|
91
|
+
showSubscripts: true,
|
|
92
|
+
});
|
|
93
|
+
return `${formatted} ${baseCurrency}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Convert value using current exchange rate
|
|
97
|
+
const convertedValue = value * exchangeRate;
|
|
98
|
+
const symbol = CURRENCY_SYMBOLS[selectedCurrency];
|
|
99
|
+
|
|
100
|
+
// Currencies that display symbol before the number (e.g., $100.00)
|
|
101
|
+
const prefixCurrencies = ["USD", "EUR", "GBP", "CAD", "AUD"];
|
|
102
|
+
|
|
103
|
+
let formatted: string;
|
|
104
|
+
|
|
105
|
+
if (selectedCurrency === "JPY" || selectedCurrency === "KRW") {
|
|
106
|
+
// Japanese Yen and Korean Won don't use decimal places
|
|
107
|
+
formatted = formatDisplayNumber(convertedValue, {
|
|
108
|
+
fractionDigits: 0,
|
|
109
|
+
showSubscripts: false,
|
|
110
|
+
});
|
|
111
|
+
} else if (selectedCurrency === "ETH" || selectedCurrency === "SOL") {
|
|
112
|
+
// Crypto currencies use more precision and subscript notation
|
|
113
|
+
// for very small amounts (e.g., 0.0₃45 ETH)
|
|
114
|
+
formatted = formatDisplayNumber(convertedValue, {
|
|
115
|
+
significantDigits: 6,
|
|
116
|
+
showSubscripts: true,
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
// Standard fiat currencies (USD, EUR, GBP, CAD, AUD)
|
|
120
|
+
// Use 2 decimal places minimum for amounts under 1000
|
|
121
|
+
formatted = formatDisplayNumber(convertedValue, {
|
|
122
|
+
significantDigits: 6,
|
|
123
|
+
fractionDigits: convertedValue < 1000 ? 2 : undefined,
|
|
124
|
+
showSubscripts: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Apply currency symbol with correct positioning
|
|
129
|
+
if (prefixCurrencies.includes(selectedCurrency)) {
|
|
130
|
+
return `${symbol}${formatted}`;
|
|
131
|
+
} else {
|
|
132
|
+
// Suffix currencies: JPY, KRW, ETH, SOL, B3
|
|
133
|
+
return `${formatted} ${symbol}`;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Formats a tooltip value showing the alternate currency representation.
|
|
139
|
+
*
|
|
140
|
+
* Behavior:
|
|
141
|
+
* - When displaying base currency: Shows USD equivalent
|
|
142
|
+
* - When displaying other currency: Shows base currency equivalent
|
|
143
|
+
* - For custom currencies: Shows appropriate conversion or original value
|
|
144
|
+
*
|
|
145
|
+
* @param value - The numeric value to format
|
|
146
|
+
* @param customCurrency - Optional custom currency override
|
|
147
|
+
* @returns Formatted tooltip string
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```tsx
|
|
151
|
+
* formatTooltipValue(100) // Returns "$150.00 USD" if displaying B3 with rate 1.5
|
|
152
|
+
* formatTooltipValue(100, "ETH") // Returns "100.0000 ETH" if custom currency
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
const formatTooltipValue = (value: number, customCurrency?: string): string => {
|
|
156
|
+
const displayCurrency = customCurrency || selectedCurrency;
|
|
157
|
+
const absoluteValue = Math.abs(value);
|
|
158
|
+
|
|
159
|
+
// Custom currency provided
|
|
160
|
+
if (customCurrency) {
|
|
161
|
+
if (customCurrency === baseCurrency) {
|
|
162
|
+
// Show USD equivalent for base currency using USD rate
|
|
163
|
+
const usdValue = usdRate ? absoluteValue * usdRate : absoluteValue;
|
|
164
|
+
const formatted = formatDisplayNumber(usdValue, {
|
|
165
|
+
significantDigits: 6,
|
|
166
|
+
fractionDigits: usdValue < 1000 ? 2 : undefined,
|
|
167
|
+
showSubscripts: true,
|
|
168
|
+
});
|
|
169
|
+
return `$${formatted} USD`;
|
|
170
|
+
} else {
|
|
171
|
+
// Show as-is for other custom currencies
|
|
172
|
+
return `${formatDisplayNumber(absoluteValue, { significantDigits: 6 })} ${customCurrency}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Showing base currency - display USD equivalent
|
|
177
|
+
if (displayCurrency === baseCurrency) {
|
|
178
|
+
const usdValue = usdRate ? absoluteValue * usdRate : absoluteValue;
|
|
179
|
+
const formatted = formatDisplayNumber(usdValue, {
|
|
180
|
+
significantDigits: 6,
|
|
181
|
+
fractionDigits: usdValue < 1000 ? 2 : undefined,
|
|
182
|
+
showSubscripts: true,
|
|
183
|
+
});
|
|
184
|
+
return `$${formatted} USD`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Showing other currency - display base currency equivalent
|
|
188
|
+
const formatted = formatDisplayNumber(absoluteValue, {
|
|
189
|
+
significantDigits: baseCurrency === "B3" ? 6 : 8,
|
|
190
|
+
showSubscripts: true,
|
|
191
|
+
});
|
|
192
|
+
return `${formatted} ${baseCurrency}`;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
/** Currently selected display currency */
|
|
197
|
+
selectedCurrency,
|
|
198
|
+
/** Base currency used for conversion (typically B3) */
|
|
199
|
+
baseCurrency,
|
|
200
|
+
/** Current exchange rate from base to selected currency (undefined while loading) */
|
|
201
|
+
exchangeRate,
|
|
202
|
+
/** Format a value with currency conversion and proper symbol/decimal handling */
|
|
203
|
+
formatCurrencyValue,
|
|
204
|
+
/** Format a tooltip value showing alternate currency representation */
|
|
205
|
+
formatTooltipValue,
|
|
206
|
+
/** Symbol for the currently selected currency (e.g., "$", "€", "ETH") */
|
|
207
|
+
selectedCurrencySymbol: CURRENCY_SYMBOLS[selectedCurrency],
|
|
208
|
+
/** Symbol for the base currency */
|
|
209
|
+
baseCurrencySymbol: CURRENCY_SYMBOLS[baseCurrency],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
interface CurrencyModalState {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
openModal: () => void;
|
|
6
|
+
closeModal: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const useCurrencyModalStore = create<CurrencyModalState>(set => ({
|
|
10
|
+
isOpen: false,
|
|
11
|
+
openModal: () => set({ isOpen: true }),
|
|
12
|
+
closeModal: () => set({ isOpen: false }),
|
|
13
|
+
}));
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { persist } from "zustand/middleware";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Supported currencies for display and conversion.
|
|
6
|
+
* Includes fiat currencies (USD, EUR, GBP, JPY, CAD, AUD, KRW) and crypto (ETH, SOL, B3).
|
|
7
|
+
*/
|
|
8
|
+
export type SupportedCurrency = "ETH" | "USD" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD" | "B3" | "SOL" | "KRW";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Currency symbols used for display formatting.
|
|
12
|
+
* Prefix currencies (USD, EUR, GBP, CAD, AUD) show symbol before the amount.
|
|
13
|
+
* Suffix currencies (JPY, KRW, ETH, SOL, B3) show symbol after the amount.
|
|
14
|
+
*/
|
|
15
|
+
export const CURRENCY_SYMBOLS: Record<SupportedCurrency, string> = {
|
|
16
|
+
ETH: "ETH",
|
|
17
|
+
USD: "$",
|
|
18
|
+
EUR: "€",
|
|
19
|
+
GBP: "£",
|
|
20
|
+
JPY: "¥",
|
|
21
|
+
CAD: "C$",
|
|
22
|
+
AUD: "A$",
|
|
23
|
+
B3: "B3",
|
|
24
|
+
SOL: "SOL",
|
|
25
|
+
KRW: "₩",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Human-readable currency names for display in selectors and labels.
|
|
30
|
+
*/
|
|
31
|
+
export const CURRENCY_NAMES: Record<SupportedCurrency, string> = {
|
|
32
|
+
ETH: "Ethereum",
|
|
33
|
+
USD: "US Dollar",
|
|
34
|
+
EUR: "Euro",
|
|
35
|
+
GBP: "British Pound",
|
|
36
|
+
JPY: "Japanese Yen",
|
|
37
|
+
CAD: "Canadian Dollar",
|
|
38
|
+
AUD: "Australian Dollar",
|
|
39
|
+
B3: "B3",
|
|
40
|
+
SOL: "Solana",
|
|
41
|
+
KRW: "Korean Won",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Currency store state interface.
|
|
46
|
+
* @property selectedCurrency - The currency currently selected for display
|
|
47
|
+
* @property baseCurrency - The base currency for conversion (typically B3)
|
|
48
|
+
* @property setSelectedCurrency - Update the selected display currency
|
|
49
|
+
* @property setBaseCurrency - Update the base currency for conversions
|
|
50
|
+
*/
|
|
51
|
+
interface CurrencyState {
|
|
52
|
+
selectedCurrency: SupportedCurrency;
|
|
53
|
+
baseCurrency: SupportedCurrency;
|
|
54
|
+
setSelectedCurrency: (currency: SupportedCurrency) => void;
|
|
55
|
+
setBaseCurrency: (currency: SupportedCurrency) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Zustand store for managing currency selection and conversion.
|
|
60
|
+
* Persists user's selected currency preference in localStorage.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
|
|
65
|
+
* // Change display currency to USD
|
|
66
|
+
* setSelectedCurrency('USD');
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const useCurrencyStore = create<CurrencyState>()(
|
|
70
|
+
persist(
|
|
71
|
+
set => ({
|
|
72
|
+
selectedCurrency: "B3",
|
|
73
|
+
baseCurrency: "B3",
|
|
74
|
+
setSelectedCurrency: currency => set({ selectedCurrency: currency }),
|
|
75
|
+
setBaseCurrency: currency => set({ baseCurrency: currency }),
|
|
76
|
+
}),
|
|
77
|
+
{
|
|
78
|
+
name: "currency-storage",
|
|
79
|
+
version: 2,
|
|
80
|
+
},
|
|
81
|
+
),
|
|
82
|
+
);
|