From 678a90934c7b819b1d5c864f7429242d7d74a1e6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Jul 2021 12:44:25 -0300 Subject: refactored backup sync UI --- .../src/components/EditableText.tsx | 69 ++++++++++ .../src/hooks/useBackupDeviceName.ts | 34 +++++ .../src/hooks/useProvidersByCurrency.ts | 42 +++--- .../src/popup/Backup.stories.tsx | 143 +++++++++++++++------ .../src/popup/BackupPage.tsx | 85 ++++++------ .../popup/ProviderAddConfirmProvider.stories.tsx | 2 - .../src/popup/ProviderAddPage.tsx | 127 +++++++++++++----- .../src/popup/ProviderAddSetUrl.stories.tsx | 19 ++- .../src/popup/ProviderDetail.stories.tsx | 54 +++++--- .../src/popup/ProviderDetailPage.tsx | 101 +++++++++++---- .../src/popup/Settings.stories.tsx | 18 +-- .../src/popup/Settings.tsx | 45 +++++-- .../taler-wallet-webextension/src/popup/popup.tsx | 5 +- .../src/popupEntryPoint.tsx | 12 +- packages/taler-wallet-webextension/src/wxApi.ts | 9 +- .../static/img/chevron-down.svg | 7 + 16 files changed, 574 insertions(+), 198 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/components/EditableText.tsx create mode 100644 packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts create mode 100644 packages/taler-wallet-webextension/static/img/chevron-down.svg diff --git a/packages/taler-wallet-webextension/src/components/EditableText.tsx b/packages/taler-wallet-webextension/src/components/EditableText.tsx new file mode 100644 index 000000000..82983d13a --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/EditableText.tsx @@ -0,0 +1,69 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems SA + + 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 + */ + +import { VNode } from "preact"; +import { useRef, useState } from "preact/hooks"; +import { JSX } from "preact/jsx-runtime"; + +interface Props { + value: string; + onChange: (s: string) => Promise; + label: string; + name: string; + description?: string; +} +export function EditableText({ name, value, onChange, label, description }: Props): JSX.Element { + const [editing, setEditing] = useState(false) + const ref = useRef() + let InputText; + if (!editing) { + InputText = () =>
+

{value}

+ +
+ } else { + InputText = () =>
+ + +
+ } + return ( +
+ + + {description && + {description} + } +
+ ); +} diff --git a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts new file mode 100644 index 000000000..e322c6727 --- /dev/null +++ b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts @@ -0,0 +1,34 @@ +import { useEffect, useState } from "preact/hooks"; +import * as wxApi from "../wxApi"; + + +export interface BackupDeviceName { + name: string; + update: (s:string) => Promise +} + + +export function useBackupDeviceName(): BackupDeviceName { + const [status, setStatus] = useState({ + name: '', + update: () => Promise.resolve() + }) + + useEffect(() => { + async function run() { + //create a first list of backup info by currency + const status = await wxApi.getBackupInfo() + + async function update(newName: string) { + await wxApi.setWalletDeviceId(newName) + setStatus(old => ({ ...old, name: newName })) + } + + setStatus({ name: status.deviceId, update }) + } + run() + }, []) + + return status +} + diff --git a/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts b/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts index 8c35705e1..09f61e468 100644 --- a/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts +++ b/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts @@ -1,37 +1,43 @@ import { Amounts } from "@gnu-taler/taler-util"; -import { ProviderInfo } 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 ProvidersByCurrency { - [s: string]: ProviderInfo | undefined -} export interface BackupStatus { deviceName: string; - providers: ProvidersByCurrency + providers: ProviderInfo[] +} + +function getStatusTypeOrder(t: ProviderPaymentStatus) { + return [ + ProviderPaymentType.InsufficientBalance, + ProviderPaymentType.TermsChanged, + ProviderPaymentType.Unpaid, + ProviderPaymentType.Paid, + ProviderPaymentType.Pending, + ].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 } export function useBackupStatus(): BackupStatus | undefined { const [status, setStatus] = useState(undefined) + useEffect(() => { async function run() { //create a first list of backup info by currency const status = await wxApi.getBackupInfo() - const providers = status.providers.reduce((p, c) => { - if (c.terms) { - p[Amounts.parseOrThrow(c.terms.annualFee).currency] = c - } - return p - }, {} as ProvidersByCurrency) - - //add all the known currency with no backup info - const list = await wxApi.listKnownCurrencies() - const currencies = list.exchanges.map(e => e.name).concat(list.auditors.map(a => a.name)) - currencies.forEach(c => { - if (!providers[c]) { - providers[c] = undefined + + const providers = status.providers.sort((a, b) => { + if (a.paymentStatus.type === ProviderPaymentType.Paid && b.paymentStatus.type === ProviderPaymentType.Paid) { + return getStatusPaidOrder(a.paymentStatus, b.paymentStatus) } + return getStatusTypeOrder(a.paymentStatus) - getStatusTypeOrder(b.paymentStatus) }) setStatus({ deviceName: status.deviceId, providers }) diff --git a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx index 1bd431633..cd40d69a9 100644 --- a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx @@ -40,46 +40,117 @@ function createExample(Component: FunctionalComponent, props: Part return r } -export const Example = createExample(TestedComponent, { - deviceName: "somedevicename", - providers: { - ARS: { - "active": true, - "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" +export const LotOfProviders = createExample(TestedComponent, { + providers: [{ + "active": true, + "syncProviderBaseUrl": "http://sync.taler:9967/", + "lastSuccessfulBackupTimestamp": { + "t_ms": 1625063925078 + }, + "paymentProposalIds": [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + ], + "paymentStatus": { + "type": ProviderPaymentType.Paid, + "paidUntil": { + "t_ms": 1656599921000 } }, - KUDOS: { - "active": false, - "syncProviderBaseUrl": "http://sync.demo.taler.net/", - "paymentProposalIds": [], - "paymentStatus": { - "type": ProviderPaymentType.Unpaid, - }, - "terms": { - "annualFee": "KUDOS:0.1", - "storageLimitInMegabytes": 16, - "supportedProtocolVersion": "0.0" + "terms": { + "annualFee": "ARS:1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + }, { + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Unpaid, + }, + "terms": { + "annualFee": "KUDOS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + },{ + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Unpaid, + }, + "terms": { + "annualFee": "KUDOS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + },{ + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Unpaid, + }, + "terms": { + "annualFee": "KUDOS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + },{ + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Unpaid, + }, + "terms": { + "annualFee": "KUDOS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + },{ + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Unpaid, + }, + "terms": { + "annualFee": "KUDOS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + }] +}); + + +export const OneProvider = createExample(TestedComponent, { + providers: [{ + "active": true, + "syncProviderBaseUrl": "http://sync.taler:9967/", + "lastSuccessfulBackupTimestamp": { + "t_ms": 1625063925078 + }, + "paymentProposalIds": [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + ], + "paymentStatus": { + "type": ProviderPaymentType.Paid, + "paidUntil": { + "t_ms": 1656599921000 } }, - USD: undefined, - EUR: undefined - } + "terms": { + "annualFee": "ARS:1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + }] }); +export const Empty = createExample(TestedComponent, { + providers: [] +}); + diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx index e0e41427b..91f1782cc 100644 --- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx @@ -15,53 +15,64 @@ */ -import { Timestamp } from "@gnu-taler/taler-util"; +import { i18n, Timestamp } from "@gnu-taler/taler-util"; +import { ProviderInfo } from "@gnu-taler/taler-wallet-core"; import { formatDuration, intervalToDuration } from "date-fns"; import { JSX, VNode } from "preact"; -import { ProvidersByCurrency, useBackupStatus } from "../hooks/useProvidersByCurrency"; +import { useBackupStatus } from "../hooks/useProvidersByCurrency"; import { Pages } from "./popup"; -export function BackupPage(): VNode { +interface Props { + onAddProvider: () => void; +} + +export function BackupPage({ onAddProvider }: Props): VNode { const status = useBackupStatus() if (!status) { return
Loading...
} - return ; + return ; } export interface ViewProps { - deviceName: string; - providers: ProvidersByCurrency + providers: ProviderInfo[], + onAddProvider: () => void; } -export function BackupView({ deviceName, providers }: ViewProps): VNode { +export function BackupView({ providers, onAddProvider }: ViewProps): VNode { return (
-
-

- {deviceName} -

-
- -
+
+
+ + {!!providers.length &&
+ {providers.map((provider, idx) => { + return + })} +
} + {!providers.length &&
+ There is not backup providers configured, add one with the button below +
} + +
+
+
+ + +
+
- {Object.keys(providers).map((currency) => { - const provider = providers[currency] - if (!provider) { - return - } - return - })}
) } @@ -70,7 +81,7 @@ interface TransactionLayoutProps { status?: any; timestamp?: Timestamp; title: string; - id: string; + id: number; subtitle?: string; active?: boolean; } @@ -96,13 +107,13 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element {
- {dateStr &&
{dateStr}
} - {!dateStr &&
never synced
} + -
{props.subtitle}
+ {dateStr &&
Last time synced: {dateStr}
} + {!dateStr &&
never synced
}
-
+
{!props.status ? "missing" : ( props.status?.type === 'paid' ? daysUntil(props.status.paidUntil) : 'unpaid' )} diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx index 679a7ce43..f286870c1 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx @@ -40,7 +40,6 @@ function createExample(Component: FunctionalComponent, props: Part } export const DemoService = createExample(TestedComponent, { - currency: 'KUDOS', url: 'https://sync.demo.taler.net/', provider: { annual_fee: 'KUDOS:0.1', @@ -50,7 +49,6 @@ export const DemoService = createExample(TestedComponent, { }); export const FreeService = createExample(TestedComponent, { - currency: 'ARS', url: 'https://sync.taler:9667/', provider: { annual_fee: 'ARS:0', diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx index 7b8712eca..1e4a44df1 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx @@ -1,37 +1,60 @@ import { Amounts, BackupBackupProviderTerms, i18n } from "@gnu-taler/taler-util"; -import { privateDecrypt } from "crypto"; -import { add, addYears } from "date-fns"; -import { VNode } from "preact"; +import { Fragment, VNode } from "preact"; import { useState } from "preact/hooks"; import * as wxApi from "../wxApi"; -import ProviderAddConfirmProviderStories from "./ProviderAddConfirmProvider.stories"; interface Props { currency: string; } -export function ProviderAddPage({ currency }: Props): VNode { +function getJsonIfOk(r: Response) { + if (r.ok) { + return r.json() + } else { + if (r.status >= 400 && r.status < 500) { + throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`) + } else { + throw new Error(`Try another server: (${r.status}) ${r.statusText || 'internal server error'}`) + } + } +} + + +export function ProviderAddPage({ }: Props): VNode { const [verifying, setVerifying] = useState<{ url: string, provider: BackupBackupProviderTerms } | undefined>(undefined) + const [readingTerms, setReadingTerms] = useState(undefined) + const alreadyCheckedTheTerms = readingTerms === false + if (!verifying) { return { setVerifying(undefined); }} onVerify={(url) => { - return fetch(url).then(r => r.json()) - .then((provider) => setVerifying({ url, provider })) + return fetch(`${url}/config`) + .catch(e => { throw new Error(`Network error`) }) + .then(getJsonIfOk) + .then((provider) => { setVerifying({ url, provider }); return undefined }) .catch((e) => e.message) }} /> } + if (readingTerms) { + return setReadingTerms(undefined)} + onAccept={() => setReadingTerms(false)} + /> + } return { setVerifying(undefined); }} + onShowTerms={() => { + setReadingTerms(true) + }} onConfirm={() => { wxApi.addBackupProvider(verifying.url).then(_ => history.go(-1)) }} @@ -39,33 +62,75 @@ export function ProviderAddPage({ currency }: Props): VNode { /> } +interface TermsOfServiceProps { + onCancel: () => void; + onAccept: () => void; +} + +function TermsOfService({ onCancel, onAccept }: TermsOfServiceProps) { + return
+
+
+ Here we will place the complete text of terms of service +
+
+
+ +
+ +
+
+
+} + export interface SetUrlViewProps { - currency: string, + initialValue?: string; onCancel: () => void; onVerify: (s: string) => Promise; + withError?: string; } +import arrowDown from '../../static/img/chevron-down.svg'; -export function SetUrlView({ currency, onCancel, onVerify }: SetUrlViewProps) { - const [value, setValue] = useState("") - const [error, setError] = useState(undefined) +export function SetUrlView({ initialValue, onCancel, onVerify, withError }: SetUrlViewProps) { + const [value, setValue] = useState(initialValue || "") + const [error, setError] = useState(withError) + const [showErrorDetail, setShowErrorDetail] = useState(false); return
- Add backup provider for storing {currency} + Add backup provider for saving coins
- {error &&
-

{error}

-
}

Backup provider URL

- setValue(e.currentTarget.value)} /> +
https://
+ setValue(e.currentTarget.value)} />

Backup providers may charge for their service

+ {error && +
+
+

Could not get provider information

+

+ +

+
+ {showErrorDetail &&
{error}
} +
+
+ }
- +
@@ -73,19 +138,16 @@ export function SetUrlView({ currency, onCancel, onVerify }: SetUrlViewProps) { export interface ConfirmProviderViewProps { provider: BackupBackupProviderTerms, - currency: string, url: string, onCancel: () => void; - onConfirm: () => void + onConfirm: () => void; + onShowTerms: () => void; + termsChecked: boolean; } -export function ConfirmProviderView({ url, provider, currency, onCancel, onConfirm }: ConfirmProviderViewProps) { +export function ConfirmProviderView({ url, termsChecked, onShowTerms, provider, onCancel, onConfirm }: ConfirmProviderViewProps) { return
-
-
- Verify provider service terms for storing {currency} -
-

{url}

+
Verify provider service terms for {url} backup provider

{Amounts.isZero(provider.annual_fee) ? 'free of charge' : provider.annual_fee} for a year of backup service

@@ -98,9 +160,14 @@ export function ConfirmProviderView({ url, provider, currency, onCancel, onConfi cancel
- + {termsChecked ? + : + + }
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx index 8b9075165..dfee115bb 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx @@ -19,7 +19,6 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; import { FunctionalComponent } from 'preact'; import { SetUrlView as TestedComponent } from './ProviderAddPage'; @@ -40,7 +39,21 @@ function createExample(Component: FunctionalComponent, props: Part return r } -export const SetUrl = createExample(TestedComponent, { - currency: 'ARS', +export const Initial = createExample(TestedComponent, { }); +export const WithValue = createExample(TestedComponent, { + initialValue: 'sync.demo.taler.net' +}); + +export const WithConnectionError = createExample(TestedComponent, { + withError: 'Network error' +}); + +export const WithClientError = createExample(TestedComponent, { + withError: 'URL may not be right: (404) Not Found' +}); + +export const WithServerError = createExample(TestedComponent, { + 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 01c0a5f05..480d7b1a4 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx @@ -40,12 +40,7 @@ function createExample(Component: FunctionalComponent, props: Part return r } -export const NotDefined = createExample(TestedComponent, { - currency: 'ARS', -}); - export const Active = createExample(TestedComponent, { - currency: 'ARS', info: { "active": true, "syncProviderBaseUrl": "http://sync.taler:9967/", @@ -62,7 +57,7 @@ export const Active = createExample(TestedComponent, { } }, "terms": { - "annualFee": "ARS:1", + "annualFee": "EUR:1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -70,7 +65,6 @@ export const Active = createExample(TestedComponent, { }); export const ActiveErrorSync = createExample(TestedComponent, { - currency: 'ARS', info: { "active": true, "syncProviderBaseUrl": "http://sync.taler:9967/", @@ -96,7 +90,7 @@ export const ActiveErrorSync = createExample(TestedComponent, { message: 'message' }, "terms": { - "annualFee": "ARS:1", + "annualFee": "EUR:1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -104,7 +98,6 @@ export const ActiveErrorSync = createExample(TestedComponent, { }); export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { - currency: 'ARS', info: { "active": true, "syncProviderBaseUrl": "http://sync.taler:9967/", @@ -124,7 +117,7 @@ export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { type: 'backup-unreadable' }, "terms": { - "annualFee": "ARS:1", + "annualFee": "EUR:1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -132,7 +125,6 @@ export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { }); export const ActiveBackupProblemDevice = createExample(TestedComponent, { - currency: 'ARS', info: { "active": true, "syncProviderBaseUrl": "http://sync.taler:9967/", @@ -157,7 +149,7 @@ export const ActiveBackupProblemDevice = createExample(TestedComponent, { } }, "terms": { - "annualFee": "ARS:1", + "annualFee": "EUR:1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -165,7 +157,6 @@ export const ActiveBackupProblemDevice = createExample(TestedComponent, { }); export const InactiveUnpaid = createExample(TestedComponent, { - currency: 'ARS', info: { "active": false, "syncProviderBaseUrl": "http://sync.demo.taler.net/", @@ -174,7 +165,7 @@ export const InactiveUnpaid = createExample(TestedComponent, { "type": ProviderPaymentType.Unpaid, }, "terms": { - "annualFee": "ARS:0.1", + "annualFee": "EUR:0.1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -182,7 +173,6 @@ export const InactiveUnpaid = createExample(TestedComponent, { }); export const InactiveInsufficientBalance = createExample(TestedComponent, { - currency: 'ARS', info: { "active": false, "syncProviderBaseUrl": "http://sync.demo.taler.net/", @@ -191,7 +181,7 @@ export const InactiveInsufficientBalance = createExample(TestedComponent, { "type": ProviderPaymentType.InsufficientBalance, }, "terms": { - "annualFee": "ARS:0.1", + "annualFee": "EUR:0.1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -199,7 +189,6 @@ export const InactiveInsufficientBalance = createExample(TestedComponent, { }); export const InactivePending = createExample(TestedComponent, { - currency: 'ARS', info: { "active": false, "syncProviderBaseUrl": "http://sync.demo.taler.net/", @@ -208,7 +197,7 @@ export const InactivePending = createExample(TestedComponent, { "type": ProviderPaymentType.Pending, }, "terms": { - "annualFee": "ARS:0.1", + "annualFee": "EUR:0.1", "storageLimitInMegabytes": 16, "supportedProtocolVersion": "0.0" } @@ -216,3 +205,32 @@ export const InactivePending = createExample(TestedComponent, { }); +export const ActiveTermsChanged = createExample(TestedComponent, { + info: { + "active": true, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.TermsChanged, + paidUntil: { + t_ms: 1656599921000 + }, + newTerms: { + "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" + } + } +}); + diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx index 59e6cda1b..1b8abf44d 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx @@ -16,7 +16,8 @@ import { BackupBackupProviderTerms, i18n, Timestamp } from "@gnu-taler/taler-util"; -import { ProviderInfo, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { ContractTermsUtil } from "@gnu-taler/taler-wallet-core/src/util/contractTerms"; import { formatDuration, intervalToDuration, format } from "date-fns"; import { Fragment, VNode } from "preact"; import { useRef, useState } from "preact/hooks"; @@ -24,42 +25,45 @@ import { useBackupStatus } from "../hooks/useProvidersByCurrency"; import * as wxApi from "../wxApi"; interface Props { - currency: string; - onAddProvider: (c: string) => void; + pid: string; onBack: () => void; } -export function ProviderDetailPage({ currency, onAddProvider, onBack }: Props): VNode { +export function ProviderDetailPage({ pid, onBack }: Props): VNode { const status = useBackupStatus() if (!status) { return
Loading...
} - const info = status.providers[currency]; - return + } + const info = status.providers[idx]; + return { null }} onDelete={() => { null }} onBack={onBack} - onAddProvider={() => onAddProvider(currency)} + onExtend={() => { null }} />; } export interface ViewProps { - currency: string; - info?: ProviderInfo; + info: ProviderInfo; onDelete: () => void; onSync: () => void; onBack: () => void; - onAddProvider: () => void; + onExtend: () => void; } -export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddProvider }: ViewProps): VNode { +export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewProps): VNode { function Footer() { return
- {info && } - {info && } - {!info && } + {info && } + {info && } + {info && }
} @@ -67,7 +71,7 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr if (info?.lastError) { return
-
{!info.lastAttemptedBackupTimestamp || info.lastAttemptedBackupTimestamp.t_ms === 'never' ? 'never' : format(new Date(info.lastAttemptedBackupTimestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}
+
last time tried {!info.lastAttemptedBackupTimestamp || info.lastAttemptedBackupTimestamp.t_ms === 'never' ? 'never' : format(new Date(info.lastAttemptedBackupTimestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}

{info.lastError.hint}

@@ -76,7 +80,7 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr switch (info.backupProblem.type) { case "backup-conflicting-device": return
-

There is another backup from {info.backupProblem.otherDeviceId}

+

There is conflict with another backup from {info.backupProblem.otherDeviceId}

case "backup-unreadable": return
@@ -84,7 +88,7 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr
default: return
-

Unkown backup problem: {JSON.stringify(info.backupProblem)}

+

Unknown backup problem: {JSON.stringify(info.backupProblem)}

} } @@ -110,6 +114,28 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr return undefined } + function descriptionByStatus(status: ProviderPaymentStatus | undefined) { + if (!status) return '' + switch (status.type) { + case ProviderPaymentType.InsufficientBalance: + return 'no enough balance to make the payment' + case ProviderPaymentType.Unpaid: + return 'not pay yet' + case ProviderPaymentType.Paid: + case ProviderPaymentType.TermsChanged: + if (status.paidUntil.t_ms === 'never') { + return 'service paid.' + } else { + return `service paid until ${format(status.paidUntil.t_ms, 'yyyy/MM/dd HH:mm:ss')}` + } + case ProviderPaymentType.Pending: + return '' + default: + break; + } + return undefined + } + return (