terms and privacy on exchange selection
This commit is contained in:
parent
6acddd6d70
commit
b011c8a32e
@ -31,15 +31,13 @@ import {
|
||||
|
||||
export interface Props {
|
||||
exchangeUrl: string;
|
||||
onChange: (v: boolean) => void;
|
||||
readOnly?: boolean;
|
||||
onChange?: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export type State =
|
||||
| State.Loading
|
||||
| State.LoadingUriError
|
||||
| State.ErrorAccepting
|
||||
| State.ShowContent
|
||||
| State.ShowButtonsAccepted
|
||||
| State.ShowButtonsNotAccepted
|
||||
| State.ShowContent;
|
||||
@ -62,21 +60,21 @@ export namespace State {
|
||||
|
||||
export interface BaseInfo {
|
||||
error: undefined;
|
||||
termsAccepted: ToggleHandler;
|
||||
showingTermsOfService: ToggleHandler;
|
||||
terms: TermsState;
|
||||
}
|
||||
export interface ShowContent extends BaseInfo {
|
||||
status: "show-content";
|
||||
error: undefined;
|
||||
termsAccepted?: ToggleHandler;
|
||||
showingTermsOfService?: ToggleHandler;
|
||||
}
|
||||
export interface ShowButtonsAccepted extends BaseInfo {
|
||||
status: "show-buttons-accepted";
|
||||
error: undefined;
|
||||
termsAccepted: ToggleHandler;
|
||||
showingTermsOfService: ToggleHandler;
|
||||
}
|
||||
export interface ShowButtonsNotAccepted extends BaseInfo {
|
||||
status: "show-buttons-not-accepted";
|
||||
error: undefined;
|
||||
showingTermsOfService: ToggleHandler;
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ import { Props, State } from "./index.js";
|
||||
import { buildTermsOfServiceState } from "./utils.js";
|
||||
|
||||
export function useComponentState(
|
||||
{ exchangeUrl, readOnly, onChange }: Props,
|
||||
{ exchangeUrl, onChange }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const [showContent, setShowContent] = useState<boolean>(false);
|
||||
// const [accepted, setAccepted] = useState<boolean>(false);
|
||||
const readOnly = !onChange;
|
||||
const [showContent, setShowContent] = useState<boolean>(readOnly);
|
||||
const [errorAccepting, setErrorAccepting] = useState<Error | undefined>(
|
||||
undefined,
|
||||
);
|
||||
@ -78,7 +78,7 @@ export function useComponentState(
|
||||
await api.setExchangeTosAccepted(exchangeUrl, undefined);
|
||||
}
|
||||
// setAccepted(accepted);
|
||||
onChange(accepted); //external update
|
||||
if (!readOnly) onChange(accepted); //external update
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
//FIXME: uncomment this and display error
|
||||
@ -90,14 +90,12 @@ export function useComponentState(
|
||||
|
||||
const accepted = state.status === "accepted";
|
||||
|
||||
const base: State.BaseInfo = {
|
||||
const base = {
|
||||
error: undefined,
|
||||
showingTermsOfService: {
|
||||
value: showContent,
|
||||
button: {
|
||||
onClick: readOnly
|
||||
? undefined
|
||||
: async () => {
|
||||
onClick: async () => {
|
||||
setShowContent(!showContent);
|
||||
},
|
||||
},
|
||||
@ -118,7 +116,10 @@ export function useComponentState(
|
||||
if (showContent) {
|
||||
return {
|
||||
status: "show-content",
|
||||
...base,
|
||||
error: undefined,
|
||||
terms: state,
|
||||
showingTermsOfService: readOnly ? undefined : base.showingTermsOfService,
|
||||
termsAccepted: readOnly ? undefined : base.termsAccepted,
|
||||
};
|
||||
}
|
||||
//showing buttons
|
@ -97,7 +97,7 @@ export type TermsState = {
|
||||
|
||||
type TermsStatus = "new" | "accepted" | "changed" | "notfound";
|
||||
|
||||
type TermsDocument =
|
||||
export type TermsDocument =
|
||||
| TermsDocumentXml
|
||||
| TermsDocumentHtml
|
||||
| TermsDocumentPlain
|
@ -17,7 +17,7 @@
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { TermsState } from "./utils.js";
|
||||
import { TermsDocument, TermsState } from "./utils.js";
|
||||
import { State } from "./index.js";
|
||||
import { CheckboxOutlined } from "../../components/CheckboxOutlined.js";
|
||||
import {
|
||||
@ -90,7 +90,6 @@ export function ShowButtonsAcceptedTosView({
|
||||
}
|
||||
|
||||
export function ShowButtonsNonAcceptedTosView({
|
||||
termsAccepted,
|
||||
showingTermsOfService,
|
||||
terms,
|
||||
}: State.ShowButtonsNotAccepted): VNode {
|
||||
@ -160,7 +159,7 @@ export function ShowTosContentView({
|
||||
}: State.ShowContent): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const ableToReviewTermsOfService =
|
||||
showingTermsOfService.button.onClick !== undefined;
|
||||
showingTermsOfService?.button.onClick !== undefined;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@ -195,7 +194,7 @@ export function ShowTosContentView({
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
{termsAccepted && ableToReviewTermsOfService && (
|
||||
{showingTermsOfService && ableToReviewTermsOfService && (
|
||||
<section style={{ justifyContent: "space-around", display: "flex" }}>
|
||||
<LinkSuccess
|
||||
upperCased
|
||||
@ -205,7 +204,7 @@ export function ShowTosContentView({
|
||||
</LinkSuccess>
|
||||
</section>
|
||||
)}
|
||||
{terms.status !== "notfound" && (
|
||||
{termsAccepted && terms.status !== "notfound" && (
|
||||
<section style={{ justifyContent: "space-around", display: "flex" }}>
|
||||
<CheckboxOutlined
|
||||
name="terms"
|
@ -23,5 +23,6 @@ import * as a1 from "./Banner.stories.js";
|
||||
import * as a2 from "./PendingTransactions.stories.js";
|
||||
import * as a3 from "./Amount.stories.js";
|
||||
import * as a4 from "./ShowFullContractTermPopup.stories.js";
|
||||
import * as a5 from "./TermsOfService/stories.js";
|
||||
|
||||
export default [a1, a2, a3, a4];
|
||||
export default [a1, a2, a3, a4, a5];
|
||||
|
@ -35,7 +35,7 @@ import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import editIcon from "../../svg/edit_24px.svg";
|
||||
import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
|
||||
import { TermsOfService } from "../TermsOfService/index.js";
|
||||
import { TermsOfService } from "../../components/TermsOfService/index.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
|
@ -24,10 +24,9 @@ import * as a3 from "./Payment/stories.jsx";
|
||||
import * as a4 from "./Refund/stories.jsx";
|
||||
import * as a5 from "./Tip/stories.jsx";
|
||||
import * as a6 from "./Withdraw/stories.jsx";
|
||||
import * as a7 from "./TermsOfService/stories.js";
|
||||
import * as a8 from "./InvoiceCreate/stories.js";
|
||||
import * as a9 from "./InvoicePay/stories.js";
|
||||
import * as a10 from "./TransferCreate/stories.js";
|
||||
import * as a11 from "./TransferPickup/stories.js";
|
||||
|
||||
export default [a1, a3, a4, a5, a6, a7, a8, a9, a10, a11];
|
||||
export default [a1, a3, a4, a5, a6, a8, a9, a10, a11];
|
||||
|
@ -300,20 +300,29 @@ function openWalletPageFromPopup(page: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
async function sendMessageToWalletBackground(
|
||||
operation: string,
|
||||
payload: any,
|
||||
): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
logger.trace("send operation to the wallet background", operation);
|
||||
chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => {
|
||||
chrome.runtime.sendMessage(
|
||||
{ operation, payload, id: `id_${i++ % 1000}` },
|
||||
(backgroundResponse) => {
|
||||
console.log("BUG: got response from background", backgroundResponse);
|
||||
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError.message);
|
||||
}
|
||||
resolve(resp);
|
||||
// const apiResponse = JSON.parse(resp)
|
||||
resolve(backgroundResponse);
|
||||
|
||||
// return true to keep the channel open
|
||||
return true;
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -364,7 +373,10 @@ function listenToAllChannels(
|
||||
) => void,
|
||||
): void {
|
||||
chrome.runtime.onMessage.addListener((m, s, c) => {
|
||||
cb(m, s, c);
|
||||
cb(m, s, (apiResponse) => {
|
||||
console.log("BUG: sending response to client", apiResponse);
|
||||
c(apiResponse);
|
||||
});
|
||||
|
||||
// keep the connection open
|
||||
return true;
|
||||
|
@ -16,11 +16,9 @@
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { Title } from "../components/styled/index.js";
|
||||
import { TermsOfService } from "../components/TermsOfService/index.js";
|
||||
import { useTranslationContext } from "../context/translation.js";
|
||||
import { TermsOfService } from "../cta/TermsOfService/index.js";
|
||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
||||
import { Button } from "../mui/Button.js";
|
||||
import * as wxApi from "../wxApi.js";
|
||||
|
||||
export interface Props {
|
||||
url: string;
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
FeeDescriptionPair,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Loading } from "../../components/Loading.js";
|
||||
import { TermsState } from "../../components/TermsOfService/utils.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
|
||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
||||
@ -31,7 +32,9 @@ import {
|
||||
ComparingView,
|
||||
ErrorLoadingView,
|
||||
NoExchangesView,
|
||||
PrivacyContentView,
|
||||
ReadyView,
|
||||
TosContentView,
|
||||
} from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
@ -46,6 +49,8 @@ export type State =
|
||||
| State.LoadingUriError
|
||||
| State.Ready
|
||||
| State.Comparing
|
||||
| State.ShowingTos
|
||||
| State.ShowingPrivacy
|
||||
| SelectExchangeState.NoExchange;
|
||||
|
||||
export namespace State {
|
||||
@ -63,6 +68,8 @@ export namespace State {
|
||||
exchanges: SelectFieldHandler;
|
||||
selected: ExchangeFullDetails;
|
||||
error: undefined;
|
||||
onShowTerms: ButtonHandler;
|
||||
onShowPrivacy: ButtonHandler;
|
||||
}
|
||||
|
||||
export interface Ready extends BaseInfo {
|
||||
@ -76,6 +83,16 @@ export namespace State {
|
||||
onReset: ButtonHandler;
|
||||
onSelect: ButtonHandler;
|
||||
}
|
||||
export interface ShowingTos {
|
||||
status: "showing-tos";
|
||||
exchangeUrl: string;
|
||||
onClose: ButtonHandler;
|
||||
}
|
||||
export interface ShowingPrivacy {
|
||||
status: "showing-privacy";
|
||||
exchangeUrl: string;
|
||||
onClose: ButtonHandler;
|
||||
}
|
||||
}
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
@ -83,6 +100,8 @@ const viewMapping: StateViewMap<State> = {
|
||||
"error-loading": ErrorLoadingView,
|
||||
comparing: ComparingView,
|
||||
"no-exchange": NoExchangesView,
|
||||
"showing-tos": TosContentView,
|
||||
"showing-privacy": PrivacyContentView,
|
||||
ready: ReadyView,
|
||||
};
|
||||
|
||||
|
@ -50,9 +50,15 @@ export function useComponentState(
|
||||
const original = !initialExchange
|
||||
? undefined
|
||||
: await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl);
|
||||
|
||||
return { exchanges, selected, original };
|
||||
}, [value]);
|
||||
|
||||
const [showingTos, setShowingTos] = useState<string | undefined>(undefined);
|
||||
const [showingPrivacy, setShowingPrivacy] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
@ -82,6 +88,27 @@ export function useComponentState(
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
if (showingPrivacy) {
|
||||
return {
|
||||
status: "showing-privacy",
|
||||
error: undefined,
|
||||
onClose: {
|
||||
onClick: async () => setShowingPrivacy(undefined),
|
||||
},
|
||||
exchangeUrl: showingPrivacy,
|
||||
};
|
||||
}
|
||||
if (showingTos) {
|
||||
return {
|
||||
status: "showing-tos",
|
||||
error: undefined,
|
||||
onClose: {
|
||||
onClick: async () => setShowingTos(undefined),
|
||||
},
|
||||
exchangeUrl: showingTos,
|
||||
};
|
||||
}
|
||||
|
||||
if (!original) {
|
||||
// !original <=> selected == original
|
||||
return {
|
||||
@ -98,6 +125,16 @@ export function useComponentState(
|
||||
onClick: onCancel,
|
||||
},
|
||||
selected,
|
||||
onShowPrivacy: {
|
||||
onClick: async () => {
|
||||
setShowingPrivacy(selected.exchangeBaseUrl);
|
||||
},
|
||||
},
|
||||
onShowTerms: {
|
||||
onClick: async () => {
|
||||
setShowingTos(selected.exchangeBaseUrl);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -140,6 +177,16 @@ export function useComponentState(
|
||||
onSelection(selected.exchangeBaseUrl);
|
||||
},
|
||||
},
|
||||
onShowPrivacy: {
|
||||
onClick: async () => {
|
||||
setShowingPrivacy(selected.exchangeBaseUrl);
|
||||
},
|
||||
},
|
||||
onShowTerms: {
|
||||
onClick: async () => {
|
||||
setShowingTos(selected.exchangeBaseUrl);
|
||||
},
|
||||
},
|
||||
selected,
|
||||
pairTimeline,
|
||||
};
|
||||
|
@ -39,6 +39,8 @@ export const Bitcoin1 = createExample(ReadyView, {
|
||||
transferFees: {},
|
||||
globalFees: [],
|
||||
} as any,
|
||||
onShowPrivacy: {},
|
||||
onShowTerms: {},
|
||||
onClose: {},
|
||||
});
|
||||
export const Bitcoin2 = createExample(ReadyView, {
|
||||
@ -57,6 +59,8 @@ export const Bitcoin2 = createExample(ReadyView, {
|
||||
transferFees: {},
|
||||
globalFees: [],
|
||||
} as any,
|
||||
onShowPrivacy: {},
|
||||
onShowTerms: {},
|
||||
onClose: {},
|
||||
});
|
||||
|
||||
@ -75,6 +79,8 @@ export const Kudos1 = createExample(ReadyView, {
|
||||
transferFees: {},
|
||||
globalFees: [],
|
||||
} as any,
|
||||
onShowPrivacy: {},
|
||||
onShowTerms: {},
|
||||
onClose: {},
|
||||
});
|
||||
export const Kudos2 = createExample(ReadyView, {
|
||||
@ -93,6 +99,8 @@ export const Kudos2 = createExample(ReadyView, {
|
||||
transferFees: {},
|
||||
globalFees: [],
|
||||
} as any,
|
||||
onShowPrivacy: {},
|
||||
onShowTerms: {},
|
||||
onClose: {},
|
||||
});
|
||||
export const ComparingBitcoin = createExample(ComparingView, {
|
||||
@ -108,6 +116,8 @@ export const ComparingBitcoin = createExample(ComparingView, {
|
||||
globalFees: [],
|
||||
} as any,
|
||||
onReset: {},
|
||||
onShowPrivacy: {},
|
||||
onShowTerms: {},
|
||||
onSelect: {},
|
||||
error: undefined,
|
||||
pairTimeline: {
|
||||
@ -130,6 +140,8 @@ export const ComparingKudos = createExample(ComparingView, {
|
||||
globalFees: [],
|
||||
} as any,
|
||||
onReset: {},
|
||||
onShowPrivacy: {},
|
||||
onShowTerms: {},
|
||||
onSelect: {},
|
||||
error: undefined,
|
||||
pairTimeline: {
|
||||
|
@ -22,6 +22,7 @@ import { Amount } from "../../components/Amount.js";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { SelectList } from "../../components/SelectList.js";
|
||||
import { Input, SvgIcon } from "../../components/styled/index.js";
|
||||
import { TermsOfService } from "../../components/TermsOfService/index.js";
|
||||
import { Time } from "../../components/Time.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
|
||||
@ -119,6 +120,36 @@ export function ErrorLoadingView({ error }: State.LoadingUriError): VNode {
|
||||
);
|
||||
}
|
||||
|
||||
export function PrivacyContentView({
|
||||
exchangeUrl,
|
||||
onClose,
|
||||
}: State.ShowingPrivacy): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" onClick={onClose.onClick}>
|
||||
<i18n.Translate>Close</i18n.Translate>
|
||||
</Button>
|
||||
<div>show privacy terms for {exchangeUrl}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TosContentView({
|
||||
exchangeUrl,
|
||||
onClose,
|
||||
}: State.ShowingTos): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" onClick={onClose.onClick}>
|
||||
<i18n.Translate>Close</i18n.Translate>
|
||||
</Button>
|
||||
<TermsOfService exchangeUrl={exchangeUrl} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NoExchangesView({
|
||||
currency,
|
||||
}: SelectExchangeState.NoExchange): VNode {
|
||||
@ -145,6 +176,8 @@ export function ComparingView({
|
||||
onReset,
|
||||
onSelect,
|
||||
pairTimeline,
|
||||
onShowPrivacy,
|
||||
onShowTerms,
|
||||
}: State.Comparing): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
@ -304,54 +337,14 @@ export function ComparingView({
|
||||
</tbody>
|
||||
</FeeDescriptionTable>{" "}
|
||||
</section>
|
||||
<section>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<i18n.Translate>Wallet operations</i18n.Translate>
|
||||
</td>
|
||||
<td>
|
||||
<i18n.Translate>Fee</i18n.Translate>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>history(i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>kyc (i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>account (i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>purse (i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wire SEPA (i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>closing SEPA(i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wad SEPA (i) </td>
|
||||
<td>0.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<ButtonGroupFooter>
|
||||
<Button variant="outlined">Privacy policy</Button>
|
||||
<Button variant="outlined">Terms of service</Button>
|
||||
<Button onClick={onShowPrivacy.onClick} variant="outlined">
|
||||
Privacy policy
|
||||
</Button>
|
||||
<Button onClick={onShowTerms.onClick} variant="outlined">
|
||||
Terms of service
|
||||
</Button>
|
||||
</ButtonGroupFooter>
|
||||
</section>
|
||||
</Container>
|
||||
@ -362,6 +355,8 @@ export function ReadyView({
|
||||
exchanges,
|
||||
selected,
|
||||
onClose,
|
||||
onShowPrivacy,
|
||||
onShowTerms,
|
||||
}: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
@ -616,8 +611,12 @@ export function ReadyView({
|
||||
</section>
|
||||
<section>
|
||||
<ButtonGroupFooter>
|
||||
<Button variant="outlined">Privacy policy</Button>
|
||||
<Button variant="outlined">Terms of service</Button>
|
||||
<Button onClick={onShowPrivacy.onClick} variant="outlined">
|
||||
Privacy policy
|
||||
</Button>
|
||||
<Button onClick={onShowTerms.onClick} variant="outlined">
|
||||
Terms of service
|
||||
</Button>
|
||||
</ButtonGroupFooter>
|
||||
</section>
|
||||
</Container>
|
||||
|
@ -36,7 +36,7 @@ import { useBackupDeviceName } from "../hooks/useBackupDeviceName.js";
|
||||
import { useAutoOpenPermissions } from "../hooks/useAutoOpenPermissions.js";
|
||||
import { ToggleHandler } from "../mui/handlers.js";
|
||||
import { Pages } from "../NavigationBar.js";
|
||||
import { buildTermsOfServiceStatus } from "../cta/TermsOfService/utils.js";
|
||||
import { buildTermsOfServiceStatus } from "../components/TermsOfService/utils.js";
|
||||
import * as wxApi from "../wxApi.js";
|
||||
import { platform } from "../platform/api.js";
|
||||
import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js";
|
||||
|
@ -182,6 +182,7 @@ async function dispatch(
|
||||
break;
|
||||
}
|
||||
r = await w.handleCoreApiRequest(req.operation, req.id, req.payload);
|
||||
console.log("response received from wallet", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -330,7 +331,9 @@ export async function wxMain(): Promise<void> {
|
||||
// script on the page
|
||||
platform.listenToAllChannels((message, sender, callback) => {
|
||||
afterWalletIsInitialized.then(() => {
|
||||
dispatch(message, sender, callback);
|
||||
dispatch(message, sender, (response: CoreApiResponse) => {
|
||||
callback(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user