diff options
Diffstat (limited to 'packages')
86 files changed, 4970 insertions, 3580 deletions
diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index 3a43f1e76..b3d0b10af 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -13,6 +13,7 @@ "compile": "tsc && rollup -c", "build-storybook": "build-storybook", "storybook": "start-storybook -s . -p 6006", + "pretty": "prettier --write src", "watch": "tsc --watch & rollup -w -c" }, "dependencies": { @@ -80,4 +81,4 @@ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$": "<rootDir>/tests/__mocks__/fileTransformer.js" } } -} +}
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx index 9edd8ca67..f206fa2dd 100644 --- a/packages/taler-wallet-webextension/src/NavigationBar.tsx +++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -28,29 +28,29 @@ import { i18n } from "@gnu-taler/taler-util"; import { ComponentChildren, JSX, h } from "preact"; import Match from "preact-router/match"; import { useDevContext } from "./context/devContext"; -import { PopupNavigation } from './components/styled' +import { PopupNavigation } from "./components/styled"; export enum Pages { - welcome = '/welcome', - balance = '/balance', - manual_withdraw = '/manual-withdraw', - settings = '/settings', - dev = '/dev', - cta = '/cta', - backup = '/backup', - history = '/history', - transaction = '/transaction/:tid', - provider_detail = '/provider/:pid', - provider_add = '/provider/add', + welcome = "/welcome", + balance = "/balance", + manual_withdraw = "/manual-withdraw", + settings = "/settings", + dev = "/dev", + cta = "/cta", + backup = "/backup", + history = "/history", + transaction = "/transaction/:tid", + provider_detail = "/provider/:pid", + provider_add = "/provider/add", - reset_required = '/reset-required', - payback = '/payback', - return_coins = '/return-coins', + reset_required = "/reset-required", + payback = "/payback", + return_coins = "/return-coins", - pay = '/pay', - refund = '/refund', - tips = '/tip', - withdraw = '/withdraw', + pay = "/pay", + refund = "/refund", + tips = "/tip", + withdraw = "/withdraw", } interface TabProps { @@ -71,23 +71,28 @@ function Tab(props: TabProps): JSX.Element { ); } -export function NavBar({ devMode, path }: { path: string, devMode: boolean }) { - return <PopupNavigation devMode={devMode}> - <div> - <Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab> - <Tab target="/history" current={path}>{i18n.str`History`}</Tab> - <Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab> - <Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab> - {devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>} - </div> - </PopupNavigation> +export function NavBar({ devMode, path }: { path: string; devMode: boolean }) { + return ( + <PopupNavigation devMode={devMode}> + <div> + <Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab> + <Tab target="/history" current={path}>{i18n.str`History`}</Tab> + <Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab> + <Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab> + {devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>} + </div> + </PopupNavigation> + ); } export function WalletNavBar() { - const { devMode } = useDevContext() - return <Match>{({ path }: any) => { - console.log("path", path) - return <NavBar devMode={devMode} path={path} /> - }}</Match> + const { devMode } = useDevContext(); + return ( + <Match> + {({ path }: any) => { + console.log("path", path); + return <NavBar devMode={devMode} path={path} />; + }} + </Match> + ); } - diff --git a/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.js b/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.js index e9492a2fb..8d958d6bd 100644 --- a/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.js +++ b/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.js @@ -21,24 +21,23 @@ exports.BrowserCryptoWorkerFactory = void 0; * @author Florian Dold */ class BrowserCryptoWorkerFactory { - startWorker() { - const workerCtor = Worker; - const workerPath = "/browserWorkerEntry.js"; - return new workerCtor(workerPath); - } - getConcurrency() { - let concurrency = 2; - try { - // only works in the browser - // tslint:disable-next-line:no-string-literal - concurrency = navigator["hardwareConcurrency"]; - concurrency = Math.max(1, Math.ceil(concurrency / 2)); - } - catch (e) { - concurrency = 2; - } - return concurrency; + startWorker() { + const workerCtor = Worker; + const workerPath = "/browserWorkerEntry.js"; + return new workerCtor(workerPath); + } + getConcurrency() { + let concurrency = 2; + try { + // only works in the browser + // tslint:disable-next-line:no-string-literal + concurrency = navigator["hardwareConcurrency"]; + concurrency = Math.max(1, Math.ceil(concurrency / 2)); + } catch (e) { + concurrency = 2; } + return concurrency; + } } exports.BrowserCryptoWorkerFactory = BrowserCryptoWorkerFactory; -//# sourceMappingURL=browserCryptoWorkerFactory.js.map
\ No newline at end of file +//# sourceMappingURL=browserCryptoWorkerFactory.js.map diff --git a/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts b/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts index a8315dc6d..ab20228ef 100644 --- a/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts +++ b/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts @@ -19,7 +19,10 @@ * @author Florian Dold */ -import type { CryptoWorker, CryptoWorkerFactory } from "@gnu-taler/taler-wallet-core"; +import type { + CryptoWorker, + CryptoWorkerFactory, +} from "@gnu-taler/taler-wallet-core"; export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory { startWorker(): CryptoWorker { diff --git a/packages/taler-wallet-webextension/src/compat.js b/packages/taler-wallet-webextension/src/compat.js index fdfcbd4b9..48e49a0a7 100644 --- a/packages/taler-wallet-webextension/src/compat.js +++ b/packages/taler-wallet-webextension/src/compat.js @@ -21,41 +21,44 @@ exports.getPermissionsApi = exports.isNode = exports.isFirefox = void 0; * WebExtension APIs consistently. */ function isFirefox() { - const rt = chrome.runtime; - if (typeof rt.getBrowserInfo === "function") { - return true; - } - return false; + const rt = chrome.runtime; + if (typeof rt.getBrowserInfo === "function") { + return true; + } + return false; } exports.isFirefox = isFirefox; /** * Check if we are running under nodejs. */ function isNode() { - return typeof process !== "undefined" && process.release.name === "node"; + return typeof process !== "undefined" && process.release.name === "node"; } exports.isNode = isNode; function getPermissionsApi() { - const myBrowser = globalThis.browser; - if (typeof myBrowser === "object" && - typeof myBrowser.permissions === "object") { - return { - addPermissionsListener: () => { - // Not supported yet. - }, - contains: myBrowser.permissions.contains, - request: myBrowser.permissions.request, - remove: myBrowser.permissions.remove, - }; - } - else { - return { - addPermissionsListener: chrome.permissions.onAdded.addListener.bind(chrome.permissions.onAdded), - contains: chrome.permissions.contains, - request: chrome.permissions.request, - remove: chrome.permissions.remove, - }; - } + const myBrowser = globalThis.browser; + if ( + typeof myBrowser === "object" && + typeof myBrowser.permissions === "object" + ) { + return { + addPermissionsListener: () => { + // Not supported yet. + }, + contains: myBrowser.permissions.contains, + request: myBrowser.permissions.request, + remove: myBrowser.permissions.remove, + }; + } else { + return { + addPermissionsListener: chrome.permissions.onAdded.addListener.bind( + chrome.permissions.onAdded, + ), + contains: chrome.permissions.contains, + request: chrome.permissions.request, + remove: chrome.permissions.remove, + }; + } } exports.getPermissionsApi = getPermissionsApi; -//# sourceMappingURL=compat.js.map
\ No newline at end of file +//# sourceMappingURL=compat.js.map diff --git a/packages/taler-wallet-webextension/src/components/Checkbox.tsx b/packages/taler-wallet-webextension/src/components/Checkbox.tsx index 2d7b98087..276ac9ff0 100644 --- a/packages/taler-wallet-webextension/src/components/Checkbox.tsx +++ b/packages/taler-wallet-webextension/src/components/Checkbox.tsx @@ -24,7 +24,13 @@ interface Props { name: string; description?: string; } -export function Checkbox({ name, enabled, onToggle, label, description }: Props): JSX.Element { +export function Checkbox({ + name, + enabled, + onToggle, + label, + description, +}: Props): JSX.Element { return ( <div> <input @@ -32,23 +38,26 @@ export function Checkbox({ name, enabled, onToggle, label, description }: Props) onClick={onToggle} type="checkbox" id={`checkbox-${name}`} - style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} /> + style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} + /> <label htmlFor={`checkbox-${name}`} style={{ marginLeft: "0.5em", fontWeight: "bold" }} > {label} </label> - {description && <span - style={{ - color: "#383838", - fontSize: "smaller", - display: "block", - marginLeft: "2em", - }} - > - {description} - </span>} + {description && ( + <span + style={{ + color: "#383838", + fontSize: "smaller", + display: "block", + marginLeft: "2em", + }} + > + {description} + </span> + )} </div> ); } diff --git a/packages/taler-wallet-webextension/src/components/CheckboxOutlined.tsx b/packages/taler-wallet-webextension/src/components/CheckboxOutlined.tsx index 5e30ee3d1..2fc8316f5 100644 --- a/packages/taler-wallet-webextension/src/components/CheckboxOutlined.tsx +++ b/packages/taler-wallet-webextension/src/components/CheckboxOutlined.tsx @@ -16,7 +16,7 @@ import { JSX } from "preact/jsx-runtime"; import { Outlined, StyledCheckboxLabel } from "./styled/index"; -import { h } from 'preact'; +import { h } from "preact"; interface Props { enabled: boolean; @@ -25,28 +25,39 @@ interface Props { name: string; } +const Tick = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + aria-hidden="true" + focusable="false" + style={{ backgroundColor: "green" }} + > + <path + fill="none" + stroke="white" + stroke-width="3" + d="M1.73 12.91l6.37 6.37L22.79 4.59" + /> + </svg> +); -const Tick = () => <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - aria-hidden="true" - focusable="false" - style={{ backgroundColor: 'green' }} -> - <path - fill="none" - stroke="white" - stroke-width="3" - d="M1.73 12.91l6.37 6.37L22.79 4.59" - /> -</svg> - -export function CheckboxOutlined({ name, enabled, onToggle, label }: Props): JSX.Element { +export function CheckboxOutlined({ + name, + enabled, + onToggle, + label, +}: Props): JSX.Element { return ( <Outlined> <StyledCheckboxLabel onClick={onToggle}> <span> - <input type="checkbox" name={name} checked={enabled} disabled={false} /> + <input + type="checkbox" + name={name} + checked={enabled} + disabled={false} + /> <div> <Tick /> </div> diff --git a/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx b/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx index f0c682ccb..952df15ae 100644 --- a/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx +++ b/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx @@ -14,9 +14,15 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { JSX, h } from "preact"; +import { JSX, h } from "preact"; -export function DebugCheckbox({ enabled, onToggle }: { enabled: boolean; onToggle: () => void; }): JSX.Element { +export function DebugCheckbox({ + enabled, + onToggle, +}: { + enabled: boolean; + onToggle: () => void; +}): JSX.Element { return ( <div> <input @@ -24,7 +30,8 @@ export function DebugCheckbox({ enabled, onToggle }: { enabled: boolean; onToggl onClick={onToggle} type="checkbox" id="checkbox-perm" - style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} /> + style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} + /> <label htmlFor="checkbox-perm" style={{ marginLeft: "0.5em", fontWeight: "bold" }} diff --git a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx index b48deb847..0f8afd525 100644 --- a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx +++ b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx @@ -21,11 +21,13 @@ import { PageLink } from "../renderHtml"; interface Props { timedOut: boolean; - diagnostics: WalletDiagnostics | undefined + diagnostics: WalletDiagnostics | undefined; } -export function Diagnostics({timedOut, diagnostics}: Props): JSX.Element | null { - +export function Diagnostics({ + timedOut, + diagnostics, +}: Props): JSX.Element | null { if (timedOut) { return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>; } @@ -60,8 +62,8 @@ export function Diagnostics({timedOut, diagnostics}: Props): JSX.Element | null <p> Your wallet database is outdated. Currently automatic migration is not supported. Please go{" "} - <PageLink pageName="/reset-required">here</PageLink> to reset - the wallet database. + <PageLink pageName="/reset-required">here</PageLink> to reset the + wallet database. </p> ) : null} </div> diff --git a/packages/taler-wallet-webextension/src/components/EditableText.tsx b/packages/taler-wallet-webextension/src/components/EditableText.tsx index 6f3388bf9..8b3e6d375 100644 --- a/packages/taler-wallet-webextension/src/components/EditableText.tsx +++ b/packages/taler-wallet-webextension/src/components/EditableText.tsx @@ -25,25 +25,37 @@ interface Props { name: string; description?: string; } -export function EditableText({ name, value, onChange, label, description }: Props): JSX.Element { - const [editing, setEditing] = useState(false) - const ref = useRef<HTMLInputElement>(null) +export function EditableText({ + name, + value, + onChange, + label, + description, +}: Props): JSX.Element { + const [editing, setEditing] = useState(false); + const ref = useRef<HTMLInputElement>(null); let InputText; if (!editing) { - InputText = () => <div style={{ display: 'flex', justifyContent: 'space-between' }}> - <p>{value}</p> - <button onClick={() => setEditing(true)}>edit</button> - </div> + InputText = () => ( + <div style={{ display: "flex", justifyContent: "space-between" }}> + <p>{value}</p> + <button onClick={() => setEditing(true)}>edit</button> + </div> + ); } else { - InputText = () => <div style={{ display: 'flex', justifyContent: 'space-between' }}> - <input - value={value} - ref={ref} - type="text" - id={`text-${name}`} - /> - <button onClick={() => { if (ref.current) onChange(ref.current.value).then(r => setEditing(false)) }}>confirm</button> - </div> + InputText = () => ( + <div style={{ display: "flex", justifyContent: "space-between" }}> + <input value={value} ref={ref} type="text" id={`text-${name}`} /> + <button + onClick={() => { + if (ref.current) + onChange(ref.current.value).then((r) => setEditing(false)); + }} + > + confirm + </button> + </div> + ); } return ( <div> @@ -54,16 +66,18 @@ export function EditableText({ name, value, onChange, label, description }: Prop {label} </label> <InputText /> - {description && <span - style={{ - color: "#383838", - fontSize: "smaller", - display: "block", - marginLeft: "2em", - }} - > - {description} - </span>} + {description && ( + <span + style={{ + color: "#383838", + fontSize: "smaller", + display: "block", + marginLeft: "2em", + }} + > + {description} + </span> + )} </div> ); } diff --git a/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx b/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx index cfcef16d5..c6b64fb6a 100644 --- a/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx +++ b/packages/taler-wallet-webextension/src/components/ErrorMessage.tsx @@ -13,22 +13,35 @@ 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 { VNode, h } from "preact"; +import { VNode, h } from "preact"; import { useState } from "preact/hooks"; -import arrowDown from '../../static/img/chevron-down.svg'; +import arrowDown from "../../static/img/chevron-down.svg"; import { ErrorBox } from "./styled"; -export function ErrorMessage({ title, description }: { title?: string|VNode; description?: string; }) { +export function ErrorMessage({ + title, + description, +}: { + title?: string | VNode; + description?: string; +}) { const [showErrorDetail, setShowErrorDetail] = useState(false); - if (!title) - return null; - return <ErrorBox style={{paddingTop: 0, paddingBottom: 0}}> - <div> - <p>{title}</p> - { description && <button onClick={() => { setShowErrorDetail(v => !v); }}> - <img style={{ height: '1.5em' }} src={arrowDown} /> - </button> } - </div> - {showErrorDetail && <p>{description}</p>} - </ErrorBox>; + if (!title) return null; + return ( + <ErrorBox style={{ paddingTop: 0, paddingBottom: 0 }}> + <div> + <p>{title}</p> + {description && ( + <button + onClick={() => { + setShowErrorDetail((v) => !v); + }} + > + <img style={{ height: "1.5em" }} src={arrowDown} /> + </button> + )} + </div> + {showErrorDetail && <p>{description}</p>} + </ErrorBox> + ); } diff --git a/packages/taler-wallet-webextension/src/components/ExchangeToS.tsx b/packages/taler-wallet-webextension/src/components/ExchangeToS.tsx index cfa20280f..6d2731cd8 100644 --- a/packages/taler-wallet-webextension/src/components/ExchangeToS.tsx +++ b/packages/taler-wallet-webextension/src/components/ExchangeToS.tsx @@ -13,66 +13,80 @@ 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 { Fragment, VNode } from "preact" -import { useState } from "preact/hooks" -import { JSXInternal } from "preact/src/jsx" -import { h } from 'preact'; +import { Fragment, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { JSXInternal } from "preact/src/jsx"; +import { h } from "preact"; export function ExchangeXmlTos({ doc }: { doc: Document }) { - const termsNode = doc.querySelector('[ids=terms-of-service]') + const termsNode = doc.querySelector("[ids=terms-of-service]"); if (!termsNode) { - return <div> - <p>The exchange send us an xml but there is no node with 'ids=terms-of-service'. This is the content:</p> - <pre>{new XMLSerializer().serializeToString(doc)}</pre> - </div> + return ( + <div> + <p> + The exchange send us an xml but there is no node with + 'ids=terms-of-service'. This is the content: + </p> + <pre>{new XMLSerializer().serializeToString(doc)}</pre> + </div> + ); } - return <Fragment> - {Array.from(termsNode.children).map(renderChild)} - </Fragment> + return <Fragment>{Array.from(termsNode.children).map(renderChild)}</Fragment>; } /** * Map XML elements into HTML - * @param child - * @returns + * @param child + * @returns */ function renderChild(child: Element): VNode { - const children = Array.from(child.children) + const children = Array.from(child.children); switch (child.nodeName) { - case 'title': return <header>{child.textContent}</header> - case '#text': return <Fragment /> - case 'paragraph': return <p>{child.textContent}</p> - case 'section': { - return <AnchorWithOpenState href={`#terms-${child.getAttribute('ids')}`}> - {children.map(renderChild)} - </AnchorWithOpenState> + case "title": + return <header>{child.textContent}</header>; + case "#text": + return <Fragment />; + case "paragraph": + return <p>{child.textContent}</p>; + case "section": { + return ( + <AnchorWithOpenState href={`#terms-${child.getAttribute("ids")}`}> + {children.map(renderChild)} + </AnchorWithOpenState> + ); } - case 'bullet_list': { - return <ul>{children.map(renderChild)}</ul> + case "bullet_list": { + return <ul>{children.map(renderChild)}</ul>; } - case 'enumerated_list': { - return <ol>{children.map(renderChild)}</ol> + case "enumerated_list": { + return <ol>{children.map(renderChild)}</ol>; } - case 'list_item': { - return <li>{children.map(renderChild)}</li> + case "list_item": { + return <li>{children.map(renderChild)}</li>; } - case 'block_quote': { - return <div>{children.map(renderChild)}</div> + case "block_quote": { + return <div>{children.map(renderChild)}</div>; } - default: return <div style={{ color: 'red', display: 'hidden' }}>unknown tag {child.nodeName} <a></a></div> + default: + return ( + <div style={{ color: "red", display: "hidden" }}> + unknown tag {child.nodeName} <a></a> + </div> + ); } } /** * Simple anchor with a state persisted into 'data-open' prop - * @returns + * @returns */ -function AnchorWithOpenState(props: JSXInternal.HTMLAttributes<HTMLAnchorElement>) { - const [open, setOpen] = useState<boolean>(false) +function AnchorWithOpenState( + props: JSXInternal.HTMLAttributes<HTMLAnchorElement>, +) { + const [open, setOpen] = useState<boolean>(false); function doClick(e: JSXInternal.TargetedMouseEvent<HTMLAnchorElement>) { setOpen(!open); e.preventDefault(); } - return <a data-open={open ? 'true' : 'false'} onClick={doClick} {...props} /> + return <a data-open={open ? "true" : "false"} onClick={doClick} {...props} />; } - diff --git a/packages/taler-wallet-webextension/src/components/LogoHeader.tsx b/packages/taler-wallet-webextension/src/components/LogoHeader.tsx index 9b75c62a1..6c47dc92a 100644 --- a/packages/taler-wallet-webextension/src/components/LogoHeader.tsx +++ b/packages/taler-wallet-webextension/src/components/LogoHeader.tsx @@ -17,15 +17,22 @@ import { h } from "preact"; export function LogoHeader() { - return <div style={{ - display: 'flex', - justifyContent: 'space-around', - margin: '2em', - }}> - <img style={{ - width: 150, - height: 70, - }} src="/static/img/logo-2021.svg" width="150" /> - </div> - -}
\ No newline at end of file + return ( + <div + style={{ + display: "flex", + justifyContent: "space-around", + margin: "2em", + }} + > + <img + style={{ + width: 150, + height: 70, + }} + src="/static/img/logo-2021.svg" + width="150" + /> + </div> + ); +} diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx index 75c9df16f..c8ecb46d2 100644 --- a/packages/taler-wallet-webextension/src/components/Part.tsx +++ b/packages/taler-wallet-webextension/src/components/Part.tsx @@ -15,18 +15,28 @@ */ import { AmountLike } from "@gnu-taler/taler-util"; import { ExtraLargeText, LargeText, SmallLightText } from "./styled"; -import { h } from 'preact'; +import { h } from "preact"; -export type Kind = 'positive' | 'negative' | 'neutral'; +export type Kind = "positive" | "negative" | "neutral"; interface Props { - title: string, text: AmountLike, kind: Kind, big?: boolean + title: string; + text: AmountLike; + kind: Kind; + big?: boolean; } export function Part({ text, title, kind, big }: Props) { const Text = big ? ExtraLargeText : LargeText; - return <div style={{ margin: '1em' }}> - <SmallLightText style={{ margin: '.5em' }}>{title}</SmallLightText> - <Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}> - {text} - </Text> - </div> + return ( + <div style={{ margin: "1em" }}> + <SmallLightText style={{ margin: ".5em" }}>{title}</SmallLightText> + <Text + style={{ + color: + kind == "positive" ? "green" : kind == "negative" ? "red" : "black", + }} + > + {text} + </Text> + </div> + ); } diff --git a/packages/taler-wallet-webextension/src/components/QR.tsx b/packages/taler-wallet-webextension/src/components/QR.tsx index 8e3f69295..4ff1af961 100644 --- a/packages/taler-wallet-webextension/src/components/QR.tsx +++ b/packages/taler-wallet-webextension/src/components/QR.tsx @@ -14,24 +14,35 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { h, VNode } from "preact"; - import { useEffect, useRef } from "preact/hooks"; - import qrcode from "qrcode-generator"; - - export function QR({ text }: { text: string; }):VNode { - const divRef = useRef<HTMLDivElement>(null); - useEffect(() => { - if (!divRef.current) return - const qr = qrcode(0, 'L'); - qr.addData(text); - qr.make(); - divRef.current.innerHTML = qr.createSvgTag({ - scalable: true, - }); - }); - - return <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}> - <div style={{ width: '50%', minWidth: 200, maxWidth: 300 }} ref={divRef} /> - </div>; - } -
\ No newline at end of file +import { h, VNode } from "preact"; +import { useEffect, useRef } from "preact/hooks"; +import qrcode from "qrcode-generator"; + +export function QR({ text }: { text: string }): VNode { + const divRef = useRef<HTMLDivElement>(null); + useEffect(() => { + if (!divRef.current) return; + const qr = qrcode(0, "L"); + qr.addData(text); + qr.make(); + divRef.current.innerHTML = qr.createSvgTag({ + scalable: true, + }); + }); + + return ( + <div + style={{ + width: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + }} + > + <div + style={{ width: "50%", minWidth: 200, maxWidth: 300 }} + ref={divRef} + /> + </div> + ); +} diff --git a/packages/taler-wallet-webextension/src/components/SelectList.tsx b/packages/taler-wallet-webextension/src/components/SelectList.tsx index 536e5b89a..f89ba19b2 100644 --- a/packages/taler-wallet-webextension/src/components/SelectList.tsx +++ b/packages/taler-wallet-webextension/src/components/SelectList.tsx @@ -23,46 +23,67 @@ interface Props { onChange: (s: string) => void; label: string; list: { - [label: string]: string - } + [label: string]: string; + }; name: string; description?: string; canBeNull?: boolean; } -export function SelectList({ name, value, list, canBeNull, onChange, label, description }: Props): JSX.Element { - return <div> - <label - htmlFor={`text-${name}`} - style={{ marginLeft: "0.5em", fontWeight: "bold" }} - > {label}</label> - <NiceSelect> - <select name={name} onChange={(e) => { - console.log(e.currentTarget.value, value) - onChange(e.currentTarget.value) - }}> - {value !== undefined ? <option selected> - {list[value]} - </option> : <option selected disabled> - Select one option - </option>} - {Object.keys(list) - .filter((l) => l !== value) - .map(key => <option value={key} key={key}>{list[key]}</option>) - } - </select> - </NiceSelect> - {description && <span - style={{ - color: "#383838", - fontSize: "smaller", - display: "block", - marginLeft: "2em", - }} - > - {description} - </span>} - - </div> - +export function SelectList({ + name, + value, + list, + canBeNull, + onChange, + label, + description, +}: Props): JSX.Element { + return ( + <div> + <label + htmlFor={`text-${name}`} + style={{ marginLeft: "0.5em", fontWeight: "bold" }} + > + {" "} + {label} + </label> + <NiceSelect> + <select + name={name} + onChange={(e) => { + console.log(e.currentTarget.value, value); + onChange(e.currentTarget.value); + }} + > + {value !== undefined ? ( + <option selected>{list[value]}</option> + ) : ( + <option selected disabled> + Select one option + </option> + )} + {Object.keys(list) + .filter((l) => l !== value) + .map((key) => ( + <option value={key} key={key}> + {list[key]} + </option> + ))} + </select> + </NiceSelect> + {description && ( + <span + style={{ + color: "#383838", + fontSize: "smaller", + display: "block", + marginLeft: "2em", + }} + > + {description} + </span> + )} + </div> + ); } diff --git a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx index 991e97c94..1917d5627 100644 --- a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx +++ b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx @@ -14,18 +14,33 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountString, Timestamp, Transaction, TransactionType } from '@gnu-taler/taler-util'; -import { format, formatDistance } from 'date-fns'; -import { h } from 'preact'; -import imageBank from '../../static/img/ri-bank-line.svg'; -import imageHandHeart from '../../static/img/ri-hand-heart-line.svg'; -import imageRefresh from '../../static/img/ri-refresh-line.svg'; -import imageRefund from '../../static/img/ri-refund-2-line.svg'; -import imageShoppingCart from '../../static/img/ri-shopping-cart-line.svg'; +import { + AmountString, + Timestamp, + Transaction, + TransactionType, +} from "@gnu-taler/taler-util"; +import { format, formatDistance } from "date-fns"; +import { h } from "preact"; +import imageBank from "../../static/img/ri-bank-line.svg"; +import imageHandHeart from "../../static/img/ri-hand-heart-line.svg"; +import imageRefresh from "../../static/img/ri-refresh-line.svg"; +import imageRefund from "../../static/img/ri-refund-2-line.svg"; +import imageShoppingCart from "../../static/img/ri-shopping-cart-line.svg"; import { Pages } from "../NavigationBar"; -import { Column, ExtraLargeText, HistoryRow, SmallLightText, LargeText, LightText } from './styled/index'; +import { + Column, + ExtraLargeText, + HistoryRow, + SmallLightText, + LargeText, + LightText, +} from "./styled/index"; -export function TransactionItem(props: { tx: Transaction, multiCurrency: boolean }): JSX.Element { +export function TransactionItem(props: { + tx: Transaction; + multiCurrency: boolean; +}): JSX.Element { const tx = props.tx; switch (tx.type) { case TransactionType.Withdrawal: @@ -112,20 +127,26 @@ export function TransactionItem(props: { tx: Transaction, multiCurrency: boolean function TransactionLayout(props: TransactionLayoutProps): JSX.Element { const date = new Date(props.timestamp.t_ms); - const dateStr = format(date, 'dd MMM, hh:mm') + const dateStr = format(date, "dd MMM, hh:mm"); return ( - <HistoryRow href={Pages.transaction.replace(':tid', props.id)}> + <HistoryRow href={Pages.transaction.replace(":tid", props.id)}> <img src={props.iconPath} /> <Column> <LargeText> <div>{props.title}</div> - {props.subtitle && <div style={{color:'gray', fontSize:'medium', marginTop: 5}}>{props.subtitle}</div>} + {props.subtitle && ( + <div style={{ color: "gray", fontSize: "medium", marginTop: 5 }}> + {props.subtitle} + </div> + )} </LargeText> - {props.pending && - <LightText style={{ marginTop: 5, marginBottom: 5 }}>Waiting for confirmation</LightText> - } - <SmallLightText style={{marginTop:5 }}>{dateStr}</SmallLightText> + {props.pending && ( + <LightText style={{ marginTop: 5, marginBottom: 5 }}> + Waiting for confirmation + </LightText> + )} + <SmallLightText style={{ marginTop: 5 }}>{dateStr}</SmallLightText> </Column> <TransactionAmount pending={props.pending} @@ -170,14 +191,18 @@ function TransactionAmount(props: TransactionAmountProps): JSX.Element { sign = ""; } return ( - <Column style={{ - textAlign: 'center', - color: - props.pending ? "gray" : - (sign === '+' ? 'darkgreen' : - (sign === '-' ? 'darkred' : - undefined)) - }}> + <Column + style={{ + textAlign: "center", + color: props.pending + ? "gray" + : sign === "+" + ? "darkgreen" + : sign === "-" + ? "darkred" + : undefined, + }} + > <ExtraLargeText> {sign} {amount} @@ -187,4 +212,3 @@ function TransactionAmount(props: TransactionAmountProps): JSX.Element { </Column> ); } - diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index 65c1f49e9..8b36dbd31 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -14,18 +14,17 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - // need to import linaria types, otherwise compiler will complain -import type * as Linaria from '@linaria/core'; +import type * as Linaria from "@linaria/core"; -import { styled } from '@linaria/react'; +import { styled } from "@linaria/react"; export const PaymentStatus = styled.div<{ color: string }>` padding: 5px; border-radius: 5px; color: white; - background-color: ${p => p.color}; -` + background-color: ${(p) => p.color}; +`; export const WalletAction = styled.div` display: flex; @@ -36,9 +35,9 @@ export const WalletAction = styled.div` margin: auto; height: 100%; - + & h1:first-child { - margin-top: 0; + margin-top: 0; } section { margin-bottom: 2em; @@ -47,7 +46,7 @@ export const WalletAction = styled.div` margin-left: 8px; } } -` +`; export const WalletActionOld = styled.section` border: solid 5px black; border-radius: 10px; @@ -59,17 +58,17 @@ export const WalletActionOld = styled.section` margin: auto; height: 100%; - + & h1:first-child { - margin-top: 0; + margin-top: 0; } -` +`; export const DateSeparator = styled.div` color: gray; - margin: .2em; + margin: 0.2em; margin-top: 1em; -` +`; export const WalletBox = styled.div<{ noPadding?: boolean }>` display: flex; flex-direction: column; @@ -79,10 +78,10 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>` width: 400px; } & > section { - padding-left: ${({ noPadding }) => noPadding ? '0px' : '8px'}; - padding-right: ${({ noPadding }) => noPadding ? '0px' : '8px'}; + padding-left: ${({ noPadding }) => (noPadding ? "0px" : "8px")}; + padding-right: ${({ noPadding }) => (noPadding ? "0px" : "8px")}; // this margin will send the section up when used with a header - margin-bottom: auto; + margin-bottom: auto; overflow: auto; table td { @@ -128,13 +127,13 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>` margin-left: 8px; } } -` +`; export const Middle = styled.div` - justify-content: space-around; - display: flex; - flex-direction: column; - height: 100%; -` + justify-content: space-around; + display: flex; + flex-direction: column; + height: 100%; +`; export const PopupBox = styled.div<{ noPadding?: boolean }>` height: 290px; @@ -144,9 +143,9 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>` justify-content: space-between; & > section { - padding: ${({ noPadding }) => noPadding ? '0px' : '8px'}; + padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")}; // this margin will send the section up when used with a header - margin-bottom: auto; + margin-bottom: auto; overflow-y: auto; table td { @@ -201,8 +200,7 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>` margin-left: 8px; } } - -` +`; export const Button = styled.button<{ upperCased?: boolean }>` display: inline-block; @@ -214,7 +212,7 @@ export const Button = styled.button<{ upperCased?: boolean }>` cursor: pointer; user-select: none; box-sizing: border-box; - text-transform: ${({ upperCased }) => upperCased ? 'uppercase' : 'none'}; + text-transform: ${({ upperCased }) => (upperCased ? "uppercase" : "none")}; font-family: inherit; font-size: 100%; @@ -223,7 +221,7 @@ export const Button = styled.button<{ upperCased?: boolean }>` color: rgba(0, 0, 0, 0.8); /* rgba supported */ border: 1px solid #999; /*IE 6/7/8*/ border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/ - background-color: '#e6e6e6'; + background-color: "#e6e6e6"; text-decoration: none; border-radius: 2px; @@ -263,7 +261,7 @@ export const Link = styled.a<{ upperCased?: boolean }>` cursor: pointer; user-select: none; box-sizing: border-box; - text-transform: ${({ upperCased }) => upperCased ? 'uppercase' : 'none'}; + text-transform: ${({ upperCased }) => (upperCased ? "uppercase" : "none")}; font-family: inherit; font-size: 100%; @@ -304,9 +302,9 @@ export const FontIcon = styled.div` text-align: center; font-weight: bold; /* vertical-align: text-top; */ -` +`; export const ButtonBox = styled(Button)` - padding: .5em; + padding: 0.5em; width: fit-content; height: 2em; @@ -322,89 +320,87 @@ export const ButtonBox = styled(Button)` border-radius: 4px; border-color: black; color: black; -` - +`; const ButtonVariant = styled(Button)` color: white; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -` +`; export const ButtonPrimary = styled(ButtonVariant)` background-color: rgb(66, 184, 221); -` +`; export const ButtonBoxPrimary = styled(ButtonBox)` color: rgb(66, 184, 221); border-color: rgb(66, 184, 221); -` +`; export const ButtonSuccess = styled(ButtonVariant)` background-color: #388e3c; -` +`; export const LinkSuccess = styled(Link)` color: #388e3c; -` +`; export const ButtonBoxSuccess = styled(ButtonBox)` color: #388e3c; border-color: #388e3c; -` +`; export const ButtonWarning = styled(ButtonVariant)` background-color: rgb(223, 117, 20); -` +`; export const LinkWarning = styled(Link)` color: rgb(223, 117, 20); -` +`; export const ButtonBoxWarning = styled(ButtonBox)` color: rgb(223, 117, 20); border-color: rgb(223, 117, 20); -` +`; export const ButtonDestructive = styled(ButtonVariant)` background-color: rgb(202, 60, 60); -` +`; export const ButtonBoxDestructive = styled(ButtonBox)` color: rgb(202, 60, 60); border-color: rgb(202, 60, 60); -` - +`; export const BoldLight = styled.div` -color: gray; -font-weight: bold; -` + color: gray; + font-weight: bold; +`; export const Centered = styled.div` text-align: center; & > :not(:first-child) { margin-top: 15px; } -` +`; export const Row = styled.div` display: flex; margin: 0.5em 0; justify-content: space-between; padding: 0.5em; -` +`; export const Row2 = styled.div` display: flex; /* margin: 0.5em 0; */ justify-content: space-between; padding: 0.5em; -` +`; export const Column = styled.div` display: flex; flex-direction: column; margin: 0em 1em; justify-content: space-between; -` +`; export const RowBorderGray = styled(Row)` border: 1px solid gray; /* border-radius: 0.5em; */ -` +`; export const RowLightBorderGray = styled(Row2)` border: 1px solid lightgray; @@ -414,7 +410,7 @@ export const RowLightBorderGray = styled(Row2)` border: 1px solid lightgray; background-color: red; } -` +`; export const HistoryRow = styled.a` text-decoration: none; @@ -423,7 +419,7 @@ export const HistoryRow = styled.a` display: flex; justify-content: space-between; padding: 0.5em; - + border: 1px solid lightgray; border-top: 0px; @@ -439,7 +435,7 @@ export const HistoryRow = styled.a` margin-left: auto; align-self: center; } -` +`; export const ListOfProducts = styled.div` & > div > a > img { @@ -453,62 +449,62 @@ export const ListOfProducts = styled.div` margin-right: auto; margin-left: 1em; } -` +`; export const LightText = styled.div` color: gray; -` +`; export const WarningText = styled.div` color: rgb(223, 117, 20); -` +`; export const SmallText = styled.div` - font-size: small; -` + font-size: small; +`; export const LargeText = styled.div` - font-size: large; -` + font-size: large; +`; export const ExtraLargeText = styled.div` - font-size: x-large; -` + font-size: x-large; +`; export const SmallLightText = styled(SmallText)` color: gray; -` +`; export const CenteredText = styled.div` white-space: nowrap; text-align: center; -` +`; export const CenteredBoldText = styled(CenteredText)` white-space: nowrap; text-align: center; font-weight: bold; color: ${((props: any): any => String(props.color) as any) as any}; -` +`; export const Input = styled.div<{ invalid?: boolean }>` & label { display: block; padding: 5px; - color: ${({ invalid }) => !invalid ? 'inherit' : 'red'} + color: ${({ invalid }) => (!invalid ? "inherit" : "red")}; } & input { display: block; padding: 5px; width: calc(100% - 4px - 10px); - border-color: ${({ invalid }) => !invalid ? 'inherit' : 'red'} + border-color: ${({ invalid }) => (!invalid ? "inherit" : "red")}; } -` +`; export const InputWithLabel = styled.div<{ invalid?: boolean }>` & label { display: block; padding: 5px; - color: ${({ invalid }) => !invalid ? 'inherit' : 'red'} + color: ${({ invalid }) => (!invalid ? "inherit" : "red")}; } & > div { position: relative; @@ -516,20 +512,20 @@ export const InputWithLabel = styled.div<{ invalid?: boolean }>` top: 0px; bottom: 0px; - & > div { + & > div { position: absolute; background-color: lightgray; padding: 5px; margin: 2px; } - & > input { + & > input { flex: 1; - padding: 5px; - border-color: ${({ invalid }) => !invalid ? 'inherit' : 'red'} + padding: 5px; + border-color: ${({ invalid }) => (!invalid ? "inherit" : "red")}; } } -` +`; export const ErrorBox = styled.div` border: 2px solid #f5c6cb; @@ -555,22 +551,22 @@ export const ErrorBox = styled.div` width: 28px; } } -` +`; export const SuccessBox = styled(ErrorBox)` color: #0f5132; background-color: #d1e7dd; border-color: #badbcc; -` +`; export const WarningBox = styled(ErrorBox)` color: #664d03; background-color: #fff3cd; border-color: #ffecb5; -` +`; export const PopupNavigation = styled.div<{ devMode?: boolean }>` - background-color:#0042b2; + background-color: #0042b2; height: 35px; justify-content: space-around; display: flex; @@ -582,7 +578,7 @@ export const PopupNavigation = styled.div<{ devMode?: boolean }>` & > div > a { color: #f8faf7; display: inline-block; - width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5}); + width: calc(400px / ${({ devMode }) => (!devMode ? 4 : 5)}); text-align: center; text-decoration: none; vertical-align: middle; @@ -597,7 +593,6 @@ export const PopupNavigation = styled.div<{ devMode?: boolean }>` `; export const NiceSelect = styled.div` - & > select { -webkit-appearance: none; -moz-appearance: none; @@ -617,19 +612,19 @@ export const NiceSelect = styled.div` display: flex; /* width: 10em; */ overflow: hidden; - border-radius: .25em; + border-radius: 0.25em; &::after { - content: '\u25BC'; + content: "\u25BC"; position: absolute; top: 0; right: 0; padding: 0.5em 1em; cursor: pointer; pointer-events: none; - -webkit-transition: .25s all ease; - -o-transition: .25s all ease; - transition: .25s all ease; + -webkit-transition: 0.25s all ease; + -o-transition: 0.25s all ease; + transition: 0.25s all ease; } &:hover::after { @@ -639,7 +634,7 @@ export const NiceSelect = styled.div` &::-ms-expand { display: none; } -` +`; export const Outlined = styled.div` border: 2px solid #388e3c; @@ -647,13 +642,12 @@ export const Outlined = styled.div` width: fit-content; border-radius: 2px; color: #388e3c; -` +`; /* { width: "1.5em", height: "1.5em", verticalAlign: "middle" } */ export const CheckboxSuccess = styled.input` vertical-align: center; - -` +`; export const TermsSection = styled.a` border: 1px solid black; @@ -664,13 +658,13 @@ export const TermsSection = styled.a` text-decoration: none; color: inherit; flex-direction: column; - + display: flex; &[data-open="true"] { - display: flex; + display: flex; } &[data-open="false"] > *:not(:first-child) { - display: none; + display: none; } header { @@ -681,11 +675,11 @@ export const TermsSection = styled.a` height: auto; } - &[data-open="true"] header:after { - content: '\\2227'; + &[data-open="true"] header:after { + content: "\\2227"; } - &[data-open="false"] header:after { - content: '\\2228'; + &[data-open="false"] header:after { + content: "\\2228"; } `; @@ -712,13 +706,13 @@ export const TermsOfService = styled.div` padding: 1em; margin-top: 2px; margin-bottom: 2px; - + display: flex; &[data-open="true"] { - display: flex; + display: flex; } &[data-open="false"] > *:not(:first-child) { - display: none; + display: none; } header { @@ -729,22 +723,20 @@ export const TermsOfService = styled.div` height: auto; } - &[data-open="true"] > header:after { - content: '\\2227'; + &[data-open="true"] > header:after { + content: "\\2227"; } - &[data-open="false"] > header:after { - content: '\\2228'; + &[data-open="false"] > header:after { + content: "\\2228"; } } - -` +`; export const StyledCheckboxLabel = styled.div` color: green; text-transform: uppercase; /* font-weight: bold; */ text-align: center; span { - input { display: none; opacity: 0; @@ -758,7 +750,7 @@ export const StyledCheckboxLabel = styled.div` margin-right: 1em; border-radius: 2px; border: 2px solid currentColor; - + svg { transition: transform 0.1s ease-in 25ms; transform: scale(0); @@ -776,12 +768,11 @@ export const StyledCheckboxLabel = styled.div` } input:disabled + div { color: #959495; - }; + } input:disabled + div + label { color: #959495; - }; + } input:focus + div + label { box-shadow: 0 0 0 0.05em #fff, 0 0 0.15em 0.1em currentColor; } - -`
\ No newline at end of file +`; diff --git a/packages/taler-wallet-webextension/src/context/devContext.ts b/packages/taler-wallet-webextension/src/context/devContext.ts index ea2ba4ceb..0344df057 100644 --- a/packages/taler-wallet-webextension/src/context/devContext.ts +++ b/packages/taler-wallet-webextension/src/context/devContext.ts @@ -15,13 +15,13 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createContext, h, VNode } from 'preact' -import { useContext, useState } from 'preact/hooks' -import { useLocalStorage } from '../hooks/useLocalStorage'; +import { createContext, h, VNode } from "preact"; +import { useContext, useState } from "preact/hooks"; +import { useLocalStorage } from "../hooks/useLocalStorage"; interface Type { devMode: boolean; @@ -29,14 +29,14 @@ interface Type { } const Context = createContext<Type>({ devMode: false, - toggleDevMode: () => null -}) + toggleDevMode: () => null, +}); export const useDevContext = (): Type => useContext(Context); export const DevContextProvider = ({ children }: { children: any }): VNode => { - const [value, setter] = useLocalStorage('devMode') - const devMode = value === "true" - const toggleDevMode = () => setter(v => !v ? "true" : undefined) + const [value, setter] = useLocalStorage("devMode"); + const devMode = value === "true"; + const toggleDevMode = () => setter((v) => (!v ? "true" : undefined)); return h(Context.Provider, { value: { devMode, toggleDevMode }, children }); -} +}; diff --git a/packages/taler-wallet-webextension/src/context/translation.ts b/packages/taler-wallet-webextension/src/context/translation.ts index 5f57958de..105da9dcf 100644 --- a/packages/taler-wallet-webextension/src/context/translation.ts +++ b/packages/taler-wallet-webextension/src/context/translation.ts @@ -15,54 +15,58 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createContext, h, VNode } from 'preact' -import { useContext, useEffect } from 'preact/hooks' -import { useLang } from '../hooks/useLang' +import { createContext, h, VNode } from "preact"; +import { useContext, useEffect } from "preact/hooks"; +import { useLang } from "../hooks/useLang"; //@ts-ignore: type declaration import * as jedLib from "jed"; import { strings } from "../i18n/strings"; -import { setupI18n } from '@gnu-taler/taler-util'; +import { setupI18n } from "@gnu-taler/taler-util"; interface Type { lang: string; changeLanguage: (l: string) => void; } const initial = { - lang: 'en', + lang: "en", changeLanguage: () => { // do not change anything - } -} -const Context = createContext<Type>(initial) + }, +}; +const Context = createContext<Type>(initial); interface Props { - initial?: string, - children: any, - forceLang?: string + initial?: string; + children: any; + forceLang?: string; } -//we use forceLang when we don't want to use the saved state, but sone forced -//runtime lang predefined lang -export const TranslationProvider = ({ initial, children, forceLang }: Props): VNode => { - const [lang, changeLanguage] = useLang(initial) +//we use forceLang when we don't want to use the saved state, but sone forced +//runtime lang predefined lang +export const TranslationProvider = ({ + initial, + children, + forceLang, +}: Props): VNode => { + const [lang, changeLanguage] = useLang(initial); useEffect(() => { if (forceLang) { - changeLanguage(forceLang) + changeLanguage(forceLang); } - }) - useEffect(()=> { - setupI18n(lang, strings) - },[lang]) + }); + useEffect(() => { + setupI18n(lang, strings); + }, [lang]); if (forceLang) { - setupI18n(forceLang, strings) + setupI18n(forceLang, strings); } else { - setupI18n(lang, strings) + setupI18n(lang, strings); } return h(Context.Provider, { value: { lang, changeLanguage }, children }); -} +}; export const useTranslationContext = (): Type => useContext(Context); diff --git a/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx b/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx index 622e7950f..c2d360d3b 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.stories.tsx @@ -15,150 +15,156 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util'; -import { createExample } from '../test-utils'; -import { PaymentRequestView as TestedComponent } from './Pay'; +import { ContractTerms, PreparePayResultType } from "@gnu-taler/taler-util"; +import { createExample } from "../test-utils"; +import { PaymentRequestView as TestedComponent } from "./Pay"; export default { - title: 'cta/pay', + title: "cta/pay", component: TestedComponent, - argTypes: { - }, + argTypes: {}, }; export const NoBalance = createExample(TestedComponent, { payStatus: { status: PreparePayResultType.InsufficientBalance, - noncePriv: '', + noncePriv: "", proposalId: "proposal1234", - contractTerms: { + contractTerms: ({ merchant: { - name: 'someone' + name: "someone", }, - summary: 'some beers', - amount: 'USD:10', - } as Partial<ContractTerms> as any, - amountRaw: 'USD:10', - } + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms>) as any, + amountRaw: "USD:10", + }, }); export const NoEnoughBalance = createExample(TestedComponent, { payStatus: { status: PreparePayResultType.InsufficientBalance, - noncePriv: '', + noncePriv: "", proposalId: "proposal1234", - contractTerms: { + contractTerms: ({ merchant: { - name: 'someone' + name: "someone", }, - summary: 'some beers', - amount: 'USD:10', - } as Partial<ContractTerms> as any, - amountRaw: 'USD:10', + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms>) as any, + amountRaw: "USD:10", }, balance: { - currency: 'USD', + currency: "USD", fraction: 40000000, - value: 9 - } + value: 9, + }, }); export const PaymentPossible = createExample(TestedComponent, { - uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0', + uri: + "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0", payStatus: { status: PreparePayResultType.PaymentPossible, - amountEffective: 'USD:10', - amountRaw: 'USD:10', - noncePriv: '', - contractTerms: { - nonce: '123213123', + amountEffective: "USD:10", + amountRaw: "USD:10", + noncePriv: "", + contractTerms: ({ + nonce: "123213123", merchant: { - name: 'someone' + name: "someone", }, - amount: 'USD:10', - summary: 'some beers', - } as Partial<ContractTerms> as any, - contractTermsHash: '123456', - proposalId: 'proposal1234' - } + amount: "USD:10", + summary: "some beers", + } as Partial<ContractTerms>) as any, + contractTermsHash: "123456", + proposalId: "proposal1234", + }, }); export const PaymentPossibleWithFee = createExample(TestedComponent, { - uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0', + uri: + "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0", payStatus: { status: PreparePayResultType.PaymentPossible, - amountEffective: 'USD:10.20', - amountRaw: 'USD:10', - noncePriv: '', - contractTerms: { - nonce: '123213123', + amountEffective: "USD:10.20", + amountRaw: "USD:10", + noncePriv: "", + contractTerms: ({ + nonce: "123213123", merchant: { - name: 'someone' + name: "someone", }, - amount: 'USD:10', - summary: 'some beers', - } as Partial<ContractTerms> as any, - contractTermsHash: '123456', - proposalId: 'proposal1234' - } + amount: "USD:10", + summary: "some beers", + } as Partial<ContractTerms>) as any, + contractTermsHash: "123456", + proposalId: "proposal1234", + }, }); export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, { payStatus: { status: PreparePayResultType.AlreadyConfirmed, - amountEffective: 'USD:10', - amountRaw: 'USD:10', - contractTerms: { + amountEffective: "USD:10", + amountRaw: "USD:10", + contractTerms: ({ merchant: { - name: 'someone' + name: "someone", }, - fulfillment_message: 'congratulations! you are looking at the fulfillment message! ', - summary: 'some beers', - amount: 'USD:10', - } as Partial<ContractTerms> as any, - contractTermsHash: '123456', - proposalId: 'proposal1234', + fulfillment_message: + "congratulations! you are looking at the fulfillment message! ", + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms>) as any, + contractTermsHash: "123456", + proposalId: "proposal1234", paid: false, - } + }, }); -export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent, { - payStatus: { - status: PreparePayResultType.AlreadyConfirmed, - amountEffective: 'USD:10', - amountRaw: 'USD:10', - contractTerms: { - merchant: { - name: 'someone' - }, - summary: 'some beers', - amount: 'USD:10', - } as Partial<ContractTerms> as any, - contractTermsHash: '123456', - proposalId: 'proposal1234', - paid: false, - } -}); +export const AlreadyConfirmedWithoutFullfilment = createExample( + TestedComponent, + { + payStatus: { + status: PreparePayResultType.AlreadyConfirmed, + amountEffective: "USD:10", + amountRaw: "USD:10", + contractTerms: ({ + merchant: { + name: "someone", + }, + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms>) as any, + contractTermsHash: "123456", + proposalId: "proposal1234", + paid: false, + }, + }, +); export const AlreadyPaid = createExample(TestedComponent, { payStatus: { status: PreparePayResultType.AlreadyConfirmed, - amountEffective: 'USD:10', - amountRaw: 'USD:10', - contractTerms: { + amountEffective: "USD:10", + amountRaw: "USD:10", + contractTerms: ({ merchant: { - name: 'someone' + name: "someone", }, - fulfillment_message: 'congratulations! you are looking at the fulfillment message! ', - summary: 'some beers', - amount: 'USD:10', - } as Partial<ContractTerms> as any, - contractTermsHash: '123456', - proposalId: 'proposal1234', + fulfillment_message: + "congratulations! you are looking at the fulfillment message! ", + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms>) as any, + contractTermsHash: "123456", + proposalId: "proposal1234", paid: true, - } + }, }); diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 675b14ff9..1023013d2 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -24,18 +24,36 @@ */ // import * as i18n from "../i18n"; -import { AmountJson, AmountLike, Amounts, ConfirmPayResult, ConfirmPayResultDone, ConfirmPayResultType, ContractTerms, getJsonI18n, i18n, PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util"; -import { Fragment, JSX, VNode } from "preact"; +import { + AmountJson, + AmountLike, + Amounts, + ConfirmPayResult, + ConfirmPayResultDone, + ConfirmPayResultType, + ContractTerms, + i18n, + PreparePayResult, + PreparePayResultType, +} from "@gnu-taler/taler-util"; +import { h, Fragment, JSX, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { LogoHeader } from "../components/LogoHeader"; import { Part } from "../components/Part"; import { QR } from "../components/QR"; -import { ButtonSuccess, ErrorBox, LinkSuccess, SuccessBox, WalletAction, WarningBox } from "../components/styled"; +import { + ButtonSuccess, + ErrorBox, + LinkSuccess, + SuccessBox, + WalletAction, + WarningBox, +} from "../components/styled"; import { useBalances } from "../hooks/useBalances"; import * as wxApi from "../wxApi"; interface Props { - talerPayUri?: string + talerPayUri?: string; } // export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) { @@ -64,7 +82,9 @@ interface Props { // </section> // } -const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => { +const doPayment = async ( + payStatus: PreparePayResult, +): Promise<ConfirmPayResultDone> => { if (payStatus.status !== "payment-possible") { throw Error(`invalid state: ${payStatus.status}`); } @@ -80,18 +100,29 @@ const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultD return res; }; - - export function PayPage({ talerPayUri }: Props): JSX.Element { - const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined); - const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined); + const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>( + undefined, + ); + const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>( + undefined, + ); const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined); - const balance = useBalances() - const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || []) + const balance = useBalances(); + const balanceWithoutError = balance?.hasError + ? [] + : balance?.response.balances || []; - const foundBalance = balanceWithoutError.find(b => payStatus && Amounts.parseOrThrow(b.available).currency === Amounts.parseOrThrow(payStatus?.amountRaw).currency) - const foundAmount = foundBalance ? Amounts.parseOrThrow(foundBalance.available) : undefined + const foundBalance = balanceWithoutError.find( + (b) => + payStatus && + Amounts.parseOrThrow(b.available).currency === + Amounts.parseOrThrow(payStatus?.amountRaw).currency, + ); + const foundAmount = foundBalance + ? Amounts.parseOrThrow(foundBalance.available) + : undefined; useEffect(() => { if (!talerPayUri) return; @@ -101,7 +132,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayStatus(p); } catch (e) { if (e instanceof Error) { - setPayErrMsg(e.message) + setPayErrMsg(e.message); } } }; @@ -109,30 +140,28 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { }, [talerPayUri]); if (!talerPayUri) { - return <span>missing pay uri</span> + return <span>missing pay uri</span>; } if (!payStatus) { if (payErrMsg) { - return <WalletAction> - <LogoHeader /> - <h2> - {i18n.str`Digital cash payment`} - </h2> - <section> - <p>Could not get the payment information for this order</p> - <ErrorBox> - {payErrMsg} - </ErrorBox> - </section> - </WalletAction> + return ( + <WalletAction> + <LogoHeader /> + <h2>{i18n.str`Digital cash payment`}</h2> + <section> + <p>Could not get the payment information for this order</p> + <ErrorBox>{payErrMsg}</ErrorBox> + </section> + </WalletAction> + ); } return <span>Loading payment information ...</span>; } const onClick = async () => { try { - const res = await doPayment(payStatus) + const res = await doPayment(payStatus); setPayResult(res); } catch (e) { console.error(e); @@ -140,13 +169,18 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayErrMsg(e.message); } } + }; - } - - return <PaymentRequestView uri={talerPayUri} - payStatus={payStatus} payResult={payResult} - onClick={onClick} payErrMsg={payErrMsg} - balance={foundAmount} />; + return ( + <PaymentRequestView + uri={talerPayUri} + payStatus={payStatus} + payResult={payResult} + onClick={onClick} + payErrMsg={payErrMsg} + balance={foundAmount} + /> + ); } export interface PaymentRequestViewProps { @@ -157,7 +191,14 @@ export interface PaymentRequestViewProps { uri: string; balance: AmountJson | undefined; } -export function PaymentRequestView({ uri, payStatus, payResult, onClick, payErrMsg, balance }: PaymentRequestViewProps) { +export function PaymentRequestView({ + uri, + payStatus, + payResult, + onClick, + payErrMsg, + balance, +}: PaymentRequestViewProps) { let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw); const contractTerms: ContractTerms = payStatus.contractTerms; @@ -185,116 +226,174 @@ export function PaymentRequestView({ uri, payStatus, payResult, onClick, payErrM } function Alternative() { - const [showQR, setShowQR] = useState<boolean>(false) - const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri - return <section> - <LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}> - {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`} - </LinkSuccess> - {showQR && <div> - <QR text={privateUri} /> - Scan the QR code or <a href={privateUri}>click here</a> - </div>} - </section> + const [showQR, setShowQR] = useState<boolean>(false); + const privateUri = + payStatus.status !== PreparePayResultType.AlreadyConfirmed + ? `${uri}&n=${payStatus.noncePriv}` + : uri; + return ( + <section> + <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}> + {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`} + </LinkSuccess> + {showQR && ( + <div> + <QR text={privateUri} /> + Scan the QR code or <a href={privateUri}>click here</a> + </div> + )} + </section> + ); } function ButtonsSection() { if (payResult) { if (payResult.type === ConfirmPayResultType.Pending) { - return <section> - <div> - <p>Processing...</p> - </div> - </section> + return ( + <section> + <div> + <p>Processing...</p> + </div> + </section> + ); } - return null + return null; } if (payErrMsg) { - return <section> - <div> - <p>Payment failed: {payErrMsg}</p> - <button class="pure-button button-success" onClick={onClick} > - {i18n.str`Retry`} - </button> - </div> - </section> - } - if (payStatus.status === PreparePayResultType.PaymentPossible) { - return <Fragment> + return ( <section> - <ButtonSuccess upperCased onClick={onClick}> - {i18n.str`Pay`} {amountToString(payStatus.amountEffective)} - </ButtonSuccess> + <div> + <p>Payment failed: {payErrMsg}</p> + <button class="pure-button button-success" onClick={onClick}> + {i18n.str`Retry`} + </button> + </div> </section> - <Alternative /> - </Fragment> + ); + } + if (payStatus.status === PreparePayResultType.PaymentPossible) { + return ( + <Fragment> + <section> + <ButtonSuccess upperCased onClick={onClick}> + {i18n.str`Pay`} {amountToString(payStatus.amountEffective)} + </ButtonSuccess> + </section> + <Alternative /> + </Fragment> + ); } if (payStatus.status === PreparePayResultType.InsufficientBalance) { - return <Fragment> - <section> - {balance ? <WarningBox> - Your balance of {amountToString(balance)} is not enough to pay for this purchase - </WarningBox> : <WarningBox> - Your balance is not enough to pay for this purchase. - </WarningBox>} - </section> - <section> - <ButtonSuccess upperCased> - {i18n.str`Withdraw digital cash`} - </ButtonSuccess> - </section> - <Alternative /> - </Fragment> + return ( + <Fragment> + <section> + {balance ? ( + <WarningBox> + Your balance of {amountToString(balance)} is not enough to pay + for this purchase + </WarningBox> + ) : ( + <WarningBox> + Your balance is not enough to pay for this purchase. + </WarningBox> + )} + </section> + <section> + <ButtonSuccess upperCased> + {i18n.str`Withdraw digital cash`} + </ButtonSuccess> + </section> + <Alternative /> + </Fragment> + ); } if (payStatus.status === PreparePayResultType.AlreadyConfirmed) { - return <Fragment> - <section> - {payStatus.paid && contractTerms.fulfillment_message && <Part title="Merchant message" text={contractTerms.fulfillment_message} kind='neutral' />} - </section> - {!payStatus.paid && <Alternative />} - </Fragment> + return ( + <Fragment> + <section> + {payStatus.paid && contractTerms.fulfillment_message && ( + <Part + title="Merchant message" + text={contractTerms.fulfillment_message} + kind="neutral" + /> + )} + </section> + {!payStatus.paid && <Alternative />} + </Fragment> + ); } - return <span /> + return <span />; } - return <WalletAction> - <LogoHeader /> - - <h2> - {i18n.str`Digital cash payment`} - </h2> - {payStatus.status === PreparePayResultType.AlreadyConfirmed && - (payStatus.paid ? <SuccessBox> Already paid </SuccessBox> : <WarningBox> Already claimed </WarningBox>) - } - {payResult && payResult.type === ConfirmPayResultType.Done && ( - <SuccessBox> - <h3>Payment complete</h3> - <p>{!payResult.contractTerms.fulfillment_message ? - "You will now be sent back to the merchant you came from." : - payResult.contractTerms.fulfillment_message - }</p> - </SuccessBox> - )} - <section> - {payStatus.status !== PreparePayResultType.InsufficientBalance && Amounts.isNonZero(totalFees) && - <Part big title="Total to pay" text={amountToString(payStatus.amountEffective)} kind='negative' /> - } - <Part big title="Purchase amount" text={amountToString(payStatus.amountRaw)} kind='neutral' /> - {Amounts.isNonZero(totalFees) && <Fragment> - <Part big title="Fee" text={amountToString(totalFees)} kind='negative' /> - </Fragment> - } - <Part title="Merchant" text={contractTerms.merchant.name} kind='neutral' /> - <Part title="Purchase" text={contractTerms.summary} kind='neutral' /> - {contractTerms.order_id && <Part title="Receipt" text={`#${contractTerms.order_id}`} kind='neutral' />} - </section> - <ButtonsSection /> + return ( + <WalletAction> + <LogoHeader /> - </WalletAction> + <h2>{i18n.str`Digital cash payment`}</h2> + {payStatus.status === PreparePayResultType.AlreadyConfirmed && + (payStatus.paid ? ( + <SuccessBox> Already paid </SuccessBox> + ) : ( + <WarningBox> Already claimed </WarningBox> + ))} + {payResult && payResult.type === ConfirmPayResultType.Done && ( + <SuccessBox> + <h3>Payment complete</h3> + <p> + {!payResult.contractTerms.fulfillment_message + ? "You will now be sent back to the merchant you came from." + : payResult.contractTerms.fulfillment_message} + </p> + </SuccessBox> + )} + <section> + {payStatus.status !== PreparePayResultType.InsufficientBalance && + Amounts.isNonZero(totalFees) && ( + <Part + big + title="Total to pay" + text={amountToString(payStatus.amountEffective)} + kind="negative" + /> + )} + <Part + big + title="Purchase amount" + text={amountToString(payStatus.amountRaw)} + kind="neutral" + /> + {Amounts.isNonZero(totalFees) && ( + <Fragment> + <Part + big + title="Fee" + text={amountToString(totalFees)} + kind="negative" + /> + </Fragment> + )} + <Part + title="Merchant" + text={contractTerms.merchant.name} + kind="neutral" + /> + <Part title="Purchase" text={contractTerms.summary} kind="neutral" /> + {contractTerms.order_id && ( + <Part + title="Receipt" + text={`#${contractTerms.order_id}`} + kind="neutral" + /> + )} + </section> + <ButtonsSection /> + </WalletAction> + ); } function amountToString(text: AmountLike) { - const aj = Amounts.jsonifyAmount(text) - const amount = Amounts.stringifyValue(aj, 2) - return `${amount} ${aj.currency}` + const aj = Amounts.jsonifyAmount(text); + const amount = Amounts.stringifyValue(aj, 2); + return `${amount} ${aj.currency}`; } diff --git a/packages/taler-wallet-webextension/src/cta/Refund.stories.tsx b/packages/taler-wallet-webextension/src/cta/Refund.stories.tsx index 88e714cb7..a0abcea58 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Refund.stories.tsx @@ -15,63 +15,61 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { OrderShortInfo } from '@gnu-taler/taler-util'; -import { createExample } from '../test-utils'; -import { View as TestedComponent } from './Refund'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { OrderShortInfo } from "@gnu-taler/taler-util"; +import { createExample } from "../test-utils"; +import { View as TestedComponent } from "./Refund"; export default { - title: 'cta/refund', + title: "cta/refund", component: TestedComponent, - argTypes: { - }, + argTypes: {}, }; export const Complete = createExample(TestedComponent, { applyResult: { - amountEffectivePaid: 'USD:10', - amountRefundGone: 'USD:0', - amountRefundGranted: 'USD:2', - contractTermsHash: 'QWEASDZXC', - info: { - summary: 'tasty cold beer', - contractTermsHash: 'QWEASDZXC', - } as Partial<OrderShortInfo> as any, + amountEffectivePaid: "USD:10", + amountRefundGone: "USD:0", + amountRefundGranted: "USD:2", + contractTermsHash: "QWEASDZXC", + info: ({ + summary: "tasty cold beer", + contractTermsHash: "QWEASDZXC", + } as Partial<OrderShortInfo>) as any, pendingAtExchange: false, proposalId: "proposal123", - } + }, }); export const Partial = createExample(TestedComponent, { applyResult: { - amountEffectivePaid: 'USD:10', - amountRefundGone: 'USD:1', - amountRefundGranted: 'USD:2', - contractTermsHash: 'QWEASDZXC', - info: { - summary: 'tasty cold beer', - contractTermsHash: 'QWEASDZXC', - } as Partial<OrderShortInfo> as any, + amountEffectivePaid: "USD:10", + amountRefundGone: "USD:1", + amountRefundGranted: "USD:2", + contractTermsHash: "QWEASDZXC", + info: ({ + summary: "tasty cold beer", + contractTermsHash: "QWEASDZXC", + } as Partial<OrderShortInfo>) as any, pendingAtExchange: false, proposalId: "proposal123", - } + }, }); export const InProgress = createExample(TestedComponent, { applyResult: { - amountEffectivePaid: 'USD:10', - amountRefundGone: 'USD:1', - amountRefundGranted: 'USD:2', - contractTermsHash: 'QWEASDZXC', - info: { - summary: 'tasty cold beer', - contractTermsHash: 'QWEASDZXC', - } as Partial<OrderShortInfo> as any, + amountEffectivePaid: "USD:10", + amountRefundGone: "USD:1", + amountRefundGranted: "USD:2", + contractTermsHash: "QWEASDZXC", + info: ({ + summary: "tasty cold beer", + contractTermsHash: "QWEASDZXC", + } as Partial<OrderShortInfo>) as any, pendingAtExchange: true, proposalId: "proposal123", - } + }, }); diff --git a/packages/taler-wallet-webextension/src/cta/Refund.tsx b/packages/taler-wallet-webextension/src/cta/Refund.tsx index 943095360..aa11dca6a 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund.tsx +++ b/packages/taler-wallet-webextension/src/cta/Refund.tsx @@ -22,45 +22,46 @@ import * as wxApi from "../wxApi"; import { AmountView } from "../renderHtml"; -import { - ApplyRefundResponse, - Amounts, -} from "@gnu-taler/taler-util"; +import { ApplyRefundResponse, Amounts } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; import { JSX } from "preact/jsx-runtime"; -import { h } from 'preact'; +import { h } from "preact"; interface Props { - talerRefundUri?: string + talerRefundUri?: string; } export interface ViewProps { applyResult: ApplyRefundResponse; } export function View({ applyResult }: ViewProps) { - return <section class="main"> - <h1>GNU Taler Wallet</h1> - <article class="fade"> - <h2>Refund Status</h2> - <p> - The product <em>{applyResult.info.summary}</em> has received a total - effective refund of{" "} - <AmountView amount={applyResult.amountRefundGranted} />. - </p> - {applyResult.pendingAtExchange ? ( - <p>Refund processing is still in progress.</p> - ) : null} - {!Amounts.isZero(applyResult.amountRefundGone) ? ( + return ( + <section class="main"> + <h1>GNU Taler Wallet</h1> + <article class="fade"> + <h2>Refund Status</h2> <p> - The refund amount of{" "} - <AmountView amount={applyResult.amountRefundGone} />{" "} - could not be applied. + The product <em>{applyResult.info.summary}</em> has received a total + effective refund of{" "} + <AmountView amount={applyResult.amountRefundGranted} />. </p> - ) : null} - </article> - </section> + {applyResult.pendingAtExchange ? ( + <p>Refund processing is still in progress.</p> + ) : null} + {!Amounts.isZero(applyResult.amountRefundGone) ? ( + <p> + The refund amount of{" "} + <AmountView amount={applyResult.amountRefundGone} /> could not be + applied. + </p> + ) : null} + </article> + </section> + ); } export function RefundPage({ talerRefundUri }: Props): JSX.Element { - const [applyResult, setApplyResult] = useState<ApplyRefundResponse | undefined>(undefined); + const [applyResult, setApplyResult] = useState< + ApplyRefundResponse | undefined + >(undefined); const [errMsg, setErrMsg] = useState<string | undefined>(undefined); useEffect(() => { diff --git a/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx b/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx index 389b183f0..8da599513 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx @@ -15,45 +15,43 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample } from '../test-utils'; -import { View as TestedComponent } from './Tip'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample } from "../test-utils"; +import { View as TestedComponent } from "./Tip"; export default { - title: 'cta/tip', + title: "cta/tip", component: TestedComponent, - argTypes: { - }, + argTypes: {}, }; export const Accepted = createExample(TestedComponent, { prepareTipResult: { accepted: true, - merchantBaseUrl: '', - exchangeBaseUrl: '', - expirationTimestamp : { - t_ms: 0 + merchantBaseUrl: "", + exchangeBaseUrl: "", + expirationTimestamp: { + t_ms: 0, }, - tipAmountEffective: 'USD:10', - tipAmountRaw: 'USD:5', - walletTipId: 'id' - } + tipAmountEffective: "USD:10", + tipAmountRaw: "USD:5", + walletTipId: "id", + }, }); export const NotYetAccepted = createExample(TestedComponent, { prepareTipResult: { accepted: false, - merchantBaseUrl: 'http://merchant.url/', - exchangeBaseUrl: 'http://exchange.url/', - expirationTimestamp : { - t_ms: 0 + merchantBaseUrl: "http://merchant.url/", + exchangeBaseUrl: "http://exchange.url/", + expirationTimestamp: { + t_ms: 0, }, - tipAmountEffective: 'USD:10', - tipAmountRaw: 'USD:5', - walletTipId: 'id' - } + tipAmountEffective: "USD:10", + tipAmountRaw: "USD:5", + walletTipId: "id", + }, }); diff --git a/packages/taler-wallet-webextension/src/cta/Tip.tsx b/packages/taler-wallet-webextension/src/cta/Tip.tsx index dc1feaed3..0a1c1238c 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip.tsx +++ b/packages/taler-wallet-webextension/src/cta/Tip.tsx @@ -25,43 +25,43 @@ import { PrepareTipResult } from "@gnu-taler/taler-util"; import { AmountView } from "../renderHtml"; import * as wxApi from "../wxApi"; import { JSX } from "preact/jsx-runtime"; -import { h } from 'preact'; +import { h } from "preact"; interface Props { - talerTipUri?: string + talerTipUri?: string; } export interface ViewProps { prepareTipResult: PrepareTipResult; onAccept: () => void; onIgnore: () => void; - } export function View({ prepareTipResult, onAccept, onIgnore }: ViewProps) { - return <section class="main"> - <h1>GNU Taler Wallet</h1> - <article class="fade"> - {prepareTipResult.accepted ? ( - <span> - Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check - your transactions list for more details. - </span> - ) : ( + return ( + <section class="main"> + <h1>GNU Taler Wallet</h1> + <article class="fade"> + {prepareTipResult.accepted ? ( + <span> + Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. + Check your transactions list for more details. + </span> + ) : ( <div> <p> The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is - offering you a tip of{" "} + offering you a tip of{" "} <strong> <AmountView amount={prepareTipResult.tipAmountEffective} /> </strong>{" "} - via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code> + via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code> </p> <button onClick={onAccept}>Accept tip</button> <button onClick={onIgnore}>Ignore</button> </div> )} - </article> - </section> - + </article> + </section> + ); } export function TipPage({ talerTipUri }: Props): JSX.Element { @@ -105,7 +105,11 @@ export function TipPage({ talerTipUri }: Props): JSX.Element { return <span>Loading ...</span>; } - return <View prepareTipResult={prepareTipResult} - onAccept={doAccept} onIgnore={doIgnore} - /> + return ( + <View + prepareTipResult={prepareTipResult} + onAccept={doAccept} + onIgnore={doIgnore} + /> + ); } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx index 5e29a3e39..90df2a27e 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx @@ -15,23 +15,22 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { amountFractionalBase, Amounts } from '@gnu-taler/taler-util'; -import { ExchangeRecord } from '@gnu-taler/taler-wallet-core'; -import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; -import { getMaxListeners } from 'process'; -import { createExample } from '../test-utils'; -import { View as TestedComponent } from './Withdraw'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { amountFractionalBase, Amounts } from "@gnu-taler/taler-util"; +import { ExchangeRecord } from "@gnu-taler/taler-wallet-core"; +import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; +import { getMaxListeners } from "process"; +import { createExample } from "../test-utils"; +import { View as TestedComponent } from "./Withdraw"; export default { - title: 'cta/withdraw', + title: "cta/withdraw", component: TestedComponent, argTypes: { - onSwitchExchange: { action: 'onRetry' }, + onSwitchExchange: { action: "onRetry" }, }, }; @@ -48,7 +47,7 @@ const termsHtml = `<html xmlns="http://www.w3.org/1999/xhtml" lang="en"> </div> </body> </html> -` +`; const termsPlain = ` Terms Of Service **************** @@ -432,7 +431,7 @@ Questions or comments We welcome comments, questions, concerns, or suggestions. Please send us a message on our contact page at legal@taler-systems.com. -` +`; const termsXml = `<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE document PUBLIC "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML" "http://docutils.sourceforge.net/docs/ref/docutils.dtd"> @@ -781,120 +780,131 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?> `; export const NewTerms = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'xml', + type: "xml", document: new DOMParser().parseFromString(termsXml, "text/xml"), }, - status: 'new' + status: "new", }, -}) +}); export const TermsReviewingPLAIN = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'plain', - content: termsPlain + type: "plain", + content: termsPlain, }, - status: 'new' + status: "new", }, - reviewing: true -}) + reviewing: true, +}); export const TermsReviewingHTML = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'html', - href: new URL(`data:text/html;base64,${Buffer.from(termsHtml).toString('base64')}`), + type: "html", + href: new URL( + `data:text/html;base64,${Buffer.from(termsHtml).toString("base64")}`, + ), }, - status: 'new' + status: "new", }, - reviewing: true -}) + reviewing: true, +}); const termsPdf = ` %PDF-1.2 @@ -909,306 +919,330 @@ endobj trailer << /Root 3 0 R >> %%EOF -` +`; export const TermsReviewingPDF = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'pdf', - location: new URL(`data:text/html;base64,${Buffer.from(termsPdf).toString('base64')}`), + type: "pdf", + location: new URL( + `data:text/html;base64,${Buffer.from(termsPdf).toString("base64")}`, + ), }, - status: 'new' + status: "new", }, - reviewing: true -}) - + reviewing: true, +}); export const TermsReviewingXML = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'xml', + type: "xml", document: new DOMParser().parseFromString(termsXml, "text/xml"), }, - status: 'new' + status: "new", }, - reviewing: true -}) + reviewing: true, +}); export const NewTermsAccepted = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'xml', + type: "xml", document: new DOMParser().parseFromString(termsXml, "text/xml"), }, - status: 'new' + status: "new", }, - reviewed: true -}) + reviewed: true, +}); export const TermsShowAgainXML = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'xml', + type: "xml", document: new DOMParser().parseFromString(termsXml, "text/xml"), }, - status: 'new' + status: "new", }, reviewed: true, reviewing: true, -}) +}); export const TermsChanged = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'xml', + type: "xml", document: new DOMParser().parseFromString(termsXml, "text/xml"), }, - status: 'changed' + status: "changed", }, -}) +}); export const TermsNotFound = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { - status: 'notfound' + status: "notfound", }, -}) +}); export const TermsAlreadyAccepted = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: amountFractionalBase * 0.5, - value: 0 + value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { - status: 'accepted' + status: "accepted", }, -}) - +}); export const WithoutFee = createExample(TestedComponent, { - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'exchange.demo.taler.net', - paytoUris: ['asd'], - }, { - currency: 'USD', - exchangeBaseUrl: 'exchange.test.taler.net', - paytoUris: ['asd'], - }], - exchangeBaseUrl: 'exchange.demo.taler.net', + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "exchange.demo.taler.net", + paytoUris: ["asd"], + }, + { + currency: "USD", + exchangeBaseUrl: "exchange.test.taler.net", + paytoUris: ["asd"], + }, + ], + exchangeBaseUrl: "exchange.demo.taler.net", details: { - content: '', - contentType: '', - currentEtag: '', + content: "", + contentType: "", + currentEtag: "", acceptedEtag: undefined, }, withdrawalFee: { - currency: 'USD', + currency: "USD", fraction: 0, value: 0, }, amount: { - currency: 'USD', + currency: "USD", value: 2, - fraction: 10000000 + fraction: 10000000, }, - onSwitchExchange: async () => { }, + onSwitchExchange: async () => {}, terms: { value: { - type: 'xml', + type: "xml", document: new DOMParser().parseFromString(termsXml, "text/xml"), }, - status: 'accepted', - } -})
\ No newline at end of file + status: "accepted", + }, +}); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index 6ef72cbe6..603dafcde 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -21,21 +21,39 @@ * @author Florian Dold */ -import { AmountJson, Amounts, ExchangeListItem, GetExchangeTosResult, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util'; -import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; +import { + AmountJson, + Amounts, + ExchangeListItem, + GetExchangeTosResult, + i18n, + WithdrawUriInfoResponse, +} from "@gnu-taler/taler-util"; +import { VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { Fragment } from 'preact/jsx-runtime'; -import { CheckboxOutlined } from '../components/CheckboxOutlined'; -import { ExchangeXmlTos } from '../components/ExchangeToS'; -import { LogoHeader } from '../components/LogoHeader'; -import { Part } from '../components/Part'; -import { SelectList } from '../components/SelectList'; -import { ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction, WarningText } from '../components/styled'; -import { useAsyncAsHook } from '../hooks/useAsyncAsHook'; +import { Fragment } from "preact/jsx-runtime"; +import { CheckboxOutlined } from "../components/CheckboxOutlined"; +import { ExchangeXmlTos } from "../components/ExchangeToS"; +import { LogoHeader } from "../components/LogoHeader"; +import { Part } from "../components/Part"; +import { SelectList } from "../components/SelectList"; +import { + ButtonSuccess, + ButtonWarning, + LinkSuccess, + TermsOfService, + WalletAction, + WarningText, +} from "../components/styled"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { - acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, setExchangeTosAccepted, listExchanges, getExchangeTos + acceptWithdrawal, + getExchangeTos, + getExchangeWithdrawalInfo, + getWithdrawalDetailsForUri, + listExchanges, + setExchangeTosAccepted, } from "../wxApi"; -import { wxMain } from '../wxBackend.js'; interface Props { talerWithdrawUri?: string; @@ -58,145 +76,193 @@ export interface ViewProps { status: TermsStatus; }; knownExchanges: ExchangeListItem[]; +} -}; - -type TermsStatus = 'new' | 'accepted' | 'changed' | 'notfound'; +type TermsStatus = "new" | "accepted" | "changed" | "notfound"; -type TermsDocument = TermsDocumentXml | TermsDocumentHtml | TermsDocumentPlain | TermsDocumentJson | TermsDocumentPdf; +type TermsDocument = + | TermsDocumentXml + | TermsDocumentHtml + | TermsDocumentPlain + | TermsDocumentJson + | TermsDocumentPdf; interface TermsDocumentXml { - type: 'xml', - document: Document, + type: "xml"; + document: Document; } interface TermsDocumentHtml { - type: 'html', - href: URL, + type: "html"; + href: URL; } interface TermsDocumentPlain { - type: 'plain', - content: string, + type: "plain"; + content: string; } interface TermsDocumentJson { - type: 'json', - data: any, + type: "json"; + data: any; } interface TermsDocumentPdf { - type: 'pdf', - location: URL, + type: "pdf"; + location: URL; } function amountToString(text: AmountJson) { - const aj = Amounts.jsonifyAmount(text) - const amount = Amounts.stringifyValue(aj) - return `${amount} ${aj.currency}` + const aj = Amounts.jsonifyAmount(text); + const amount = Amounts.stringifyValue(aj); + return `${amount} ${aj.currency}`; } -export function View({ details, withdrawalFee, exchangeBaseUrl, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, reviewed, confirmed }: ViewProps) { - const needsReview = terms.status === 'changed' || terms.status === 'new' - - const [switchingExchange, setSwitchingExchange] = useState<string | undefined>(undefined) - const exchanges = knownExchanges.reduce((prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), {}) +export function View({ + details, + withdrawalFee, + exchangeBaseUrl, + knownExchanges, + amount, + onWithdraw, + onSwitchExchange, + terms, + reviewing, + onReview, + onAccept, + reviewed, + confirmed, +}: ViewProps) { + const needsReview = terms.status === "changed" || terms.status === "new"; + + const [switchingExchange, setSwitchingExchange] = useState< + string | undefined + >(undefined); + const exchanges = knownExchanges.reduce( + (prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), + {}, + ); return ( <WalletAction> <LogoHeader /> - <h2> - {i18n.str`Digital cash withdrawal`} - </h2> + <h2>{i18n.str`Digital cash withdrawal`}</h2> <section> - <Part title="Total to withdraw" text={amountToString(Amounts.sub(amount, withdrawalFee).amount)} kind='positive' /> - <Part title="Chosen amount" text={amountToString(amount)} kind='neutral' /> - {Amounts.isNonZero(withdrawalFee) && - <Part title="Exchange fee" text={amountToString(withdrawalFee)} kind='negative' /> - } - <Part title="Exchange" text={exchangeBaseUrl} kind='neutral' big /> + <Part + title="Total to withdraw" + text={amountToString(Amounts.sub(amount, withdrawalFee).amount)} + kind="positive" + /> + <Part + title="Chosen amount" + text={amountToString(amount)} + kind="neutral" + /> + {Amounts.isNonZero(withdrawalFee) && ( + <Part + title="Exchange fee" + text={amountToString(withdrawalFee)} + kind="negative" + /> + )} + <Part title="Exchange" text={exchangeBaseUrl} kind="neutral" big /> </section> - {!reviewing && + {!reviewing && ( <section> - {switchingExchange !== undefined ? <Fragment> - <div> - <SelectList label="Known exchanges" list={exchanges} name="" onChange={onSwitchExchange} /> - </div> - <LinkSuccess upperCased onClick={() => onSwitchExchange(switchingExchange)}> - {i18n.str`Confirm exchange selection`} - </LinkSuccess> - </Fragment> - : <LinkSuccess upperCased onClick={() => setSwitchingExchange("")}> + {switchingExchange !== undefined ? ( + <Fragment> + <div> + <SelectList + label="Known exchanges" + list={exchanges} + name="" + onChange={onSwitchExchange} + /> + </div> + <LinkSuccess + upperCased + onClick={() => onSwitchExchange(switchingExchange)} + > + {i18n.str`Confirm exchange selection`} + </LinkSuccess> + </Fragment> + ) : ( + <LinkSuccess upperCased onClick={() => setSwitchingExchange("")}> {i18n.str`Switch exchange`} - </LinkSuccess>} - + </LinkSuccess> + )} </section> - } - {!reviewing && reviewed && + )} + {!reviewing && reviewed && ( <section> - <LinkSuccess - upperCased - onClick={() => onReview(true)} - > + <LinkSuccess upperCased onClick={() => onReview(true)}> {i18n.str`Show terms of service`} </LinkSuccess> </section> - } - {terms.status === 'notfound' && + )} + {terms.status === "notfound" && ( <section> <WarningText> {i18n.str`Exchange doesn't have terms of service`} </WarningText> </section> - } - {reviewing && + )} + {reviewing && ( <section> - {terms.status !== 'accepted' && terms.value && terms.value.type === 'xml' && - <TermsOfService> - <ExchangeXmlTos doc={terms.value.document} /> - </TermsOfService> - } - {terms.status !== 'accepted' && terms.value && terms.value.type === 'plain' && - <div style={{ textAlign: 'left' }}> - <pre>{terms.value.content}</pre> - </div> - } - {terms.status !== 'accepted' && terms.value && terms.value.type === 'html' && - <iframe src={terms.value.href.toString()} /> - } - {terms.status !== 'accepted' && terms.value && terms.value.type === 'pdf' && - <a href={terms.value.location.toString()} download="tos.pdf" >Download Terms of Service</a> - } - </section>} - {reviewing && reviewed && + {terms.status !== "accepted" && + terms.value && + terms.value.type === "xml" && ( + <TermsOfService> + <ExchangeXmlTos doc={terms.value.document} /> + </TermsOfService> + )} + {terms.status !== "accepted" && + terms.value && + terms.value.type === "plain" && ( + <div style={{ textAlign: "left" }}> + <pre>{terms.value.content}</pre> + </div> + )} + {terms.status !== "accepted" && + terms.value && + terms.value.type === "html" && ( + <iframe src={terms.value.href.toString()} /> + )} + {terms.status !== "accepted" && + terms.value && + terms.value.type === "pdf" && ( + <a href={terms.value.location.toString()} download="tos.pdf"> + Download Terms of Service + </a> + )} + </section> + )} + {reviewing && reviewed && ( <section> - <LinkSuccess - upperCased - onClick={() => onReview(false)} - > + <LinkSuccess upperCased onClick={() => onReview(false)}> {i18n.str`Hide terms of service`} </LinkSuccess> </section> - } - {(reviewing || reviewed) && + )} + {(reviewing || reviewed) && ( <section> <CheckboxOutlined name="terms" enabled={reviewed} label={i18n.str`I accept the exchange terms of service`} onToggle={() => { - onAccept(!reviewed) - onReview(false) + onAccept(!reviewed); + onReview(false); }} /> </section> - } + )} {/** * Main action section */} <section> - {terms.status === 'new' && !reviewed && !reviewing && + {terms.status === "new" && !reviewed && !reviewing && ( <ButtonSuccess upperCased disabled={!exchangeBaseUrl} @@ -204,8 +270,8 @@ export function View({ details, withdrawalFee, exchangeBaseUrl, knownExchanges, > {i18n.str`Review exchange terms of service`} </ButtonSuccess> - } - {terms.status === 'changed' && !reviewed && !reviewing && + )} + {terms.status === "changed" && !reviewed && !reviewing && ( <ButtonWarning upperCased disabled={!exchangeBaseUrl} @@ -213,8 +279,8 @@ export function View({ details, withdrawalFee, exchangeBaseUrl, knownExchanges, > {i18n.str`Review new version of terms of service`} </ButtonWarning> - } - {(terms.status === 'accepted' || (needsReview && reviewed)) && + )} + {(terms.status === "accepted" || (needsReview && reviewed)) && ( <ButtonSuccess upperCased disabled={!exchangeBaseUrl || confirmed} @@ -222,8 +288,8 @@ export function View({ details, withdrawalFee, exchangeBaseUrl, knownExchanges, > {i18n.str`Confirm withdrawal`} </ButtonSuccess> - } - {terms.status === 'notfound' && + )} + {terms.status === "notfound" && ( <ButtonWarning upperCased disabled={!exchangeBaseUrl} @@ -231,60 +297,88 @@ export function View({ details, withdrawalFee, exchangeBaseUrl, knownExchanges, > {i18n.str`Withdraw anyway`} </ButtonWarning> - } + )} </section> </WalletAction> - ) + ); } -export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriInfo: WithdrawUriInfoResponse }) { - const [customExchange, setCustomExchange] = useState<string | undefined>(undefined) - const [errorAccepting, setErrorAccepting] = useState<string | undefined>(undefined) - - const [reviewing, setReviewing] = useState<boolean>(false) - const [reviewed, setReviewed] = useState<boolean>(false) - const [confirmed, setConfirmed] = useState<boolean>(false) - - const knownExchangesHook = useAsyncAsHook(() => listExchanges()) - - const knownExchanges = !knownExchangesHook || knownExchangesHook.hasError ? [] : knownExchangesHook.response.exchanges - const withdrawAmount = Amounts.parseOrThrow(uriInfo.amount) - const thisCurrencyExchanges = knownExchanges.filter(ex => ex.currency === withdrawAmount.currency) - - const exchange = customExchange || uriInfo.defaultExchangeBaseUrl || thisCurrencyExchanges[0]?.exchangeBaseUrl +export function WithdrawPageWithParsedURI({ + uri, + uriInfo, +}: { + uri: string; + uriInfo: WithdrawUriInfoResponse; +}) { + const [customExchange, setCustomExchange] = useState<string | undefined>( + undefined, + ); + const [errorAccepting, setErrorAccepting] = useState<string | undefined>( + undefined, + ); + + const [reviewing, setReviewing] = useState<boolean>(false); + const [reviewed, setReviewed] = useState<boolean>(false); + const [confirmed, setConfirmed] = useState<boolean>(false); + + const knownExchangesHook = useAsyncAsHook(() => listExchanges()); + + const knownExchanges = + !knownExchangesHook || knownExchangesHook.hasError + ? [] + : knownExchangesHook.response.exchanges; + const withdrawAmount = Amounts.parseOrThrow(uriInfo.amount); + const thisCurrencyExchanges = knownExchanges.filter( + (ex) => ex.currency === withdrawAmount.currency, + ); + + const exchange = + customExchange || + uriInfo.defaultExchangeBaseUrl || + thisCurrencyExchanges[0]?.exchangeBaseUrl; const detailsHook = useAsyncAsHook(async () => { - if (!exchange) throw Error('no default exchange') - const tos = await getExchangeTos(exchange, ['text/xml']) + if (!exchange) throw Error("no default exchange"); + const tos = await getExchangeTos(exchange, ["text/xml"]); const info = await getExchangeWithdrawalInfo({ exchangeBaseUrl: exchange, amount: withdrawAmount, - tosAcceptedFormat: ['text/xml'] - }) - return { tos, info } - }) + tosAcceptedFormat: ["text/xml"], + }); + return { tos, info }; + }); if (!detailsHook) { - return <span><i18n.Translate>Getting withdrawal details.</i18n.Translate></span>; + return ( + <span> + <i18n.Translate>Getting withdrawal details.</i18n.Translate> + </span> + ); } if (detailsHook.hasError) { - return <span><i18n.Translate>Problems getting details: {detailsHook.message}</i18n.Translate></span>; + return ( + <span> + <i18n.Translate> + Problems getting details: {detailsHook.message} + </i18n.Translate> + </span> + ); } - const details = detailsHook.response + const details = detailsHook.response; const onAccept = async (): Promise<void> => { try { - await setExchangeTosAccepted(exchange, details.tos.currentEtag) - setReviewed(true) + await setExchangeTosAccepted(exchange, details.tos.currentEtag); + setReviewed(true); } catch (e) { if (e instanceof Error) { - setErrorAccepting(e.message) + setErrorAccepting(e.message); } } - } + }; const onWithdraw = async (): Promise<void> => { - setConfirmed(true) + setConfirmed(true); console.log("accepting exchange", exchange); try { const res = await acceptWithdrawal(uri, exchange); @@ -293,91 +387,121 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriIn document.location.href = res.confirmTransferUrl; } } catch (e) { - setConfirmed(false) + setConfirmed(false); } }; - const termsContent: TermsDocument | undefined = parseTermsOfServiceContent(details.tos.contentType, details.tos.content); - - const status: TermsStatus = !termsContent ? 'notfound' : ( - !details.tos.acceptedEtag ? 'new' : ( - details.tos.acceptedEtag !== details.tos.currentEtag ? 'changed' : 'accepted' - )) - - - return <View onWithdraw={onWithdraw} - details={details.tos} amount={withdrawAmount} - exchangeBaseUrl={exchange} - withdrawalFee={details.info.withdrawFee} //FIXME - terms={{ - status, value: termsContent - }} - onSwitchExchange={setCustomExchange} - knownExchanges={knownExchanges} - confirmed={confirmed} - reviewed={reviewed} onAccept={onAccept} - reviewing={reviewing} onReview={setReviewing} - /> + const termsContent: TermsDocument | undefined = parseTermsOfServiceContent( + details.tos.contentType, + details.tos.content, + ); + + const status: TermsStatus = !termsContent + ? "notfound" + : !details.tos.acceptedEtag + ? "new" + : details.tos.acceptedEtag !== details.tos.currentEtag + ? "changed" + : "accepted"; + + return ( + <View + onWithdraw={onWithdraw} + details={details.tos} + amount={withdrawAmount} + exchangeBaseUrl={exchange} + withdrawalFee={details.info.withdrawFee} //FIXME + terms={{ + status, + value: termsContent, + }} + onSwitchExchange={setCustomExchange} + knownExchanges={knownExchanges} + confirmed={confirmed} + reviewed={reviewed} + onAccept={onAccept} + reviewing={reviewing} + onReview={setReviewing} + /> + ); } -export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element { - const uriInfoHook = useAsyncAsHook(() => !talerWithdrawUri ? Promise.reject(undefined) : - getWithdrawalDetailsForUri({ talerWithdrawUri }) - ) +export function WithdrawPage({ talerWithdrawUri }: Props): VNode { + const uriInfoHook = useAsyncAsHook(() => + !talerWithdrawUri + ? Promise.reject(undefined) + : getWithdrawalDetailsForUri({ talerWithdrawUri }), + ); if (!talerWithdrawUri) { - return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; + return ( + <span> + <i18n.Translate>missing withdraw uri</i18n.Translate> + </span> + ); } if (!uriInfoHook) { - return <span><i18n.Translate>Loading...</i18n.Translate></span>; + return ( + <span> + <i18n.Translate>Loading...</i18n.Translate> + </span> + ); } if (uriInfoHook.hasError) { - return <span><i18n.Translate>This URI is not valid anymore: {uriInfoHook.message}</i18n.Translate></span>; + return ( + <span> + <i18n.Translate> + This URI is not valid anymore: {uriInfoHook.message} + </i18n.Translate> + </span> + ); } - return <WithdrawPageWithParsedURI uri={talerWithdrawUri} uriInfo={uriInfoHook.response} /> + return ( + <WithdrawPageWithParsedURI + uri={talerWithdrawUri} + uriInfo={uriInfoHook.response} + /> + ); } -function parseTermsOfServiceContent(type: string, text: string): TermsDocument | undefined { - if (type === 'text/xml') { +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 } + const document = new DOMParser().parseFromString(text, "text/xml"); + return { type: "xml", document }; } catch (e) { - console.log(e) - debugger; + console.log(e); } - } else if (type === 'text/html') { + } else if (type === "text/html") { try { - const href = new URL(text) - return { type: 'html', href } + const href = new URL(text); + return { type: "html", href }; } catch (e) { - console.log(e) - debugger; + console.log(e); } - } else if (type === 'text/json') { + } else if (type === "text/json") { try { - const data = JSON.parse(text) - return { type: 'json', data } + const data = JSON.parse(text); + return { type: "json", data }; } catch (e) { - console.log(e) - debugger; + console.log(e); } - } else if (type === 'text/pdf') { + } else if (type === "text/pdf") { try { - const location = new URL(text) - return { type: 'pdf', location } + const location = new URL(text); + return { type: "pdf", location }; } catch (e) { - console.log(e) - debugger; + console.log(e); } - } else if (type === 'text/plain') { + } else if (type === "text/plain") { try { - const content = text - return { type: 'plain', content } + const content = text; + return { type: "plain", content }; } catch (e) { - console.log(e) - debugger; + console.log(e); } } - return undefined + return undefined; } - diff --git a/packages/taler-wallet-webextension/src/cta/payback.tsx b/packages/taler-wallet-webextension/src/cta/payback.tsx index 1e27fd912..60cb8c513 100644 --- a/packages/taler-wallet-webextension/src/cta/payback.tsx +++ b/packages/taler-wallet-webextension/src/cta/payback.tsx @@ -15,7 +15,7 @@ */ import { JSX } from "preact/jsx-runtime"; -import { h } from 'preact'; +import { h } from "preact"; /** * View and edit auditors. diff --git a/packages/taler-wallet-webextension/src/cta/reset-required.tsx b/packages/taler-wallet-webextension/src/cta/reset-required.tsx index e66c0db57..3949318c4 100644 --- a/packages/taler-wallet-webextension/src/cta/reset-required.tsx +++ b/packages/taler-wallet-webextension/src/cta/reset-required.tsx @@ -63,7 +63,7 @@ class ResetNotification extends Component<any, State> { type="checkbox" checked={this.state.checked} onChange={() => { - this.setState(prev => ({ checked: prev.checked })) + this.setState((prev) => ({ checked: prev.checked })); }} />{" "} <label htmlFor="check"> diff --git a/packages/taler-wallet-webextension/src/cta/return-coins.tsx b/packages/taler-wallet-webextension/src/cta/return-coins.tsx index 43d73b5fe..548202cab 100644 --- a/packages/taler-wallet-webextension/src/cta/return-coins.tsx +++ b/packages/taler-wallet-webextension/src/cta/return-coins.tsx @@ -15,7 +15,7 @@ */ import { JSX } from "preact/jsx-runtime"; -import { h } from 'preact'; +import { h } from "preact"; /** * Return coins to own bank account. * diff --git a/packages/taler-wallet-webextension/src/custom.d.ts b/packages/taler-wallet-webextension/src/custom.d.ts index 1981067d4..521b824c7 100644 --- a/packages/taler-wallet-webextension/src/custom.d.ts +++ b/packages/taler-wallet-webextension/src/custom.d.ts @@ -21,7 +21,7 @@ declare module "*.png" { const content: any; export default content; } -declare module '*.svg' { +declare module "*.svg" { const content: any; export default content; } diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts index 2131d45cb..aa6695c3e 100644 --- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts +++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts @@ -29,7 +29,7 @@ interface HookError { export type HookResponse<T> = HookOk<T> | HookError | undefined; -export function useAsyncAsHook<T> (fn: (() => Promise<T>)): HookResponse<T> { +export function useAsyncAsHook<T>(fn: () => Promise<T>): HookResponse<T> { const [result, setHookResponse] = useState<HookResponse<T>>(undefined); useEffect(() => { async function doAsync() { @@ -42,7 +42,7 @@ export function useAsyncAsHook<T> (fn: (() => Promise<T>)): HookResponse<T> { } } } - doAsync() + doAsync(); }, []); return result; } diff --git a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts index f3b1b3b5f..1aa711a90 100644 --- a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts +++ b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts @@ -17,34 +17,31 @@ import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; - export interface BackupDeviceName { name: string; - update: (s:string) => Promise<void> + update: (s: string) => Promise<void>; } - export function useBackupDeviceName(): BackupDeviceName { const [status, setStatus] = useState<BackupDeviceName>({ - name: '', - update: () => Promise.resolve() - }) + name: "", + update: () => Promise.resolve(), + }); useEffect(() => { async function run() { //create a first list of backup info by currency - const status = await wxApi.getBackupInfo() + const status = await wxApi.getBackupInfo(); async function update(newName: string) { - await wxApi.setWalletDeviceId(newName) - setStatus(old => ({ ...old, name: newName })) + await wxApi.setWalletDeviceId(newName); + setStatus((old) => ({ ...old, name: newName })); } - setStatus({ name: status.deviceId, update }) + setStatus({ name: status.deviceId, update }); } - run() - }, []) + run(); + }, []); - return status + return status; } - diff --git a/packages/taler-wallet-webextension/src/hooks/useBackupStatus.ts b/packages/taler-wallet-webextension/src/hooks/useBackupStatus.ts index c46ab6a5f..8a8fd6f2f 100644 --- a/packages/taler-wallet-webextension/src/hooks/useBackupStatus.ts +++ b/packages/taler-wallet-webextension/src/hooks/useBackupStatus.ts @@ -14,11 +14,15 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { ProviderInfo, ProviderPaymentPaid, ProviderPaymentStatus, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { + ProviderInfo, + ProviderPaymentPaid, + ProviderPaymentStatus, + ProviderPaymentType, +} from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; - export interface BackupStatus { deviceName: string; providers: ProviderInfo[]; @@ -32,40 +36,46 @@ function getStatusTypeOrder(t: ProviderPaymentStatus) { ProviderPaymentType.Unpaid, ProviderPaymentType.Paid, ProviderPaymentType.Pending, - ].indexOf(t.type) + ].indexOf(t.type); } function getStatusPaidOrder(a: ProviderPaymentPaid, b: ProviderPaymentPaid) { - return a.paidUntil.t_ms === 'never' ? -1 : - b.paidUntil.t_ms === 'never' ? 1 : - a.paidUntil.t_ms - b.paidUntil.t_ms + return a.paidUntil.t_ms === "never" + ? -1 + : b.paidUntil.t_ms === "never" + ? 1 + : a.paidUntil.t_ms - b.paidUntil.t_ms; } export function useBackupStatus(): BackupStatus | undefined { - const [status, setStatus] = useState<BackupStatus | undefined>(undefined) + const [status, setStatus] = useState<BackupStatus | undefined>(undefined); useEffect(() => { async function run() { //create a first list of backup info by currency - const status = await wxApi.getBackupInfo() + const status = await wxApi.getBackupInfo(); const providers = status.providers.sort((a, b) => { - if (a.paymentStatus.type === ProviderPaymentType.Paid && b.paymentStatus.type === ProviderPaymentType.Paid) { - return getStatusPaidOrder(a.paymentStatus, b.paymentStatus) + if ( + a.paymentStatus.type === ProviderPaymentType.Paid && + b.paymentStatus.type === ProviderPaymentType.Paid + ) { + return getStatusPaidOrder(a.paymentStatus, b.paymentStatus); } - return getStatusTypeOrder(a.paymentStatus) - getStatusTypeOrder(b.paymentStatus) - }) + return ( + getStatusTypeOrder(a.paymentStatus) - + getStatusTypeOrder(b.paymentStatus) + ); + }); async function sync() { - await wxApi.syncAllProviders() + await wxApi.syncAllProviders(); } - - setStatus({ deviceName: status.deviceId, providers, sync }) + + setStatus({ deviceName: status.deviceId, providers, sync }); } - run() - }, []) + run(); + }, []); - return status + return status; } - - diff --git a/packages/taler-wallet-webextension/src/hooks/useBalances.ts b/packages/taler-wallet-webextension/src/hooks/useBalances.ts index 37424fb05..403ce7b87 100644 --- a/packages/taler-wallet-webextension/src/hooks/useBalances.ts +++ b/packages/taler-wallet-webextension/src/hooks/useBalances.ts @@ -18,7 +18,6 @@ import { BalancesResponse } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; - interface BalancesHookOk { hasError: false; response: BalancesResponse; @@ -46,7 +45,7 @@ export function useBalances(): BalancesHook { } } } - checkBalance() + checkBalance(); return wxApi.onUpdateNotification(checkBalance); }, []); diff --git a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts index 888d4d5f1..48aff2602 100644 --- a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts +++ b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts @@ -21,7 +21,7 @@ import * as wxApi from "../wxApi"; export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { const [timedOut, setTimedOut] = useState(false); const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>( - undefined + undefined, ); useEffect(() => { @@ -41,5 +41,5 @@ export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { console.log("fetching diagnostics"); doFetch(); }, []); - return [diagnostics, timedOut] -}
\ No newline at end of file + return [diagnostics, timedOut]; +} diff --git a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts index a92425760..aaab0aa43 100644 --- a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts +++ b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts @@ -19,13 +19,12 @@ import * as wxApi from "../wxApi"; import { getPermissionsApi } from "../compat"; import { extendedPermissions } from "../permissions"; - export function useExtendedPermissions(): [boolean, () => void] { const [enabled, setEnabled] = useState(false); const toggle = () => { - setEnabled(v => !v); - handleExtendedPerm(enabled).then(result => { + setEnabled((v) => !v); + handleExtendedPerm(enabled).then((result) => { setEnabled(result); }); }; @@ -65,5 +64,5 @@ async function handleExtendedPerm(isEnabled: boolean): Promise<boolean> { nextVal = res.newValue; } console.log("new permissions applied:", nextVal ?? false); - return nextVal ?? false -}
\ No newline at end of file + return nextVal ?? false; +} diff --git a/packages/taler-wallet-webextension/src/hooks/useLang.ts b/packages/taler-wallet-webextension/src/hooks/useLang.ts index 70b9614f6..cc4ff3fc8 100644 --- a/packages/taler-wallet-webextension/src/hooks/useLang.ts +++ b/packages/taler-wallet-webextension/src/hooks/useLang.ts @@ -14,10 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { useNotNullLocalStorage } from './useLocalStorage'; +import { useNotNullLocalStorage } from "./useLocalStorage"; -export function useLang(initial?: string): [string, (s:string) => void] { - const browserLang: string | undefined = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined; - const defaultLang = (browserLang || initial || 'en').substring(0, 2) - return useNotNullLocalStorage('lang-preference', defaultLang) +export function useLang(initial?: string): [string, (s: string) => void] { + const browserLang: string | undefined = + typeof window !== "undefined" + ? navigator.language || (navigator as any).userLanguage + : undefined; + const defaultLang = (browserLang || initial || "en").substring(0, 2); + return useNotNullLocalStorage("lang-preference", defaultLang); } diff --git a/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts b/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts index 78a8b65d5..3883aff04 100644 --- a/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts +++ b/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts @@ -15,38 +15,52 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { StateUpdater, useState } from "preact/hooks"; -export function useLocalStorage(key: string, initialValue?: string): [string | undefined, StateUpdater<string | undefined>] { - const [storedValue, setStoredValue] = useState<string | undefined>((): string | undefined => { - return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; +export function useLocalStorage( + key: string, + initialValue?: string, +): [string | undefined, StateUpdater<string | undefined>] { + const [storedValue, setStoredValue] = useState<string | undefined>((): + | string + | undefined => { + return typeof window !== "undefined" + ? window.localStorage.getItem(key) || initialValue + : initialValue; }); - const setValue = (value?: string | ((val?: string) => string | undefined)) => { - setStoredValue(p => { - const toStore = value instanceof Function ? value(p) : value + const setValue = ( + value?: string | ((val?: string) => string | undefined), + ) => { + setStoredValue((p) => { + const toStore = value instanceof Function ? value(p) : value; if (typeof window !== "undefined") { if (!toStore) { - window.localStorage.removeItem(key) + window.localStorage.removeItem(key); } else { window.localStorage.setItem(key, toStore); } } - return toStore - }) + return toStore; + }); }; return [storedValue, setValue]; } //TODO: merge with the above function -export function useNotNullLocalStorage(key: string, initialValue: string): [string, StateUpdater<string>] { +export function useNotNullLocalStorage( + key: string, + initialValue: string, +): [string, StateUpdater<string>] { const [storedValue, setStoredValue] = useState<string>((): string => { - return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; + return typeof window !== "undefined" + ? window.localStorage.getItem(key) || initialValue + : initialValue; }); const setValue = (value: string | ((val: string) => string)) => { @@ -54,7 +68,7 @@ export function useNotNullLocalStorage(key: string, initialValue: string): [stri setStoredValue(valueToStore); if (typeof window !== "undefined") { if (!valueToStore) { - window.localStorage.removeItem(key) + window.localStorage.removeItem(key); } else { window.localStorage.setItem(key, valueToStore); } diff --git a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts index 6520848a5..ea167463e 100644 --- a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts +++ b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts @@ -32,7 +32,9 @@ export function useProviderStatus(url: string): ProviderStatus | undefined { //create a first list of backup info by currency const status = await wxApi.getBackupInfo(); - const providers = status.providers.filter(p => p.syncProviderBaseUrl === url); + const providers = status.providers.filter( + (p) => p.syncProviderBaseUrl === url, + ); const info = providers.length ? providers[0] : undefined; async function sync() { diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts index ff9cc029a..96a278401 100644 --- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts +++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts @@ -17,15 +17,18 @@ import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; -export function useTalerActionURL(): [string | undefined, (s: boolean) => void] { +export function useTalerActionURL(): [ + string | undefined, + (s: boolean) => void, +] { const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>( - undefined + undefined, ); const [dismissed, setDismissed] = useState(false); useEffect(() => { async function check(): Promise<void> { const talerUri = await findTalerUriInActiveTab(); - setTalerActionUrl(talerUri) + setTalerActionUrl(talerUri); } check(); }, []); diff --git a/packages/taler-wallet-webextension/src/i18n/strings.ts b/packages/taler-wallet-webextension/src/i18n/strings.ts index 5b1257830..0fefb0f70 100644 --- a/packages/taler-wallet-webextension/src/i18n/strings.ts +++ b/packages/taler-wallet-webextension/src/i18n/strings.ts @@ -193,7 +193,7 @@ strings["es"] = { "Order redirected": [""], "Payment aborted": [""], "Payment Sent": [""], - "Backup": ["Resguardo"], + Backup: ["Resguardo"], "Order accepted": [""], "Reserve balance updated": [""], "Payment refund": [""], diff --git a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx index d256f6d98..232b0da73 100644 --- a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx @@ -15,179 +15,184 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; -import { addDays } from 'date-fns'; -import { BackupView as TestedComponent } from './BackupPage'; -import { createExample } from '../test-utils'; +import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { addDays } from "date-fns"; +import { BackupView as TestedComponent } from "./BackupPage"; +import { createExample } from "../test-utils"; export default { - title: 'popup/backup/list', + title: "popup/backup/list", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const LotOfProviders = createExample(TestedComponent, { - providers: [{ - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" - ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } - }, - "terms": { - "annualFee": "ARS:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 + providers: [ + { + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", + ], + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "ARS:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" - ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": addDays(new Date(), 13).getTime() - } + { + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", + ], + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: addDays(new Date(), 13).getTime(), + }, + }, + terms: { + annualFee: "ARS:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "ARS:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Pending, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Pending, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.InsufficientBalance, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.InsufficientBalance, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.TermsChanged, - newTerms: { - annualFee: 'USD:2', - storageLimitInMegabytes: 8, - supportedProtocolVersion: '2', - }, - oldTerms: { - annualFee: 'USD:1', + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.TermsChanged, + newTerms: { + annualFee: "USD:2", + storageLimitInMegabytes: 8, + supportedProtocolVersion: "2", + }, + oldTerms: { + annualFee: "USD:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "1", + }, + paidUntil: { + t_ms: "never", + }, + }, + terms: { + annualFee: "KUDOS:0.1", storageLimitInMegabytes: 16, - supportedProtocolVersion: '1', - + supportedProtocolVersion: "0.0", }, - paidUntil: { - t_ms: 'never' - } }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Unpaid, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Unpaid, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }] + ], }); - export const OneProvider = createExample(TestedComponent, { - providers: [{ - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" - ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + providers: [ + { + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", + ], + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "ARS:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "ARS:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }] + ], }); - export const Empty = createExample(TestedComponent, { - providers: [] + providers: [], }); - diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx index dcc5e5313..894c8a791 100644 --- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx @@ -14,15 +14,28 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { i18n, Timestamp } from "@gnu-taler/taler-util"; -import { ProviderInfo, ProviderPaymentStatus } from "@gnu-taler/taler-wallet-core"; -import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns"; +import { + ProviderInfo, + ProviderPaymentStatus, +} from "@gnu-taler/taler-wallet-core"; +import { + differenceInMonths, + formatDuration, + intervalToDuration, +} from "date-fns"; import { Fragment, JSX, VNode, h } from "preact"; import { - BoldLight, ButtonPrimary, ButtonSuccess, Centered, - CenteredText, CenteredBoldText, PopupBox, RowBorderGray, - SmallText, SmallLightText + BoldLight, + ButtonPrimary, + ButtonSuccess, + Centered, + CenteredText, + CenteredBoldText, + PopupBox, + RowBorderGray, + SmallText, + SmallLightText, } from "../components/styled"; import { useBackupStatus } from "../hooks/useBackupStatus"; import { Pages } from "../NavigationBar"; @@ -32,49 +45,68 @@ interface Props { } export function BackupPage({ onAddProvider }: Props): VNode { - const status = useBackupStatus() + const status = useBackupStatus(); if (!status) { - return <div>Loading...</div> + return <div>Loading...</div>; } - return <BackupView providers={status.providers} onAddProvider={onAddProvider} onSyncAll={status.sync} />; + return ( + <BackupView + providers={status.providers} + onAddProvider={onAddProvider} + onSyncAll={status.sync} + /> + ); } export interface ViewProps { - providers: ProviderInfo[], + providers: ProviderInfo[]; onAddProvider: () => void; onSyncAll: () => Promise<void>; } -export function BackupView({ providers, onAddProvider, onSyncAll }: ViewProps): VNode { +export function BackupView({ + providers, + onAddProvider, + onSyncAll, +}: ViewProps): VNode { return ( <PopupBox> <section> - {providers.map((provider) => <BackupLayout - status={provider.paymentStatus} - timestamp={provider.lastSuccessfulBackupTimestamp} - id={provider.syncProviderBaseUrl} - active={provider.active} - title={provider.name} - /> + {providers.map((provider) => ( + <BackupLayout + status={provider.paymentStatus} + timestamp={provider.lastSuccessfulBackupTimestamp} + id={provider.syncProviderBaseUrl} + active={provider.active} + title={provider.name} + /> + ))} + {!providers.length && ( + <Centered style={{ marginTop: 100 }}> + <BoldLight>No backup providers configured</BoldLight> + <ButtonSuccess onClick={onAddProvider}> + <i18n.Translate>Add provider</i18n.Translate> + </ButtonSuccess> + </Centered> )} - {!providers.length && <Centered style={{marginTop: 100}}> - <BoldLight>No backup providers configured</BoldLight> - <ButtonSuccess onClick={onAddProvider}><i18n.Translate>Add provider</i18n.Translate></ButtonSuccess> - </Centered>} </section> - {!!providers.length && <footer> - <div /> - <div> - <ButtonPrimary onClick={onSyncAll}>{ - providers.length > 1 ? - <i18n.Translate>Sync all backups</i18n.Translate> : - <i18n.Translate>Sync now</i18n.Translate> - }</ButtonPrimary> - <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess> - </div> - </footer>} + {!!providers.length && ( + <footer> + <div /> + <div> + <ButtonPrimary onClick={onSyncAll}> + {providers.length > 1 ? ( + <i18n.Translate>Sync all backups</i18n.Translate> + ) : ( + <i18n.Translate>Sync now</i18n.Translate> + )} + </ButtonPrimary> + <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess> + </div> + </footer> + )} </PopupBox> - ) + ); } interface TransactionLayoutProps { @@ -92,55 +124,73 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element { timeStyle: "short", } as any); - return ( <RowBorderGray> <div style={{ color: !props.active ? "grey" : undefined }}> - <a href={Pages.provider_detail.replace(':pid', encodeURIComponent(props.id))}><span>{props.title}</span></a> - - {dateStr && <SmallText style={{marginTop: 5}}>Last synced: {dateStr}</SmallText>} - {!dateStr && <SmallLightText style={{marginTop: 5}}>Not synced</SmallLightText>} + <a + href={Pages.provider_detail.replace( + ":pid", + encodeURIComponent(props.id), + )} + > + <span>{props.title}</span> + </a> + + {dateStr && ( + <SmallText style={{ marginTop: 5 }}>Last synced: {dateStr}</SmallText> + )} + {!dateStr && ( + <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText> + )} </div> <div> - {props.status?.type === 'paid' ? - <ExpirationText until={props.status.paidUntil} /> : + {props.status?.type === "paid" ? ( + <ExpirationText until={props.status.paidUntil} /> + ) : ( <div>{props.status.type}</div> - } + )} </div> </RowBorderGray> ); } function ExpirationText({ until }: { until: Timestamp }) { - return <Fragment> - <CenteredText> Expires in </CenteredText> - <CenteredBoldText {...({ color: colorByTimeToExpire(until) })}> {daysUntil(until)} </CenteredBoldText> - </Fragment> + return ( + <Fragment> + <CenteredText> Expires in </CenteredText> + <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}> + {" "} + {daysUntil(until)}{" "} + </CenteredBoldText> + </Fragment> + ); } function colorByTimeToExpire(d: Timestamp) { - if (d.t_ms === 'never') return 'rgb(28, 184, 65)' - const months = differenceInMonths(d.t_ms, new Date()) - return months > 1 ? 'rgb(28, 184, 65)' : 'rgb(223, 117, 20)'; + if (d.t_ms === "never") return "rgb(28, 184, 65)"; + const months = differenceInMonths(d.t_ms, new Date()); + return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)"; } function daysUntil(d: Timestamp) { - if (d.t_ms === 'never') return undefined + if (d.t_ms === "never") return undefined; const duration = intervalToDuration({ start: d.t_ms, end: new Date(), - }) + }); const str = formatDuration(duration, { - delimiter: ', ', + delimiter: ", ", format: [ - duration?.years ? 'years' : ( - duration?.months ? 'months' : ( - duration?.days ? 'days' : ( - duration.hours ? 'hours' : 'minutes' - ) - ) - ) - ] - }) - return `${str}` -}
\ No newline at end of file + duration?.years + ? "years" + : duration?.months + ? "months" + : duration?.days + ? "days" + : duration.hours + ? "hours" + : "minutes", + ], + }); + return `${str}`; +} diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx index 382f9b549..80203f6d3 100644 --- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx @@ -15,28 +15,25 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample, NullLink } from '../test-utils'; -import { BalanceView as TestedComponent } from './BalancePage'; +import { createExample, NullLink } from "../test-utils"; +import { BalanceView as TestedComponent } from "./BalancePage"; export default { - title: 'popup/balance', + title: "popup/balance", component: TestedComponent, - argTypes: { - } + argTypes: {}, }; - -export const NotYetLoaded = createExample(TestedComponent, { -}); +export const NotYetLoaded = createExample(TestedComponent, {}); export const GotError = createExample(TestedComponent, { balance: { hasError: true, - message: 'Network error' + message: "Network error", }, Linker: NullLink, }); @@ -45,7 +42,7 @@ export const EmptyBalance = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [] + balances: [], }, }, Linker: NullLink, @@ -55,13 +52,15 @@ export const SomeCoins = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:10.5', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:10.5", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -71,13 +70,15 @@ export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:2.23', - hasPendingTransactions: false, - pendingIncoming: 'USD:5.11', - pendingOutgoing: 'USD:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:2.23", + hasPendingTransactions: false, + pendingIncoming: "USD:5.11", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -87,13 +88,15 @@ export const SomeCoinsAndOutgoingMoney = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:2.23', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:5.11', - requiresUserInput: false - }] + balances: [ + { + available: "USD:2.23", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:5.11", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -103,13 +106,15 @@ export const SomeCoinsAndMovingMoney = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:2.23', - hasPendingTransactions: false, - pendingIncoming: 'USD:2', - pendingOutgoing: 'USD:5.11', - requiresUserInput: false - }] + balances: [ + { + available: "USD:2.23", + hasPendingTransactions: false, + pendingIncoming: "USD:2", + pendingOutgoing: "USD:5.11", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -119,19 +124,22 @@ export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:2', - hasPendingTransactions: false, - pendingIncoming: 'USD:5.1', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'EUR:4', - hasPendingTransactions: false, - pendingIncoming: 'EUR:0', - pendingOutgoing: 'EUR:3.01', - requiresUserInput: false - }] + balances: [ + { + available: "USD:2", + hasPendingTransactions: false, + pendingIncoming: "USD:5.1", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "EUR:4", + hasPendingTransactions: false, + pendingIncoming: "EUR:0", + pendingOutgoing: "EUR:3.01", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -141,78 +149,89 @@ export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:1', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'COL:2000', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'EUR:4', - hasPendingTransactions: false, - pendingIncoming: 'EUR:15', - pendingOutgoing: 'EUR:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:1", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "COL:2000", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "EUR:4", + hasPendingTransactions: false, + pendingIncoming: "EUR:15", + pendingOutgoing: "EUR:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, }); - export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:13451', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'EUR:202.02', - hasPendingTransactions: false, - pendingIncoming: 'EUR:0', - pendingOutgoing: 'EUR:0', - requiresUserInput: false - },{ - available: 'ARS:30', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'JPY:51223233', - hasPendingTransactions: false, - pendingIncoming: 'EUR:0', - pendingOutgoing: 'EUR:0', - requiresUserInput: false - },{ - available: 'JPY:51223233', - hasPendingTransactions: false, - pendingIncoming: 'EUR:0', - pendingOutgoing: 'EUR:0', - requiresUserInput: false - },{ - available: 'DEMOKUDOS:6', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'TESTKUDOS:6', - hasPendingTransactions: false, - pendingIncoming: 'USD:5', - pendingOutgoing: 'USD:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:13451", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "EUR:202.02", + hasPendingTransactions: false, + pendingIncoming: "EUR:0", + pendingOutgoing: "EUR:0", + requiresUserInput: false, + }, + { + available: "ARS:30", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "JPY:51223233", + hasPendingTransactions: false, + pendingIncoming: "EUR:0", + pendingOutgoing: "EUR:0", + requiresUserInput: false, + }, + { + available: "JPY:51223233", + hasPendingTransactions: false, + pendingIncoming: "EUR:0", + pendingOutgoing: "EUR:0", + requiresUserInput: false, + }, + { + available: "DEMOKUDOS:6", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "TESTKUDOS:6", + hasPendingTransactions: false, + pendingIncoming: "USD:5", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx index 8e5c5c42e..2913f60e0 100644 --- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -15,20 +15,37 @@ */ import { - amountFractionalBase, Amounts, - Balance, BalancesResponse, - i18n + amountFractionalBase, + Amounts, + Balance, + BalancesResponse, + i18n, } from "@gnu-taler/taler-util"; import { JSX, h, Fragment } from "preact"; import { ErrorMessage } from "../components/ErrorMessage"; -import { PopupBox, Centered, ButtonPrimary, ErrorBox, Middle } from "../components/styled/index"; +import { + PopupBox, + Centered, + ButtonPrimary, + ErrorBox, + Middle, +} from "../components/styled/index"; import { BalancesHook, useBalances } from "../hooks/useBalances"; import { PageLink, renderAmount } from "../renderHtml"; - -export function BalancePage({ goToWalletManualWithdraw }: { goToWalletManualWithdraw: () => void }) { - const balance = useBalances() - return <BalanceView balance={balance} Linker={PageLink} goToWalletManualWithdraw={goToWalletManualWithdraw} /> +export function BalancePage({ + goToWalletManualWithdraw, +}: { + goToWalletManualWithdraw: () => void; +}) { + const balance = useBalances(); + return ( + <BalanceView + balance={balance} + Linker={PageLink} + goToWalletManualWithdraw={goToWalletManualWithdraw} + /> + ); } export interface BalanceViewProps { balance: BalancesHook; @@ -46,22 +63,26 @@ function formatPending(entry: Balance): JSX.Element { if (!Amounts.isZero(pendingIncoming)) { incoming = ( - <span><i18n.Translate> - <span style={{ color: "darkgreen" }} title="incoming amount"> - {"+"} - {renderAmount(entry.pendingIncoming)} - </span>{" "} - </i18n.Translate></span> + <span> + <i18n.Translate> + <span style={{ color: "darkgreen" }} title="incoming amount"> + {"+"} + {renderAmount(entry.pendingIncoming)} + </span>{" "} + </i18n.Translate> + </span> ); } if (!Amounts.isZero(pendingOutgoing)) { payment = ( - <span><i18n.Translate> - <span style={{ color: "darkred" }} title="outgoing amount"> - {"-"} - {renderAmount(entry.pendingOutgoing)} - </span>{" "} - </i18n.Translate></span> + <span> + <i18n.Translate> + <span style={{ color: "darkred" }} title="outgoing amount"> + {"-"} + {renderAmount(entry.pendingOutgoing)} + </span>{" "} + </i18n.Translate> + </span> ); } @@ -80,76 +101,110 @@ function formatPending(entry: Balance): JSX.Element { ); } - -export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: BalanceViewProps) { - +export function BalanceView({ + balance, + Linker, + goToWalletManualWithdraw, +}: BalanceViewProps) { function Content() { if (!balance) { - return <span /> + return <span />; } if (balance.hasError) { - return (<section> - <ErrorBox>{balance.message}</ErrorBox> - <p> - Click <Linker pageName="welcome">here</Linker> for help and - diagnostics. - </p> - </section>) + return ( + <section> + <ErrorBox>{balance.message}</ErrorBox> + <p> + Click <Linker pageName="welcome">here</Linker> for help and + diagnostics. + </p> + </section> + ); } if (balance.response.balances.length === 0) { - return (<section data-expanded> - <Middle> - <p><i18n.Translate> - You have no balance to show. Need some{" "} - <Linker pageName="/welcome">help</Linker> getting started? - </i18n.Translate></p> - </Middle> - </section>) + return ( + <section data-expanded> + <Middle> + <p> + <i18n.Translate> + You have no balance to show. Need some{" "} + <Linker pageName="/welcome">help</Linker> getting started? + </i18n.Translate> + </p> + </Middle> + </section> + ); } - return <section data-expanded data-centered> - <table style={{width:'100%'}}>{balance.response.balances.map((entry) => { - const av = Amounts.parseOrThrow(entry.available); - // Create our number formatter. - let formatter; - try { - formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: av.currency, - currencyDisplay: 'symbol' - // These options are needed to round to whole numbers if that's what you want. - //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1) - //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501) - }); - } catch { - formatter = new Intl.NumberFormat('en-US', { - // style: 'currency', - // currency: av.currency, - // These options are needed to round to whole numbers if that's what you want. - //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1) - //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501) - }); - } - - const v = formatter.format(av.value + av.fraction / amountFractionalBase); - const fontSize = v.length < 8 ? '3em' : (v.length < 13 ? '2em' : '1em') - return (<tr> - <td style={{ height: 50, fontSize, width: '60%', textAlign: 'right', padding: 0 }}>{v}</td> - <td style={{ maxWidth: '2em', overflowX: 'hidden' }}>{av.currency}</td> - <td style={{ fontSize: 'small', color: 'gray' }}>{formatPending(entry)}</td> - </tr> - ); - })}</table> - </section> + return ( + <section data-expanded data-centered> + <table style={{ width: "100%" }}> + {balance.response.balances.map((entry) => { + const av = Amounts.parseOrThrow(entry.available); + // Create our number formatter. + let formatter; + try { + formatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: av.currency, + currencyDisplay: "symbol", + // These options are needed to round to whole numbers if that's what you want. + //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1) + //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501) + }); + } catch { + formatter = new Intl.NumberFormat("en-US", { + // style: 'currency', + // currency: av.currency, + // These options are needed to round to whole numbers if that's what you want. + //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1) + //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501) + }); + } + + const v = formatter.format( + av.value + av.fraction / amountFractionalBase, + ); + const fontSize = + v.length < 8 ? "3em" : v.length < 13 ? "2em" : "1em"; + return ( + <tr> + <td + style={{ + height: 50, + fontSize, + width: "60%", + textAlign: "right", + padding: 0, + }} + > + {v} + </td> + <td style={{ maxWidth: "2em", overflowX: "hidden" }}> + {av.currency} + </td> + <td style={{ fontSize: "small", color: "gray" }}> + {formatPending(entry)} + </td> + </tr> + ); + })} + </table> + </section> + ); } - return <PopupBox> - {/* <section> */} - <Content /> - {/* </section> */} - <footer> - <div /> - <ButtonPrimary onClick={goToWalletManualWithdraw}>Withdraw</ButtonPrimary> - </footer> - </PopupBox> + return ( + <PopupBox> + {/* <section> */} + <Content /> + {/* </section> */} + <footer> + <div /> + <ButtonPrimary onClick={goToWalletManualWithdraw}> + Withdraw + </ButtonPrimary> + </footer> + </PopupBox> + ); } diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx b/packages/taler-wallet-webextension/src/popup/Debug.tsx index ccc747466..8722c1cf8 100644 --- a/packages/taler-wallet-webextension/src/popup/Debug.tsx +++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx @@ -19,13 +19,14 @@ import { Diagnostics } from "../components/Diagnostics"; import { useDiagnostics } from "../hooks/useDiagnostics.js"; import * as wxApi from "../wxApi"; - export function DeveloperPage(props: any): JSX.Element { const [status, timedOut] = useDiagnostics(); return ( <div> <p>Debug tools:</p> - <button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button> + <button onClick={openExtensionPage("/static/popup.html")}> + wallet tab + </button> <br /> <button onClick={confirmReset}>reset</button> <Diagnostics diagnostics={status} timedOut={timedOut} /> @@ -46,7 +47,7 @@ export async function confirmReset(): Promise<void> { if ( confirm( "Do you want to IRREVOCABLY DESTROY everything inside your" + - " wallet and LOSE ALL YOUR COINS?", + " wallet and LOSE ALL YOUR COINS?", ) ) { await wxApi.resetDb(); @@ -61,4 +62,3 @@ export function openExtensionPage(page: string) { }); }; } - diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx b/packages/taler-wallet-webextension/src/popup/History.stories.tsx index daa263a81..95f4a547a 100644 --- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx @@ -15,135 +15,149 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { PaymentStatus, - TransactionCommon, TransactionDeposit, TransactionPayment, - TransactionRefresh, TransactionRefund, TransactionTip, TransactionType, + TransactionCommon, + TransactionDeposit, + TransactionPayment, + TransactionRefresh, + TransactionRefund, + TransactionTip, + TransactionType, TransactionWithdrawal, - WithdrawalType -} from '@gnu-taler/taler-util'; -import { createExample } from '../test-utils'; -import { HistoryView as TestedComponent } from './History'; + WithdrawalType, +} from "@gnu-taler/taler-util"; +import { createExample } from "../test-utils"; +import { HistoryView as TestedComponent } from "./History"; export default { - title: 'popup/history/list', + title: "popup/history/list", component: TestedComponent, }; const commonTransaction = { - amountRaw: 'USD:10', - amountEffective: 'USD:9', + amountRaw: "USD:10", + amountEffective: "USD:9", pending: false, timestamp: { - t_ms: new Date().getTime() + t_ms: new Date().getTime(), }, - transactionId: '12', -} as TransactionCommon + transactionId: "12", +} as TransactionCommon; const exampleData = { withdraw: { ...commonTransaction, type: TransactionType.Withdrawal, - exchangeBaseUrl: 'http://exchange.demo.taler.net', + exchangeBaseUrl: "http://exchange.demo.taler.net", withdrawalDetails: { confirmed: false, - exchangePaytoUris: ['payto://x-taler-bank/bank/account'], + exchangePaytoUris: ["payto://x-taler-bank/bank/account"], type: WithdrawalType.ManualTransfer, - } + }, } as TransactionWithdrawal, payment: { ...commonTransaction, - amountEffective: 'USD:11', + amountEffective: "USD:11", type: TransactionType.Payment, info: { - contractTermsHash: 'ASDZXCASD', + contractTermsHash: "ASDZXCASD", merchant: { - name: 'the merchant', + name: "the merchant", }, - orderId: '2021.167-03NPY6MCYMVGT', + orderId: "2021.167-03NPY6MCYMVGT", products: [], - summary: 'the summary', - fulfillmentMessage: '', + summary: "the summary", + fulfillmentMessage: "", }, - proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + proposalId: "1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", status: PaymentStatus.Accepted, } as TransactionPayment, deposit: { ...commonTransaction, type: TransactionType.Deposit, - depositGroupId: '#groupId', - targetPaytoUri: 'payto://x-taler-bank/bank/account', + depositGroupId: "#groupId", + targetPaytoUri: "payto://x-taler-bank/bank/account", } as TransactionDeposit, refresh: { ...commonTransaction, type: TransactionType.Refresh, - exchangeBaseUrl: 'http://exchange.taler', + exchangeBaseUrl: "http://exchange.taler", } as TransactionRefresh, tip: { ...commonTransaction, type: TransactionType.Tip, - merchantBaseUrl: 'http://merchant.taler', + merchantBaseUrl: "http://merchant.taler", } as TransactionTip, refund: { ...commonTransaction, type: TransactionType.Refund, - refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + refundedTransactionId: + "payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", info: { - contractTermsHash: 'ASDZXCASD', + contractTermsHash: "ASDZXCASD", merchant: { - name: 'the merchant', + name: "the merchant", }, - orderId: '2021.167-03NPY6MCYMVGT', + orderId: "2021.167-03NPY6MCYMVGT", products: [], - summary: 'the summary', - fulfillmentMessage: '', + summary: "the summary", + fulfillmentMessage: "", }, } as TransactionRefund, -} +}; export const EmptyWithBalance = createExample(TestedComponent, { list: [], - balances: [{ - available: 'TESTKUDOS:10', - pendingIncoming: 'TESTKUDOS:0', - pendingOutgoing: 'TESTKUDOS:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "TESTKUDOS:10", + pendingIncoming: "TESTKUDOS:0", + pendingOutgoing: "TESTKUDOS:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const EmptyWithNoBalance = createExample(TestedComponent, { list: [], - balances: [] + balances: [], }); export const One = createExample(TestedComponent, { list: [exampleData.withdraw], - balances: [{ - available: 'USD:10', - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const OnePending = createExample(TestedComponent, { - list: [{ - ...exampleData.withdraw, - pending: true, - }], - balances: [{ - available: 'USD:10', - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + list: [ + { + ...exampleData.withdraw, + pending: true, + }, + ], + balances: [ + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const Several = createExample(TestedComponent, { @@ -157,13 +171,15 @@ export const Several = createExample(TestedComponent, { exampleData.tip, exampleData.deposit, ], - balances: [{ - available: 'TESTKUDOS:10', - pendingIncoming: 'TESTKUDOS:0', - pendingOutgoing: 'TESTKUDOS:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "TESTKUDOS:10", + pendingIncoming: "TESTKUDOS:0", + pendingOutgoing: "TESTKUDOS:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const SeveralWithTwoCurrencies = createExample(TestedComponent, { @@ -177,18 +193,20 @@ export const SeveralWithTwoCurrencies = createExample(TestedComponent, { exampleData.tip, exampleData.deposit, ], - balances: [{ - available: 'TESTKUDOS:10', - pendingIncoming: 'TESTKUDOS:0', - pendingOutgoing: 'TESTKUDOS:0', - hasPendingTransactions: false, - requiresUserInput: false, - }, { - available: 'USD:10', - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "TESTKUDOS:10", + pendingIncoming: "TESTKUDOS:0", + pendingOutgoing: "TESTKUDOS:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); - diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx index 1447da9b0..8fe6de16c 100644 --- a/packages/taler-wallet-webextension/src/popup/History.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.tsx @@ -14,7 +14,13 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountString, Balance, i18n, Transaction, TransactionsResponse } from "@gnu-taler/taler-util"; +import { + AmountString, + Balance, + i18n, + Transaction, + TransactionsResponse, +} from "@gnu-taler/taler-util"; import { h, JSX } from "preact"; import { useEffect, useState } from "preact/hooks"; import { PopupBox } from "../components/styled"; @@ -22,13 +28,14 @@ import { TransactionItem } from "../components/TransactionItem"; import { useBalances } from "../hooks/useBalances"; import * as wxApi from "../wxApi"; - export function HistoryPage(props: any): JSX.Element { const [transactions, setTransactions] = useState< TransactionsResponse | undefined >(undefined); - const balance = useBalances() - const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || []) + const balance = useBalances(); + const balanceWithoutError = balance?.hasError + ? [] + : balance?.response.balances || []; useEffect(() => { const fetchData = async (): Promise<void> => { @@ -42,46 +49,79 @@ export function HistoryPage(props: any): JSX.Element { return <div>Loading ...</div>; } - return <HistoryView balances={balanceWithoutError} list={[...transactions.transactions].reverse()} />; + return ( + <HistoryView + balances={balanceWithoutError} + list={[...transactions.transactions].reverse()} + /> + ); } function amountToString(c: AmountString) { - const idx = c.indexOf(':') - return `${c.substring(idx + 1)} ${c.substring(0, idx)}` + const idx = c.indexOf(":"); + return `${c.substring(idx + 1)} ${c.substring(0, idx)}`; } - - -export function HistoryView({ list, balances }: { list: Transaction[], balances: Balance[] }) { - const multiCurrency = balances.length > 1 - return <PopupBox noPadding> - {balances.length > 0 && <header> - {multiCurrency ? <div class="title"> - Balance: <ul style={{ margin: 0 }}> - {balances.map(b => <li>{b.available}</li>)} - </ul> - </div> : <div class="title"> - Balance: <span>{amountToString(balances[0].available)}</span> - </div>} - </header>} - {list.length === 0 ? <section data-expanded data-centered> - <p><i18n.Translate> - You have no history yet, here you will be able to check your last transactions. - </i18n.Translate></p> - </section> : - <section> - {list.slice(0, 3).map((tx, i) => ( - <TransactionItem key={i} tx={tx} multiCurrency={multiCurrency} /> - ))} - </section> - } - <footer style={{ justifyContent: 'space-around' }}> - {list.length > 0 && - <a target="_blank" - rel="noopener noreferrer" - style={{ color: 'darkgreen', textDecoration: 'none' }} - href={chrome.extension ? chrome.extension.getURL(`/static/wallet.html#/history`) : '#'}>VIEW MORE TRANSACTIONS</a> - } - </footer> - </PopupBox> +export function HistoryView({ + list, + balances, +}: { + list: Transaction[]; + balances: Balance[]; +}) { + const multiCurrency = balances.length > 1; + return ( + <PopupBox noPadding> + {balances.length > 0 && ( + <header> + {multiCurrency ? ( + <div class="title"> + Balance:{" "} + <ul style={{ margin: 0 }}> + {balances.map((b) => ( + <li>{b.available}</li> + ))} + </ul> + </div> + ) : ( + <div class="title"> + Balance: <span>{amountToString(balances[0].available)}</span> + </div> + )} + </header> + )} + {list.length === 0 ? ( + <section data-expanded data-centered> + <p> + <i18n.Translate> + You have no history yet, here you will be able to check your last + transactions. + </i18n.Translate> + </p> + </section> + ) : ( + <section> + {list.slice(0, 3).map((tx, i) => ( + <TransactionItem key={i} tx={tx} multiCurrency={multiCurrency} /> + ))} + </section> + )} + <footer style={{ justifyContent: "space-around" }}> + {list.length > 0 && ( + <a + target="_blank" + rel="noopener noreferrer" + style={{ color: "darkgreen", textDecoration: "none" }} + href={ + chrome.extension + ? chrome.extension.getURL(`/static/wallet.html#/history`) + : "#" + } + > + VIEW MORE TRANSACTIONS + </a> + )} + </footer> + </PopupBox> + ); } diff --git a/packages/taler-wallet-webextension/src/popup/Popup.stories.tsx b/packages/taler-wallet-webextension/src/popup/Popup.stories.tsx index cd443e9d4..5009684c5 100644 --- a/packages/taler-wallet-webextension/src/popup/Popup.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Popup.stories.tsx @@ -15,30 +15,29 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { NavBar as TestedComponent } from '../NavigationBar'; +import { createExample } from "../test-utils"; +import { NavBar as TestedComponent } from "../NavigationBar"; export default { - title: 'popup/header', + title: "popup/header", // component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const OnBalance = createExample(TestedComponent, { - devMode:false, - path:'/balance' + devMode: false, + path: "/balance", }); export const OnHistoryWithDevMode = createExample(TestedComponent, { - devMode:true, - path:'/history' + devMode: true, + path: "/history", }); diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx index de1f67b96..0cff7f75f 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx @@ -15,38 +15,37 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { ConfirmProviderView as TestedComponent } from './ProviderAddPage'; +import { createExample } from "../test-utils"; +import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage"; export default { - title: 'popup/backup/confirm', + title: "popup/backup/confirm", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const DemoService = createExample(TestedComponent, { - url: 'https://sync.demo.taler.net/', + url: "https://sync.demo.taler.net/", provider: { - annual_fee: 'KUDOS:0.1', - storage_limit_in_megabytes: 20, - supported_protocol_version: '1' - } + annual_fee: "KUDOS:0.1", + storage_limit_in_megabytes: 20, + supported_protocol_version: "1", + }, }); export const FreeService = createExample(TestedComponent, { - url: 'https://sync.taler:9667/', + url: "https://sync.taler:9667/", provider: { - annual_fee: 'ARS:0', - storage_limit_in_megabytes: 20, - supported_protocol_version: '1' - } + annual_fee: "ARS:0", + storage_limit_in_megabytes: 20, + supported_protocol_version: "1", + }, }); diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx index 2daf49e0c..9a2f97051 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx @@ -15,39 +15,37 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { SetUrlView as TestedComponent } from './ProviderAddPage'; +import { createExample } from "../test-utils"; +import { SetUrlView as TestedComponent } from "./ProviderAddPage"; export default { - title: 'popup/backup/add', + title: "popup/backup/add", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - -export const Initial = createExample(TestedComponent, { -}); +export const Initial = createExample(TestedComponent, {}); export const WithValue = createExample(TestedComponent, { - initialValue: 'sync.demo.taler.net' -}); + initialValue: "sync.demo.taler.net", +}); export const WithConnectionError = createExample(TestedComponent, { - withError: 'Network error' -}); + withError: "Network error", +}); export const WithClientError = createExample(TestedComponent, { - withError: 'URL may not be right: (404) Not Found' -}); + withError: "URL may not be right: (404) Not Found", +}); export const WithServerError = createExample(TestedComponent, { - withError: 'Try another server: (500) Internal Server Error' -}); + withError: "Try another server: (500) Internal Server Error", +}); diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx index 4416608f8..fab21398a 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx @@ -15,224 +15,221 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; -import { createExample } from '../test-utils'; -import { ProviderView as TestedComponent } from './ProviderDetailPage'; +import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { createExample } from "../test-utils"; +import { ProviderView as TestedComponent } from "./ProviderDetailPage"; export default { - title: 'popup/backup/details', + title: "popup/backup/details", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const Active = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const ActiveErrorSync = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, }, lastAttemptedBackupTimestamp: { - "t_ms": 1625063925078 + t_ms: 1625063925078, }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, }, lastError: { code: 2002, - details: 'details', - hint: 'error hint from the server', - message: 'message' - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + details: "details", + hint: "error hint from the server", + message: "message", + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, }, backupProblem: { - type: 'backup-unreadable' - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + type: "backup-unreadable", + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const ActiveBackupProblemDevice = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, }, backupProblem: { - type: 'backup-conflicting-device', - myDeviceId: 'my-device-id', - otherDeviceId: 'other-device-id', + type: "backup-conflicting-device", + myDeviceId: "my-device-id", + otherDeviceId: "other-device-id", backupTimestamp: { - "t_ms": 1656599921000 - } - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const InactiveUnpaid = createExample(TestedComponent, { info: { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Unpaid, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const InactiveInsufficientBalance = createExample(TestedComponent, { info: { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.InsufficientBalance, - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.InsufficientBalance, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const InactivePending = createExample(TestedComponent, { info: { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Pending, - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Pending, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); - export const ActiveTermsChanged = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.TermsChanged, + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.TermsChanged, paidUntil: { - t_ms: 1656599921000 + t_ms: 1656599921000, }, newTerms: { - "annualFee": "EUR:10", - "storageLimitInMegabytes": 8, - "supportedProtocolVersion": "0.0" + annualFee: "EUR:10", + storageLimitInMegabytes: 8, + supportedProtocolVersion: "0.0", }, oldTerms: { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); - diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx index 04adbb21c..9617c9a41 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx @@ -14,13 +14,23 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { i18n, Timestamp } from "@gnu-taler/taler-util"; -import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { + ProviderInfo, + ProviderPaymentStatus, + ProviderPaymentType, +} from "@gnu-taler/taler-wallet-core"; import { format, formatDuration, intervalToDuration } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { ErrorMessage } from "../components/ErrorMessage"; -import { Button, ButtonDestructive, ButtonPrimary, PaymentStatus, PopupBox, SmallLightText } from "../components/styled"; +import { + Button, + ButtonDestructive, + ButtonPrimary, + PaymentStatus, + PopupBox, + SmallLightText, +} from "../components/styled"; import { useProviderStatus } from "../hooks/useProviderStatus"; interface Props { @@ -29,20 +39,29 @@ interface Props { } export function ProviderDetailPage({ pid, onBack }: Props): VNode { - const status = useProviderStatus(pid) + const status = useProviderStatus(pid); if (!status) { - return <div><i18n.Translate>Loading...</i18n.Translate></div> + return ( + <div> + <i18n.Translate>Loading...</i18n.Translate> + </div> + ); } if (!status.info) { - onBack() - return <div /> + onBack(); + return <div />; } - return <ProviderView info={status.info} - onSync={status.sync} - onDelete={() => status.remove().then(onBack)} - onBack={onBack} - onExtend={() => { null }} - />; + return ( + <ProviderView + info={status.info} + onSync={status.sync} + onDelete={() => status.remove().then(onBack)} + onBack={onBack} + onExtend={() => { + null; + }} + /> + ); } export interface ViewProps { @@ -53,124 +72,185 @@ export interface ViewProps { onExtend: () => void; } -export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewProps): VNode { - const lb = info?.lastSuccessfulBackupTimestamp - const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged +export function ProviderView({ + info, + onDelete, + onSync, + onBack, + onExtend, +}: ViewProps): VNode { + const lb = info?.lastSuccessfulBackupTimestamp; + const isPaid = + info.paymentStatus.type === ProviderPaymentType.Paid || + info.paymentStatus.type === ProviderPaymentType.TermsChanged; return ( <PopupBox> <Error info={info} /> <header> - <h3>{info.name} <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText></h3> - <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus> + <h3> + {info.name}{" "} + <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText> + </h3> + <PaymentStatus color={isPaid ? "rgb(28, 184, 65)" : "rgb(202, 60, 60)"}> + {isPaid ? "Paid" : "Unpaid"} + </PaymentStatus> </header> <section> - <p><b>Last backup:</b> {lb == null || lb.t_ms == "never" ? "never" : format(lb.t_ms, 'dd MMM yyyy')} </p> - <ButtonPrimary onClick={onSync}><i18n.Translate>Back up</i18n.Translate></ButtonPrimary> - {info.terms && <Fragment> - <p><b>Provider fee:</b> {info.terms && info.terms.annualFee} per year</p> - </Fragment> - } + <p> + <b>Last backup:</b>{" "} + {lb == null || lb.t_ms == "never" + ? "never" + : format(lb.t_ms, "dd MMM yyyy")}{" "} + </p> + <ButtonPrimary onClick={onSync}> + <i18n.Translate>Back up</i18n.Translate> + </ButtonPrimary> + {info.terms && ( + <Fragment> + <p> + <b>Provider fee:</b> {info.terms && info.terms.annualFee} per year + </p> + </Fragment> + )} <p>{descriptionByStatus(info.paymentStatus)}</p> - <ButtonPrimary disabled onClick={onExtend}><i18n.Translate>Extend</i18n.Translate></ButtonPrimary> - - {info.paymentStatus.type === ProviderPaymentType.TermsChanged && <div> - <p><i18n.Translate>terms has changed, extending the service will imply accepting the new terms of service</i18n.Translate></p> - <table> - <thead> - <tr> - <td></td> - <td><i18n.Translate>old</i18n.Translate></td> - <td> -></td> - <td><i18n.Translate>new</i18n.Translate></td> - </tr> - </thead> - <tbody> - - <tr> - <td><i18n.Translate>fee</i18n.Translate></td> - <td>{info.paymentStatus.oldTerms.annualFee}</td> - <td>-></td> - <td>{info.paymentStatus.newTerms.annualFee}</td> - </tr> - <tr> - <td><i18n.Translate>storage</i18n.Translate></td> - <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td> - <td>-></td> - <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td> - </tr> - </tbody> - </table> - </div>} + <ButtonPrimary disabled onClick={onExtend}> + <i18n.Translate>Extend</i18n.Translate> + </ButtonPrimary> + {info.paymentStatus.type === ProviderPaymentType.TermsChanged && ( + <div> + <p> + <i18n.Translate> + terms has changed, extending the service will imply accepting + the new terms of service + </i18n.Translate> + </p> + <table> + <thead> + <tr> + <td></td> + <td> + <i18n.Translate>old</i18n.Translate> + </td> + <td> -></td> + <td> + <i18n.Translate>new</i18n.Translate> + </td> + </tr> + </thead> + <tbody> + <tr> + <td> + <i18n.Translate>fee</i18n.Translate> + </td> + <td>{info.paymentStatus.oldTerms.annualFee}</td> + <td>-></td> + <td>{info.paymentStatus.newTerms.annualFee}</td> + </tr> + <tr> + <td> + <i18n.Translate>storage</i18n.Translate> + </td> + <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td> + <td>-></td> + <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td> + </tr> + </tbody> + </table> + </div> + )} </section> <footer> - <Button onClick={onBack}><i18n.Translate> < back</i18n.Translate></Button> + <Button onClick={onBack}> + <i18n.Translate> < back</i18n.Translate> + </Button> <div> - <ButtonDestructive onClick={onDelete}><i18n.Translate>remove provider</i18n.Translate></ButtonDestructive> + <ButtonDestructive onClick={onDelete}> + <i18n.Translate>remove provider</i18n.Translate> + </ButtonDestructive> </div> </footer> </PopupBox> - ) + ); } function daysSince(d?: Timestamp) { - if (!d || d.t_ms === 'never') return 'never synced' + if (!d || d.t_ms === "never") return "never synced"; const duration = intervalToDuration({ start: d.t_ms, end: new Date(), - }) + }); const str = formatDuration(duration, { - delimiter: ', ', + delimiter: ", ", format: [ - duration?.years ? i18n.str`years` : ( - duration?.months ? i18n.str`months` : ( - duration?.days ? i18n.str`days` : ( - duration?.hours ? i18n.str`hours` : ( - duration?.minutes ? i18n.str`minutes` : i18n.str`seconds` - ) - ) - ) - ) - ] - }) - return `synced ${str} ago` + duration?.years + ? i18n.str`years` + : duration?.months + ? i18n.str`months` + : duration?.days + ? i18n.str`days` + : duration?.hours + ? i18n.str`hours` + : duration?.minutes + ? i18n.str`minutes` + : i18n.str`seconds`, + ], + }); + return `synced ${str} ago`; } function Error({ info }: { info: ProviderInfo }) { if (info.lastError) { - return <ErrorMessage title={info.lastError.hint} /> + return <ErrorMessage title={info.lastError.hint} />; } if (info.backupProblem) { switch (info.backupProblem.type) { case "backup-conflicting-device": - return <ErrorMessage title={<Fragment> - <i18n.Translate>There is conflict with another backup from <b>{info.backupProblem.otherDeviceId}</b></i18n.Translate> - </Fragment>} /> + return ( + <ErrorMessage + title={ + <Fragment> + <i18n.Translate> + There is conflict with another backup from{" "} + <b>{info.backupProblem.otherDeviceId}</b> + </i18n.Translate> + </Fragment> + } + /> + ); case "backup-unreadable": - return <ErrorMessage title="Backup is not readable" /> + return <ErrorMessage title="Backup is not readable" />; default: - return <ErrorMessage title={<Fragment> - <i18n.Translate>Unknown backup problem: {JSON.stringify(info.backupProblem)}</i18n.Translate> - </Fragment>} /> + return ( + <ErrorMessage + title={ + <Fragment> + <i18n.Translate> + Unknown backup problem: {JSON.stringify(info.backupProblem)} + </i18n.Translate> + </Fragment> + } + /> + ); } } - return null + return null; } function colorByStatus(status: ProviderPaymentType) { switch (status) { case ProviderPaymentType.InsufficientBalance: - return 'rgb(223, 117, 20)' + return "rgb(223, 117, 20)"; case ProviderPaymentType.Unpaid: - return 'rgb(202, 60, 60)' + return "rgb(202, 60, 60)"; case ProviderPaymentType.Paid: - return 'rgb(28, 184, 65)' + return "rgb(28, 184, 65)"; case ProviderPaymentType.Pending: - return 'gray' + return "gray"; case ProviderPaymentType.InsufficientBalance: - return 'rgb(202, 60, 60)' + return "rgb(202, 60, 60)"; case ProviderPaymentType.TermsChanged: - return 'rgb(202, 60, 60)' + return "rgb(202, 60, 60)"; } } @@ -180,16 +260,19 @@ function descriptionByStatus(status: ProviderPaymentStatus) { // return i18n.str`not paid yet` case ProviderPaymentType.Paid: case ProviderPaymentType.TermsChanged: - if (status.paidUntil.t_ms === 'never') { - return i18n.str`service paid` + if (status.paidUntil.t_ms === "never") { + return i18n.str`service paid`; } else { - return <Fragment> - <b>Backup valid until:</b> {format(status.paidUntil.t_ms, 'dd MMM yyyy')} - </Fragment> + return ( + <Fragment> + <b>Backup valid until:</b>{" "} + {format(status.paidUntil.t_ms, "dd MMM yyyy")} + </Fragment> + ); } case ProviderPaymentType.Unpaid: case ProviderPaymentType.InsufficientBalance: case ProviderPaymentType.Pending: - return '' + return ""; } } diff --git a/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx b/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx index 06e33c9d3..ae8e54ba1 100644 --- a/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx @@ -15,29 +15,28 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { SettingsView as TestedComponent } from './Settings'; +import { createExample } from "../test-utils"; +import { SettingsView as TestedComponent } from "./Settings"; export default { - title: 'popup/settings', + title: "popup/settings", component: TestedComponent, argTypes: { setDeviceName: () => Promise.resolve(), - } + }, }; export const AllOff = createExample(TestedComponent, { - deviceName: 'this-is-the-device-name', + deviceName: "this-is-the-device-name", setDeviceName: () => Promise.resolve(), }); export const OneChecked = createExample(TestedComponent, { - deviceName: 'this-is-the-device-name', + deviceName: "this-is-the-device-name", permissionsEnabled: true, setDeviceName: () => Promise.resolve(), }); - diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx index 8595c87ff..3b83f0762 100644 --- a/packages/taler-wallet-webextension/src/popup/Settings.tsx +++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx @@ -14,7 +14,6 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { i18n } from "@gnu-taler/taler-util"; import { VNode, h } from "preact"; import { Checkbox } from "../components/Checkbox"; @@ -28,15 +27,21 @@ import { useLang } from "../hooks/useLang"; export function SettingsPage(): VNode { const [permissionsEnabled, togglePermissions] = useExtendedPermissions(); - const { devMode, toggleDevMode } = useDevContext() - const { name, update } = useBackupDeviceName() - const [lang, changeLang] = useLang() - return <SettingsView - lang={lang} changeLang={changeLang} - deviceName={name} setDeviceName={update} - permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions} - developerMode={devMode} toggleDeveloperMode={toggleDevMode} - />; + const { devMode, toggleDevMode } = useDevContext(); + const { name, update } = useBackupDeviceName(); + const [lang, changeLang] = useLang(); + return ( + <SettingsView + lang={lang} + changeLang={changeLang} + deviceName={name} + setDeviceName={update} + permissionsEnabled={permissionsEnabled} + togglePermissions={togglePermissions} + developerMode={devMode} + toggleDeveloperMode={toggleDevMode} + /> + ); } export interface ViewProps { @@ -50,23 +55,31 @@ export interface ViewProps { toggleDeveloperMode: () => void; } -import { strings as messages } from '../i18n/strings' +import { strings as messages } from "../i18n/strings"; type LangsNames = { - [P in keyof typeof messages]: string -} + [P in keyof typeof messages]: string; +}; const names: LangsNames = { - es: 'Español [es]', - en: 'English [en]', - fr: 'Français [fr]', - de: 'Deutsch [de]', - sv: 'Svenska [sv]', - it: 'Italiano [it]', -} - + es: "Español [es]", + en: "English [en]", + fr: "Français [fr]", + de: "Deutsch [de]", + sv: "Svenska [sv]", + it: "Italiano [it]", +}; -export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode { +export function SettingsView({ + lang, + changeLang, + deviceName, + setDeviceName, + permissionsEnabled, + togglePermissions, + developerMode, + toggleDeveloperMode, +}: ViewProps): VNode { return ( <PopupBox> <section> @@ -86,25 +99,39 @@ export function SettingsView({ lang, changeLang, deviceName, setDeviceName, perm label={i18n.str`Device name`} description="(This is how you will recognize the wallet in the backup provider)" /> */} - <h2><i18n.Translate>Permissions</i18n.Translate></h2> - <Checkbox label="Automatically open wallet based on page content" + <h2> + <i18n.Translate>Permissions</i18n.Translate> + </h2> + <Checkbox + label="Automatically open wallet based on page content" name="perm" description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" - enabled={permissionsEnabled} onToggle={togglePermissions} + enabled={permissionsEnabled} + onToggle={togglePermissions} /> <h2>Config</h2> - <Checkbox label="Developer mode" + <Checkbox + label="Developer mode" name="devMode" description="(More options and information useful for debugging)" - enabled={developerMode} onToggle={toggleDeveloperMode} + enabled={developerMode} + onToggle={toggleDeveloperMode} /> </section> - <footer style={{ justifyContent: 'space-around' }}> - <a target="_blank" + <footer style={{ justifyContent: "space-around" }}> + <a + target="_blank" rel="noopener noreferrer" - style={{ color: 'darkgreen', textDecoration: 'none' }} - href={chrome.extension ? chrome.extension.getURL(`/static/wallet.html#/settings`) : '#'}>VIEW MORE SETTINGS</a> + style={{ color: "darkgreen", textDecoration: "none" }} + href={ + chrome.extension + ? chrome.extension.getURL(`/static/wallet.html#/settings`) + : "#" + } + > + VIEW MORE SETTINGS + </a> </footer> </PopupBox> - ) -}
\ No newline at end of file + ); +} diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx b/packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx index 88c7c725e..f20403d6a 100644 --- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx @@ -15,38 +15,38 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { TalerActionFound as TestedComponent } from './TalerActionFound'; +import { createExample } from "../test-utils"; +import { TalerActionFound as TestedComponent } from "./TalerActionFound"; export default { - title: 'popup/TalerActionFound', + title: "popup/TalerActionFound", component: TestedComponent, }; export const PayAction = createExample(TestedComponent, { - url: 'taler://pay/something' + url: "taler://pay/something", }); export const WithdrawalAction = createExample(TestedComponent, { - url: 'taler://withdraw/something' + url: "taler://withdraw/something", }); export const TipAction = createExample(TestedComponent, { - url: 'taler://tip/something' + url: "taler://tip/something", }); export const NotifyAction = createExample(TestedComponent, { - url: 'taler://notify-reserve/something' + url: "taler://notify-reserve/something", }); export const RefundAction = createExample(TestedComponent, { - url: 'taler://refund/something' + url: "taler://refund/something", }); export const InvalidAction = createExample(TestedComponent, { - url: 'taler://something/asd' + url: "taler://something/asd", }); diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx index ef0ec341c..cbdcbeb15 100644 --- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx +++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx @@ -1,5 +1,31 @@ +/* + This file is part of GNU Taler + (C) 2021 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 { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; -import { ButtonPrimary, ButtonSuccess, PopupBox } from "../components/styled/index"; +import { + ButtonPrimary, + ButtonSuccess, + PopupBox, +} from "../components/styled/index"; +import { h } from "preact"; export interface Props { url: string; @@ -8,54 +34,89 @@ export interface Props { export function TalerActionFound({ url, onDismiss }: Props) { const uriType = classifyTalerUri(url); - return <PopupBox> - <section> - <h1>Taler Action </h1> - {uriType === TalerUriType.TalerPay && <div> - <p>This page has pay action.</p> - <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": actionForTalerUri(uriType, url) }); }}> - Open pay page - </ButtonSuccess> - </div>} - {uriType === TalerUriType.TalerWithdraw && <div> - <p>This page has a withdrawal action.</p> - <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": actionForTalerUri(uriType, url) }); }}> - Open withdraw page - </ButtonSuccess> - </div>} - {uriType === TalerUriType.TalerTip && <div> - <p>This page has a tip action.</p> - <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": actionForTalerUri(uriType, url) }); }}> - Open tip page - </ButtonSuccess> - </div>} - {uriType === TalerUriType.TalerNotifyReserve && <div> - <p>This page has a notify reserve action.</p> - <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": actionForTalerUri(uriType, url) }); }}> - Notify - </ButtonSuccess> - </div>} - {uriType === TalerUriType.TalerRefund && <div> - <p>This page has a refund action.</p> - <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": actionForTalerUri(uriType, url) }); }}> - Open refund page - </ButtonSuccess> - </div>} - {uriType === TalerUriType.Unknown && <div> - <p>This page has a malformed taler uri.</p> - <p>{url}</p> - </div>} - - </section> - <footer> - <div /> - <ButtonPrimary onClick={() => onDismiss()}> Dismiss </ButtonPrimary> - </footer> - </PopupBox>; - + return ( + <PopupBox> + <section> + <h1>Taler Action </h1> + {uriType === TalerUriType.TalerPay && ( + <div> + <p>This page has pay action.</p> + <ButtonSuccess + onClick={() => { + chrome.tabs.create({ url: actionForTalerUri(uriType, url) }); + }} + > + Open pay page + </ButtonSuccess> + </div> + )} + {uriType === TalerUriType.TalerWithdraw && ( + <div> + <p>This page has a withdrawal action.</p> + <ButtonSuccess + onClick={() => { + chrome.tabs.create({ url: actionForTalerUri(uriType, url) }); + }} + > + Open withdraw page + </ButtonSuccess> + </div> + )} + {uriType === TalerUriType.TalerTip && ( + <div> + <p>This page has a tip action.</p> + <ButtonSuccess + onClick={() => { + chrome.tabs.create({ url: actionForTalerUri(uriType, url) }); + }} + > + Open tip page + </ButtonSuccess> + </div> + )} + {uriType === TalerUriType.TalerNotifyReserve && ( + <div> + <p>This page has a notify reserve action.</p> + <ButtonSuccess + onClick={() => { + chrome.tabs.create({ url: actionForTalerUri(uriType, url) }); + }} + > + Notify + </ButtonSuccess> + </div> + )} + {uriType === TalerUriType.TalerRefund && ( + <div> + <p>This page has a refund action.</p> + <ButtonSuccess + onClick={() => { + chrome.tabs.create({ url: actionForTalerUri(uriType, url) }); + }} + > + Open refund page + </ButtonSuccess> + </div> + )} + {uriType === TalerUriType.Unknown && ( + <div> + <p>This page has a malformed taler uri.</p> + <p>{url}</p> + </div> + )} + </section> + <footer> + <div /> + <ButtonPrimary onClick={() => onDismiss()}> Dismiss </ButtonPrimary> + </footer> + </PopupBox> + ); } -function actionForTalerUri(uriType: TalerUriType, talerUri: string): string | undefined { +function actionForTalerUri( + uriType: TalerUriType, + talerUri: string, +): string | undefined { switch (uriType) { case TalerUriType.TalerWithdraw: return makeExtensionUrlWithParams("static/wallet.html#/withdraw", { @@ -91,8 +152,10 @@ function makeExtensionUrlWithParams( ): string { const innerUrl = new URL(chrome.extension.getURL("/" + url)); if (params) { - const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&') - innerUrl.hash = innerUrl.hash + '?' + hParams + const hParams = Object.keys(params) + .map((k) => `${k}=${params[k]}`) + .join("&"); + innerUrl.hash = innerUrl.hash + "?" + hParams; } return innerUrl.href; } diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx index 070df554c..a5723ccb5 100644 --- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx @@ -22,19 +22,17 @@ import { setupI18n } from "@gnu-taler/taler-util"; import { createHashHistory } from "history"; -import { render, h, VNode } from "preact"; -import Router, { route, Route, getCurrentUrl } from "preact-router"; -import { useEffect, useState } from "preact/hooks"; +import { render, h } from "preact"; +import Router, { route, Route } from "preact-router"; +import { useEffect } from "preact/hooks"; import { DevContextProvider } from "./context/devContext"; import { useTalerActionURL } from "./hooks/useTalerActionURL"; import { strings } from "./i18n/strings"; +import { Pages, WalletNavBar } from "./NavigationBar"; import { BackupPage } from "./popup/BackupPage"; import { BalancePage } from "./popup/BalancePage"; -import { DeveloperPage as DeveloperPage } from "./popup/Debug"; +import { DeveloperPage } from "./popup/Debug"; import { HistoryPage } from "./popup/History"; -import { - Pages, WalletNavBar -} from "./NavigationBar"; import { ProviderAddPage } from "./popup/ProviderAddPage"; import { ProviderDetailPage } from "./popup/ProviderDetailPage"; import { SettingsPage } from "./popup/Settings"; @@ -64,11 +62,11 @@ if (document.readyState === "loading") { } function Application() { - const [talerActionUrl, setDismissed] = useTalerActionURL() + const [talerActionUrl, setDismissed] = useTalerActionURL(); useEffect(() => { - if (talerActionUrl) route(Pages.cta) - },[talerActionUrl]) + if (talerActionUrl) route(Pages.cta); + }, [talerActionUrl]); return ( <div> @@ -78,33 +76,54 @@ function Application() { <Router history={createHashHistory()}> <Route path={Pages.dev} component={DeveloperPage} /> - <Route path={Pages.balance} component={BalancePage} - goToWalletManualWithdraw={() => goToWalletPage(Pages.manual_withdraw)} + <Route + path={Pages.balance} + component={BalancePage} + goToWalletManualWithdraw={() => + goToWalletPage(Pages.manual_withdraw) + } /> <Route path={Pages.settings} component={SettingsPage} /> - <Route path={Pages.cta} component={() => <TalerActionFound url={talerActionUrl!} onDismiss={() => { - setDismissed(true) - route(Pages.balance) - }} />} /> + <Route + path={Pages.cta} + component={() => ( + <TalerActionFound + url={talerActionUrl!} + onDismiss={() => { + setDismissed(true); + route(Pages.balance); + }} + /> + )} + /> - <Route path={Pages.transaction} - component={({ tid }: { tid: string }) => goToWalletPage(Pages.transaction.replace(':tid', tid))} + <Route + path={Pages.transaction} + component={({ tid }: { tid: string }) => + goToWalletPage(Pages.transaction.replace(":tid", tid)) + } /> <Route path={Pages.history} component={HistoryPage} /> - <Route path={Pages.backup} component={BackupPage} + <Route + path={Pages.backup} + component={BackupPage} onAddProvider={() => { - route(Pages.provider_add) + route(Pages.provider_add); }} /> - <Route path={Pages.provider_detail} component={ProviderDetailPage} + <Route + path={Pages.provider_detail} + component={ProviderDetailPage} onBack={() => { - route(Pages.backup) + route(Pages.backup); }} /> - <Route path={Pages.provider_add} component={ProviderAddPage} + <Route + path={Pages.provider_add} + component={ProviderAddPage} onBack={() => { - route(Pages.backup) + route(Pages.backup); }} /> <Route default component={Redirect} to={Pages.balance} /> @@ -119,13 +138,13 @@ function goToWalletPage(page: Pages | string): null { chrome.tabs.create({ active: true, url: chrome.extension.getURL(`/static/wallet.html#${page}`), - }) - return null + }); + return null; } function Redirect({ to }: { to: string }): null { useEffect(() => { - route(to, true) - }) - return null + route(to, true); + }); + return null; } diff --git a/packages/taler-wallet-webextension/src/renderHtml.tsx b/packages/taler-wallet-webextension/src/renderHtml.tsx index bbe8e465c..9c2a794dd 100644 --- a/packages/taler-wallet-webextension/src/renderHtml.tsx +++ b/packages/taler-wallet-webextension/src/renderHtml.tsx @@ -87,10 +87,7 @@ interface CollapsibleProps { * Component that shows/hides its children when clicking * a heading. */ -export class Collapsible extends Component< - CollapsibleProps, - CollapsibleState -> { +export class Collapsible extends Component<CollapsibleProps, CollapsibleState> { constructor(props: CollapsibleProps) { super(props); this.state = { collapsed: props.initiallyCollapsed }; @@ -139,23 +136,20 @@ export function ExpanderText({ text }: ExpanderTextProps): JSX.Element { return <span>{text}</span>; } -export interface LoadingButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> { +export interface LoadingButtonProps + extends JSX.HTMLAttributes<HTMLButtonElement> { isLoading: boolean; } -export function ProgressButton({isLoading, ...rest}: LoadingButtonProps): JSX.Element { +export function ProgressButton({ + isLoading, + ...rest +}: LoadingButtonProps): JSX.Element { return ( - <button - class="pure-button pure-button-primary" - type="button" - {...rest} - > + <button class="pure-button pure-button-primary" type="button" {...rest}> {isLoading ? ( <span> - <object - class="svg-icon svg-baseline" - data="/img/spinner-bars.svg" - /> + <object class="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> ) : null}{" "} {rest.children} @@ -163,17 +157,13 @@ export function ProgressButton({isLoading, ...rest}: LoadingButtonProps): JSX.El ); } -export function PageLink( - props: { pageName: string, children?: ComponentChildren }, -): JSX.Element { +export function PageLink(props: { + pageName: string; + children?: ComponentChildren; +}): JSX.Element { const url = chrome.extension.getURL(`/static/wallet.html#/${props.pageName}`); return ( - <a - class="actionLink" - href={url} - target="_blank" - rel="noopener noreferrer" - > + <a class="actionLink" href={url} target="_blank" rel="noopener noreferrer"> {props.children} </a> ); diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts index 6bf1be3ff..28622bb85 100644 --- a/packages/taler-wallet-webextension/src/test-utils.ts +++ b/packages/taler-wallet-webextension/src/test-utils.ts @@ -14,15 +14,17 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { ComponentChildren, FunctionalComponent, h as render } from 'preact'; +import { ComponentChildren, FunctionalComponent, h as render } from "preact"; -export function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { - const r = (args: any) => render(Component, args) - r.args = props - return r +export function createExample<Props>( + Component: FunctionalComponent<Props>, + props: Partial<Props>, +) { + const r = (args: any) => render(Component, args); + r.args = props; + return r; } - export function NullLink({ children }: { children?: ComponentChildren }) { - return render('a', { children, href: 'javascript:void(0);' }) + return render("a", { children, href: "javascript:void(0);" }); } diff --git a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx index 9a53fefe2..b2771bc2a 100644 --- a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx @@ -15,179 +15,184 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; -import { addDays } from 'date-fns'; -import { BackupView as TestedComponent } from './BackupPage'; -import { createExample } from '../test-utils'; +import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { addDays } from "date-fns"; +import { BackupView as TestedComponent } from "./BackupPage"; +import { createExample } from "../test-utils"; export default { - title: 'wallet/backup/list', + title: "wallet/backup/list", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const LotOfProviders = createExample(TestedComponent, { - providers: [{ - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" - ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } - }, - "terms": { - "annualFee": "ARS:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 + providers: [ + { + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", + ], + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "ARS:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" - ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": addDays(new Date(), 13).getTime() - } + { + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", + ], + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: addDays(new Date(), 13).getTime(), + }, + }, + terms: { + annualFee: "ARS:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "ARS:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Pending, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Pending, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.InsufficientBalance, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.InsufficientBalance, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.TermsChanged, - newTerms: { - annualFee: 'USD:2', - storageLimitInMegabytes: 8, - supportedProtocolVersion: '2', - }, - oldTerms: { - annualFee: 'USD:1', + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.TermsChanged, + newTerms: { + annualFee: "USD:2", + storageLimitInMegabytes: 8, + supportedProtocolVersion: "2", + }, + oldTerms: { + annualFee: "USD:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "1", + }, + paidUntil: { + t_ms: "never", + }, + }, + terms: { + annualFee: "KUDOS:0.1", storageLimitInMegabytes: 16, - supportedProtocolVersion: '1', - + supportedProtocolVersion: "0.0", }, - paidUntil: { - t_ms: 'never' - } }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Unpaid, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, + { + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Unpaid, + }, + terms: { + annualFee: "KUDOS:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }] + ], }); - export const OneProvider = createExample(TestedComponent, { - providers: [{ - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" - ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + providers: [ + { + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", + ], + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "ARS:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, }, - "terms": { - "annualFee": "ARS:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }] + ], }); - export const Empty = createExample(TestedComponent, { - providers: [] + providers: [], }); - diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx index 712329bf8..c3be0203e 100644 --- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -14,15 +14,29 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { i18n, Timestamp } from "@gnu-taler/taler-util"; -import { ProviderInfo, ProviderPaymentStatus } from "@gnu-taler/taler-wallet-core"; -import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns"; +import { + ProviderInfo, + ProviderPaymentStatus, +} from "@gnu-taler/taler-wallet-core"; +import { + differenceInMonths, + formatDuration, + intervalToDuration, +} from "date-fns"; import { Fragment, JSX, VNode, h } from "preact"; import { - BoldLight, ButtonPrimary, ButtonSuccess, Centered, - CenteredText, CenteredBoldText, PopupBox, RowBorderGray, - SmallText, SmallLightText, WalletBox + BoldLight, + ButtonPrimary, + ButtonSuccess, + Centered, + CenteredText, + CenteredBoldText, + PopupBox, + RowBorderGray, + SmallText, + SmallLightText, + WalletBox, } from "../components/styled"; import { useBackupStatus } from "../hooks/useBackupStatus"; import { Pages } from "../NavigationBar"; @@ -32,49 +46,68 @@ interface Props { } export function BackupPage({ onAddProvider }: Props): VNode { - const status = useBackupStatus() + const status = useBackupStatus(); if (!status) { - return <div>Loading...</div> + return <div>Loading...</div>; } - return <BackupView providers={status.providers} onAddProvider={onAddProvider} onSyncAll={status.sync} />; + return ( + <BackupView + providers={status.providers} + onAddProvider={onAddProvider} + onSyncAll={status.sync} + /> + ); } export interface ViewProps { - providers: ProviderInfo[], + providers: ProviderInfo[]; onAddProvider: () => void; onSyncAll: () => Promise<void>; } -export function BackupView({ providers, onAddProvider, onSyncAll }: ViewProps): VNode { +export function BackupView({ + providers, + onAddProvider, + onSyncAll, +}: ViewProps): VNode { return ( <WalletBox> <section> - {providers.map((provider) => <BackupLayout - status={provider.paymentStatus} - timestamp={provider.lastSuccessfulBackupTimestamp} - id={provider.syncProviderBaseUrl} - active={provider.active} - title={provider.name} - /> + {providers.map((provider) => ( + <BackupLayout + status={provider.paymentStatus} + timestamp={provider.lastSuccessfulBackupTimestamp} + id={provider.syncProviderBaseUrl} + active={provider.active} + title={provider.name} + /> + ))} + {!providers.length && ( + <Centered style={{ marginTop: 100 }}> + <BoldLight>No backup providers configured</BoldLight> + <ButtonSuccess onClick={onAddProvider}> + <i18n.Translate>Add provider</i18n.Translate> + </ButtonSuccess> + </Centered> )} - {!providers.length && <Centered style={{ marginTop: 100 }}> - <BoldLight>No backup providers configured</BoldLight> - <ButtonSuccess onClick={onAddProvider}><i18n.Translate>Add provider</i18n.Translate></ButtonSuccess> - </Centered>} </section> - {!!providers.length && <footer> - <div /> - <div> - <ButtonPrimary onClick={onSyncAll}>{ - providers.length > 1 ? - <i18n.Translate>Sync all backups</i18n.Translate> : - <i18n.Translate>Sync now</i18n.Translate> - }</ButtonPrimary> - <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess> - </div> - </footer>} + {!!providers.length && ( + <footer> + <div /> + <div> + <ButtonPrimary onClick={onSyncAll}> + {providers.length > 1 ? ( + <i18n.Translate>Sync all backups</i18n.Translate> + ) : ( + <i18n.Translate>Sync now</i18n.Translate> + )} + </ButtonPrimary> + <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess> + </div> + </footer> + )} </WalletBox> - ) + ); } interface TransactionLayoutProps { @@ -92,55 +125,73 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element { timeStyle: "short", } as any); - return ( <RowBorderGray> <div style={{ color: !props.active ? "grey" : undefined }}> - <a href={Pages.provider_detail.replace(':pid', encodeURIComponent(props.id))}><span>{props.title}</span></a> - - {dateStr && <SmallText style={{ marginTop: 5 }}>Last synced: {dateStr}</SmallText>} - {!dateStr && <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText>} + <a + href={Pages.provider_detail.replace( + ":pid", + encodeURIComponent(props.id), + )} + > + <span>{props.title}</span> + </a> + + {dateStr && ( + <SmallText style={{ marginTop: 5 }}>Last synced: {dateStr}</SmallText> + )} + {!dateStr && ( + <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText> + )} </div> <div> - {props.status?.type === 'paid' ? - <ExpirationText until={props.status.paidUntil} /> : + {props.status?.type === "paid" ? ( + <ExpirationText until={props.status.paidUntil} /> + ) : ( <div>{props.status.type}</div> - } + )} </div> </RowBorderGray> ); } function ExpirationText({ until }: { until: Timestamp }) { - return <Fragment> - <CenteredText> Expires in </CenteredText> - <CenteredBoldText {...({ color: colorByTimeToExpire(until) })}> {daysUntil(until)} </CenteredBoldText> - </Fragment> + return ( + <Fragment> + <CenteredText> Expires in </CenteredText> + <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}> + {" "} + {daysUntil(until)}{" "} + </CenteredBoldText> + </Fragment> + ); } function colorByTimeToExpire(d: Timestamp) { - if (d.t_ms === 'never') return 'rgb(28, 184, 65)' - const months = differenceInMonths(d.t_ms, new Date()) - return months > 1 ? 'rgb(28, 184, 65)' : 'rgb(223, 117, 20)'; + if (d.t_ms === "never") return "rgb(28, 184, 65)"; + const months = differenceInMonths(d.t_ms, new Date()); + return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)"; } function daysUntil(d: Timestamp) { - if (d.t_ms === 'never') return undefined + if (d.t_ms === "never") return undefined; const duration = intervalToDuration({ start: d.t_ms, end: new Date(), - }) + }); const str = formatDuration(duration, { - delimiter: ', ', + delimiter: ", ", format: [ - duration?.years ? 'years' : ( - duration?.months ? 'months' : ( - duration?.days ? 'days' : ( - duration.hours ? 'hours' : 'minutes' - ) - ) - ) - ] - }) - return `${str}` -}
\ No newline at end of file + duration?.years + ? "years" + : duration?.months + ? "months" + : duration?.days + ? "days" + : duration.hours + ? "hours" + : "minutes", + ], + }); + return `${str}`; +} diff --git a/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx index cccda203e..2432c31eb 100644 --- a/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx @@ -15,28 +15,25 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample, NullLink } from '../test-utils'; -import { BalanceView as TestedComponent } from './BalancePage'; +import { createExample, NullLink } from "../test-utils"; +import { BalanceView as TestedComponent } from "./BalancePage"; export default { - title: 'wallet/balance', + title: "wallet/balance", component: TestedComponent, - argTypes: { - } + argTypes: {}, }; - -export const NotYetLoaded = createExample(TestedComponent, { -}); +export const NotYetLoaded = createExample(TestedComponent, {}); export const GotError = createExample(TestedComponent, { balance: { hasError: true, - message: 'Network error' + message: "Network error", }, Linker: NullLink, }); @@ -45,7 +42,7 @@ export const EmptyBalance = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [] + balances: [], }, }, Linker: NullLink, @@ -55,13 +52,15 @@ export const SomeCoins = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:10.5', - hasPendingTransactions: false, - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:10.5", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -71,13 +70,15 @@ export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:2.23', - hasPendingTransactions: false, - pendingIncoming: 'USD:5.11', - pendingOutgoing: 'USD:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:2.23", + hasPendingTransactions: false, + pendingIncoming: "USD:5.11", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, @@ -87,19 +88,22 @@ export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, { balance: { hasError: false, response: { - balances: [{ - available: 'USD:2', - hasPendingTransactions: false, - pendingIncoming: 'USD:5', - pendingOutgoing: 'USD:0', - requiresUserInput: false - },{ - available: 'EUR:4', - hasPendingTransactions: false, - pendingIncoming: 'EUR:5', - pendingOutgoing: 'EUR:0', - requiresUserInput: false - }] + balances: [ + { + available: "USD:2", + hasPendingTransactions: false, + pendingIncoming: "USD:5", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }, + { + available: "EUR:4", + hasPendingTransactions: false, + pendingIncoming: "EUR:5", + pendingOutgoing: "EUR:0", + requiresUserInput: false, + }, + ], }, }, Linker: NullLink, diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx index eb5a0447c..f3c08a3e8 100644 --- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx @@ -15,19 +15,30 @@ */ import { - amountFractionalBase, Amounts, - Balance, BalancesResponse, - i18n + amountFractionalBase, + Amounts, + Balance, + BalancesResponse, + i18n, } from "@gnu-taler/taler-util"; -import { JSX } from "preact"; +import { JSX, h } from "preact"; import { ButtonPrimary, Centered, WalletBox } from "../components/styled/index"; import { BalancesHook, useBalances } from "../hooks/useBalances"; import { PageLink, renderAmount } from "../renderHtml"; - -export function BalancePage({ goToWalletManualWithdraw }: { goToWalletManualWithdraw: () => void }) { - const balance = useBalances() - return <BalanceView balance={balance} Linker={PageLink} goToWalletManualWithdraw={goToWalletManualWithdraw} /> +export function BalancePage({ + goToWalletManualWithdraw, +}: { + goToWalletManualWithdraw: () => void; +}) { + const balance = useBalances(); + return ( + <BalanceView + balance={balance} + Linker={PageLink} + goToWalletManualWithdraw={goToWalletManualWithdraw} + /> + ); } export interface BalanceViewProps { @@ -36,9 +47,13 @@ export interface BalanceViewProps { goToWalletManualWithdraw: () => void; } -export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: BalanceViewProps) { +export function BalanceView({ + balance, + Linker, + goToWalletManualWithdraw, +}: BalanceViewProps) { if (!balance) { - return <span /> + return <span />; } if (balance.hasError) { @@ -50,19 +65,24 @@ export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: Balan diagnostics. </p> </div> - ) + ); } if (balance.response.balances.length === 0) { return ( - <p><i18n.Translate> - You have no balance to show. Need some{" "} - <Linker pageName="/welcome">help</Linker> getting started? - </i18n.Translate></p> - ) + <p> + <i18n.Translate> + You have no balance to show. Need some{" "} + <Linker pageName="/welcome">help</Linker> getting started? + </i18n.Translate> + </p> + ); } - return <ShowBalances wallet={balance.response} - onWithdraw={goToWalletManualWithdraw} - /> + return ( + <ShowBalances + wallet={balance.response} + onWithdraw={goToWalletManualWithdraw} + /> + ); } function formatPending(entry: Balance): JSX.Element { @@ -75,13 +95,15 @@ function formatPending(entry: Balance): JSX.Element { if (!Amounts.isZero(pendingIncoming)) { incoming = ( - <span><i18n.Translate> - <span style={{ color: "darkgreen" }}> - {"+"} - {renderAmount(entry.pendingIncoming)} - </span>{" "} - incoming - </i18n.Translate></span> + <span> + <i18n.Translate> + <span style={{ color: "darkgreen" }}> + {"+"} + {renderAmount(entry.pendingIncoming)} + </span>{" "} + incoming + </i18n.Translate> + </span> ); } @@ -100,27 +122,36 @@ function formatPending(entry: Balance): JSX.Element { ); } - -function ShowBalances({ wallet, onWithdraw }: { wallet: BalancesResponse, onWithdraw: () => void }) { - return <WalletBox> - <section> - <Centered>{wallet.balances.map((entry) => { - const av = Amounts.parseOrThrow(entry.available); - const v = av.value + av.fraction / amountFractionalBase; - return ( - <p key={av.currency}> - <span> - <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "} - <span>{av.currency}</span> - </span> - {formatPending(entry)} - </p> - ); - })}</Centered> - </section> - <footer> - <div /> - <ButtonPrimary onClick={onWithdraw} >Withdraw</ButtonPrimary> - </footer> - </WalletBox> +function ShowBalances({ + wallet, + onWithdraw, +}: { + wallet: BalancesResponse; + onWithdraw: () => void; +}) { + return ( + <WalletBox> + <section> + <Centered> + {wallet.balances.map((entry) => { + const av = Amounts.parseOrThrow(entry.available); + const v = av.value + av.fraction / amountFractionalBase; + return ( + <p key={av.currency}> + <span> + <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "} + <span>{av.currency}</span> + </span> + {formatPending(entry)} + </p> + ); + })} + </Centered> + </section> + <footer> + <div /> + <ButtonPrimary onClick={onWithdraw}>Withdraw</ButtonPrimary> + </footer> + </WalletBox> + ); } diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx index 35da52392..6eab8dc3a 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx @@ -15,42 +15,39 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { CreateManualWithdraw as TestedComponent } from './CreateManualWithdraw'; +import { createExample } from "../test-utils"; +import { CreateManualWithdraw as TestedComponent } from "./CreateManualWithdraw"; export default { - title: 'wallet/manual withdraw/creation', + title: "wallet/manual withdraw/creation", component: TestedComponent, - argTypes: { - } + argTypes: {}, }; - -export const InitialState = createExample(TestedComponent, { -}); +export const InitialState = createExample(TestedComponent, {}); export const WithExchangeFilled = createExample(TestedComponent, { - currency: 'COL', - initialExchange: 'http://exchange.taler:8081', + currency: "COL", + initialExchange: "http://exchange.taler:8081", }); export const WithExchangeAndAmountFilled = createExample(TestedComponent, { - currency: 'COL', - initialExchange: 'http://exchange.taler:8081', - initialAmount: '10' + currency: "COL", + initialExchange: "http://exchange.taler:8081", + initialAmount: "10", }); export const WithExchangeError = createExample(TestedComponent, { - initialExchange: 'http://exchange.tal', - error: 'The exchange url seems invalid' + initialExchange: "http://exchange.tal", + error: "The exchange url seems invalid", }); export const WithAmountError = createExample(TestedComponent, { - currency: 'COL', - initialExchange: 'http://exchange.taler:8081', - initialAmount: 'e' + currency: "COL", + initialExchange: "http://exchange.taler:8081", + initialAmount: "e", }); diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx index be2cbe41d..b48dcbaf2 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx @@ -1,8 +1,35 @@ +/* + This file is part of GNU Taler + (C) 2021 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 { AmountJson, Amounts } from "@gnu-taler/taler-util"; -import { VNode } from "preact"; +import { VNode, h } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { ErrorMessage } from "../components/ErrorMessage"; -import { ButtonPrimary, Input, InputWithLabel, LightText, WalletBox } from "../components/styled"; +import { + ButtonPrimary, + Input, + InputWithLabel, + LightText, + WalletBox, +} from "../components/styled"; export interface Props { error: string | undefined; @@ -13,44 +40,73 @@ export interface Props { onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>; } -export function CreateManualWithdraw({ onExchangeChange, initialExchange, initialAmount, error, currency, onCreate }: Props): VNode { +export function CreateManualWithdraw({ + onExchangeChange, + initialExchange, + initialAmount, + error, + currency, + onCreate, +}: Props): VNode { const [exchange, setExchange] = useState(initialExchange || ""); const [amount, setAmount] = useState(initialAmount || ""); - const parsedAmount = Amounts.parse(`${currency}:${amount}`) + const parsedAmount = Amounts.parse(`${currency}:${amount}`); let timeout = useRef<number | undefined>(undefined); useEffect(() => { - if (timeout) window.clearTimeout(timeout.current) + if (timeout) window.clearTimeout(timeout.current); timeout.current = window.setTimeout(async () => { - onExchangeChange(exchange) + onExchangeChange(exchange); }, 1000); - }, [exchange]) - + }, [exchange]); return ( <WalletBox> <section> - <ErrorMessage title={error && "Can't create the reserve"} description={error} /> + <ErrorMessage + title={error && "Can't create the reserve"} + description={error} + /> <h2>Manual Withdrawal</h2> - <LightText>Choose a exchange to create a reserve and then fill the reserve to withdraw the coins</LightText> + <LightText> + Choose a exchange to create a reserve and then fill the reserve to + withdraw the coins + </LightText> <p> <Input invalid={!!exchange && !currency}> <label>Exchange</label> - <input type="text" placeholder="https://" value={exchange} onChange={(e) => setExchange(e.currentTarget.value)} /> + <input + type="text" + placeholder="https://" + value={exchange} + onChange={(e) => setExchange(e.currentTarget.value)} + /> <small>http://exchange.taler:8081</small> </Input> - {currency && <InputWithLabel invalid={!!amount && !parsedAmount}> - <label>Amount</label> - <div> - <div>{currency}</div> - <input type="number" style={{ paddingLeft: `${currency.length}em` }} value={amount} onChange={e => setAmount(e.currentTarget.value)} /> - </div> - </InputWithLabel>} + {currency && ( + <InputWithLabel invalid={!!amount && !parsedAmount}> + <label>Amount</label> + <div> + <div>{currency}</div> + <input + type="number" + style={{ paddingLeft: `${currency.length}em` }} + value={amount} + onChange={(e) => setAmount(e.currentTarget.value)} + /> + </div> + </InputWithLabel> + )} </p> </section> <footer> <div /> - <ButtonPrimary disabled={!parsedAmount || !exchange} onClick={() => onCreate(exchange, parsedAmount!)}>Create</ButtonPrimary> + <ButtonPrimary + disabled={!parsedAmount || !exchange} + onClick={() => onCreate(exchange, parsedAmount!)} + > + Create + </ButtonPrimary> </footer> </WalletBox> ); diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index 0ac4be9a6..9ae3ac3bd 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -15,133 +15,146 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { PaymentStatus, - TransactionCommon, TransactionDeposit, TransactionPayment, - TransactionRefresh, TransactionRefund, TransactionTip, TransactionType, + TransactionCommon, + TransactionDeposit, + TransactionPayment, + TransactionRefresh, + TransactionRefund, + TransactionTip, + TransactionType, TransactionWithdrawal, - WithdrawalType -} from '@gnu-taler/taler-util'; -import { HistoryView as TestedComponent } from './History'; -import { createExample } from '../test-utils'; - + WithdrawalType, +} from "@gnu-taler/taler-util"; +import { HistoryView as TestedComponent } from "./History"; +import { createExample } from "../test-utils"; export default { - title: 'wallet/history/list', + title: "wallet/history/list", component: TestedComponent, }; -let count = 0 -const commonTransaction = () => ({ - amountRaw: 'USD:10', - amountEffective: 'USD:9', - pending: false, - timestamp: { - t_ms: new Date().getTime() - (count++ * 1000 * 60 * 60 * 7) - }, - transactionId: '12', -} as TransactionCommon) +let count = 0; +const commonTransaction = () => + ({ + amountRaw: "USD:10", + amountEffective: "USD:9", + pending: false, + timestamp: { + t_ms: new Date().getTime() - count++ * 1000 * 60 * 60 * 7, + }, + transactionId: "12", + } as TransactionCommon); const exampleData = { withdraw: { ...commonTransaction(), type: TransactionType.Withdrawal, - exchangeBaseUrl: 'http://exchange.demo.taler.net', + exchangeBaseUrl: "http://exchange.demo.taler.net", withdrawalDetails: { confirmed: false, - exchangePaytoUris: ['payto://x-taler-bank/bank/account'], + exchangePaytoUris: ["payto://x-taler-bank/bank/account"], type: WithdrawalType.ManualTransfer, - } + }, } as TransactionWithdrawal, payment: { ...commonTransaction(), - amountEffective: 'USD:11', + amountEffective: "USD:11", type: TransactionType.Payment, info: { - contractTermsHash: 'ASDZXCASD', + contractTermsHash: "ASDZXCASD", merchant: { - name: 'Blog', + name: "Blog", }, - orderId: '2021.167-03NPY6MCYMVGT', + orderId: "2021.167-03NPY6MCYMVGT", products: [], - summary: 'the summary', - fulfillmentMessage: '', + summary: "the summary", + fulfillmentMessage: "", }, - proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + proposalId: "1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", status: PaymentStatus.Accepted, } as TransactionPayment, deposit: { ...commonTransaction(), type: TransactionType.Deposit, - depositGroupId: '#groupId', - targetPaytoUri: 'payto://x-taler-bank/bank/account', + depositGroupId: "#groupId", + targetPaytoUri: "payto://x-taler-bank/bank/account", } as TransactionDeposit, refresh: { ...commonTransaction(), type: TransactionType.Refresh, - exchangeBaseUrl: 'http://exchange.taler', + exchangeBaseUrl: "http://exchange.taler", } as TransactionRefresh, tip: { ...commonTransaction(), type: TransactionType.Tip, - merchantBaseUrl: 'http://ads.merchant.taler.net/', + merchantBaseUrl: "http://ads.merchant.taler.net/", } as TransactionTip, refund: { ...commonTransaction(), type: TransactionType.Refund, - refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + refundedTransactionId: + "payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", info: { - contractTermsHash: 'ASDZXCASD', + contractTermsHash: "ASDZXCASD", merchant: { - name: 'the merchant', + name: "the merchant", }, - orderId: '2021.167-03NPY6MCYMVGT', + orderId: "2021.167-03NPY6MCYMVGT", products: [], - summary: 'the summary', - fulfillmentMessage: '', + summary: "the summary", + fulfillmentMessage: "", }, } as TransactionRefund, -} +}; export const Empty = createExample(TestedComponent, { list: [], - balances: [{ - available: 'TESTKUDOS:10', - pendingIncoming: 'TESTKUDOS:0', - pendingOutgoing: 'TESTKUDOS:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "TESTKUDOS:10", + pendingIncoming: "TESTKUDOS:0", + pendingOutgoing: "TESTKUDOS:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); - export const One = createExample(TestedComponent, { list: [exampleData.withdraw], - balances: [{ - available: 'USD:10', - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const OnePending = createExample(TestedComponent, { - list: [{ - ...exampleData.withdraw, - pending: true - }], - balances: [{ - available: 'USD:10', - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + list: [ + { + ...exampleData.withdraw, + pending: true, + }, + ], + balances: [ + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const Several = createExample(TestedComponent, { @@ -154,20 +167,23 @@ export const Several = createExample(TestedComponent, { ...exampleData.payment, info: { ...exampleData.payment.info, - summary: 'this is a long summary that may be cropped because its too long', + summary: + "this is a long summary that may be cropped because its too long", }, }, exampleData.refund, exampleData.tip, exampleData.deposit, ], - balances: [{ - available: 'TESTKUDOS:10', - pendingIncoming: 'TESTKUDOS:0', - pendingOutgoing: 'TESTKUDOS:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "TESTKUDOS:10", + pendingIncoming: "TESTKUDOS:0", + pendingOutgoing: "TESTKUDOS:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); export const SeveralWithTwoCurrencies = createExample(TestedComponent, { @@ -181,18 +197,20 @@ export const SeveralWithTwoCurrencies = createExample(TestedComponent, { exampleData.tip, exampleData.deposit, ], - balances: [{ - available: 'TESTKUDOS:10', - pendingIncoming: 'TESTKUDOS:0', - pendingOutgoing: 'TESTKUDOS:0', - hasPendingTransactions: false, - requiresUserInput: false, - }, { - available: 'USD:10', - pendingIncoming: 'USD:0', - pendingOutgoing: 'USD:0', - hasPendingTransactions: false, - requiresUserInput: false, - }] + balances: [ + { + available: "TESTKUDOS:10", + pendingIncoming: "TESTKUDOS:0", + pendingOutgoing: "TESTKUDOS:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + { + available: "USD:10", + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + hasPendingTransactions: false, + requiresUserInput: false, + }, + ], }); - diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 8160f8574..aabe50a29 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -14,7 +14,12 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountString, Balance, Transaction, TransactionsResponse } from "@gnu-taler/taler-util"; +import { + AmountString, + Balance, + Transaction, + TransactionsResponse, +} from "@gnu-taler/taler-util"; import { format } from "date-fns"; import { Fragment, h, JSX } from "preact"; import { useEffect, useState } from "preact/hooks"; @@ -23,13 +28,14 @@ import { TransactionItem } from "../components/TransactionItem"; import { useBalances } from "../hooks/useBalances"; import * as wxApi from "../wxApi"; - export function HistoryPage(props: any): JSX.Element { const [transactions, setTransactions] = useState< TransactionsResponse | undefined >(undefined); - const balance = useBalances() - const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || []) + const balance = useBalances(); + const balanceWithoutError = balance?.hasError + ? [] + : balance?.response.balances || []; useEffect(() => { const fetchData = async (): Promise<void> => { @@ -43,45 +49,74 @@ export function HistoryPage(props: any): JSX.Element { return <div>Loading ...</div>; } - return <HistoryView balances={balanceWithoutError} list={[...transactions.transactions].reverse()} />; + return ( + <HistoryView + balances={balanceWithoutError} + list={[...transactions.transactions].reverse()} + /> + ); } function amountToString(c: AmountString) { - const idx = c.indexOf(':') - return `${c.substring(idx + 1)} ${c.substring(0, idx)}` + const idx = c.indexOf(":"); + return `${c.substring(idx + 1)} ${c.substring(0, idx)}`; } - - -export function HistoryView({ list, balances }: { list: Transaction[], balances: Balance[] }) { +export function HistoryView({ + list, + balances, +}: { + list: Transaction[]; + balances: Balance[]; +}) { const byDate = list.reduce(function (rv, x) { - const theDate = x.timestamp.t_ms === "never" ? "never" : format(x.timestamp.t_ms, 'dd MMMM yyyy'); + const theDate = + x.timestamp.t_ms === "never" + ? "never" + : format(x.timestamp.t_ms, "dd MMMM yyyy"); (rv[theDate] = rv[theDate] || []).push(x); return rv; }, {} as { [x: string]: Transaction[] }); - const multiCurrency = balances.length > 1 + const multiCurrency = balances.length > 1; - return <WalletBox noPadding> - {balances.length > 0 && <header> - {balances.length === 1 && <div class="title"> - Balance: <span>{amountToString(balances[0].available)}</span> - </div>} - {balances.length > 1 && <div class="title"> - Balance: <ul style={{ margin: 0 }}> - {balances.map(b => <li>{b.available}</li>)} - </ul> - </div>} - </header>} - <section> - {Object.keys(byDate).map((d,i) => { - return <Fragment key={i}> - <DateSeparator>{d}</DateSeparator> - {byDate[d].map((tx, i) => ( - <TransactionItem key={i} tx={tx} multiCurrency={multiCurrency}/> - ))} - </Fragment> - })} - </section> - </WalletBox> + return ( + <WalletBox noPadding> + {balances.length > 0 && ( + <header> + {balances.length === 1 && ( + <div class="title"> + Balance: <span>{amountToString(balances[0].available)}</span> + </div> + )} + {balances.length > 1 && ( + <div class="title"> + Balance:{" "} + <ul style={{ margin: 0 }}> + {balances.map((b) => ( + <li>{b.available}</li> + ))} + </ul> + </div> + )} + </header> + )} + <section> + {Object.keys(byDate).map((d, i) => { + return ( + <Fragment key={i}> + <DateSeparator>{d}</DateSeparator> + {byDate[d].map((tx, i) => ( + <TransactionItem + key={i} + tx={tx} + multiCurrency={multiCurrency} + /> + ))} + </Fragment> + ); + })} + </section> + </WalletBox> + ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx index dcc0002e6..102978f9e 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx @@ -14,68 +14,84 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - -import { VNode } from "preact"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { VNode, h } from "preact"; +import { useState } from "preact/hooks"; import { CreateManualWithdraw } from "./CreateManualWithdraw"; -import * as wxApi from '../wxApi' -import { AcceptManualWithdrawalResult, AmountJson, Amounts } from "@gnu-taler/taler-util"; +import * as wxApi from "../wxApi"; +import { + AcceptManualWithdrawalResult, + AmountJson, + Amounts, +} from "@gnu-taler/taler-util"; import { ReserveCreated } from "./ReserveCreated.js"; -import { route } from 'preact-router'; +import { route } from "preact-router"; import { Pages } from "../NavigationBar.js"; -interface Props { - -} +interface Props {} -export function ManualWithdrawPage({ }: Props): VNode { - const [success, setSuccess] = useState<AcceptManualWithdrawalResult | undefined>(undefined) - const [currency, setCurrency] = useState<string | undefined>(undefined) - const [error, setError] = useState<string | undefined>(undefined) +export function ManualWithdrawPage({}: Props): VNode { + const [success, setSuccess] = useState< + AcceptManualWithdrawalResult | undefined + >(undefined); + const [currency, setCurrency] = useState<string | undefined>(undefined); + const [error, setError] = useState<string | undefined>(undefined); - async function onExchangeChange(exchange: string | undefined) { - if (!exchange) return + async function onExchangeChange(exchange: string | undefined): Promise<void> { + if (!exchange) return; try { - const r = await fetch(`${exchange}/keys`) - const j = await r.json() + const r = await fetch(`${exchange}/keys`); + const j = await r.json(); if (j.currency) { await wxApi.addExchange({ exchangeBaseUrl: `${exchange}/`, - forceUpdate: true - }) - setCurrency(j.currency) + forceUpdate: true, + }); + setCurrency(j.currency); } } catch (e) { - setError('The exchange url seems invalid') - setCurrency(undefined) + setError("The exchange url seems invalid"); + setCurrency(undefined); } } - async function doCreate(exchangeBaseUrl: string, amount: AmountJson) { + async function doCreate( + exchangeBaseUrl: string, + amount: AmountJson, + ): Promise<void> { try { - const resp = await wxApi.acceptManualWithdrawal(exchangeBaseUrl, Amounts.stringify(amount)) - setSuccess(resp) + const resp = await wxApi.acceptManualWithdrawal( + exchangeBaseUrl, + Amounts.stringify(amount), + ); + setSuccess(resp); } catch (e) { if (e instanceof Error) { - setError(e.message) + setError(e.message); } else { - setError('unexpected error') + setError("unexpected error"); } - setSuccess(undefined) + setSuccess(undefined); } } if (success) { - return <ReserveCreated reservePub={success.reservePub} paytos={success.exchangePaytoUris} onBack={() => { - route(Pages.balance) - }}/> + return ( + <ReserveCreated + reservePub={success.reservePub} + paytos={success.exchangePaytoUris} + onBack={() => { + route(Pages.balance); + }} + /> + ); } - return <CreateManualWithdraw - error={error} currency={currency} - onCreate={doCreate} onExchangeChange={onExchangeChange} - />; + return ( + <CreateManualWithdraw + error={error} + currency={currency} + onCreate={doCreate} + onExchangeChange={onExchangeChange} + /> + ); } - - - diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx index d1e76c053..5c4e56b15 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx @@ -15,38 +15,37 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { ConfirmProviderView as TestedComponent } from './ProviderAddPage'; +import { createExample } from "../test-utils"; +import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage"; export default { - title: 'wallet/backup/confirm', + title: "wallet/backup/confirm", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const DemoService = createExample(TestedComponent, { - url: 'https://sync.demo.taler.net/', + url: "https://sync.demo.taler.net/", provider: { - annual_fee: 'KUDOS:0.1', - storage_limit_in_megabytes: 20, - supported_protocol_version: '1' - } + annual_fee: "KUDOS:0.1", + storage_limit_in_megabytes: 20, + supported_protocol_version: "1", + }, }); export const FreeService = createExample(TestedComponent, { - url: 'https://sync.taler:9667/', + url: "https://sync.taler:9667/", provider: { - annual_fee: 'ARS:0', - storage_limit_in_megabytes: 20, - supported_protocol_version: '1' - } + annual_fee: "ARS:0", + storage_limit_in_megabytes: 20, + supported_protocol_version: "1", + }, }); diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx index 4890e5e9c..75292b7e4 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx @@ -15,39 +15,37 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { SetUrlView as TestedComponent } from './ProviderAddPage'; +import { createExample } from "../test-utils"; +import { SetUrlView as TestedComponent } from "./ProviderAddPage"; export default { - title: 'wallet/backup/add', + title: "wallet/backup/add", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - -export const Initial = createExample(TestedComponent, { -}); +export const Initial = createExample(TestedComponent, {}); export const WithValue = createExample(TestedComponent, { - initialValue: 'sync.demo.taler.net' -}); + initialValue: "sync.demo.taler.net", +}); export const WithConnectionError = createExample(TestedComponent, { - withError: 'Network error' -}); + withError: "Network error", +}); export const WithClientError = createExample(TestedComponent, { - withError: 'URL may not be right: (404) Not Found' -}); + withError: "URL may not be right: (404) Not Found", +}); export const WithServerError = createExample(TestedComponent, { - withError: 'Try another server: (500) Internal Server Error' -}); + withError: "Try another server: (500) Internal Server Error", +}); diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx index 67ff83442..a170620a3 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx @@ -15,224 +15,221 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; -import { createExample } from '../test-utils'; -import { ProviderView as TestedComponent } from './ProviderDetailPage'; +import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { createExample } from "../test-utils"; +import { ProviderView as TestedComponent } from "./ProviderDetailPage"; export default { - title: 'wallet/backup/details', + title: "wallet/backup/details", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; - export const Active = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const ActiveErrorSync = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, }, lastAttemptedBackupTimestamp: { - "t_ms": 1625063925078 + t_ms: 1625063925078, }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, }, lastError: { code: 2002, - details: 'details', - hint: 'error hint from the server', - message: 'message' - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + details: "details", + hint: "error hint from the server", + message: "message", + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, }, backupProblem: { - type: 'backup-unreadable' - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + type: "backup-unreadable", + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const ActiveBackupProblemDevice = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.taler:9967/", - "lastSuccessfulBackupTimestamp": { - "t_ms": 1625063925078 - }, - "paymentProposalIds": [ - "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.taler:9967/", + lastSuccessfulBackupTimestamp: { + t_ms: 1625063925078, + }, + paymentProposalIds: [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", ], - "paymentStatus": { - "type": ProviderPaymentType.Paid, - "paidUntil": { - "t_ms": 1656599921000 - } + paymentStatus: { + type: ProviderPaymentType.Paid, + paidUntil: { + t_ms: 1656599921000, + }, }, backupProblem: { - type: 'backup-conflicting-device', - myDeviceId: 'my-device-id', - otherDeviceId: 'other-device-id', + type: "backup-conflicting-device", + myDeviceId: "my-device-id", + otherDeviceId: "other-device-id", backupTimestamp: { - "t_ms": 1656599921000 - } - }, - "terms": { - "annualFee": "EUR:1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + t_ms: 1656599921000, + }, + }, + terms: { + annualFee: "EUR:1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const InactiveUnpaid = createExample(TestedComponent, { info: { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Unpaid, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const InactiveInsufficientBalance = createExample(TestedComponent, { info: { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.InsufficientBalance, - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.InsufficientBalance, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); export const InactivePending = createExample(TestedComponent, { info: { - "active": false, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Pending, - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + active: false, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.Pending, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); - export const ActiveTermsChanged = createExample(TestedComponent, { info: { - "active": true, - name:'sync.demo', - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.TermsChanged, + active: true, + name: "sync.demo", + syncProviderBaseUrl: "http://sync.demo.taler.net/", + paymentProposalIds: [], + paymentStatus: { + type: ProviderPaymentType.TermsChanged, paidUntil: { - t_ms: 1656599921000 + t_ms: 1656599921000, }, newTerms: { - "annualFee": "EUR:10", - "storageLimitInMegabytes": 8, - "supportedProtocolVersion": "0.0" + annualFee: "EUR:10", + storageLimitInMegabytes: 8, + supportedProtocolVersion: "0.0", }, oldTerms: { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - }, - "terms": { - "annualFee": "EUR:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" - } - } + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, + terms: { + annualFee: "EUR:0.1", + storageLimitInMegabytes: 16, + supportedProtocolVersion: "0.0", + }, + }, }); - diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index c45458eb7..bd64b0760 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -14,13 +14,23 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { i18n, Timestamp } from "@gnu-taler/taler-util"; -import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { + ProviderInfo, + ProviderPaymentStatus, + ProviderPaymentType, +} from "@gnu-taler/taler-wallet-core"; import { format, formatDuration, intervalToDuration } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { ErrorMessage } from "../components/ErrorMessage"; -import { Button, ButtonDestructive, ButtonPrimary, PaymentStatus, WalletBox, SmallLightText } from "../components/styled"; +import { + Button, + ButtonDestructive, + ButtonPrimary, + PaymentStatus, + WalletBox, + SmallLightText, +} from "../components/styled"; import { useProviderStatus } from "../hooks/useProviderStatus"; interface Props { @@ -29,20 +39,29 @@ interface Props { } export function ProviderDetailPage({ pid, onBack }: Props): VNode { - const status = useProviderStatus(pid) + const status = useProviderStatus(pid); if (!status) { - return <div><i18n.Translate>Loading...</i18n.Translate></div> + return ( + <div> + <i18n.Translate>Loading...</i18n.Translate> + </div> + ); } if (!status.info) { - onBack() - return <div /> + onBack(); + return <div />; } - return <ProviderView info={status.info} - onSync={status.sync} - onDelete={() => status.remove().then(onBack)} - onBack={onBack} - onExtend={() => { null }} - />; + return ( + <ProviderView + info={status.info} + onSync={status.sync} + onDelete={() => status.remove().then(onBack)} + onBack={onBack} + onExtend={() => { + null; + }} + /> + ); } export interface ViewProps { @@ -53,124 +72,185 @@ export interface ViewProps { onExtend: () => void; } -export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewProps): VNode { - const lb = info?.lastSuccessfulBackupTimestamp - const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged +export function ProviderView({ + info, + onDelete, + onSync, + onBack, + onExtend, +}: ViewProps): VNode { + const lb = info?.lastSuccessfulBackupTimestamp; + const isPaid = + info.paymentStatus.type === ProviderPaymentType.Paid || + info.paymentStatus.type === ProviderPaymentType.TermsChanged; return ( <WalletBox> <Error info={info} /> <header> - <h3>{info.name} <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText></h3> - <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus> + <h3> + {info.name}{" "} + <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText> + </h3> + <PaymentStatus color={isPaid ? "rgb(28, 184, 65)" : "rgb(202, 60, 60)"}> + {isPaid ? "Paid" : "Unpaid"} + </PaymentStatus> </header> <section> - <p><b>Last backup:</b> {lb == null || lb.t_ms == "never" ? "never" : format(lb.t_ms, 'dd MMM yyyy')} </p> - <ButtonPrimary onClick={onSync}><i18n.Translate>Back up</i18n.Translate></ButtonPrimary> - {info.terms && <Fragment> - <p><b>Provider fee:</b> {info.terms && info.terms.annualFee} per year</p> - </Fragment> - } + <p> + <b>Last backup:</b>{" "} + {lb == null || lb.t_ms == "never" + ? "never" + : format(lb.t_ms, "dd MMM yyyy")}{" "} + </p> + <ButtonPrimary onClick={onSync}> + <i18n.Translate>Back up</i18n.Translate> + </ButtonPrimary> + {info.terms && ( + <Fragment> + <p> + <b>Provider fee:</b> {info.terms && info.terms.annualFee} per year + </p> + </Fragment> + )} <p>{descriptionByStatus(info.paymentStatus)}</p> - <ButtonPrimary disabled onClick={onExtend}><i18n.Translate>Extend</i18n.Translate></ButtonPrimary> - - {info.paymentStatus.type === ProviderPaymentType.TermsChanged && <div> - <p><i18n.Translate>terms has changed, extending the service will imply accepting the new terms of service</i18n.Translate></p> - <table> - <thead> - <tr> - <td></td> - <td><i18n.Translate>old</i18n.Translate></td> - <td> -></td> - <td><i18n.Translate>new</i18n.Translate></td> - </tr> - </thead> - <tbody> - - <tr> - <td><i18n.Translate>fee</i18n.Translate></td> - <td>{info.paymentStatus.oldTerms.annualFee}</td> - <td>-></td> - <td>{info.paymentStatus.newTerms.annualFee}</td> - </tr> - <tr> - <td><i18n.Translate>storage</i18n.Translate></td> - <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td> - <td>-></td> - <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td> - </tr> - </tbody> - </table> - </div>} + <ButtonPrimary disabled onClick={onExtend}> + <i18n.Translate>Extend</i18n.Translate> + </ButtonPrimary> + {info.paymentStatus.type === ProviderPaymentType.TermsChanged && ( + <div> + <p> + <i18n.Translate> + terms has changed, extending the service will imply accepting + the new terms of service + </i18n.Translate> + </p> + <table> + <thead> + <tr> + <td></td> + <td> + <i18n.Translate>old</i18n.Translate> + </td> + <td> -></td> + <td> + <i18n.Translate>new</i18n.Translate> + </td> + </tr> + </thead> + <tbody> + <tr> + <td> + <i18n.Translate>fee</i18n.Translate> + </td> + <td>{info.paymentStatus.oldTerms.annualFee}</td> + <td>-></td> + <td>{info.paymentStatus.newTerms.annualFee}</td> + </tr> + <tr> + <td> + <i18n.Translate>storage</i18n.Translate> + </td> + <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td> + <td>-></td> + <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td> + </tr> + </tbody> + </table> + </div> + )} </section> <footer> - <Button onClick={onBack}><i18n.Translate> < back</i18n.Translate></Button> + <Button onClick={onBack}> + <i18n.Translate> < back</i18n.Translate> + </Button> <div> - <ButtonDestructive onClick={onDelete}><i18n.Translate>remove provider</i18n.Translate></ButtonDestructive> + <ButtonDestructive onClick={onDelete}> + <i18n.Translate>remove provider</i18n.Translate> + </ButtonDestructive> </div> </footer> </WalletBox> - ) + ); } function daysSince(d?: Timestamp) { - if (!d || d.t_ms === 'never') return 'never synced' + if (!d || d.t_ms === "never") return "never synced"; const duration = intervalToDuration({ start: d.t_ms, end: new Date(), - }) + }); const str = formatDuration(duration, { - delimiter: ', ', + delimiter: ", ", format: [ - duration?.years ? i18n.str`years` : ( - duration?.months ? i18n.str`months` : ( - duration?.days ? i18n.str`days` : ( - duration?.hours ? i18n.str`hours` : ( - duration?.minutes ? i18n.str`minutes` : i18n.str`seconds` - ) - ) - ) - ) - ] - }) - return `synced ${str} ago` + duration?.years + ? i18n.str`years` + : duration?.months + ? i18n.str`months` + : duration?.days + ? i18n.str`days` + : duration?.hours + ? i18n.str`hours` + : duration?.minutes + ? i18n.str`minutes` + : i18n.str`seconds`, + ], + }); + return `synced ${str} ago`; } function Error({ info }: { info: ProviderInfo }) { if (info.lastError) { - return <ErrorMessage title={info.lastError.hint} /> + return <ErrorMessage title={info.lastError.hint} />; } if (info.backupProblem) { switch (info.backupProblem.type) { case "backup-conflicting-device": - return <ErrorMessage title={<Fragment> - <i18n.Translate>There is conflict with another backup from <b>{info.backupProblem.otherDeviceId}</b></i18n.Translate> - </Fragment>} /> + return ( + <ErrorMessage + title={ + <Fragment> + <i18n.Translate> + There is conflict with another backup from{" "} + <b>{info.backupProblem.otherDeviceId}</b> + </i18n.Translate> + </Fragment> + } + /> + ); case "backup-unreadable": - return <ErrorMessage title="Backup is not readable" /> + return <ErrorMessage title="Backup is not readable" />; default: - return <ErrorMessage title={<Fragment> - <i18n.Translate>Unknown backup problem: {JSON.stringify(info.backupProblem)}</i18n.Translate> - </Fragment>} /> + return ( + <ErrorMessage + title={ + <Fragment> + <i18n.Translate> + Unknown backup problem: {JSON.stringify(info.backupProblem)} + </i18n.Translate> + </Fragment> + } + /> + ); } } - return null + return null; } function colorByStatus(status: ProviderPaymentType) { switch (status) { case ProviderPaymentType.InsufficientBalance: - return 'rgb(223, 117, 20)' + return "rgb(223, 117, 20)"; case ProviderPaymentType.Unpaid: - return 'rgb(202, 60, 60)' + return "rgb(202, 60, 60)"; case ProviderPaymentType.Paid: - return 'rgb(28, 184, 65)' + return "rgb(28, 184, 65)"; case ProviderPaymentType.Pending: - return 'gray' + return "gray"; case ProviderPaymentType.InsufficientBalance: - return 'rgb(202, 60, 60)' + return "rgb(202, 60, 60)"; case ProviderPaymentType.TermsChanged: - return 'rgb(202, 60, 60)' + return "rgb(202, 60, 60)"; } } @@ -180,16 +260,19 @@ function descriptionByStatus(status: ProviderPaymentStatus) { // return i18n.str`not paid yet` case ProviderPaymentType.Paid: case ProviderPaymentType.TermsChanged: - if (status.paidUntil.t_ms === 'never') { - return i18n.str`service paid` + if (status.paidUntil.t_ms === "never") { + return i18n.str`service paid`; } else { - return <Fragment> - <b>Backup valid until:</b> {format(status.paidUntil.t_ms, 'dd MMM yyyy')} - </Fragment> + return ( + <Fragment> + <b>Backup valid until:</b>{" "} + {format(status.paidUntil.t_ms, "dd MMM yyyy")} + </Fragment> + ); } case ProviderPaymentType.Unpaid: case ProviderPaymentType.InsufficientBalance: case ProviderPaymentType.Pending: - return '' + return ""; } } diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx index ca524f4e2..c552b19ba 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx @@ -15,26 +15,23 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { ReserveCreated as TestedComponent } from './ReserveCreated'; +import { createExample } from "../test-utils"; +import { ReserveCreated as TestedComponent } from "./ReserveCreated"; export default { - title: 'wallet/manual withdraw/reserve created', + title: "wallet/manual withdraw/reserve created", component: TestedComponent, - argTypes: { - } + argTypes: {}, }; - export const InitialState = createExample(TestedComponent, { - reservePub: 'ASLKDJQWLKEJASLKDJSADLKASJDLKSADJ', + reservePub: "ASLKDJQWLKEJASLKDJSADLKASJDLKSADJ", paytos: [ - 'payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG', - 'payto://x-taler-bank/international-bank.com/myaccount?amount=COL%3A1&message=Taler+Withdrawal+TYQTE7VA4M9GZQ4TR06YBNGA05AJGMFNSK4Q62NXR2FKNDB1J4EX', - ] + "payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", + "payto://x-taler-bank/international-bank.com/myaccount?amount=COL%3A1&message=Taler+Withdrawal+TYQTE7VA4M9GZQ4TR06YBNGA05AJGMFNSK4Q62NXR2FKNDB1J4EX", + ], }); - diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx index e01336e02..9008e9751 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx @@ -1,8 +1,7 @@ -import { Fragment, VNode } from "preact"; +import { h, Fragment, VNode } from "preact"; import { useState } from "preact/hooks"; import { QR } from "../components/QR"; import { ButtonBox, FontIcon, WalletBox } from "../components/styled"; - export interface Props { reservePub: string; paytos: string[]; @@ -10,30 +9,57 @@ export interface Props { } export function ReserveCreated({ reservePub, paytos, onBack }: Props): VNode { - const [opened, setOpened] = useState(-1) + const [opened, setOpened] = useState(-1); return ( <WalletBox> <section> <h2>Reserve created!</h2> - <p>Now you need to send money to the exchange to one of the following accounts</p> - <p>To complete the setup of the reserve, you must now initiate a wire transfer using the given wire transfer subject and crediting the specified amount to the indicated account of the exchange.</p> + <p> + Now you need to send money to the exchange to one of the following + accounts + </p> + <p> + To complete the setup of the reserve, you must now initiate a wire + transfer using the given wire transfer subject and crediting the + specified amount to the indicated account of the exchange. + </p> </section> <section> <ul> {paytos.map((href, idx) => { - const url = new URL(href) - return <li key={idx}><p> - <a href="" onClick={(e) => { setOpened(o => o === idx ? -1 : idx); e.preventDefault() }}>{url.pathname}</a> - {opened === idx && <Fragment> - <p>If your system supports RFC 8905, you can do this by opening <a href={href}>this URI</a> or scan the QR with your wallet</p> - <QR text={href} /> - </Fragment>} - </p></li> + const url = new URL(href); + return ( + <li key={idx}> + <p> + <a + href="" + onClick={(e) => { + setOpened((o) => (o === idx ? -1 : idx)); + e.preventDefault(); + }} + > + {url.pathname} + </a> + {opened === idx && ( + <Fragment> + <p> + If your system supports RFC 8905, you can do this by + opening <a href={href}>this URI</a> or scan the QR with + your wallet + </p> + <QR text={href} /> + </Fragment> + )} + </p> + </li> + ); })} </ul> </section> <footer> - <ButtonBox onClick={onBack}><FontIcon>←</FontIcon></ButtonBox> + <ButtonBox onClick={onBack}> + <FontIcon>←</FontIcon> + </ButtonBox> <div /> </footer> </WalletBox> diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx index a04a0b4fd..6cc1368d5 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx @@ -15,39 +15,41 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample } from '../test-utils'; -import { SettingsView as TestedComponent } from './Settings'; +import { createExample } from "../test-utils"; +import { SettingsView as TestedComponent } from "./Settings"; export default { - title: 'wallet/settings', + title: "wallet/settings", component: TestedComponent, argTypes: { setDeviceName: () => Promise.resolve(), - } + }, }; export const AllOff = createExample(TestedComponent, { - deviceName: 'this-is-the-device-name', + deviceName: "this-is-the-device-name", setDeviceName: () => Promise.resolve(), }); export const OneChecked = createExample(TestedComponent, { - deviceName: 'this-is-the-device-name', + deviceName: "this-is-the-device-name", permissionsEnabled: true, setDeviceName: () => Promise.resolve(), }); export const WithOneExchange = createExample(TestedComponent, { - deviceName: 'this-is-the-device-name', + deviceName: "this-is-the-device-name", permissionsEnabled: true, setDeviceName: () => Promise.resolve(), - knownExchanges: [{ - currency: 'USD', - exchangeBaseUrl: 'http://exchange.taler', - paytoUris: ['payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator'] - }] + knownExchanges: [ + { + currency: "USD", + exchangeBaseUrl: "http://exchange.taler", + paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"], + }, + ], }); diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index 8d18586b1..8d8f3cdbc 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -14,7 +14,6 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - import { ExchangeListItem, i18n } from "@gnu-taler/taler-util"; import { VNode, h, Fragment } from "preact"; import { Checkbox } from "../components/Checkbox"; @@ -30,18 +29,28 @@ import * as wxApi from "../wxApi"; export function SettingsPage(): VNode { const [permissionsEnabled, togglePermissions] = useExtendedPermissions(); - const { devMode, toggleDevMode } = useDevContext() - const { name, update } = useBackupDeviceName() - const [lang, changeLang] = useLang() + const { devMode, toggleDevMode } = useDevContext(); + const { name, update } = useBackupDeviceName(); + const [lang, changeLang] = useLang(); const exchangesHook = useAsyncAsHook(() => wxApi.listExchanges()); - return <SettingsView - lang={lang} changeLang={changeLang} - knownExchanges={!exchangesHook || exchangesHook.hasError ? [] : exchangesHook.response.exchanges} - deviceName={name} setDeviceName={update} - permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions} - developerMode={devMode} toggleDeveloperMode={toggleDevMode} - />; + return ( + <SettingsView + lang={lang} + changeLang={changeLang} + knownExchanges={ + !exchangesHook || exchangesHook.hasError + ? [] + : exchangesHook.response.exchanges + } + deviceName={name} + setDeviceName={update} + permissionsEnabled={permissionsEnabled} + togglePermissions={togglePermissions} + developerMode={devMode} + toggleDeveloperMode={toggleDevMode} + /> + ); } export interface ViewProps { @@ -56,52 +65,72 @@ export interface ViewProps { knownExchanges: Array<ExchangeListItem>; } -import { strings as messages } from '../i18n/strings' +import { strings as messages } from "../i18n/strings"; type LangsNames = { - [P in keyof typeof messages]: string -} + [P in keyof typeof messages]: string; +}; const names: LangsNames = { - es: 'Español [es]', - en: 'English [en]', - fr: 'Français [fr]', - de: 'Deutsch [de]', - sv: 'Svenska [sv]', - it: 'Italiano [it]', -} + es: "Español [es]", + en: "English [en]", + fr: "Français [fr]", + de: "Deutsch [de]", + sv: "Svenska [sv]", + it: "Italiano [it]", +}; - -export function SettingsView({ knownExchanges, lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode { +export function SettingsView({ + knownExchanges, + lang, + changeLang, + deviceName, + setDeviceName, + permissionsEnabled, + togglePermissions, + developerMode, + toggleDeveloperMode, +}: ViewProps): VNode { return ( <WalletBox> <section> - - <h2><i18n.Translate>Known exchanges</i18n.Translate></h2> - {!knownExchanges || !knownExchanges.length ? <div> - No exchange yet! - </div> : + <h2> + <i18n.Translate>Known exchanges</i18n.Translate> + </h2> + {!knownExchanges || !knownExchanges.length ? ( + <div>No exchange yet!</div> + ) : ( <table> - {knownExchanges.map(e => <tr> - <td>{e.currency}</td> - <td><a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a></td> - </tr>)} + {knownExchanges.map((e) => ( + <tr> + <td>{e.currency}</td> + <td> + <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a> + </td> + </tr> + ))} </table> - } - - <h2><i18n.Translate>Permissions</i18n.Translate></h2> - <Checkbox label="Automatically open wallet based on page content" + )} + + <h2> + <i18n.Translate>Permissions</i18n.Translate> + </h2> + <Checkbox + label="Automatically open wallet based on page content" name="perm" description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" - enabled={permissionsEnabled} onToggle={togglePermissions} + enabled={permissionsEnabled} + onToggle={togglePermissions} /> <h2>Config</h2> - <Checkbox label="Developer mode" + <Checkbox + label="Developer mode" name="devMode" description="(More options and information useful for debugging)" - enabled={developerMode} onToggle={toggleDeveloperMode} + enabled={developerMode} + onToggle={toggleDeveloperMode} /> </section> </WalletBox> - ) + ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index 535509cef..c9a3f47cb 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -15,110 +15,116 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { PaymentStatus, - TransactionCommon, TransactionDeposit, TransactionPayment, - TransactionRefresh, TransactionRefund, TransactionTip, TransactionType, + TransactionCommon, + TransactionDeposit, + TransactionPayment, + TransactionRefresh, + TransactionRefund, + TransactionTip, + TransactionType, TransactionWithdrawal, - WithdrawalType -} from '@gnu-taler/taler-util'; -import { createExample } from '../test-utils'; -import { TransactionView as TestedComponent } from './Transaction'; + WithdrawalType, +} from "@gnu-taler/taler-util"; +import { createExample } from "../test-utils"; +import { TransactionView as TestedComponent } from "./Transaction"; export default { - title: 'wallet/history/details', + title: "wallet/history/details", component: TestedComponent, argTypes: { - onRetry: { action: 'onRetry' }, - onDelete: { action: 'onDelete' }, - onBack: { action: 'onBack' }, - } + onRetry: { action: "onRetry" }, + onDelete: { action: "onDelete" }, + onBack: { action: "onBack" }, + }, }; const commonTransaction = { - amountRaw: 'KUDOS:11', - amountEffective: 'KUDOS:9.2', + amountRaw: "KUDOS:11", + amountEffective: "KUDOS:9.2", pending: false, timestamp: { - t_ms: new Date().getTime() + t_ms: new Date().getTime(), }, - transactionId: '12', -} as TransactionCommon + transactionId: "12", +} as TransactionCommon; const exampleData = { withdraw: { ...commonTransaction, type: TransactionType.Withdrawal, - exchangeBaseUrl: 'http://exchange.taler', + exchangeBaseUrl: "http://exchange.taler", withdrawalDetails: { confirmed: false, - exchangePaytoUris: ['payto://x-taler-bank/bank/account'], + exchangePaytoUris: ["payto://x-taler-bank/bank/account"], type: WithdrawalType.ManualTransfer, - } + }, } as TransactionWithdrawal, payment: { ...commonTransaction, - amountEffective: 'KUDOS:11', + amountEffective: "KUDOS:11", type: TransactionType.Payment, info: { - contractTermsHash: 'ASDZXCASD', + contractTermsHash: "ASDZXCASD", merchant: { - name: 'the merchant', + name: "the merchant", }, - orderId: '2021.167-03NPY6MCYMVGT', + orderId: "2021.167-03NPY6MCYMVGT", products: [], summary: "Essay: Why the Devil's Advocate Doesn't Help Reach the Truth", - fulfillmentMessage: '', + fulfillmentMessage: "", }, - proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + proposalId: "1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", status: PaymentStatus.Accepted, } as TransactionPayment, deposit: { ...commonTransaction, type: TransactionType.Deposit, - depositGroupId: '#groupId', - targetPaytoUri: 'payto://x-taler-bank/bank/account', + depositGroupId: "#groupId", + targetPaytoUri: "payto://x-taler-bank/bank/account", } as TransactionDeposit, refresh: { ...commonTransaction, type: TransactionType.Refresh, - exchangeBaseUrl: 'http://exchange.taler', + exchangeBaseUrl: "http://exchange.taler", } as TransactionRefresh, tip: { ...commonTransaction, type: TransactionType.Tip, - merchantBaseUrl: 'http://merchant.taler', + merchantBaseUrl: "http://merchant.taler", } as TransactionTip, refund: { ...commonTransaction, type: TransactionType.Refund, - refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0', + refundedTransactionId: + "payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0", info: { - contractTermsHash: 'ASDZXCASD', + contractTermsHash: "ASDZXCASD", merchant: { - name: 'the merchant', + name: "the merchant", }, - orderId: '2021.167-03NPY6MCYMVGT', + orderId: "2021.167-03NPY6MCYMVGT", products: [], - summary: 'the summary', - fulfillmentMessage: '', + summary: "the summary", + fulfillmentMessage: "", }, } as TransactionRefund, -} +}; const transactionError = { code: 2000, details: "details", hint: "this is a hint for the error", - message: 'message' -} + message: "message", +}; export const Withdraw = createExample(TestedComponent, { - transaction: exampleData.withdraw + transaction: exampleData.withdraw, }); export const WithdrawError = createExample(TestedComponent, { @@ -132,24 +138,22 @@ export const WithdrawPending = createExample(TestedComponent, { transaction: { ...exampleData.withdraw, pending: true }, }); - export const Payment = createExample(TestedComponent, { - transaction: exampleData.payment + transaction: exampleData.payment, }); export const PaymentError = createExample(TestedComponent, { transaction: { ...exampleData.payment, - error: transactionError + error: transactionError, }, }); export const PaymentWithoutFee = createExample(TestedComponent, { transaction: { ...exampleData.payment, - amountRaw: 'KUDOS:11', - - } + amountRaw: "KUDOS:11", + }, }); export const PaymentPending = createExample(TestedComponent, { @@ -161,27 +165,33 @@ export const PaymentWithProducts = createExample(TestedComponent, { ...exampleData.payment, info: { ...exampleData.payment.info, - summary: 'this order has 5 products', - products: [{ - description: 't-shirt', - unit: 'shirts', - quantity: 1, - }, { - description: 't-shirt', - unit: 'shirts', - quantity: 1, - }, { - description: 'e-book', - }, { - description: 'beer', - unit: 'pint', - quantity: 15, - }, { - description: 'beer', - unit: 'pint', - quantity: 15, - }] - } + summary: "this order has 5 products", + products: [ + { + description: "t-shirt", + unit: "shirts", + quantity: 1, + }, + { + description: "t-shirt", + unit: "shirts", + quantity: 1, + }, + { + description: "e-book", + }, + { + description: "beer", + unit: "pint", + quantity: 15, + }, + { + description: "beer", + unit: "pint", + quantity: 15, + }, + ], + }, } as TransactionPayment, }); @@ -190,75 +200,79 @@ export const PaymentWithLongSummary = createExample(TestedComponent, { ...exampleData.payment, info: { ...exampleData.payment.info, - summary: 'this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, ', - products: [{ - description: 'an xl sized t-shirt with some drawings on it, color pink', - unit: 'shirts', - quantity: 1, - }, { - description: 'beer', - unit: 'pint', - quantity: 15, - }] - } + summary: + "this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, ", + products: [ + { + description: + "an xl sized t-shirt with some drawings on it, color pink", + unit: "shirts", + quantity: 1, + }, + { + description: "beer", + unit: "pint", + quantity: 15, + }, + ], + }, } as TransactionPayment, }); - export const Deposit = createExample(TestedComponent, { - transaction: exampleData.deposit + transaction: exampleData.deposit, }); export const DepositError = createExample(TestedComponent, { transaction: { ...exampleData.deposit, - error: transactionError + error: transactionError, }, }); export const DepositPending = createExample(TestedComponent, { - transaction: { ...exampleData.deposit, pending: true } + transaction: { ...exampleData.deposit, pending: true }, }); export const Refresh = createExample(TestedComponent, { - transaction: exampleData.refresh + transaction: exampleData.refresh, }); export const RefreshError = createExample(TestedComponent, { transaction: { ...exampleData.refresh, - error: transactionError + error: transactionError, }, }); export const Tip = createExample(TestedComponent, { - transaction: exampleData.tip + transaction: exampleData.tip, }); export const TipError = createExample(TestedComponent, { transaction: { ...exampleData.tip, - error: transactionError + error: transactionError, }, }); export const TipPending = createExample(TestedComponent, { - transaction: { ...exampleData.tip, pending: true } + transaction: { ...exampleData.tip, pending: true }, }); export const Refund = createExample(TestedComponent, { - transaction: exampleData.refund + transaction: exampleData.refund, }); export const RefundError = createExample(TestedComponent, { transaction: { ...exampleData.refund, - error: transactionError + error: transactionError, }, }); export const RefundPending = createExample(TestedComponent, { - transaction: { ...exampleData.refund, pending: true } + transaction: { ...exampleData.refund, pending: true }, }); export const RefundWithProducts = createExample(TestedComponent, { @@ -266,11 +280,14 @@ export const RefundWithProducts = createExample(TestedComponent, { ...exampleData.refund, info: { ...exampleData.refund.info, - products: [{ - description: 't-shirt', - }, { - description: 'beer', - }] - } + products: [ + { + description: "t-shirt", + }, + { + description: "beer", + }, + ], + }, } as TransactionRefund, }); diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index cf41efb59..7de6982e7 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -14,27 +14,43 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountLike, Amounts, i18n, Transaction, TransactionType } from "@gnu-taler/taler-util"; +import { + AmountLike, + Amounts, + i18n, + Transaction, + TransactionType, +} from "@gnu-taler/taler-util"; import { format } from "date-fns"; -import { JSX, VNode } from "preact"; -import { route } from 'preact-router'; +import { JSX, VNode, h } from "preact"; +import { route } from "preact-router"; import { useEffect, useState } from "preact/hooks"; import emptyImg from "../../static/img/empty.png"; import { ErrorMessage } from "../components/ErrorMessage"; import { Part } from "../components/Part"; -import { ButtonBox, ButtonBoxDestructive, ButtonPrimary, FontIcon, ListOfProducts, RowBorderGray, SmallLightText, WalletBox, WarningBox } from "../components/styled"; +import { + ButtonBox, + ButtonBoxDestructive, + ButtonPrimary, + FontIcon, + ListOfProducts, + RowBorderGray, + SmallLightText, + WalletBox, + WarningBox, +} from "../components/styled"; import { Pages } from "../NavigationBar"; import * as wxApi from "../wxApi"; -export function TransactionPage({ tid }: { tid: string; }): JSX.Element { - const [transaction, setTransaction] = useState< - Transaction | undefined - >(undefined); +export function TransactionPage({ tid }: { tid: string }): JSX.Element { + const [transaction, setTransaction] = useState<Transaction | undefined>( + undefined, + ); useEffect(() => { const fetchData = async (): Promise<void> => { const res = await wxApi.getTransactions(); - const ts = res.transactions.filter(t => t.transactionId === tid); + const ts = res.transactions.filter((t) => t.transactionId === tid); if (ts.length === 1) { setTransaction(ts[0]); } else { @@ -45,13 +61,22 @@ export function TransactionPage({ tid }: { tid: string; }): JSX.Element { }, [tid]); if (!transaction) { - return <div><i18n.Translate>Loading ...</i18n.Translate></div>; + return ( + <div> + <i18n.Translate>Loading ...</i18n.Translate> + </div> + ); } - return <TransactionView - transaction={transaction} - onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))} - onRetry={() => wxApi.retryTransaction(tid).then(_ => history.go(-1))} - onBack={() => { route(Pages.history) }} />; + return ( + <TransactionView + transaction={transaction} + onDelete={() => wxApi.deleteTransaction(tid).then((_) => history.go(-1))} + onRetry={() => wxApi.retryTransaction(tid).then((_) => history.go(-1))} + onBack={() => { + route(Pages.history); + }} + /> + ); } export interface WalletTransactionProps { @@ -61,173 +86,310 @@ export interface WalletTransactionProps { onBack: () => void; } -export function TransactionView({ transaction, onDelete, onRetry, onBack }: WalletTransactionProps) { - +export function TransactionView({ + transaction, + onDelete, + onRetry, + onBack, +}: WalletTransactionProps) { function TransactionTemplate({ children }: { children: VNode[] }) { - return <WalletBox> - <section style={{ padding: 8, textAlign: 'center'}}> - <ErrorMessage title={transaction?.error?.hint} /> - {transaction.pending && <WarningBox>This transaction is not completed</WarningBox>} - </section> - <section> - <div style={{ textAlign: 'center' }}> - {children} - </div> - </section> - <footer> - <ButtonBox onClick={onBack}><i18n.Translate> <FontIcon>←</FontIcon> </i18n.Translate></ButtonBox> - <div> - {transaction?.error ? <ButtonPrimary onClick={onRetry}><i18n.Translate>retry</i18n.Translate></ButtonPrimary> : null} - <ButtonBoxDestructive onClick={onDelete}><i18n.Translate>🗑</i18n.Translate></ButtonBoxDestructive> - </div> - </footer> - </WalletBox> + return ( + <WalletBox> + <section style={{ padding: 8, textAlign: "center" }}> + <ErrorMessage title={transaction?.error?.hint} /> + {transaction.pending && ( + <WarningBox>This transaction is not completed</WarningBox> + )} + </section> + <section> + <div style={{ textAlign: "center" }}>{children}</div> + </section> + <footer> + <ButtonBox onClick={onBack}> + <i18n.Translate> + {" "} + <FontIcon>←</FontIcon>{" "} + </i18n.Translate> + </ButtonBox> + <div> + {transaction?.error ? ( + <ButtonPrimary onClick={onRetry}> + <i18n.Translate>retry</i18n.Translate> + </ButtonPrimary> + ) : null} + <ButtonBoxDestructive onClick={onDelete}> + <i18n.Translate>🗑</i18n.Translate> + </ButtonBoxDestructive> + </div> + </footer> + </WalletBox> + ); } function amountToString(text: AmountLike) { - const aj = Amounts.jsonifyAmount(text) - const amount = Amounts.stringifyValue(aj) - return `${amount} ${aj.currency}` + const aj = Amounts.jsonifyAmount(text); + const amount = Amounts.stringifyValue(aj); + return `${amount} ${aj.currency}`; } - if (transaction.type === TransactionType.Withdrawal) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), - ).amount - return <TransactionTemplate> - <h2>Withdrawal</h2> - <div>{transaction.timestamp.t_ms === 'never' ? 'never' : format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> - <br /> - <Part title="Total withdrawn" text={amountToString(transaction.amountEffective)} kind='positive' /> - <Part title="Chosen amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> - <Part title="Exchange fee" text={amountToString(fee)} kind='negative' /> - <Part title="Exchange" text={new URL(transaction.exchangeBaseUrl).hostname} kind='neutral' /> - </TransactionTemplate> + ).amount; + return ( + <TransactionTemplate> + <h2>Withdrawal</h2> + <div> + {transaction.timestamp.t_ms === "never" + ? "never" + : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")} + </div> + <br /> + <Part + title="Total withdrawn" + text={amountToString(transaction.amountEffective)} + kind="positive" + /> + <Part + title="Chosen amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part title="Exchange fee" text={amountToString(fee)} kind="negative" /> + <Part + title="Exchange" + text={new URL(transaction.exchangeBaseUrl).hostname} + kind="neutral" + /> + </TransactionTemplate> + ); } - const showLargePic = () => { - - } + const showLargePic = () => {}; if (transaction.type === TransactionType.Payment) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountEffective), Amounts.parseOrThrow(transaction.amountRaw), - ).amount - - return <TransactionTemplate> - <h2>Payment </h2> - <div>{transaction.timestamp.t_ms === 'never' ? 'never' : format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> - <br /> - <Part big title="Total paid" text={amountToString(transaction.amountEffective)} kind='negative' /> - <Part big title="Purchase amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> - <Part big title="Fee" text={amountToString(fee)} kind='negative' /> - <Part title="Merchant" text={transaction.info.merchant.name} kind='neutral' /> - <Part title="Purchase" text={transaction.info.summary} kind='neutral' /> - <Part title="Receipt" text={`#${transaction.info.orderId}`} kind='neutral' /> + ).amount; - <div> - {transaction.info.products && transaction.info.products.length > 0 && - <ListOfProducts> - {transaction.info.products.map((p, k) => <RowBorderGray key={k}> - <a href="#" onClick={showLargePic}> - <img src={p.image ? p.image : emptyImg} /> - </a> - <div> - {p.quantity && p.quantity > 0 && <SmallLightText>x {p.quantity} {p.unit}</SmallLightText>} - <div>{p.description}</div> - </div> - </RowBorderGray>)} - </ListOfProducts> - } - </div> - </TransactionTemplate> + return ( + <TransactionTemplate> + <h2>Payment </h2> + <div> + {transaction.timestamp.t_ms === "never" + ? "never" + : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")} + </div> + <br /> + <Part + big + title="Total paid" + text={amountToString(transaction.amountEffective)} + kind="negative" + /> + <Part + big + title="Purchase amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + <Part + title="Merchant" + text={transaction.info.merchant.name} + kind="neutral" + /> + <Part title="Purchase" text={transaction.info.summary} kind="neutral" /> + <Part + title="Receipt" + text={`#${transaction.info.orderId}`} + kind="neutral" + /> + + <div> + {transaction.info.products && transaction.info.products.length > 0 && ( + <ListOfProducts> + {transaction.info.products.map((p, k) => ( + <RowBorderGray key={k}> + <a href="#" onClick={showLargePic}> + <img src={p.image ? p.image : emptyImg} /> + </a> + <div> + {p.quantity && p.quantity > 0 && ( + <SmallLightText> + x {p.quantity} {p.unit} + </SmallLightText> + )} + <div>{p.description}</div> + </div> + </RowBorderGray> + ))} + </ListOfProducts> + )} + </div> + </TransactionTemplate> + ); } if (transaction.type === TransactionType.Deposit) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), - ).amount - return <TransactionTemplate> - <h2>Deposit </h2> - <div>{transaction.timestamp.t_ms === 'never' ? 'never' : format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> - <br /> - <Part big title="Total deposit" text={amountToString(transaction.amountEffective)} kind='negative' /> - <Part big title="Purchase amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> - <Part big title="Fee" text={amountToString(fee)} kind='negative' /> - </TransactionTemplate> + ).amount; + return ( + <TransactionTemplate> + <h2>Deposit </h2> + <div> + {transaction.timestamp.t_ms === "never" + ? "never" + : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")} + </div> + <br /> + <Part + big + title="Total deposit" + text={amountToString(transaction.amountEffective)} + kind="negative" + /> + <Part + big + title="Purchase amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + </TransactionTemplate> + ); } if (transaction.type === TransactionType.Refresh) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), - ).amount - return <TransactionTemplate> - <h2>Refresh</h2> - <div>{transaction.timestamp.t_ms === 'never' ? 'never' : format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> - <br /> - <Part big title="Total refresh" text={amountToString(transaction.amountEffective)} kind='negative' /> - <Part big title="Refresh amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> - <Part big title="Fee" text={amountToString(fee)} kind='negative' /> - </TransactionTemplate> + ).amount; + return ( + <TransactionTemplate> + <h2>Refresh</h2> + <div> + {transaction.timestamp.t_ms === "never" + ? "never" + : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")} + </div> + <br /> + <Part + big + title="Total refresh" + text={amountToString(transaction.amountEffective)} + kind="negative" + /> + <Part + big + title="Refresh amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + </TransactionTemplate> + ); } if (transaction.type === TransactionType.Tip) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), - ).amount - return <TransactionTemplate> - <h2>Tip</h2> - <div>{transaction.timestamp.t_ms === 'never' ? 'never' : format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> - <br /> - <Part big title="Total tip" text={amountToString(transaction.amountEffective)} kind='positive' /> - <Part big title="Received amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> - <Part big title="Fee" text={amountToString(fee)} kind='negative' /> - </TransactionTemplate> + ).amount; + return ( + <TransactionTemplate> + <h2>Tip</h2> + <div> + {transaction.timestamp.t_ms === "never" + ? "never" + : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")} + </div> + <br /> + <Part + big + title="Total tip" + text={amountToString(transaction.amountEffective)} + kind="positive" + /> + <Part + big + title="Received amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + </TransactionTemplate> + ); } if (transaction.type === TransactionType.Refund) { const fee = Amounts.sub( Amounts.parseOrThrow(transaction.amountRaw), Amounts.parseOrThrow(transaction.amountEffective), - ).amount - return <TransactionTemplate> - <h2>Refund</h2> - <div>{transaction.timestamp.t_ms === 'never' ? 'never' : format(transaction.timestamp.t_ms, 'dd MMMM yyyy, HH:mm')}</div> - <br /> - <Part big title="Total refund" text={amountToString(transaction.amountEffective)} kind='positive' /> - <Part big title="Refund amount" text={amountToString(transaction.amountRaw)} kind='neutral' /> - <Part big title="Fee" text={amountToString(fee)} kind='negative' /> - <Part title="Merchant" text={transaction.info.merchant.name} kind='neutral' /> - <Part title="Purchase" text={transaction.info.summary} kind='neutral' /> - <Part title="Receipt" text={`#${transaction.info.orderId}`} kind='neutral' /> - - <p> - {transaction.info.summary} - </p> - <div> - {transaction.info.products && transaction.info.products.length > 0 && - <ListOfProducts> - {transaction.info.products.map((p, k) => <RowBorderGray key={k}> - <a href="#" onClick={showLargePic}> - <img src={p.image ? p.image : emptyImg} /> - </a> - <div> - {p.quantity && p.quantity > 0 && <SmallLightText>x {p.quantity} {p.unit}</SmallLightText>} - <div>{p.description}</div> - </div> - </RowBorderGray>)} - </ListOfProducts> - } - </div> - </TransactionTemplate> - } + ).amount; + return ( + <TransactionTemplate> + <h2>Refund</h2> + <div> + {transaction.timestamp.t_ms === "never" + ? "never" + : format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")} + </div> + <br /> + <Part + big + title="Total refund" + text={amountToString(transaction.amountEffective)} + kind="positive" + /> + <Part + big + title="Refund amount" + text={amountToString(transaction.amountRaw)} + kind="neutral" + /> + <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + <Part + title="Merchant" + text={transaction.info.merchant.name} + kind="neutral" + /> + <Part title="Purchase" text={transaction.info.summary} kind="neutral" /> + <Part + title="Receipt" + text={`#${transaction.info.orderId}`} + kind="neutral" + /> + <p>{transaction.info.summary}</p> + <div> + {transaction.info.products && transaction.info.products.length > 0 && ( + <ListOfProducts> + {transaction.info.products.map((p, k) => ( + <RowBorderGray key={k}> + <a href="#" onClick={showLargePic}> + <img src={p.image ? p.image : emptyImg} /> + </a> + <div> + {p.quantity && p.quantity > 0 && ( + <SmallLightText> + x {p.quantity} {p.unit} + </SmallLightText> + )} + <div>{p.description}</div> + </div> + </RowBorderGray> + ))} + </ListOfProducts> + )} + </div> + </TransactionTemplate> + ); + } - return <div></div> + return <div></div>; } diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx index 6579450b3..7e6588fac 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx @@ -15,16 +15,15 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample } from '../test-utils'; -import { View as TestedComponent } from './Welcome'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample } from "../test-utils"; +import { View as TestedComponent } from "./Welcome"; export default { - title: 'wallet/welcome', + title: "wallet/welcome", component: TestedComponent, }; @@ -32,11 +31,11 @@ export const Normal = createExample(TestedComponent, { permissionsEnabled: true, diagnostics: { errors: [], - walletManifestVersion: '1.0', - walletManifestDisplayVersion: '1.0', + walletManifestVersion: "1.0", + walletManifestDisplayVersion: "1.0", firefoxIdbProblem: false, dbOutdated: false, - } + }, }); export const TimedoutDiagnostics = createExample(TestedComponent, { @@ -47,4 +46,3 @@ export const TimedoutDiagnostics = createExample(TestedComponent, { export const RunningDiagnostics = createExample(TestedComponent, { permissionsEnabled: false, }); - diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx index d11070d9a..0b8e5c609 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx @@ -27,43 +27,55 @@ import { Diagnostics } from "../components/Diagnostics"; import { WalletBox } from "../components/styled"; import { useDiagnostics } from "../hooks/useDiagnostics"; import { WalletDiagnostics } from "@gnu-taler/taler-util"; -import { h } from 'preact'; +import { h } from "preact"; export function WelcomePage() { - const [permissionsEnabled, togglePermissions] = useExtendedPermissions() - const [diagnostics, timedOut] = useDiagnostics() - return <View - permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions} - diagnostics={diagnostics} timedOut={timedOut} - /> + const [permissionsEnabled, togglePermissions] = useExtendedPermissions(); + const [diagnostics, timedOut] = useDiagnostics(); + return ( + <View + permissionsEnabled={permissionsEnabled} + togglePermissions={togglePermissions} + diagnostics={diagnostics} + timedOut={timedOut} + /> + ); } export interface ViewProps { - permissionsEnabled: boolean, - togglePermissions: () => void, - diagnostics: WalletDiagnostics | undefined, - timedOut: boolean, + permissionsEnabled: boolean; + togglePermissions: () => void; + diagnostics: WalletDiagnostics | undefined; + timedOut: boolean; } -export function View({ permissionsEnabled, togglePermissions, diagnostics, timedOut }: ViewProps): JSX.Element { - return (<WalletBox> - <h1>Browser Extension Installed!</h1> - <div> - <p>Thank you for installing the wallet.</p> - <Diagnostics diagnostics={diagnostics} timedOut={timedOut} /> - <h2>Permissions</h2> - <Checkbox label="Automatically open wallet based on page content" - name="perm" - description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" - enabled={permissionsEnabled} onToggle={togglePermissions} - /> - <h2>Next Steps</h2> - <a href="https://demo.taler.net/" style={{ display: "block" }}> - Try the demo » - </a> - <a href="https://demo.taler.net/" style={{ display: "block" }}> - Learn how to top up your wallet balance » - </a> - </div> - </WalletBox> +export function View({ + permissionsEnabled, + togglePermissions, + diagnostics, + timedOut, +}: ViewProps): JSX.Element { + return ( + <WalletBox> + <h1>Browser Extension Installed!</h1> + <div> + <p>Thank you for installing the wallet.</p> + <Diagnostics diagnostics={diagnostics} timedOut={timedOut} /> + <h2>Permissions</h2> + <Checkbox + label="Automatically open wallet based on page content" + name="perm" + description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" + enabled={permissionsEnabled} + onToggle={togglePermissions} + /> + <h2>Next Steps</h2> + <a href="https://demo.taler.net/" style={{ display: "block" }}> + Try the demo » + </a> + <a href="https://demo.taler.net/" style={{ display: "block" }}> + Learn how to top up your wallet balance » + </a> + </div> + </WalletBox> ); } diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx index 023ee94c5..f097d58b5 100644 --- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx @@ -21,7 +21,7 @@ */ import { setupI18n } from "@gnu-taler/taler-util"; -import { createHashHistory } from 'history'; +import { createHashHistory } from "history"; import { Fragment, h, render } from "preact"; import Router, { route, Route } from "preact-router"; import { useEffect } from "preact/hooks"; @@ -29,22 +29,19 @@ import { LogoHeader } from "./components/LogoHeader"; import { DevContextProvider } from "./context/devContext"; import { PayPage } from "./cta/Pay"; import { RefundPage } from "./cta/Refund"; -import { TipPage } from './cta/Tip'; +import { TipPage } from "./cta/Tip"; import { WithdrawPage } from "./cta/Withdraw"; import { strings } from "./i18n/strings"; -import { - Pages, WalletNavBar -} from "./NavigationBar"; +import { Pages, WalletNavBar } from "./NavigationBar"; import { BalancePage } from "./wallet/BalancePage"; import { HistoryPage } from "./wallet/History"; import { SettingsPage } from "./wallet/Settings"; -import { TransactionPage } from './wallet/Transaction'; +import { TransactionPage } from "./wallet/Transaction"; import { WelcomePage } from "./wallet/Welcome"; -import { BackupPage } from './wallet/BackupPage'; +import { BackupPage } from "./wallet/BackupPage"; import { DeveloperPage } from "./popup/Debug.js"; import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage.js"; - function main(): void { try { const container = document.getElementById("container"); @@ -69,51 +66,86 @@ if (document.readyState === "loading") { } function withLogoAndNavBar(Component: any) { - return (props: any) => <Fragment> - <LogoHeader /> - <WalletNavBar /> - <Component {...props} /> - </Fragment> + return (props: any) => ( + <Fragment> + <LogoHeader /> + <WalletNavBar /> + <Component {...props} /> + </Fragment> + ); } function Application() { - return <div> - <DevContextProvider> - <Router history={createHashHistory()} > - - <Route path={Pages.welcome} component={withLogoAndNavBar(WelcomePage)} /> - - <Route path={Pages.history} component={withLogoAndNavBar(HistoryPage)} /> - <Route path={Pages.transaction} component={withLogoAndNavBar(TransactionPage)} /> - <Route path={Pages.balance} component={withLogoAndNavBar(BalancePage)} - goToWalletManualWithdraw={() => route(Pages.manual_withdraw)} - /> - <Route path={Pages.settings} component={withLogoAndNavBar(SettingsPage)} /> - <Route path={Pages.backup} component={withLogoAndNavBar(BackupPage)} /> - - <Route path={Pages.manual_withdraw} component={withLogoAndNavBar(ManualWithdrawPage)} /> - - <Route path={Pages.reset_required} component={() => <div>no yet implemented</div>} /> - <Route path={Pages.payback} component={() => <div>no yet implemented</div>} /> - <Route path={Pages.return_coins} component={() => <div>no yet implemented</div>} /> - - <Route path={Pages.dev} component={withLogoAndNavBar(DeveloperPage)} /> - - {/** call to action */} - <Route path={Pages.pay} component={PayPage} /> - <Route path={Pages.refund} component={RefundPage} /> - <Route path={Pages.tips} component={TipPage} /> - <Route path={Pages.withdraw} component={WithdrawPage} /> - - <Route default component={Redirect} to={Pages.history} /> - </Router> - </DevContextProvider> - </div> + return ( + <div> + <DevContextProvider> + <Router history={createHashHistory()}> + <Route + path={Pages.welcome} + component={withLogoAndNavBar(WelcomePage)} + /> + + <Route + path={Pages.history} + component={withLogoAndNavBar(HistoryPage)} + /> + <Route + path={Pages.transaction} + component={withLogoAndNavBar(TransactionPage)} + /> + <Route + path={Pages.balance} + component={withLogoAndNavBar(BalancePage)} + goToWalletManualWithdraw={() => route(Pages.manual_withdraw)} + /> + <Route + path={Pages.settings} + component={withLogoAndNavBar(SettingsPage)} + /> + <Route + path={Pages.backup} + component={withLogoAndNavBar(BackupPage)} + /> + + <Route + path={Pages.manual_withdraw} + component={withLogoAndNavBar(ManualWithdrawPage)} + /> + + <Route + path={Pages.reset_required} + component={() => <div>no yet implemented</div>} + /> + <Route + path={Pages.payback} + component={() => <div>no yet implemented</div>} + /> + <Route + path={Pages.return_coins} + component={() => <div>no yet implemented</div>} + /> + + <Route + path={Pages.dev} + component={withLogoAndNavBar(DeveloperPage)} + /> + + {/** call to action */} + <Route path={Pages.pay} component={PayPage} /> + <Route path={Pages.refund} component={RefundPage} /> + <Route path={Pages.tips} component={TipPage} /> + <Route path={Pages.withdraw} component={WithdrawPage} /> + + <Route default component={Redirect} to={Pages.history} /> + </Router> + </DevContextProvider> + </div> + ); } function Redirect({ to }: { to: string }): null { useEffect(() => { - route(to, true) - }) - return null + route(to, true); + }); + return null; } diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 92597cbd2..90cfd3ed6 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -47,7 +47,12 @@ import { AddExchangeRequest, GetExchangeTosResult, } from "@gnu-taler/taler-util"; -import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core"; +import { + AddBackupProviderRequest, + BackupProviderState, + OperationFailedError, + RemoveBackupProviderRequest, +} from "@gnu-taler/taler-wallet-core"; import { BackupInfo } from "@gnu-taler/taler-wallet-core"; import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; @@ -149,78 +154,89 @@ interface CurrencyInfo { pub: string; } interface ListOfKnownCurrencies { - auditors: CurrencyInfo[], - exchanges: CurrencyInfo[], + auditors: CurrencyInfo[]; + exchanges: CurrencyInfo[]; } /** * Get a list of currencies from known auditors and exchanges */ export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> { - return callBackend("listCurrencies", {}).then(result => { - console.log("result list", result) - const auditors = result.trustedAuditors.map((a: Record<string, string>) => ({ - name: a.currency, - baseUrl: a.auditorBaseUrl, - pub: a.auditorPub, - })) - const exchanges = result.trustedExchanges.map((a: Record<string, string>) => ({ - name: a.currency, - baseUrl: a.exchangeBaseUrl, - pub: a.exchangeMasterPub, - })) - return { auditors, exchanges } + return callBackend("listCurrencies", {}).then((result) => { + console.log("result list", result); + const auditors = result.trustedAuditors.map( + (a: Record<string, string>) => ({ + name: a.currency, + baseUrl: a.auditorBaseUrl, + pub: a.auditorPub, + }), + ); + const exchanges = result.trustedExchanges.map( + (a: Record<string, string>) => ({ + name: a.currency, + baseUrl: a.exchangeBaseUrl, + pub: a.exchangeMasterPub, + }), + ); + return { auditors, exchanges }; }); } export function listExchanges(): Promise<ExchangesListRespose> { - return callBackend("listExchanges", {}) + return callBackend("listExchanges", {}); } /** * Get information about the current state of wallet backups. */ export function getBackupInfo(): Promise<BackupInfo> { - return callBackend("getBackupInfo", {}) + return callBackend("getBackupInfo", {}); } /** * Add a backup provider and activate it */ -export function addBackupProvider(backupProviderBaseUrl: string, name: string): Promise<void> { +export function addBackupProvider( + backupProviderBaseUrl: string, + name: string, +): Promise<void> { return callBackend("addBackupProvider", { - backupProviderBaseUrl, activate: true, name - } as AddBackupProviderRequest) + backupProviderBaseUrl, + activate: true, + name, + } as AddBackupProviderRequest); } export function setWalletDeviceId(walletDeviceId: string): Promise<void> { return callBackend("setWalletDeviceId", { - walletDeviceId - } as SetWalletDeviceIdRequest) + walletDeviceId, + } as SetWalletDeviceIdRequest); } export function syncAllProviders(): Promise<void> { - return callBackend("runBackupCycle", {}) + return callBackend("runBackupCycle", {}); } export function syncOneProvider(url: string): Promise<void> { - return callBackend("runBackupCycle", { providers: [url] }) + return callBackend("runBackupCycle", { providers: [url] }); } export function removeProvider(url: string): Promise<void> { - return callBackend("removeBackupProvider", { provider: url } as RemoveBackupProviderRequest) + return callBackend("removeBackupProvider", { + provider: url, + } as RemoveBackupProviderRequest); } export function extendedProvider(url: string): Promise<void> { - return callBackend("extendBackupProvider", { provider: url }) + return callBackend("extendBackupProvider", { provider: url }); } /** * Retry a transaction - * @param transactionId - * @returns + * @param transactionId + * @returns */ export function retryTransaction(transactionId: string): Promise<void> { return callBackend("retryTransaction", { - transactionId + transactionId, } as RetryTransactionRequest); } @@ -229,7 +245,7 @@ export function retryTransaction(transactionId: string): Promise<void> { */ export function deleteTransaction(transactionId: string): Promise<void> { return callBackend("deleteTransaction", { - transactionId + transactionId, } as DeleteTransactionRequest); } @@ -264,29 +280,30 @@ export function acceptWithdrawal( /** * Create a reserve into the exchange that expect the amount indicated - * @param exchangeBaseUrl - * @param amount - * @returns + * @param exchangeBaseUrl + * @param amount + * @returns */ export function acceptManualWithdrawal( exchangeBaseUrl: string, amount: string, ): Promise<AcceptManualWithdrawalResult> { return callBackend("acceptManualWithdrawal", { - amount, exchangeBaseUrl + amount, + exchangeBaseUrl, }); } export function setExchangeTosAccepted( exchangeBaseUrl: string, - etag: string | undefined + etag: string | undefined, ): Promise<void> { return callBackend("setExchangeTosAccepted", { - exchangeBaseUrl, etag - } as AcceptExchangeTosRequest) + exchangeBaseUrl, + etag, + } as AcceptExchangeTosRequest); } - /** * Get diagnostics information */ @@ -319,7 +336,6 @@ export function getWithdrawalDetailsForUri( return callBackend("getWithdrawalDetailsForUri", req); } - /** * Get diagnostics information */ @@ -333,17 +349,15 @@ export function getExchangeTos( acceptedFormat: string[], ): Promise<GetExchangeTosResult> { return callBackend("getExchangeTos", { - exchangeBaseUrl, acceptedFormat + exchangeBaseUrl, + acceptedFormat, }); } -export function addExchange( - req: AddExchangeRequest, -): Promise<void> { +export function addExchange(req: AddExchangeRequest): Promise<void> { return callBackend("addExchange", req); } - export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> { return callBackend("prepareTip", req); } diff --git a/packages/taler-wallet-webextension/tsconfig.json b/packages/taler-wallet-webextension/tsconfig.json index cff3d8857..25920a120 100644 --- a/packages/taler-wallet-webextension/tsconfig.json +++ b/packages/taler-wallet-webextension/tsconfig.json @@ -1,9 +1,13 @@ { "compilerOptions": { "composite": true, - "lib": ["es6", "DOM"], - "jsx": "react-jsx", - "jsxImportSource": "preact", + "lib": [ + "es6", + "DOM" + ], + "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "jsxFactory": "h", /* Specify the JSX factory function to use when targeting react JSX emit, e.g. React.createElement or h. */ + "jsxFragmentFactory": "Fragment", // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#custom-jsx-factories "moduleResolution": "Node", "module": "ESNext", "target": "ES6", @@ -16,7 +20,9 @@ "esModuleInterop": true, "importHelpers": true, "rootDir": "./src", - "typeRoots": ["./node_modules/@types"] + "typeRoots": [ + "./node_modules/@types" + ] }, "references": [ { @@ -26,5 +32,7 @@ "path": "../taler-util/" } ], - "include": ["src/**/*"] -} + "include": [ + "src/**/*" + ] +}
\ No newline at end of file |