summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-07-06 12:44:25 -0300
committerSebastian <sebasjm@gmail.com>2021-07-06 12:44:25 -0300
commit678a90934c7b819b1d5c864f7429242d7d74a1e6 (patch)
tree0711e8268406e0f6044c0a6736d82598da71a758 /packages
parent550905f0e7eed38fa1ef598b4015faf10648cf1b (diff)
downloadwallet-core-678a90934c7b819b1d5c864f7429242d7d74a1e6.tar.gz
wallet-core-678a90934c7b819b1d5c864f7429242d7d74a1e6.tar.bz2
wallet-core-678a90934c7b819b1d5c864f7429242d7d74a1e6.zip
refactored backup sync UI
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-wallet-webextension/src/components/EditableText.tsx69
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts34
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useProvidersByCurrency.ts42
-rw-r--r--packages/taler-wallet-webextension/src/popup/Backup.stories.tsx143
-rw-r--r--packages/taler-wallet-webextension/src/popup/BackupPage.tsx85
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx127
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx19
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx54
-rw-r--r--packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx101
-rw-r--r--packages/taler-wallet-webextension/src/popup/Settings.stories.tsx18
-rw-r--r--packages/taler-wallet-webextension/src/popup/Settings.tsx45
-rw-r--r--packages/taler-wallet-webextension/src/popup/popup.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/popupEntryPoint.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts9
-rw-r--r--packages/taler-wallet-webextension/static/img/chevron-down.svg7
16 files changed, 574 insertions, 198 deletions
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 <http://www.gnu.org/licenses/>
+ */
+
+import { VNode } from "preact";
+import { useRef, useState } from "preact/hooks";
+import { JSX } from "preact/jsx-runtime";
+
+interface Props {
+ value: string;
+ onChange: (s: string) => Promise<void>;
+ 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<HTMLInputElement>()
+ let InputText;
+ if (!editing) {
+ 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={() => { onChange(ref.current.value).then(r => setEditing(false)) }}>confirm</button>
+ </div>
+ }
+ return (
+ <div>
+ <label
+ htmlFor={`text-${name}`}
+ style={{ marginLeft: "0.5em", fontWeight: "bold" }}
+ >
+ {label}
+ </label>
+ <InputText />
+ {description && <span
+ style={{
+ color: "#383838",
+ fontSize: "smaller",
+ display: "block",
+ marginLeft: "2em",
+ }}
+ >
+ {description}
+ </span>}
+ </div>
+ );
+}
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<void>
+}
+
+
+export function useBackupDeviceName(): BackupDeviceName {
+ const [status, setStatus] = useState<BackupDeviceName>({
+ 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<BackupStatus | undefined>(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<Props>(Component: FunctionalComponent<Props>, 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 <div>Loading...</div>
}
- return <BackupView deviceName={status.deviceName} providers={status.providers}/>;
+ return <BackupView providers={status.providers} onAddProvider={onAddProvider} />;
}
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 (
<div style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}>
- <div style={{ display: 'flex', flexDirection: 'row', width: '100%', justifyContent: 'space-between' }}>
- <h2 style={{ width: 240, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', marginTop: 10, marginBottom:10 }}>
- {deviceName}
- </h2>
- <div style={{ flexDirection: 'row', marginTop: 'auto', marginBottom: 'auto' }}>
- <button class="pure-button button-secondary">rename</button>
- </div>
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
+ <section style={{ flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}>
+
+ {!!providers.length && <div>
+ {providers.map((provider, idx) => {
+ return <BackupLayout
+ status={provider.paymentStatus}
+ timestamp={provider.lastSuccessfulBackupTimestamp}
+ id={idx}
+ active={provider.active}
+ subtitle={provider.syncProviderBaseUrl}
+ title={provider.syncProviderBaseUrl}
+ />
+ })}
+ </div>}
+ {!providers.length && <div>
+ There is not backup providers configured, add one with the button below
+ </div>}
+
+ </section>
+ <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}>
+ <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
+ <button class="pure-button button-secondary" disabled={!providers.length} style={{ marginLeft: 5 }} onClick={onAddProvider}>{
+ providers.length > 1 ?
+ <i18n.Translate>sync all now</i18n.Translate>:
+ <i18n.Translate>sync now</i18n.Translate>
+ }</button>
+ <button class="pure-button button-success" style={{ marginLeft: 5 }} onClick={onAddProvider}><i18n.Translate>add provider</i18n.Translate></button>
+ </div>
+ </footer>
</div>
- {Object.keys(providers).map((currency) => {
- const provider = providers[currency]
- if (!provider) {
- return <BackupLayout
- id={currency}
- title={currency}
- />
- }
- return <BackupLayout
- status={provider.paymentStatus}
- timestamp={provider.lastSuccessfulBackupTimestamp}
- id={currency}
- active={provider.active}
- subtitle={provider.syncProviderBaseUrl}
- title={currency}
- />
- })}
</div>
)
}
@@ -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 {
<div
style={{ display: "flex", flexDirection: "column", color: !props.active ? "gray" : undefined }}
>
- {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={Pages.provider_detail.replace(':currency', props.id)}><span>{props.title}</span></a>
+ <a href={Pages.provider_detail.replace(':pid', String(props.id))}><span>{props.title}</span></a>
</div>
- <div>{props.subtitle}</div>
+ {dateStr && <div style={{ fontSize: "small" }}>Last time synced: {dateStr}</div>}
+ {!dateStr && <div style={{ fontSize: "small", color: "red" }}>never synced</div>}
</div>
<div style={{
marginLeft: "auto",
@@ -111,7 +122,7 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element {
alignItems: "center",
alignSelf: "center"
}}>
- <div style={{}}>
+ <div style={{ whiteSpace: 'nowrap' }}>
{!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<Props>(Component: FunctionalComponent<Props>, 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<boolean | undefined>(undefined)
+ const alreadyCheckedTheTerms = readingTerms === false
+
if (!verifying) {
return <SetUrlView
- currency={currency}
onCancel={() => {
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 <TermsOfService
+ onCancel={() => setReadingTerms(undefined)}
+ onAccept={() => setReadingTerms(false)}
+ />
+ }
return <ConfirmProviderView
provider={verifying.provider}
- currency={currency}
+ termsChecked={alreadyCheckedTheTerms}
url={verifying.url}
onCancel={() => {
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 <div style={{ display: 'flex', flexDirection: 'column' }}>
+ <section style={{ height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}>
+ <div>
+ Here we will place the complete text of terms of service
+ </div>
+ </section>
+ <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}>
+ <button class="pure-button" onClick={onCancel}><i18n.Translate>cancel</i18n.Translate></button>
+ <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
+ <button class="pure-button" onClick={onAccept}><i18n.Translate>accept</i18n.Translate></button>
+ </div>
+ </footer>
+ </div>
+}
+
export interface SetUrlViewProps {
- currency: string,
+ initialValue?: string;
onCancel: () => void;
onVerify: (s: string) => Promise<string | undefined>;
+ withError?: string;
}
+import arrowDown from '../../static/img/chevron-down.svg';
-export function SetUrlView({ currency, onCancel, onVerify }: SetUrlViewProps) {
- const [value, setValue] = useState<string>("")
- const [error, setError] = useState<string | undefined>(undefined)
+export function SetUrlView({ initialValue, onCancel, onVerify, withError }: SetUrlViewProps) {
+ const [value, setValue] = useState<string>(initialValue || "")
+ const [error, setError] = useState<string | undefined>(withError)
+ const [showErrorDetail, setShowErrorDetail] = useState(false);
return <div style={{ display: 'flex', flexDirection: 'column' }}>
<section style={{ height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}>
<div>
- Add backup provider for storing <b>{currency}</b>
+ Add backup provider for saving coins
</div>
- {error && <div class="errorbox" style={{ marginTop: 10 }} >
- <p>{error}</p>
- </div>}
<h3>Backup provider URL</h3>
- <input style={{ width: 'calc(100% - 8px)' }} value={value} onChange={(e) => setValue(e.currentTarget.value)} />
+ <div style={{ width: '3em', display: 'inline-block' }}>https://</div>
+ <input style={{ width: 'calc(100% - 8px - 4em)', marginLeft: 5 }} value={value} onChange={(e) => setValue(e.currentTarget.value)} />
<p>
Backup providers may charge for their service
</p>
+ {error && <Fragment>
+ <div class="errorbox" style={{ marginTop: 10 }} >
+ <div style={{ width: '100%', flexDirection: 'row', justifyContent: 'space-between', display: 'flex' }}>
+ <p style={{ alignSelf: 'center' }}>Could not get provider information</p>
+ <p>
+ <button style={{ fontSize: '100%', padding: 0, height: 28, width: 28 }} onClick={() => { setShowErrorDetail(v => !v) }} >
+ <img style={{ height: '1.5em' }} src={arrowDown} />
+ </button>
+ </p>
+ </div>
+ {showErrorDetail && <div>{error}</div>}
+ </div>
+ </Fragment>
+ }
</section>
<footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}>
<button class="pure-button" onClick={onCancel}><i18n.Translate>cancel</i18n.Translate></button>
<div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
- <button class="pure-button button-secondary" style={{ marginLeft: 5 }} onClick={() => onVerify(value).then(r => r ? setError(r) : undefined)}><i18n.Translate>verify service terms</i18n.Translate></button>
+ <button class="pure-button button-secondary" style={{ marginLeft: 5 }}
+ disabled={!value}
+ onClick={() => {
+ let url = value.startsWith('http://') || value.startsWith('https://') ? value : `https://${value}`
+ url = url.endsWith('/') ? url.substring(0, url.length - 1) : url;
+ return onVerify(url).then(r => r ? setError(r) : undefined)
+ }}><i18n.Translate>next</i18n.Translate></button>
</div>
</footer>
</div>
@@ -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 <div style={{ display: 'flex', flexDirection: 'column' }}>
-
<section style={{ height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}>
- <div>
- Verify provider service terms for storing <b>{currency}</b>
- </div>
- <h3>{url}</h3>
+ <div>Verify provider service terms for <b>{url}</b> backup provider</div>
<p>
{Amounts.isZero(provider.annual_fee) ? 'free of charge' : provider.annual_fee} for a year of backup service
</p>
@@ -98,9 +160,14 @@ export function ConfirmProviderView({ url, provider, currency, onCancel, onConfi
<i18n.Translate>cancel</i18n.Translate>
</button>
<div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
- <button class="pure-button button-success" style={{ marginLeft: 5 }} onClick={onConfirm}>
- <i18n.Translate>confirm</i18n.Translate>
- </button>
+ {termsChecked ?
+ <button class="pure-button button-success" style={{ marginLeft: 5 }} onClick={onConfirm}>
+ <i18n.Translate>confirm</i18n.Translate>
+ </button> :
+ <button class="pure-button button-success" style={{ marginLeft: 5 }} onClick={onShowTerms}>
+ <i18n.Translate>review terms</i18n.Translate>
+ </button>
+ }
</div>
</footer>
</div>
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<Props>(Component: FunctionalComponent<Props>, 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<Props>(Component: FunctionalComponent<Props>, 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 <div>Loading...</div>
}
- const info = status.providers[currency];
- return <ProviderView currency={currency} info={info}
+ const idx = parseInt(pid, 10)
+ if (Number.isNaN(idx) || !(status.providers[idx])) {
+ onBack()
+ return <div />
+ }
+ const info = status.providers[idx];
+ return <ProviderView info={info}
onSync={() => { 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 <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}>
<button class="pure-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>}
+ {info && <button class="pure-button button-destructive" disabled onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>}
+ {info && <button class="pure-button button-secondary" disabled style={{ marginLeft: 5 }} onClick={onExtend}><i18n.Translate>extend</i18n.Translate></button>}
+ {info && <button class="pure-button button-secondary" disabled style={{ marginLeft: 5 }} onClick={onSync}><i18n.Translate>sync now</i18n.Translate></button>}
</div>
</footer>
}
@@ -67,7 +71,7 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr
if (info?.lastError) {
return <Fragment>
<div class="errorbox" style={{ marginTop: 10 }} >
- <div style={{ height: 0, textAlign: 'right', color: 'gray', fontSize: 'small' }}>{!info.lastAttemptedBackupTimestamp || info.lastAttemptedBackupTimestamp.t_ms === 'never' ? 'never' : format(new Date(info.lastAttemptedBackupTimestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}</div>
+ <div style={{ height: 0, textAlign: 'right', color: 'gray', fontSize: 'small' }}>last time tried {!info.lastAttemptedBackupTimestamp || info.lastAttemptedBackupTimestamp.t_ms === 'never' ? 'never' : format(new Date(info.lastAttemptedBackupTimestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}</div>
<p>{info.lastError.hint}</p>
</div>
</Fragment>
@@ -76,7 +80,7 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr
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>
+ <p>There is conflict with another backup from <b>{info.backupProblem.otherDeviceId}</b></p>
</div>
case "backup-unreadable":
return <div class="errorbox" style={{ marginTop: 10 }}>
@@ -84,7 +88,7 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr
</div>
default:
return <div class="errorbox" style={{ marginTop: 10 }}>
- <p>Unkown backup problem: {JSON.stringify(info.backupProblem)}</p>
+ <p>Unknown backup problem: {JSON.stringify(info.backupProblem)}</p>
</div>
}
}
@@ -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 (
<div style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}>
<style>{`
@@ -120,18 +146,49 @@ export function ProviderView({ currency, info, onDelete, onSync, onBack, onAddPr
<div style={{ display: 'flex', flexDirection: 'column' }}>
<section style={{ flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 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 }}>
+ {/* {info && <span style={{ float: "right", fontSize: "small", color: "gray", padding: 5 }}>
From <b>{info.syncProviderBaseUrl}</b>
- </span>}
+ </span>} */}
+ {info && <div style={{ float: 'right', fontSize: "large", padding: 5 }}>{info.terms?.annualFee} / year</div>}
<Error />
+ <h3>{info?.syncProviderBaseUrl}</h3>
<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>{daysSince(info?.lastSuccessfulBackupTimestamp)} </div>
</div>
- <div>{daysSince(info?.lastSuccessfulBackupTimestamp)} </div>
+ <p>{descriptionByStatus(info?.paymentStatus)}</p>
+
+ {info?.paymentStatus.type === ProviderPaymentType.TermsChanged && <div>
+ <p>terms has changed, extending the service will imply accepting the new terms of service</p>
+ <table>
+ <thead>
+ <tr>
+ <td></td>
+ <td>old</td>
+ <td> -&gt;</td>
+ <td>new</td>
+ </tr>
+ </thead>
+ <tbody>
+
+ <tr>
+ <td>fee</td>
+ <td>{info.paymentStatus.oldTerms.annualFee}</td>
+ <td>-&gt;</td>
+ <td>{info.paymentStatus.newTerms.annualFee}</td>
+ </tr>
+ <tr>
+ <td>storage</td>
+ <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
+ <td>-&gt;</td>
+ <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>}
+
</section>
<Footer />
</div>
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx b/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx
index b6d852d52..07e1538b7 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.stories.tsx
@@ -19,13 +19,6 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import {
- PaymentStatus,
- TransactionCommon, TransactionDeposit, TransactionPayment,
- TransactionRefresh, TransactionRefund, TransactionTip, TransactionType,
- TransactionWithdrawal,
- WithdrawalType
-} from '@gnu-taler/taler-util';
import { FunctionalComponent } from 'preact';
import { SettingsView as TestedComponent } from './Settings';
@@ -33,9 +26,7 @@ export default {
title: 'popup/settings',
component: TestedComponent,
argTypes: {
- onRetry: { action: 'onRetry' },
- onDelete: { action: 'onDelete' },
- onBack: { action: 'onBack' },
+ setDeviceName: () => Promise.resolve(),
}
};
@@ -46,9 +37,14 @@ function createExample<Props>(Component: FunctionalComponent<Props>, props: Part
return r
}
-export const AllOff = createExample(TestedComponent, {});
+export const AllOff = createExample(TestedComponent, {
+ deviceName: 'this-is-the-device-name',
+ setDeviceName: () => Promise.resolve(),
+});
export const OneChecked = createExample(TestedComponent, {
+ 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 0a57092bb..d8cd04380 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -17,40 +17,57 @@
import { VNode } from "preact";
import { Checkbox } from "../components/Checkbox";
+import { EditableText } from "../components/EditableText";
import { useDevContext } from "../context/useDevContext";
+import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
export function SettingsPage(): VNode {
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
const { devMode, toggleDevMode } = useDevContext()
- return <SettingsView
+ const { name, update } = useBackupDeviceName()
+ return <SettingsView
+ deviceName={name} setDeviceName={update}
permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions}
developerMode={devMode} toggleDeveloperMode={toggleDevMode}
/>;
}
export interface ViewProps {
+ deviceName: string;
+ setDeviceName: (s: string) => Promise<void>;
permissionsEnabled: boolean;
togglePermissions: () => void;
developerMode: boolean;
toggleDeveloperMode: () => void;
}
-export function SettingsView({permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode}: ViewProps): VNode {
+export function SettingsView({ deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode {
return (
<div>
- <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>Config</h2>
- <Checkbox label="Developer mode"
- name="devMode"
- description="(More options and information useful for debugging)"
- enabled={developerMode} onToggle={toggleDeveloperMode}
- />
+ <section style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}>
+
+ <h2>Wallet</h2>
+ <EditableText
+ value={deviceName}
+ onChange={setDeviceName}
+ name="device-id"
+ label="Device name"
+ description="(This is how you will recognize the wallet in the backup provider)"
+ />
+ <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>Config</h2>
+ <Checkbox label="Developer mode"
+ name="devMode"
+ description="(More options and information useful for debugging)"
+ enabled={developerMode} onToggle={toggleDeveloperMode}
+ />
+ </section>
</div>
)
} \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx b/packages/taler-wallet-webextension/src/popup/popup.tsx
index 2ed9dbab9..f7b3cec94 100644
--- a/packages/taler-wallet-webextension/src/popup/popup.tsx
+++ b/packages/taler-wallet-webextension/src/popup/popup.tsx
@@ -36,8 +36,8 @@ export enum Pages {
backup = '/backup',
history = '/history',
transaction = '/transaction/:tid',
- provider_detail = '/provider/:currency',
- provider_add = '/provider/:currency/add',
+ provider_detail = '/provider/:pid',
+ provider_add = '/provider/add',
}
interface TabProps {
@@ -61,7 +61,6 @@ function Tab(props: TabProps): JSX.Element {
export function WalletNavBar() {
const { devMode } = useDevContext()
return <Match>{({ path }: any) => {
- console.log("current", path)
return (
<div class="nav" id="header">
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index d73b3566b..80a2a2bd3 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -99,8 +99,16 @@ function Application() {
<Route path={Pages.settings} component={SettingsPage} />
<Route path={Pages.dev} component={DeveloperPage} />
<Route path={Pages.history} component={HistoryPage} />
- <Route path={Pages.backup} component={BackupPage} />
- <Route path={Pages.provider_detail} component={ProviderDetailPage} />
+ <Route path={Pages.backup} component={BackupPage}
+ onAddProvider={() => {
+ route(Pages.provider_add)
+ }}
+ />
+ <Route path={Pages.provider_detail} component={ProviderDetailPage}
+ onBack={() => {
+ route(Pages.backup)
+ }}
+ />
<Route path={Pages.provider_add} component={ProviderAddPage} />
<Route path={Pages.transaction} component={TransactionPage} />
<Route default component={Redirect} to={Pages.balance} />
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts
index 393c41102..db440e913 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -37,6 +37,7 @@ import {
AcceptTipRequest,
DeleteTransactionRequest,
RetryTransactionRequest,
+ SetWalletDeviceIdRequest,
} from "@gnu-taler/taler-util";
import { AddBackupProviderRequest, BackupProviderState, OperationFailedError } from "@gnu-taler/taler-wallet-core";
import { BackupInfo } from "@gnu-taler/taler-wallet-core";
@@ -179,13 +180,17 @@ export function addBackupProvider(backupProviderBaseUrl: string): Promise<void>
} as AddBackupProviderRequest)
}
+export function setWalletDeviceId(walletDeviceId: string): Promise<void> {
+ return callBackend("setWalletDeviceId", {
+ walletDeviceId
+ } as SetWalletDeviceIdRequest)
+}
+
export function syncAllProviders(): Promise<void> {
return callBackend("runBackupCycle", {})
}
-
-
/**
* Retry a transaction
* @param transactionId
diff --git a/packages/taler-wallet-webextension/static/img/chevron-down.svg b/packages/taler-wallet-webextension/static/img/chevron-down.svg
new file mode 100644
index 000000000..36adbc1c6
--- /dev/null
+++ b/packages/taler-wallet-webextension/static/img/chevron-down.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="92px" height="92px" viewBox="0 0 92 92" enable-background="new 0 0 92 92" xml:space="preserve">
+<path id="XMLID_467_" d="M46,63c-1.1,0-2.1-0.4-2.9-1.2l-25-26c-1.5-1.6-1.5-4.1,0.1-5.7c1.6-1.5,4.1-1.5,5.7,0.1l22.1,23l22.1-23
+ c1.5-1.6,4.1-1.6,5.7-0.1c1.6,1.5,1.6,4.1,0.1,5.7l-25,26C48.1,62.6,47.1,63,46,63z"/>
+</svg>