diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/components/TermsOfService')
6 files changed, 671 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts new file mode 100644 index 000000000..1585e3992 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts @@ -0,0 +1,91 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { ComponentChildren } from "preact"; +import { Loading } from "../../components/Loading.js"; +import { ErrorAlert } from "../../context/alert.js"; +import { SelectFieldHandler, ToggleHandler } from "../../mui/handlers.js"; +import { StateViewMap, compose } from "../../utils/index.js"; +import { ErrorAlertView } from "../CurrentAlerts.js"; +import { useComponentState } from "./state.js"; +import { TermsState } from "./utils.js"; +import { + ShowButtonsAcceptedTosView, + ShowButtonsNonAcceptedTosView, + ShowTosContentView, +} from "./views.js"; + +export interface Props { + exchangeUrl: string; + readOnly?: boolean; + showEvenIfaccepted?: boolean; + children: ComponentChildren; +} + +export type State = + | State.Loading + | State.Error + | State.ShowButtonsAccepted + | State.ShowButtonsNotAccepted + | State.ShowContent; + +export namespace State { + export interface Loading { + status: "loading"; + error: undefined; + } + + export interface Error { + status: "error"; + error: ErrorAlert; + } + + export interface BaseInfo { + error: undefined; + terms: TermsState; + } + export interface ShowContent extends BaseInfo { + status: "show-content"; + termsAccepted: ToggleHandler; + showingTermsOfService?: ToggleHandler; + tosLang: SelectFieldHandler; + tosFormat: SelectFieldHandler; + } + export interface ShowButtonsAccepted extends BaseInfo { + status: "show-buttons-accepted"; + termsAccepted: ToggleHandler; + showingTermsOfService: ToggleHandler; + children: ComponentChildren, + } + export interface ShowButtonsNotAccepted extends BaseInfo { + status: "show-buttons-not-accepted"; + showingTermsOfService: ToggleHandler; + } +} + +const viewMapping: StateViewMap<State> = { + loading: Loading, + error: ErrorAlertView, + "show-content": ShowTosContentView, + "show-buttons-accepted": ShowButtonsAcceptedTosView, + "show-buttons-not-accepted": ShowButtonsNonAcceptedTosView, +}; + +export const TermsOfService = compose( + "TermsOfService", + (p: Props) => useComponentState(p), + viewMapping, +); diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts new file mode 100644 index 000000000..76524f0f4 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts @@ -0,0 +1,160 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useState } from "preact/hooks"; +import { alertFromError, useAlertContext } from "../../context/alert.js"; +import { useBackendContext } from "../../context/backend.js"; +import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; +import { Props, State } from "./index.js"; +import { buildTermsOfServiceState } from "./utils.js"; + +const supportedFormats = { + "text/html": "HTML", + "text/xml" : "XML", + "text/markdown" : "Markdown", + "text/plain" : "Plain text", + "text/pdf" : "PDF", +} + +export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, children }: Props): State { + const api = useBackendContext(); + const [showContent, setShowContent] = useState<boolean>(!!readOnly); + const { i18n, lang } = useTranslationContext(); + const [tosLang, setTosLang] = useState<string>() + const { pushAlertOnError } = useAlertContext(); + + const [format, setFormat] = useState("text/html") + + const acceptedLang = tosLang ?? lang + /** + * For the exchange selected, bring the status of the terms of service + */ + const terms = useAsyncAsHook(async () => { + const exchangeTos = await api.wallet.call( + WalletApiOperation.GetExchangeTos, + { + exchangeBaseUrl: exchangeUrl, + acceptedFormat: [format], + acceptLanguage: acceptedLang, + }, + ); + + const supportedLangs = exchangeTos.tosAvailableLanguages.reduce((prev, cur) => { + prev[cur] = cur + return prev; + }, {} as Record<string, string>) + + const state = buildTermsOfServiceState(exchangeTos); + + return { state, supportedLangs }; + }, [acceptedLang, format]); + + if (!terms) { + return { + status: "loading", + error: undefined, + }; + } + if (terms.hasError) { + return { + status: "error", + error: alertFromError( + i18n, + i18n.str`Could not load the status of the term of service`, + terms, + ), + }; + } + const { state, supportedLangs } = terms.response; + + async function onUpdate(accepted: boolean): Promise<void> { + if (!state) return; + + if (accepted) { + await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { + exchangeBaseUrl: exchangeUrl, + }); + } else { + // mark as not accepted + } + terms?.retry() + } + + const accepted = state.status === "accepted"; + + const base = { + error: undefined, + showingTermsOfService: { + value: showContent && (!accepted || showEvenIfaccepted), + button: { + onClick: accepted && !showEvenIfaccepted ? undefined : pushAlertOnError(async () => { + setShowContent(!showContent); + }), + }, + }, + terms: state, + termsAccepted: { + value: accepted, + button: { + onClick: readOnly ? undefined : pushAlertOnError(async () => { + const newValue = !accepted; //toggle + await onUpdate(newValue); + setShowContent(false); + }), + }, + }, + }; + + if (accepted) { + return { + status: "show-buttons-accepted", + ...base, + children, + }; + } + + if ((accepted && showEvenIfaccepted) || showContent) { + return { + status: "show-content", + error: undefined, + terms: state, + showingTermsOfService: readOnly ? undefined : base.showingTermsOfService, + termsAccepted: base.termsAccepted, + tosFormat: { + onChange: pushAlertOnError(async (s) => { + setFormat(s) + }), + list: supportedFormats, + value: format ?? "" + }, + tosLang: { + onChange: pushAlertOnError(async (s) => { + setTosLang(s) + }), + list: supportedLangs, + value: tosLang ?? lang + } + }; + } + //showing buttons + return { + status: "show-buttons-not-accepted", + ...base, + }; + +} diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/stories.tsx b/packages/taler-wallet-webextension/src/components/TermsOfService/stories.tsx new file mode 100644 index 000000000..a28729eae --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/stories.tsx @@ -0,0 +1,59 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import * as tests from "@gnu-taler/web-util/testing"; +import { ShowTosContentView } from "./views.js"; +import { ExchangeTosStatus } from "@gnu-taler/taler-util"; + +export default { + title: "TermsOfService", +}; + +export const Ready = tests.createExample(ShowTosContentView, { + tosLang: { + list: { + es: "es", + en: "en", + }, + value: "es", + onChange: (() => { }) as any + }, + tosFormat: { + list: { + es: "es", + en: "en", + }, + value: "es", + onChange: (() => { }) as any + }, + terms: { + content: { + type: "plain", + content: "hola" + }, + status: ExchangeTosStatus.Accepted, + version: "1" + }, + status: "show-content", + termsAccepted: { + button: {}, + } +}); diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts new file mode 100644 index 000000000..170e7cad8 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts @@ -0,0 +1,28 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { expect } from "chai"; + +describe("Term of service states", () => { + it.skip("should assert", () => { + expect([]).deep.equals([]); + }); +}); diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/utils.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/utils.ts new file mode 100644 index 000000000..96e268689 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/utils.ts @@ -0,0 +1,108 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + ExchangeTosStatus, + GetExchangeTosResult, + Logger, +} from "@gnu-taler/taler-util"; + +export function buildTermsOfServiceState( + tos: GetExchangeTosResult, +): TermsState { + const content: TermsDocument | undefined = parseTermsOfServiceContent( + tos.contentType, + tos.content, + ); + + return { content, status: tos.tosStatus, version: tos.currentEtag }; +} + +const logger = new Logger("termsofservice"); + +function parseTermsOfServiceContent( + type: string, + text: string, +): TermsDocument | undefined { + if (type === "text/xml") { + try { + const document = new DOMParser().parseFromString(text, "text/xml"); + return { type: "xml", document }; + } catch (e) { + logger.error("error parsing xml", e); + } + } else if (type === "text/html") { + try { + return { type: "html", html: text }; + } catch (e) { + logger.error("error parsing url", e); + } + } else if (type === "text/json") { + try { + const data = JSON.parse(text); + return { type: "json", data }; + } catch (e) { + logger.error("error parsing json", e); + } + } else if (type === "text/pdf") { + try { + const location = new URL(text); + return { type: "pdf", location }; + } catch (e) { + logger.error("error parsing url", e); + } + } + const content = text; + return { type: "plain", content }; +} + +export type TermsState = { + content: TermsDocument | undefined; + status: ExchangeTosStatus; + version: string; +}; + +export type TermsDocument = + | TermsDocumentXml + | TermsDocumentHtml + | TermsDocumentPlain + | TermsDocumentJson + | TermsDocumentPdf; + +export interface TermsDocumentXml { + type: "xml"; + document: Document; +} + +export interface TermsDocumentHtml { + type: "html"; + html: string; +} + +export interface TermsDocumentPlain { + type: "plain"; + content: string; +} + +export interface TermsDocumentJson { + type: "json"; + data: any; +} + +export interface TermsDocumentPdf { + type: "pdf"; + location: URL; +} diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx b/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx new file mode 100644 index 000000000..40cfba3bc --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx @@ -0,0 +1,225 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { ExchangeTosStatus } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, h, VNode } from "preact"; +import { CheckboxOutlined } from "../../components/CheckboxOutlined.js"; +import { ExchangeXmlTos } from "../../components/ExchangeToS.js"; +import { + Input, + LinkSuccess, + TermsOfServiceStyle, + WarningBox +} from "../../components/styled/index.js"; +import { Button } from "../../mui/Button.js"; +import { State } from "./index.js"; +import { SelectList } from "../SelectList.js"; +import { EnabledBySettings } from "../EnabledBySettings.js"; + +export function ShowButtonsAcceptedTosView({ + termsAccepted, + showingTermsOfService, + children, +}: State.ShowButtonsAccepted): VNode { + const { i18n } = useTranslationContext(); + + return ( + <Fragment> + {showingTermsOfService.button.onClick !== undefined && ( + <Fragment> + <section style={{ justifyContent: "space-around", display: "flex" }}> + <LinkSuccess + upperCased + onClick={showingTermsOfService.button.onClick} + > + <i18n.Translate>Show terms of service</i18n.Translate> + </LinkSuccess> + </section> + {termsAccepted.button.onClick !== undefined && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + <CheckboxOutlined + name="terms" + enabled={termsAccepted.value} + label={ + <i18n.Translate> + I accept the exchange terms of service + </i18n.Translate> + } + onToggle={termsAccepted.button.onClick} + /> + </section> + )} + </Fragment> + )} + {children} + </Fragment> + ); +} + +export function ShowButtonsNonAcceptedTosView({ + showingTermsOfService, + terms, +}: State.ShowButtonsNotAccepted): VNode { + const { i18n } = useTranslationContext(); + // const ableToReviewTermsOfService = + // showingTermsOfService.button.onClick !== undefined; + + // if (!ableToReviewTermsOfService) { + // return ( + // <Fragment> + // {terms.status === ExchangeTosStatus.Pending && ( + // <section style={{ justifyContent: "space-around", display: "flex" }}> + // <WarningText> + // <i18n.Translate> + // Exchange doesn't have terms of service + // </i18n.Translate> + // </WarningText> + // </section> + // )} + // </Fragment> + // ); + // } + + return ( + <Fragment> + {/* {terms.status === ExchangeTosStatus.NotFound && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + <WarningText> + <i18n.Translate> + Exchange doesn't have terms of service + </i18n.Translate> + </WarningText> + </section> + )} */} + <section style={{ justifyContent: "space-around", display: "flex" }}> + <Button + variant="contained" + color="success" + onClick={showingTermsOfService.button.onClick} + > + <i18n.Translate>Review exchange terms of service</i18n.Translate> + </Button> + </section> + </Fragment> + ); +} + +export function ShowTosContentView({ + termsAccepted, + showingTermsOfService, + terms, + tosLang, + tosFormat, +}: State.ShowContent): VNode { + const { i18n } = useTranslationContext(); + const ableToReviewTermsOfService = + termsAccepted.button.onClick !== undefined; + + return ( + <section> + <Input style={{ display: "flex", justifyContent: "end" }}> + <EnabledBySettings name="selectTosFormat"> + <SelectList + label={i18n.str`Format`} + list={tosFormat.list} + name="format" + value={tosFormat.value} + onChange={tosFormat.onChange} + /> + </EnabledBySettings> + <SelectList + label={i18n.str`Language`} + list={tosLang.list} + name="lang" + value={tosLang.value} + onChange={tosLang.onChange} + /> + </Input> + + {!terms.content && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + <WarningBox> + <i18n.Translate> + The exchange replied with a empty terms of service + </i18n.Translate> + </WarningBox> + </section> + )} + {terms.content && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + {terms.content.type === "xml" && + (!terms.content.document ? ( + <WarningBox> + <i18n.Translate> + No terms of service. The exchange replied with a empty + document + </i18n.Translate> + </WarningBox> + ) : ( + <TermsOfServiceStyle> + <ExchangeXmlTos doc={terms.content.document} /> + </TermsOfServiceStyle> + ))} + {terms.content.type === "plain" && + (!terms.content.content ? ( + <WarningBox> + <i18n.Translate> + No terms of service. The exchange replied with a empty text + </i18n.Translate> + </WarningBox> + ) : ( + <div style={{ textAlign: "left" }}> + <pre>{terms.content.content}</pre> + </div> + ))} + {terms.content.type === "html" && ( + <iframe style={{ width: "100%" }} srcDoc={terms.content.html} /> + )} + {terms.content.type === "pdf" && ( + <a href={terms.content.location.toString()} download="tos.pdf"> + <i18n.Translate>Download Terms of Service</i18n.Translate> + </a> + )} + </section> + )} + {showingTermsOfService && ableToReviewTermsOfService && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + <LinkSuccess + upperCased + onClick={showingTermsOfService.button.onClick} + > + <i18n.Translate>Hide terms of service</i18n.Translate> + </LinkSuccess> + </section> + )} + {termsAccepted.button.onClick && terms.status !== ExchangeTosStatus.Accepted && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + <CheckboxOutlined + name="terms" + enabled={termsAccepted.value} + label={ + <i18n.Translate> + I accept the exchange terms of service + </i18n.Translate> + } + onToggle={termsAccepted.button.onClick} + /> + </section> + )} + </section> + ); +} |