terms and privacy on exchange selection

This commit is contained in:
Sebastian 2022-10-14 16:12:24 -03:00
parent 6acddd6d70
commit b011c8a32e
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
17 changed files with 179 additions and 91 deletions

View File

@ -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;
}
}

View File

@ -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,16 +90,14 @@ export function useComponentState(
const accepted = state.status === "accepted";
const base: State.BaseInfo = {
const base = {
error: undefined,
showingTermsOfService: {
value: showContent,
button: {
onClick: readOnly
? undefined
: async () => {
setShowContent(!showContent);
},
onClick: async () => {
setShowContent(!showContent);
},
},
},
terms: state,
@ -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

View File

@ -97,7 +97,7 @@ export type TermsState = {
type TermsStatus = "new" | "accepted" | "changed" | "notfound";
type TermsDocument =
export type TermsDocument =
| TermsDocumentXml
| TermsDocumentHtml
| TermsDocumentPlain

View File

@ -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"

View File

@ -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];

View File

@ -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 {

View File

@ -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];

View File

@ -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) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
}
resolve(resp);
// return true to keep the channel open
return true;
});
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);
}
// 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;

View File

@ -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;

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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: {

View File

@ -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>

View File

@ -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";

View File

@ -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);
});
});
});