@oxy.so/services 11.4.0 → 11.5.0
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/lib/commonjs/index.js +0 -28
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/boot/runProviderColdBoot.js +47 -3
- package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyEmailAccountChoice.js +113 -0
- package/lib/commonjs/ui/components/signIn/OxyEmailAccountChoice.js.map +1 -0
- package/lib/commonjs/ui/components/signIn/OxySignInPanel.js +70 -3
- package/lib/commonjs/ui/components/signIn/OxySignInPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js +2 -0
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountMembersScreen.js +20 -63
- package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PreferencesScreen.js +3 -4
- package/lib/commonjs/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/module/index.js +0 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/boot/runProviderColdBoot.js +47 -3
- package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyEmailAccountChoice.js +110 -0
- package/lib/module/ui/components/signIn/OxyEmailAccountChoice.js.map +1 -0
- package/lib/module/ui/components/signIn/OxySignInPanel.js +71 -4
- package/lib/module/ui/components/signIn/OxySignInPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/accountFlowParts.js +2 -0
- package/lib/module/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/module/ui/screens/AccountMembersScreen.js +20 -63
- package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
- package/lib/module/ui/screens/PreferencesScreen.js +3 -4
- package/lib/module/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +0 -4
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyEmailAccountChoice.d.ts +20 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxyEmailAccountChoice.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts +1 -2
- package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/PreferencesScreen.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +0 -4
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyEmailAccountChoice.d.ts +20 -0
- package/lib/typescript/module/ui/components/signIn/OxyEmailAccountChoice.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts +2 -0
- package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts +1 -2
- package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/PreferencesScreen.d.ts.map +1 -1
- package/package.json +5 -6
- package/src/index.ts +0 -5
- package/src/ui/boot/__tests__/coldBootNoIdpRedirect.test.ts +166 -3
- package/src/ui/boot/runProviderColdBoot.ts +49 -4
- package/src/ui/components/signIn/OxyEmailAccountChoice.tsx +116 -0
- package/src/ui/components/signIn/OxySignInPanel.tsx +89 -4
- package/src/ui/components/signIn/accountFlowParts.tsx +2 -0
- package/src/ui/screens/AccountMembersScreen.tsx +27 -71
- package/src/ui/screens/PreferencesScreen.tsx +0 -2
- package/lib/commonjs/ui/components/AccountInvitationAcceptance.js +0 -97
- package/lib/commonjs/ui/components/AccountInvitationAcceptance.js.map +0 -1
- package/lib/commonjs/ui/components/DisplayCurrencySelector.js +0 -49
- package/lib/commonjs/ui/components/DisplayCurrencySelector.js.map +0 -1
- package/lib/commonjs/ui/hooks/useDisplayCurrency.js +0 -77
- package/lib/commonjs/ui/hooks/useDisplayCurrency.js.map +0 -1
- package/lib/commonjs/ui/hooks/useDisplayPrice.js +0 -59
- package/lib/commonjs/ui/hooks/useDisplayPrice.js.map +0 -1
- package/lib/module/ui/components/AccountInvitationAcceptance.js +0 -94
- package/lib/module/ui/components/AccountInvitationAcceptance.js.map +0 -1
- package/lib/module/ui/components/DisplayCurrencySelector.js +0 -44
- package/lib/module/ui/components/DisplayCurrencySelector.js.map +0 -1
- package/lib/module/ui/hooks/useDisplayCurrency.js +0 -72
- package/lib/module/ui/hooks/useDisplayCurrency.js.map +0 -1
- package/lib/module/ui/hooks/useDisplayPrice.js +0 -56
- package/lib/module/ui/hooks/useDisplayPrice.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountInvitationAcceptance.d.ts +0 -5
- package/lib/typescript/commonjs/ui/components/AccountInvitationAcceptance.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/DisplayCurrencySelector.d.ts +0 -3
- package/lib/typescript/commonjs/ui/components/DisplayCurrencySelector.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useDisplayCurrency.d.ts +0 -11
- package/lib/typescript/commonjs/ui/hooks/useDisplayCurrency.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useDisplayPrice.d.ts +0 -6
- package/lib/typescript/commonjs/ui/hooks/useDisplayPrice.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountInvitationAcceptance.d.ts +0 -5
- package/lib/typescript/module/ui/components/AccountInvitationAcceptance.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/DisplayCurrencySelector.d.ts +0 -3
- package/lib/typescript/module/ui/components/DisplayCurrencySelector.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useDisplayCurrency.d.ts +0 -11
- package/lib/typescript/module/ui/hooks/useDisplayCurrency.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useDisplayPrice.d.ts +0 -6
- package/lib/typescript/module/ui/hooks/useDisplayPrice.d.ts.map +0 -1
- package/src/ui/components/AccountInvitationAcceptance.tsx +0 -90
- package/src/ui/components/DisplayCurrencySelector.tsx +0 -38
- package/src/ui/components/__tests__/AccountInvitationAcceptance.test.tsx +0 -61
- package/src/ui/hooks/useDisplayCurrency.ts +0 -64
- package/src/ui/hooks/useDisplayPrice.ts +0 -40
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["React","View","SegmentedControl","SegmentedControlItem","SegmentedControlItemText","Text","DISPLAY_CURRENCIES","useDisplayCurrency","useI18n","jsx","_jsx","jsxs","_jsxs","DisplayCurrencySelector","currency","selectCurrency","isSaving","error","t","children","type","label","value","onValueChange","selected","find","item","map","disabled","accessibilityLiveRegion"],"sourceRoot":"../../../../src","sources":["ui/components/DisplayCurrencySelector.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,IAAI,QAAQ,cAAc;AACnC,SACEC,gBAAgB,EAChBC,oBAAoB,EACpBC,wBAAwB,QACnB,iCAAiC;AACxC,SAASC,IAAI,QAAQ,0BAA0B;AAC/C,SAASC,kBAAkB,EAAEC,kBAAkB,QAAQ,gCAA6B;AACpF,SAASC,OAAO,QAAQ,qBAAkB;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA;AAE3C,OAAO,SAASC,uBAAuBA,CAAA,EAAG;EACxC,MAAM;IAAEC,QAAQ;IAAEC,cAAc;IAAEC,QAAQ;IAAEC;EAAM,CAAC,GAAGV,kBAAkB,CAAC,CAAC;EAC1E,MAAM;IAAEW;EAAE,CAAC,GAAGV,OAAO,CAAC,CAAC;EACvB,oBACEI,KAAA,CAACX,IAAI;IAAAkB,QAAA,gBACHT,IAAA,CAACL,IAAI;MAAAc,QAAA,EAAED,CAAC,CAAC,mCAAmC;IAAC,CAAO,CAAC,eACrDR,IAAA,CAACR,gBAAgB;MACfkB,IAAI,EAAC,OAAO;MACZC,KAAK,EAAEH,CAAC,CAAC,mCAAmC,CAAE;MAC9CI,KAAK,EAAER,QAAS;MAChBS,aAAa,EAAGD,KAAK,IAAK;QACxB,MAAME,QAAQ,GAAGlB,kBAAkB,CAACmB,IAAI,CAAEC,IAAI,IAAKA,IAAI,KAAKJ,KAAK,CAAC;QAClE,IAAIE,QAAQ,IAAI,CAACR,QAAQ,EAAED,cAAc,CAACS,QAAQ,CAAC;MACrD,CAAE;MAAAL,QAAA,EAEDb,kBAAkB,CAACqB,GAAG,CAAEL,KAAK,iBAC5BZ,IAAA,CAACP,oBAAoB;QAAamB,KAAK,EAAEA,KAAM;QAACM,QAAQ,EAAEZ,QAAS;QAAAG,QAAA,eACjET,IAAA,CAACN,wBAAwB;UAAAe,QAAA,EAAEG;QAAK,CAA2B;MAAC,GADnCA,KAEL,CACvB;IAAC,CACc,CAAC,EAClBL,KAAK,iBACJP,IAAA,CAACL,IAAI;MAACwB,uBAAuB,EAAC,QAAQ;MAAAV,QAAA,EAAED,CAAC,CAAC,wCAAwC;IAAC,CAAO,CAC3F;EAAA,CACG,CAAC;AAEX","ignoreList":[]}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
4
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
5
|
-
import { authenticatedApiCall } from '@oxy.so/core';
|
|
6
|
-
import { useOxy } from "../context/OxyContext.js";
|
|
7
|
-
import { useCurrentUser } from "./queries/useAccountQueries.js";
|
|
8
|
-
import { queryKeys } from "./queries/queryKeys.js";
|
|
9
|
-
import { useAuthStore } from "../stores/authStore.js";
|
|
10
|
-
import { mutationKeys } from "./mutations/mutationKeys.js";
|
|
11
|
-
export const DISPLAY_CURRENCIES = ['USD', 'EUR', 'FAIR'];
|
|
12
|
-
let anonymousCurrency = 'USD';
|
|
13
|
-
const anonymousListeners = new Set();
|
|
14
|
-
const subscribeAnonymous = listener => {
|
|
15
|
-
anonymousListeners.add(listener);
|
|
16
|
-
return () => {
|
|
17
|
-
anonymousListeners.delete(listener);
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
const anonymousSnapshot = () => anonymousCurrency;
|
|
21
|
-
|
|
22
|
-
/** Account-scoped server preference. A late save can never replace another account. */
|
|
23
|
-
export function useDisplayCurrency() {
|
|
24
|
-
const {
|
|
25
|
-
oxyServices,
|
|
26
|
-
activeSessionId,
|
|
27
|
-
isAuthenticated,
|
|
28
|
-
user
|
|
29
|
-
} = useOxy();
|
|
30
|
-
const current = useCurrentUser({
|
|
31
|
-
enabled: isAuthenticated
|
|
32
|
-
});
|
|
33
|
-
const client = useQueryClient();
|
|
34
|
-
const anonymous = useSyncExternalStore(subscribeAnonymous, anonymousSnapshot, () => 'USD');
|
|
35
|
-
const currency = isAuthenticated ? current.data?.userPreferences?.displayCurrency ?? user?.userPreferences?.displayCurrency ?? 'USD' : anonymous;
|
|
36
|
-
const mutation = useMutation({
|
|
37
|
-
mutationKey: [...mutationKeys.account.updateSettings],
|
|
38
|
-
mutationFn: async input => {
|
|
39
|
-
const updated = await authenticatedApiCall(oxyServices, input.sessionId, () => oxyServices.users.updateMe({
|
|
40
|
-
userPreferences: {
|
|
41
|
-
displayCurrency: input.currency
|
|
42
|
-
}
|
|
43
|
-
}));
|
|
44
|
-
if (updated.id !== input.userId) throw new Error('Account changed. Please try again.');
|
|
45
|
-
return updated;
|
|
46
|
-
},
|
|
47
|
-
onSuccess: (updated, input) => {
|
|
48
|
-
client.setQueryData(queryKeys.accounts.current(input.sessionId), updated);
|
|
49
|
-
if (useAuthStore.getState().user?.id === input.userId) useAuthStore.getState().setUser(updated);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
function selectCurrency(next) {
|
|
53
|
-
if (!DISPLAY_CURRENCIES.includes(next)) return;
|
|
54
|
-
if (!isAuthenticated) {
|
|
55
|
-
anonymousCurrency = next;
|
|
56
|
-
for (const listener of anonymousListeners) listener();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (activeSessionId && user?.id) mutation.mutate({
|
|
60
|
-
currency: next,
|
|
61
|
-
sessionId: activeSessionId,
|
|
62
|
-
userId: user.id
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
currency,
|
|
67
|
-
selectCurrency,
|
|
68
|
-
isSaving: mutation.isPending,
|
|
69
|
-
error: mutation.error
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
//# sourceMappingURL=useDisplayCurrency.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["useSyncExternalStore","useMutation","useQueryClient","authenticatedApiCall","useOxy","useCurrentUser","queryKeys","useAuthStore","mutationKeys","DISPLAY_CURRENCIES","anonymousCurrency","anonymousListeners","Set","subscribeAnonymous","listener","add","delete","anonymousSnapshot","useDisplayCurrency","oxyServices","activeSessionId","isAuthenticated","user","current","enabled","client","anonymous","currency","data","userPreferences","displayCurrency","mutation","mutationKey","account","updateSettings","mutationFn","input","updated","sessionId","users","updateMe","id","userId","Error","onSuccess","setQueryData","accounts","getState","setUser","selectCurrency","next","includes","mutate","isSaving","isPending","error"],"sourceRoot":"../../../../src","sources":["ui/hooks/useDisplayCurrency.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { useQuery } from '@tanstack/react-query';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
5
|
-
import { convertDisplayAmount, readDisplayRates } from '@peable.to/sdk/display';
|
|
6
|
-
import { useDisplayCurrency } from "./useDisplayCurrency.js";
|
|
7
|
-
import { useI18n } from "./useI18n.js";
|
|
8
|
-
|
|
9
|
-
/** Display conversion only. Pricing and checkout request bodies retain their source units. */
|
|
10
|
-
export function useDisplayPrice() {
|
|
11
|
-
const {
|
|
12
|
-
currency
|
|
13
|
-
} = useDisplayCurrency();
|
|
14
|
-
const {
|
|
15
|
-
t
|
|
16
|
-
} = useI18n();
|
|
17
|
-
const [now, setNow] = useState(Date.now);
|
|
18
|
-
const rates = useQuery({
|
|
19
|
-
queryKey: ['display-currency', 'faircoin-indexed-price'],
|
|
20
|
-
queryFn: ({
|
|
21
|
-
signal
|
|
22
|
-
}) => readDisplayRates({
|
|
23
|
-
signal
|
|
24
|
-
}),
|
|
25
|
-
enabled: currency !== 'USD',
|
|
26
|
-
staleTime: 30_000,
|
|
27
|
-
gcTime: 0,
|
|
28
|
-
retry: false,
|
|
29
|
-
refetchInterval: 60_000,
|
|
30
|
-
refetchOnWindowFocus: 'always'
|
|
31
|
-
});
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const timer = setInterval(() => setNow(Date.now()), 1000);
|
|
34
|
-
return () => clearInterval(timer);
|
|
35
|
-
}, []);
|
|
36
|
-
function formatUsd(minorUnits) {
|
|
37
|
-
const display = convertDisplayAmount({
|
|
38
|
-
units: String(minorUnits),
|
|
39
|
-
currency: 'USD',
|
|
40
|
-
displayCurrency: currency,
|
|
41
|
-
rates: rates.isError ? null : rates.data,
|
|
42
|
-
now
|
|
43
|
-
});
|
|
44
|
-
if (!display) return t('preferences.displayCurrency.unavailable', {
|
|
45
|
-
currency
|
|
46
|
-
});
|
|
47
|
-
return currency === 'USD' ? display : t('preferences.displayCurrency.approximate', {
|
|
48
|
-
amount: display
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
currency,
|
|
53
|
-
formatUsd
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=useDisplayPrice.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["useQuery","useEffect","useState","convertDisplayAmount","readDisplayRates","useDisplayCurrency","useI18n","useDisplayPrice","currency","t","now","setNow","Date","rates","queryKey","queryFn","signal","enabled","staleTime","gcTime","retry","refetchInterval","refetchOnWindowFocus","timer","setInterval","clearInterval","formatUsd","minorUnits","display","units","String","displayCurrency","isError","data","amount"],"sourceRoot":"../../../../src","sources":["ui/hooks/useDisplayPrice.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,uBAAuB;AAChD,SAASC,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAC3C,SAASC,oBAAoB,EAAEC,gBAAgB,QAAQ,wBAAwB;AAC/E,SAASC,kBAAkB,QAAQ,yBAAsB;AACzD,SAASC,OAAO,QAAQ,cAAW;;AAEnC;AACA,OAAO,SAASC,eAAeA,CAAA,EAAG;EAChC,MAAM;IAAEC;EAAS,CAAC,GAAGH,kBAAkB,CAAC,CAAC;EACzC,MAAM;IAAEI;EAAE,CAAC,GAAGH,OAAO,CAAC,CAAC;EACvB,MAAM,CAACI,GAAG,EAAEC,MAAM,CAAC,GAAGT,QAAQ,CAACU,IAAI,CAACF,GAAG,CAAC;EACxC,MAAMG,KAAK,GAAGb,QAAQ,CAAC;IACrBc,QAAQ,EAAE,CAAC,kBAAkB,EAAE,wBAAwB,CAAC;IACxDC,OAAO,EAAEA,CAAC;MAAEC;IAAO,CAAC,KAAKZ,gBAAgB,CAAC;MAAEY;IAAO,CAAC,CAAC;IACrDC,OAAO,EAAET,QAAQ,KAAK,KAAK;IAC3BU,SAAS,EAAE,MAAM;IACjBC,MAAM,EAAE,CAAC;IACTC,KAAK,EAAE,KAAK;IACZC,eAAe,EAAE,MAAM;IACvBC,oBAAoB,EAAE;EACxB,CAAC,CAAC;EACFrB,SAAS,CAAC,MAAM;IACd,MAAMsB,KAAK,GAAGC,WAAW,CAAC,MAAMb,MAAM,CAACC,IAAI,CAACF,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;IACzD,OAAO,MAAMe,aAAa,CAACF,KAAK,CAAC;EACnC,CAAC,EAAE,EAAE,CAAC;EACN,SAASG,SAASA,CAACC,UAAkB,EAAE;IACrC,MAAMC,OAAO,GAAGzB,oBAAoB,CAAC;MACnC0B,KAAK,EAAEC,MAAM,CAACH,UAAU,CAAC;MACzBnB,QAAQ,EAAE,KAAK;MACfuB,eAAe,EAAEvB,QAAQ;MACzBK,KAAK,EAAEA,KAAK,CAACmB,OAAO,GAAG,IAAI,GAAGnB,KAAK,CAACoB,IAAI;MACxCvB;IACF,CAAC,CAAC;IACF,IAAI,CAACkB,OAAO,EAAE,OAAOnB,CAAC,CAAC,yCAAyC,EAAE;MAAED;IAAS,CAAC,CAAC;IAC/E,OAAOA,QAAQ,KAAK,KAAK,GACrBoB,OAAO,GACPnB,CAAC,CAAC,yCAAyC,EAAE;MAAEyB,MAAM,EAAEN;IAAQ,CAAC,CAAC;EACvE;EACA,OAAO;IAAEpB,QAAQ;IAAEkB;EAAU,CAAC;AAChC","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AccountInvitationAcceptance.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/AccountInvitationAcceptance.tsx"],"names":[],"mappings":"AAQA,yFAAyF;AACzF,wBAAgB,2BAA2B,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,+BAEvE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DisplayCurrencySelector.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/DisplayCurrencySelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAW1B,wBAAgB,uBAAuB,sBA0BtC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type UserPreferences } from '@oxy.so/core';
|
|
2
|
-
export type DisplayCurrency = NonNullable<UserPreferences['displayCurrency']>;
|
|
3
|
-
export declare const DISPLAY_CURRENCIES: readonly DisplayCurrency[];
|
|
4
|
-
/** Account-scoped server preference. A late save can never replace another account. */
|
|
5
|
-
export declare function useDisplayCurrency(): {
|
|
6
|
-
currency: DisplayCurrency;
|
|
7
|
-
selectCurrency: (next: DisplayCurrency) => void;
|
|
8
|
-
isSaving: boolean;
|
|
9
|
-
error: Error | null;
|
|
10
|
-
};
|
|
11
|
-
//# sourceMappingURL=useDisplayCurrency.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDisplayCurrency.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useDisplayCurrency.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAO1E,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAC9E,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EAA2B,CAAC;AAYrF,uFAAuF;AACvF,wBAAgB,kBAAkB;;2BA6BF,eAAe;;;EAW9C"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** Display conversion only. Pricing and checkout request bodies retain their source units. */
|
|
2
|
-
export declare function useDisplayPrice(): {
|
|
3
|
-
currency: import("./useDisplayCurrency").DisplayCurrency;
|
|
4
|
-
formatUsd: (minorUnits: number) => string;
|
|
5
|
-
};
|
|
6
|
-
//# sourceMappingURL=useDisplayPrice.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDisplayPrice.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useDisplayPrice.ts"],"names":[],"mappings":"AAMA,8FAA8F;AAC9F,wBAAgB,eAAe;;4BAkBE,MAAM;EActC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AccountInvitationAcceptance.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/AccountInvitationAcceptance.tsx"],"names":[],"mappings":"AAQA,yFAAyF;AACzF,wBAAgB,2BAA2B,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,+BAEvE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DisplayCurrencySelector.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/DisplayCurrencySelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAW1B,wBAAgB,uBAAuB,sBA0BtC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type UserPreferences } from '@oxy.so/core';
|
|
2
|
-
export type DisplayCurrency = NonNullable<UserPreferences['displayCurrency']>;
|
|
3
|
-
export declare const DISPLAY_CURRENCIES: readonly DisplayCurrency[];
|
|
4
|
-
/** Account-scoped server preference. A late save can never replace another account. */
|
|
5
|
-
export declare function useDisplayCurrency(): {
|
|
6
|
-
currency: DisplayCurrency;
|
|
7
|
-
selectCurrency: (next: DisplayCurrency) => void;
|
|
8
|
-
isSaving: boolean;
|
|
9
|
-
error: Error | null;
|
|
10
|
-
};
|
|
11
|
-
//# sourceMappingURL=useDisplayCurrency.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDisplayCurrency.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useDisplayCurrency.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAO1E,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAC9E,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EAA2B,CAAC;AAYrF,uFAAuF;AACvF,wBAAgB,kBAAkB;;2BA6BF,eAAe;;;EAW9C"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** Display conversion only. Pricing and checkout request bodies retain their source units. */
|
|
2
|
-
export declare function useDisplayPrice(): {
|
|
3
|
-
currency: import("./useDisplayCurrency.js").DisplayCurrency;
|
|
4
|
-
formatUsd: (minorUnits: number) => string;
|
|
5
|
-
};
|
|
6
|
-
//# sourceMappingURL=useDisplayPrice.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDisplayPrice.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useDisplayPrice.ts"],"names":[],"mappings":"AAMA,8FAA8F;AAC9F,wBAAgB,eAAe;;4BAkBE,MAAM;EActC"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
3
|
-
import { Text } from '@oxy.so/bloom/typography';
|
|
4
|
-
import { Button } from '@oxy.so/bloom/button';
|
|
5
|
-
import type { AccountInvitationPreview } from '@oxy.so/core';
|
|
6
|
-
import { getNormalizedUserHandle } from '@oxy.so/core';
|
|
7
|
-
import { useOxy } from '../context/OxyContext';
|
|
8
|
-
|
|
9
|
-
/** The link never grants access on navigation, preview, sign-in or account switching. */
|
|
10
|
-
export function AccountInvitationAcceptance({ token }: { token: string }) {
|
|
11
|
-
return <InvitationContent key={token} token={token} />;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function InvitationContent({ token }: { token: string }) {
|
|
15
|
-
const { oxyServices, user, canUsePrivateApi, openAccountDialog } = useOxy();
|
|
16
|
-
const [preview, setPreview] = useState<AccountInvitationPreview | null>(null);
|
|
17
|
-
const [error, setError] = useState('');
|
|
18
|
-
const [busy, setBusy] = useState(false);
|
|
19
|
-
const [accepted, setAccepted] = useState(false);
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
let alive = true;
|
|
22
|
-
setPreview(null);
|
|
23
|
-
setError('');
|
|
24
|
-
if (canUsePrivateApi && token) {
|
|
25
|
-
oxyServices.accounts.invitations
|
|
26
|
-
.preview(token)
|
|
27
|
-
.then((value) => {
|
|
28
|
-
if (alive) setPreview(value);
|
|
29
|
-
})
|
|
30
|
-
.catch((reason) => {
|
|
31
|
-
if (alive) setError(reason instanceof Error ? reason.message : 'Invitation unavailable');
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return () => {
|
|
35
|
-
alive = false;
|
|
36
|
-
};
|
|
37
|
-
}, [oxyServices, canUsePrivateApi, token]);
|
|
38
|
-
|
|
39
|
-
const accept = async () => {
|
|
40
|
-
if (!user || !preview) return;
|
|
41
|
-
setBusy(true);
|
|
42
|
-
setError('');
|
|
43
|
-
try {
|
|
44
|
-
await oxyServices.accounts.invitations.accept(token, user.id);
|
|
45
|
-
setAccepted(true);
|
|
46
|
-
} catch (reason) {
|
|
47
|
-
setError(reason instanceof Error ? reason.message : 'Could not accept invitation');
|
|
48
|
-
} finally {
|
|
49
|
-
setBusy(false);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
return (
|
|
53
|
-
<View className="gap-space-16 p-space-24">
|
|
54
|
-
<Text className="text-title font-semibold text-text">Account invitation</Text>
|
|
55
|
-
{canUsePrivateApi && token && !preview && !error ? (
|
|
56
|
-
<Text accessibilityRole="progressbar">Loading invitation…</Text>
|
|
57
|
-
) : null}
|
|
58
|
-
{accepted ? (
|
|
59
|
-
<Text>Invitation accepted. You can now access this account.</Text>
|
|
60
|
-
) : (
|
|
61
|
-
<>
|
|
62
|
-
{preview ? (
|
|
63
|
-
<Text>
|
|
64
|
-
Join {preview.account.displayName ?? preview.account.username} as{' '}
|
|
65
|
-
{preview.invitation.role}.
|
|
66
|
-
</Text>
|
|
67
|
-
) : null}
|
|
68
|
-
<Text>
|
|
69
|
-
Selected account:{' '}
|
|
70
|
-
{user
|
|
71
|
-
? `${user.name?.displayName ?? getNormalizedUserHandle(user) ?? user.id} (${user.email ?? user.id})`
|
|
72
|
-
: 'Not signed in'}
|
|
73
|
-
</Text>
|
|
74
|
-
<Text>
|
|
75
|
-
The selected account receives access. Its email can differ from the invitation address.
|
|
76
|
-
No access is granted until you confirm.
|
|
77
|
-
</Text>
|
|
78
|
-
<Button disabled={busy} onPress={() => openAccountDialog()}>
|
|
79
|
-
{user ? 'Change account' : 'Sign in or create account'}
|
|
80
|
-
</Button>
|
|
81
|
-
<Button disabled={busy || !user || !preview || user.kind !== 'personal'} onPress={accept}>
|
|
82
|
-
Confirm and accept
|
|
83
|
-
</Button>
|
|
84
|
-
</>
|
|
85
|
-
)}
|
|
86
|
-
{error ? <Text accessibilityRole="alert">{error}</Text> : null}
|
|
87
|
-
{!token ? <Text>This invitation link is incomplete.</Text> : null}
|
|
88
|
-
</View>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
SegmentedControl,
|
|
5
|
-
SegmentedControlItem,
|
|
6
|
-
SegmentedControlItemText,
|
|
7
|
-
} from '@oxy.so/bloom/segmented-control';
|
|
8
|
-
import { Text } from '@oxy.so/bloom/typography';
|
|
9
|
-
import { DISPLAY_CURRENCIES, useDisplayCurrency } from '../hooks/useDisplayCurrency';
|
|
10
|
-
import { useI18n } from '../hooks/useI18n';
|
|
11
|
-
|
|
12
|
-
export function DisplayCurrencySelector() {
|
|
13
|
-
const { currency, selectCurrency, isSaving, error } = useDisplayCurrency();
|
|
14
|
-
const { t } = useI18n();
|
|
15
|
-
return (
|
|
16
|
-
<View>
|
|
17
|
-
<Text>{t('preferences.displayCurrency.label')}</Text>
|
|
18
|
-
<SegmentedControl
|
|
19
|
-
type="radio"
|
|
20
|
-
label={t('preferences.displayCurrency.label')}
|
|
21
|
-
value={currency}
|
|
22
|
-
onValueChange={(value) => {
|
|
23
|
-
const selected = DISPLAY_CURRENCIES.find((item) => item === value);
|
|
24
|
-
if (selected && !isSaving) selectCurrency(selected);
|
|
25
|
-
}}
|
|
26
|
-
>
|
|
27
|
-
{DISPLAY_CURRENCIES.map((value) => (
|
|
28
|
-
<SegmentedControlItem key={value} value={value} disabled={isSaving}>
|
|
29
|
-
<SegmentedControlItemText>{value}</SegmentedControlItemText>
|
|
30
|
-
</SegmentedControlItem>
|
|
31
|
-
))}
|
|
32
|
-
</SegmentedControl>
|
|
33
|
-
{error && (
|
|
34
|
-
<Text accessibilityLiveRegion="polite">{t('preferences.displayCurrency.saveFailed')}</Text>
|
|
35
|
-
)}
|
|
36
|
-
</View>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
3
|
-
import { AccountInvitationAcceptance } from '../AccountInvitationAcceptance';
|
|
4
|
-
const mockPreview = jest.fn();
|
|
5
|
-
const mockAccept = jest.fn();
|
|
6
|
-
const mockSwitch = jest.fn();
|
|
7
|
-
let mockUser: { id: string; kind: string; email: string; username: string } | null = null;
|
|
8
|
-
const mockServices = { accounts: { invitations: { preview: mockPreview, accept: mockAccept } } };
|
|
9
|
-
jest.mock('../../context/OxyContext', () => ({
|
|
10
|
-
useOxy: () => ({
|
|
11
|
-
oxyServices: mockServices,
|
|
12
|
-
user: mockUser,
|
|
13
|
-
canUsePrivateApi: !!mockUser,
|
|
14
|
-
openAccountDialog: mockSwitch,
|
|
15
|
-
}),
|
|
16
|
-
}));
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
jest.clearAllMocks();
|
|
19
|
-
mockUser = null;
|
|
20
|
-
mockPreview.mockResolvedValue({
|
|
21
|
-
invitation: { role: 'editor' },
|
|
22
|
-
account: { displayName: 'Community' },
|
|
23
|
-
});
|
|
24
|
-
mockAccept.mockResolvedValue({ success: true });
|
|
25
|
-
});
|
|
26
|
-
test('sign-in and switching never accept; confirmation names the displayed selected account', async () => {
|
|
27
|
-
const { rerender } = render(<AccountInvitationAcceptance token="secret" />);
|
|
28
|
-
fireEvent.click(screen.getByText('Sign in or create account'));
|
|
29
|
-
expect(mockSwitch).toHaveBeenCalled();
|
|
30
|
-
expect(mockAccept).not.toHaveBeenCalled();
|
|
31
|
-
mockUser = { id: 'first', kind: 'personal', username: 'first', email: 'different@example.com' };
|
|
32
|
-
rerender(<AccountInvitationAcceptance token="secret" />);
|
|
33
|
-
await screen.findByText('Join Community as editor.');
|
|
34
|
-
fireEvent.click(screen.getByText('Change account'));
|
|
35
|
-
expect(mockAccept).not.toHaveBeenCalled();
|
|
36
|
-
mockUser = { id: 'second', kind: 'personal', username: 'second', email: 'second@example.com' };
|
|
37
|
-
rerender(<AccountInvitationAcceptance token="secret" />);
|
|
38
|
-
expect(screen.getByText(/Selected account: second/)).toBeTruthy();
|
|
39
|
-
fireEvent.click(screen.getByText('Confirm and accept'));
|
|
40
|
-
await waitFor(() => expect(mockAccept).toHaveBeenCalledWith('secret', 'second'));
|
|
41
|
-
await screen.findByText('Invitation accepted. You can now access this account.');
|
|
42
|
-
});
|
|
43
|
-
test('a new link resets previous acceptance and does not auto-accept', async () => {
|
|
44
|
-
mockUser = { id: 'first', kind: 'personal', username: 'first', email: 'first@example.com' };
|
|
45
|
-
const { rerender } = render(<AccountInvitationAcceptance token="old" />);
|
|
46
|
-
await screen.findByText('Join Community as editor.');
|
|
47
|
-
fireEvent.click(screen.getByText('Confirm and accept'));
|
|
48
|
-
await screen.findByText('Invitation accepted. You can now access this account.');
|
|
49
|
-
rerender(<AccountInvitationAcceptance token="new" />);
|
|
50
|
-
await screen.findByText('Join Community as editor.');
|
|
51
|
-
expect(screen.queryByText('Invitation accepted. You can now access this account.')).toBeNull();
|
|
52
|
-
expect(mockAccept).toHaveBeenCalledTimes(1);
|
|
53
|
-
});
|
|
54
|
-
test('preview failure leaves confirmation disabled', async () => {
|
|
55
|
-
mockUser = { id: 'first', kind: 'personal', username: 'first', email: 'first@example.com' };
|
|
56
|
-
mockPreview.mockRejectedValue(new Error('Invitation expired'));
|
|
57
|
-
render(<AccountInvitationAcceptance token="expired" />);
|
|
58
|
-
await screen.findByText('Invitation expired');
|
|
59
|
-
fireEvent.click(screen.getByText('Confirm and accept'));
|
|
60
|
-
expect(mockAccept).not.toHaveBeenCalled();
|
|
61
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { useSyncExternalStore } from 'react';
|
|
2
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
3
|
-
import { authenticatedApiCall, type UserPreferences } from '@oxy.so/core';
|
|
4
|
-
import { useOxy } from '../context/OxyContext';
|
|
5
|
-
import { useCurrentUser } from './queries/useAccountQueries';
|
|
6
|
-
import { queryKeys } from './queries/queryKeys';
|
|
7
|
-
import { useAuthStore } from '../stores/authStore';
|
|
8
|
-
import { mutationKeys } from './mutations/mutationKeys';
|
|
9
|
-
|
|
10
|
-
export type DisplayCurrency = NonNullable<UserPreferences['displayCurrency']>;
|
|
11
|
-
export const DISPLAY_CURRENCIES: readonly DisplayCurrency[] = ['USD', 'EUR', 'FAIR'];
|
|
12
|
-
|
|
13
|
-
let anonymousCurrency: DisplayCurrency = 'USD';
|
|
14
|
-
const anonymousListeners = new Set<() => void>();
|
|
15
|
-
const subscribeAnonymous = (listener: () => void) => {
|
|
16
|
-
anonymousListeners.add(listener);
|
|
17
|
-
return () => {
|
|
18
|
-
anonymousListeners.delete(listener);
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
const anonymousSnapshot = () => anonymousCurrency;
|
|
22
|
-
|
|
23
|
-
/** Account-scoped server preference. A late save can never replace another account. */
|
|
24
|
-
export function useDisplayCurrency() {
|
|
25
|
-
const { oxyServices, activeSessionId, isAuthenticated, user } = useOxy();
|
|
26
|
-
const current = useCurrentUser({ enabled: isAuthenticated });
|
|
27
|
-
const client = useQueryClient();
|
|
28
|
-
const anonymous = useSyncExternalStore(
|
|
29
|
-
subscribeAnonymous,
|
|
30
|
-
anonymousSnapshot,
|
|
31
|
-
() => 'USD' as const,
|
|
32
|
-
);
|
|
33
|
-
const currency = isAuthenticated
|
|
34
|
-
? (current.data?.userPreferences?.displayCurrency ??
|
|
35
|
-
user?.userPreferences?.displayCurrency ??
|
|
36
|
-
'USD')
|
|
37
|
-
: anonymous;
|
|
38
|
-
const mutation = useMutation({
|
|
39
|
-
mutationKey: [...mutationKeys.account.updateSettings],
|
|
40
|
-
mutationFn: async (input: { currency: DisplayCurrency; sessionId: string; userId: string }) => {
|
|
41
|
-
const updated = await authenticatedApiCall(oxyServices, input.sessionId, () =>
|
|
42
|
-
oxyServices.users.updateMe({ userPreferences: { displayCurrency: input.currency } }),
|
|
43
|
-
);
|
|
44
|
-
if (updated.id !== input.userId) throw new Error('Account changed. Please try again.');
|
|
45
|
-
return updated;
|
|
46
|
-
},
|
|
47
|
-
onSuccess: (updated, input) => {
|
|
48
|
-
client.setQueryData(queryKeys.accounts.current(input.sessionId), updated);
|
|
49
|
-
if (useAuthStore.getState().user?.id === input.userId)
|
|
50
|
-
useAuthStore.getState().setUser(updated);
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
function selectCurrency(next: DisplayCurrency) {
|
|
54
|
-
if (!DISPLAY_CURRENCIES.includes(next)) return;
|
|
55
|
-
if (!isAuthenticated) {
|
|
56
|
-
anonymousCurrency = next;
|
|
57
|
-
for (const listener of anonymousListeners) listener();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (activeSessionId && user?.id)
|
|
61
|
-
mutation.mutate({ currency: next, sessionId: activeSessionId, userId: user.id });
|
|
62
|
-
}
|
|
63
|
-
return { currency, selectCurrency, isSaving: mutation.isPending, error: mutation.error };
|
|
64
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { convertDisplayAmount, readDisplayRates } from '@peable.to/sdk/display';
|
|
4
|
-
import { useDisplayCurrency } from './useDisplayCurrency';
|
|
5
|
-
import { useI18n } from './useI18n';
|
|
6
|
-
|
|
7
|
-
/** Display conversion only. Pricing and checkout request bodies retain their source units. */
|
|
8
|
-
export function useDisplayPrice() {
|
|
9
|
-
const { currency } = useDisplayCurrency();
|
|
10
|
-
const { t } = useI18n();
|
|
11
|
-
const [now, setNow] = useState(Date.now);
|
|
12
|
-
const rates = useQuery({
|
|
13
|
-
queryKey: ['display-currency', 'faircoin-indexed-price'],
|
|
14
|
-
queryFn: ({ signal }) => readDisplayRates({ signal }),
|
|
15
|
-
enabled: currency !== 'USD',
|
|
16
|
-
staleTime: 30_000,
|
|
17
|
-
gcTime: 0,
|
|
18
|
-
retry: false,
|
|
19
|
-
refetchInterval: 60_000,
|
|
20
|
-
refetchOnWindowFocus: 'always',
|
|
21
|
-
});
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const timer = setInterval(() => setNow(Date.now()), 1000);
|
|
24
|
-
return () => clearInterval(timer);
|
|
25
|
-
}, []);
|
|
26
|
-
function formatUsd(minorUnits: number) {
|
|
27
|
-
const display = convertDisplayAmount({
|
|
28
|
-
units: String(minorUnits),
|
|
29
|
-
currency: 'USD',
|
|
30
|
-
displayCurrency: currency,
|
|
31
|
-
rates: rates.isError ? null : rates.data,
|
|
32
|
-
now,
|
|
33
|
-
});
|
|
34
|
-
if (!display) return t('preferences.displayCurrency.unavailable', { currency });
|
|
35
|
-
return currency === 'USD'
|
|
36
|
-
? display
|
|
37
|
-
: t('preferences.displayCurrency.approximate', { amount: display });
|
|
38
|
-
}
|
|
39
|
-
return { currency, formatUsd };
|
|
40
|
-
}
|