diff options
author | Sebastian <sebasjm@gmail.com> | 2021-07-01 11:33:26 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-07-01 11:33:49 -0300 |
commit | 26a12809605ac8099acf7931676bfbad0298a3f2 (patch) | |
tree | 75726a6fd2cb5aa065e123893312dcc49c4ce199 | |
parent | 7ba33273225edbdce74e9b33e964e667fe31c453 (diff) |
first working version of provider
9 files changed, 417 insertions, 8 deletions
diff --git a/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts b/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts index dedaf6f86..8c35705e1 100644 --- a/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts +++ b/packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts @@ -1,5 +1,5 @@ import { Amounts } from "@gnu-taler/taler-util"; -import { ProviderInfo } from "@gnu-taler/taler-wallet-core/src/operations/backup"; +import { ProviderInfo } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; diff --git a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx index 856360ebf..1bd431633 100644 --- a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx +++ b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core/src/operations/backup'; +import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; import { FunctionalComponent } from 'preact'; import { BackupView as TestedComponent } from './BackupPage'; diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx index 9900720d9..968898a63 100644 --- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx @@ -16,10 +16,10 @@ import { Timestamp } from "@gnu-taler/taler-util"; -// import { ProviderPaymentStatus } from "@gnu-taler/taler-wallet-core/src/operations/backup"; import { formatDuration, intervalToDuration } from "date-fns"; import { JSX, VNode } from "preact"; import { ProvidersByCurrency, useBackupStatus } from "../hooks/useProvidersByCurrency"; +import { Pages } from "./popup"; export function BackupPage(): VNode { const status = useBackupStatus() @@ -99,7 +99,7 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element { {dateStr && <div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>} {!dateStr && <div style={{ fontSize: "small", color: "red" }}>never synced</div>} <div style={{ fontVariant: "small-caps", fontSize: "x-large" }}> - <a href=""><span>{props.title}</span></a> + <a href={Pages.provider.replace(':currency', props.id)}><span>{props.title}</span></a> </div> <div>{props.subtitle}</div> diff --git a/packages/taler-wallet-webextension/src/popup/Provider.stories.tsx b/packages/taler-wallet-webextension/src/popup/Provider.stories.tsx new file mode 100644 index 000000000..7b059103b --- /dev/null +++ b/packages/taler-wallet-webextension/src/popup/Provider.stories.tsx @@ -0,0 +1,215 @@ +/* + 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 { ProviderPaymentType } from '@gnu-taler/taler-wallet-core'; +import { FunctionalComponent } from 'preact'; +import { ProviderView as TestedComponent } from './ProviderPage'; + +export default { + title: 'popup/backup/details', + component: TestedComponent, + argTypes: { + onRetry: { action: 'onRetry' }, + onDelete: { action: 'onDelete' }, + onBack: { action: 'onBack' }, + } +}; + + +function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { + const r = (args: any) => <Component {...args} /> + r.args = props + return r +} + +export const NotDefined = createExample(TestedComponent, { + currency: 'ARS', +}); + +export const Active = createExample(TestedComponent, { + currency: 'ARS', + info: { + "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 ActiveErrorSync = createExample(TestedComponent, { + currency: 'ARS', + info: { + "active": true, + "syncProviderBaseUrl": "http://sync.taler:9967/", + "lastSuccessfulBackupTimestamp": { + "t_ms": 1625063925078 + }, + "paymentProposalIds": [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + ], + "paymentStatus": { + "type": ProviderPaymentType.Paid, + "paidUntil": { + "t_ms": 1656599921000 + } + }, + lastError: { + code: 2002, + details: 'details', + hint: 'error hint from the server', + message: 'message' + }, + "terms": { + "annualFee": "ARS:1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + } +}); + +export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { + currency: 'ARS', + info: { + "active": true, + "syncProviderBaseUrl": "http://sync.taler:9967/", + "lastSuccessfulBackupTimestamp": { + "t_ms": 1625063925078 + }, + "paymentProposalIds": [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + ], + "paymentStatus": { + "type": ProviderPaymentType.Paid, + "paidUntil": { + "t_ms": 1656599921000 + } + }, + backupProblem: { + type: 'backup-unreadable' + }, + "terms": { + "annualFee": "ARS:1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + } +}); + +export const ActiveBackupProblemDevice = createExample(TestedComponent, { + currency: 'ARS', + info: { + "active": true, + "syncProviderBaseUrl": "http://sync.taler:9967/", + "lastSuccessfulBackupTimestamp": { + "t_ms": 1625063925078 + }, + "paymentProposalIds": [ + "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG" + ], + "paymentStatus": { + "type": ProviderPaymentType.Paid, + "paidUntil": { + "t_ms": 1656599921000 + } + }, + backupProblem: { + type: 'backup-conflicting-device', + myDeviceId: 'my-device-id', + otherDeviceId: 'other-device-id', + backupTimestamp: { + "t_ms": 1656599921000 + } + }, + "terms": { + "annualFee": "ARS:1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + } +}); + +export const InactiveUnpaid = createExample(TestedComponent, { + currency: 'ARS', + info: { + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Unpaid, + }, + "terms": { + "annualFee": "ARS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + } +}); + +export const InactiveInsufficientBalance = createExample(TestedComponent, { + currency: 'ARS', + info: { + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.InsufficientBalance, + }, + "terms": { + "annualFee": "ARS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + } +}); + +export const InactivePending = createExample(TestedComponent, { + currency: 'ARS', + info: { + "active": false, + "syncProviderBaseUrl": "http://sync.demo.taler.net/", + "paymentProposalIds": [], + "paymentStatus": { + "type": ProviderPaymentType.Pending, + }, + "terms": { + "annualFee": "ARS:0.1", + "storageLimitInMegabytes": 16, + "supportedProtocolVersion": "0.0" + } + } +}); + + diff --git a/packages/taler-wallet-webextension/src/popup/ProviderPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderPage.tsx new file mode 100644 index 000000000..1112017fc --- /dev/null +++ b/packages/taler-wallet-webextension/src/popup/ProviderPage.tsx @@ -0,0 +1,174 @@ +/* + This file is part of TALER + (C) 2016 GNUnet e.V. + + 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. + + 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 + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + + +import { i18n, Timestamp } from "@gnu-taler/taler-util"; +import { ProviderInfo, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; +import { formatDuration, intervalToDuration } from "date-fns"; +import { VNode } from "preact"; +import { useRef, useState } from "preact/hooks"; +import { useBackupStatus } from "../hooks/useProvidersByCurrency"; +import * as wxApi from "../wxApi"; + +interface Props { + currency: string; +} + +export function ProviderPage({ currency }: Props): VNode { + const status = useBackupStatus() + const [adding, setAdding] = useState<boolean>(false) + if (!status) { + return <div>Loading...</div> + } + if (adding) { + return <AddProviderView onConfirm={(value) => { + console.log(value) + wxApi.addBackupProvider(value).then(_ => history.go(-1)) + setAdding(false) + }} /> + } + const info = status.providers[currency]; + return <ProviderView currency={currency} info={info} + onSync={() => { null }} + onDelete={() => { null }} + onBack={() => { history.go(-1); }} + onAddProvider={() => { setAdding(true) }} + />; +} + +function AddProviderView({ onConfirm }: { onConfirm: (s: string) => void }) { + const textInput = useRef<HTMLInputElement>(null) + return <div> + <input ref={textInput} /> + <button onClick={() => onConfirm(textInput?.current.value)}>confirm</button> + </div> +} + +export interface ViewProps { + currency: string; + info?: ProviderInfo; + onDelete: () => void; + onSync: () => void; + onBack: () => void; + onAddProvider: () => void; +} + +export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddProvider }: ViewProps): VNode { + function Footer() { + return <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}> + <button onClick={onBack}><i18n.Translate>back</i18n.Translate></button> + <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}> + {info && <button class="pure-button button-destructive" onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>} + {info && <button class="pure-button button-secondary" style={{ marginLeft: 5 }} onClick={onSync}><i18n.Translate>sync now</i18n.Translate></button>} + {!info && <button class="pure-button button-success" style={{ marginLeft: 5 }} onClick={onAddProvider}><i18n.Translate>add provider</i18n.Translate></button>} + </div> + </footer> + } + function Error() { + if (info?.lastError) { + return <div class="errorbox" style={{ marginTop: 10 }} > + <p>{info.lastError.hint}</p> + </div> + } + if (info?.backupProblem) { + switch (info.backupProblem.type) { + case "backup-conflicting-device": + return <div class="errorbox" style={{ marginTop: 10 }}> + <p>There is another backup from <b>{info.backupProblem.otherDeviceId}</b></p> + </div> + case "backup-unreadable": + return <div class="errorbox" style={{ marginTop: 10 }}> + <p>Backup is not readable</p> + </div> + default: + return <div class="errorbox" style={{ marginTop: 10 }}> + <p>Unkown backup problem: {JSON.stringify(info.backupProblem)}</p> + </div> + } + } + return null + } + function colorByStatus(status: ProviderPaymentType | undefined) { + switch (status) { + case ProviderPaymentType.InsufficientBalance: + return 'rgb(223, 117, 20)' + case ProviderPaymentType.Unpaid: + return 'rgb(202, 60, 60)' + case ProviderPaymentType.Paid: + return 'rgb(28, 184, 65)' + case ProviderPaymentType.Pending: + return 'gray' + case ProviderPaymentType.InsufficientBalance: + return 'rgb(202, 60, 60)' + case ProviderPaymentType.TermsChanged: + return 'rgb(202, 60, 60)' + default: + break; + } + return undefined + } + + return ( + <div style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}> + <style>{` + table td { + padding: 5px 10px; + } + `}</style> + <div style={{ display: 'flex', flexDirection: 'column' }}> + <section style={{ flex: '1 0 auto', height: 'calc(320px - 34px - 45px - 16px)', overflow: 'auto' }}> + <span style={{ padding: 5, display: 'inline-block', backgroundColor: colorByStatus(info?.paymentStatus.type), borderRadius: 5, color: 'white' }}>{info?.paymentStatus.type}</span> + {info && <span style={{ float: "right", fontSize: "small", color: "gray", padding: 5 }}> + From <b>{info.syncProviderBaseUrl}</b> + </span>} + + <Error /> + + <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", }}> + <h1>{currency}</h1> + {info && <div style={{ marginTop: 'auto', marginBottom: 'auto' }}>{info.terms?.annualFee} / year</div>} + </div> + + <div>{daysSince(info?.lastSuccessfulBackupTimestamp)} </div> + </section> + <Footer /> + </div> + </div> + ) +} + +function daysSince(d?: Timestamp) { + if (!d || d.t_ms === 'never') return 'never synced' + const duration = intervalToDuration({ + start: d.t_ms, + end: new Date(), + }) + const str = formatDuration(duration, { + delimiter: ', ', + format: [ + duration?.years ? 'years' : ( + duration?.months ? 'months' : ( + duration?.days ? 'days' : ( + duration?.hours ? 'hours' : ( + duration?.minutes ? 'minutes' : 'seconds' + ) + ) + ) + ) + ] + }) + return `synced ${str} ago` +} diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.tsx b/packages/taler-wallet-webextension/src/popup/Transaction.tsx index 7a66d7a0e..d6a85d64d 100644 --- a/packages/taler-wallet-webextension/src/popup/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/popup/Transaction.tsx @@ -97,7 +97,7 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall return ( <div style={{ display: 'flex', flexDirection: 'column' }} > <section style={{ color: transaction.pending ? 'gray' : '', flex: '1 0 auto', height: 'calc(320px - 34px - 45px - 16px)', overflow: 'auto' }}> - <span style="flat: left; font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> + <span style="font-size:small; color:gray">{transaction.timestamp.t_ms === "never" ? "never" : format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</span> <span style="float: right; font-size:small; color:gray"> From <b>{transaction.exchangeBaseUrl}</b> </span> diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx b/packages/taler-wallet-webextension/src/popup/popup.tsx index 288e04477..6b8f110e1 100644 --- a/packages/taler-wallet-webextension/src/popup/popup.tsx +++ b/packages/taler-wallet-webextension/src/popup/popup.tsx @@ -36,6 +36,7 @@ export enum Pages { backup = '/backup', history = '/history', transaction = '/transaction/:tid', + provider = '/provider/:currency', } interface TabProps { diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx index c777e01de..8fb5121e5 100644 --- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx @@ -38,6 +38,7 @@ import { useTalerActionURL } from "./hooks/useTalerActionURL"; import { createHashHistory } from "history"; import { DevContextProvider } from "./context/useDevContext"; import { BackupPage } from "./popup/BackupPage"; +import { ProviderPage } from "./popup/ProviderPage.js"; function main(): void { try { @@ -99,6 +100,7 @@ function Application() { <Route path={Pages.dev} component={DeveloperPage} /> <Route path={Pages.history} component={HistoryPage} /> <Route path={Pages.backup} component={BackupPage} /> + <Route path={Pages.provider} component={ProviderPage} /> <Route path={Pages.transaction} component={TransactionPage} /> <Route default component={Redirect} to={Pages.balance} /> </Router> diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 81f418d40..393c41102 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -38,8 +38,8 @@ import { DeleteTransactionRequest, RetryTransactionRequest, } from "@gnu-taler/taler-util"; -import { BackupProviderState, OperationFailedError } from "@gnu-taler/taler-wallet-core"; -import { BackupInfo } from "@gnu-taler/taler-wallet-core/src/operations/backup"; +import { AddBackupProviderRequest, BackupProviderState, OperationFailedError } from "@gnu-taler/taler-wallet-core"; +import { BackupInfo } from "@gnu-taler/taler-wallet-core"; export interface ExtendedPermissionsResponse { newValue: boolean; @@ -166,9 +166,26 @@ export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> { /** * Get information about the current state of wallet backups. */ - export function getBackupInfo(): Promise<BackupInfo> { +export function getBackupInfo(): Promise<BackupInfo> { return callBackend("getBackupInfo", {}) } + +/** + * Add a backup provider and activate it + */ +export function addBackupProvider(backupProviderBaseUrl: string): Promise<void> { + return callBackend("addBackupProvider", { + backupProviderBaseUrl, activate: true + } as AddBackupProviderRequest) +} + +export function syncAllProviders(): Promise<void> { + return callBackend("runBackupCycle", {}) +} + + + + /** * Retry a transaction * @param transactionId |