gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: refactored backup sync UI


From: gnunet
Subject: [taler-wallet-core] branch master updated: refactored backup sync UI
Date: Tue, 06 Jul 2021 17:44:35 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 678a9093 refactored backup sync UI
678a9093 is described below

commit 678a90934c7b819b1d5c864f7429242d7d74a1e6
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Jul 6 12:44:25 2021 -0300

    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(-)

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 00000000..82983d13
--- /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 00000000..e322c672
--- /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 8c35705e..09f61e46 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 1bd43163..cd40d69a 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 e0e41427..91f1782c 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 679a7ce4..f286870c 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 7b8712ec..1e4a44df 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 8b907516..dfee115b 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 01c0a5f0..480d7b1a 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 59e6cda1..1b8abf44 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 b6d852d5..07e1538b 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 0a57092b..d8cd0438 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 2ed9dbab..f7b3cec9 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 d73b3566..80a2a2bd 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 393c4110..db440e91 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 00000000..36adbc1c
--- /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>

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]