gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 01/02: copy from popup to wallet


From: gnunet
Subject: [taler-wallet-core] 01/02: copy from popup to wallet
Date: Tue, 24 Aug 2021 20:16:43 +0200

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

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

commit 0bc235c64b6936aa092a2df40e0c4909e4ac05d5
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Aug 24 13:29:37 2021 -0300

    copy from popup to wallet
---
 .../src/popup/Balance.stories.tsx                  |   2 +-
 .../src/popupEntryPoint.tsx                        |   2 -
 .../src/wallet/Backup.stories.tsx                  | 193 +++++++++++++++++
 .../src/wallet/BackupPage.tsx                      | 146 +++++++++++++
 .../src/{popup => wallet}/Balance.stories.tsx      |   2 +-
 .../src/wallet/BalancePage.tsx                     | 117 ++++++++++
 .../wallet/ProviderAddConfirmProvider.stories.tsx  |  52 +++++
 .../src/wallet/ProviderAddPage.tsx                 | 150 +++++++++++++
 .../src/wallet/ProviderAddSetUrl.stories.tsx       |  53 +++++
 .../src/wallet/ProviderDetail.stories.tsx          | 238 +++++++++++++++++++++
 .../src/wallet/ProviderDetailPage.tsx              | 197 +++++++++++++++++
 .../src/wallet/Settings.stories.tsx                |  43 ++++
 .../src/wallet/Settings.tsx                        | 103 +++++++++
 .../src/{popup => wallet}/Transaction.stories.tsx  |   2 +-
 .../src/{popup => wallet}/Transaction.tsx          |   6 +-
 .../src/walletEntryPoint.tsx                       |  29 ++-
 16 files changed, 1316 insertions(+), 19 deletions(-)

diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
index 4a2e1045..a0655d37 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
@@ -23,7 +23,7 @@ import { createExample, NullLink } from '../test-utils';
 import { BalanceView as TestedComponent } from './BalancePage';
 
 export default {
-  title: 'popup/balance/detail',
+  title: 'popup/balance',
   component: TestedComponent,
   argTypes: {
   }
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index 77c19c15..c72ea85c 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -38,7 +38,6 @@ import {
 import { ProviderAddPage } from "./popup/ProviderAddPage";
 import { ProviderDetailPage } from "./popup/ProviderDetailPage";
 import { SettingsPage } from "./popup/Settings";
-import { TransactionPage } from "./popup/Transaction";
 
 function main(): void {
   try {
@@ -114,7 +113,6 @@ function Application() {
                 route(Pages.backup)
               }}
             />
-            <Route path={Pages.transaction} component={TransactionPage} />
             <Route default component={Redirect} to={Pages.balance} />
           </Router>
         </div>
diff --git a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx
new file mode 100644
index 00000000..9a53fefe
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx
@@ -0,0 +1,193 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core';
+import { addDays } from 'date-fns';
+import { BackupView as TestedComponent } from './BackupPage';
+import { createExample } from '../test-utils';
+
+export default {
+  title: 'wallet/backup/list',
+  component: TestedComponent,
+  argTypes: {
+    onRetry: { action: 'onRetry' },
+    onDelete: { action: 'onDelete' },
+    onBack: { action: 'onBack' },
+  }
+};
+
+
+export const LotOfProviders = createExample(TestedComponent, {
+  providers: [{
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": 1656599921000
+      }
+    },
+    "terms": {
+      "annualFee": "ARS:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }, {
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": addDays(new Date(), 13).getTime()
+      }
+    },
+    "terms": {
+      "annualFee": "ARS:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }, {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Pending,
+    },
+    "terms": {
+      "annualFee": "KUDOS:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }, {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.InsufficientBalance,
+    },
+    "terms": {
+      "annualFee": "KUDOS:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }, {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.TermsChanged,
+      newTerms: {
+        annualFee: 'USD:2',
+        storageLimitInMegabytes: 8,
+        supportedProtocolVersion: '2',
+      },
+      oldTerms: {
+        annualFee: 'USD:1',
+        storageLimitInMegabytes: 16,
+        supportedProtocolVersion: '1',
+
+      },
+      paidUntil: {
+        t_ms: 'never'
+      }
+    },
+    "terms": {
+      "annualFee": "KUDOS:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }, {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Unpaid,
+    },
+    "terms": {
+      "annualFee": "KUDOS:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }, {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Unpaid,
+    },
+    "terms": {
+      "annualFee": "KUDOS:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }]
+});
+
+
+export const OneProvider = createExample(TestedComponent, {
+  providers: [{
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": 1656599921000
+      }
+    },
+    "terms": {
+      "annualFee": "ARS:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }]
+});
+
+
+export const Empty = createExample(TestedComponent, {
+  providers: []
+});
+
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
new file mode 100644
index 00000000..8b88432e
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+
+import { i18n, Timestamp } from "@gnu-taler/taler-util";
+import { ProviderInfo, ProviderPaymentStatus } from 
"@gnu-taler/taler-wallet-core";
+import { differenceInMonths, formatDuration, intervalToDuration } from 
"date-fns";
+import { Fragment, JSX, VNode, h } from "preact";
+import {
+  BoldLight, ButtonPrimary, ButtonSuccess, Centered,
+  CenteredText, CenteredTextBold, PopupBox, RowBorderGray,
+  SmallText, SmallTextLight, WalletBox
+} from "../components/styled";
+import { useBackupStatus } from "../hooks/useBackupStatus";
+import { Pages } from "../NavigationBar";
+
+interface Props {
+  onAddProvider: () => void;
+}
+
+export function BackupPage({ onAddProvider }: Props): VNode {
+  const status = useBackupStatus()
+  if (!status) {
+    return <div>Loading...</div>
+  }
+  return <BackupView providers={status.providers} 
onAddProvider={onAddProvider} onSyncAll={status.sync} />;
+}
+
+export interface ViewProps {
+  providers: ProviderInfo[],
+  onAddProvider: () => void;
+  onSyncAll: () => Promise<void>;
+}
+
+export function BackupView({ providers, onAddProvider, onSyncAll }: 
ViewProps): VNode {
+  return (
+    <WalletBox>
+      <section>
+        {providers.map((provider) => <BackupLayout
+          status={provider.paymentStatus}
+          timestamp={provider.lastSuccessfulBackupTimestamp}
+          id={provider.syncProviderBaseUrl}
+          active={provider.active}
+          title={provider.name}
+        />
+        )}
+        {!providers.length && <Centered style={{marginTop: 100}}>
+          <BoldLight>No backup providers configured</BoldLight>
+          <ButtonSuccess onClick={onAddProvider}><i18n.Translate>Add 
provider</i18n.Translate></ButtonSuccess>
+        </Centered>}
+      </section>
+      {!!providers.length && <footer>
+        <div />
+        <div>
+          <ButtonPrimary onClick={onSyncAll}>{
+            providers.length > 1 ?
+              <i18n.Translate>Sync all backups</i18n.Translate> :
+              <i18n.Translate>Sync now</i18n.Translate>
+          }</ButtonPrimary>
+          <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess>
+        </div>
+      </footer>}
+    </WalletBox>
+  )
+}
+
+interface TransactionLayoutProps {
+  status: ProviderPaymentStatus;
+  timestamp?: Timestamp;
+  title: string;
+  id: string;
+  active: boolean;
+}
+
+function BackupLayout(props: TransactionLayoutProps): JSX.Element {
+  const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
+  const dateStr = date?.toLocaleString([], {
+    dateStyle: "medium",
+    timeStyle: "short",
+  } as any);
+
+
+  return (
+    <RowBorderGray>
+      <div style={{ color: !props.active ? "grey" : undefined }}>
+        <a href={Pages.provider_detail.replace(':pid', 
encodeURIComponent(props.id))}><span>{props.title}</span></a>
+
+        {dateStr && <SmallText style={{marginTop: 5}}>Last synced: 
{dateStr}</SmallText>}
+        {!dateStr && <SmallTextLight style={{marginTop: 5}}>Not 
synced</SmallTextLight>}
+      </div>
+      <div>
+        {props.status?.type === 'paid' ?
+          <ExpirationText until={props.status.paidUntil} /> :
+          <div>{props.status.type}</div>
+        }
+      </div>
+    </RowBorderGray>
+  );
+}
+
+function ExpirationText({ until }: { until: Timestamp }) {
+  return <Fragment>
+    <CenteredText> Expires in </CenteredText>
+    <CenteredTextBold {...({ color: colorByTimeToExpire(until) })}> 
{daysUntil(until)} </CenteredTextBold>
+  </Fragment>
+}
+
+function colorByTimeToExpire(d: Timestamp) {
+  if (d.t_ms === 'never') return 'rgb(28, 184, 65)'
+  const months = differenceInMonths(d.t_ms, new Date())
+  return months > 1 ? 'rgb(28, 184, 65)' : 'rgb(223, 117, 20)';
+}
+
+function daysUntil(d: Timestamp) {
+  if (d.t_ms === 'never') return undefined
+  const duration = intervalToDuration({
+    start: d.t_ms,
+    end: new Date(),
+  })
+  const str = formatDuration(duration, {
+    delimiter: ', ',
+    format: [
+      duration?.years ? 'years' : (
+        duration?.months ? 'months' : (
+          duration?.days ? 'days' : (
+            duration.hours ? 'hours' : 'minutes'
+          )
+        )
+      )
+    ]
+  })
+  return `${str}`
+}
\ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
similarity index 98%
copy from packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
copy to packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
index 4a2e1045..1b145345 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
@@ -23,7 +23,7 @@ import { createExample, NullLink } from '../test-utils';
 import { BalanceView as TestedComponent } from './BalancePage';
 
 export default {
-  title: 'popup/balance/detail',
+  title: 'wallet/balance',
   component: TestedComponent,
   argTypes: {
   }
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
new file mode 100644
index 00000000..4846d47f
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -0,0 +1,117 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import {
+  amountFractionalBase, Amounts,
+  Balance, BalancesResponse,
+  i18n
+} from "@gnu-taler/taler-util";
+import { JSX, h } from "preact";
+import { WalletBox, Centered } from "../components/styled/index";
+import { BalancesHook, useBalances } from "../hooks/useBalances";
+import { PageLink, renderAmount } from "../renderHtml";
+
+
+export function BalancePage() {
+  const balance = useBalances()
+  return <BalanceView balance={balance} Linker={PageLink} />
+}
+export interface BalanceViewProps {
+  balance: BalancesHook,
+  Linker: typeof PageLink,
+}
+export function BalanceView({ balance, Linker }: BalanceViewProps) {
+  if (!balance) {
+    return <span />
+  }
+
+  if (balance.error) {
+    return (
+      <div>
+        <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+        <p>
+          Click <Linker pageName="welcome">here</Linker> for help and
+          diagnostics.
+        </p>
+      </div>
+    )
+  }
+  if (balance.response.balances.length === 0) {
+    return (
+      <p><i18n.Translate>
+        You have no balance to show. Need some{" "}
+        <Linker pageName="/welcome">help</Linker> getting started?
+      </i18n.Translate></p>
+    )
+  }
+  return <ShowBalances wallet={balance.response} />
+}
+
+function formatPending(entry: Balance): JSX.Element {
+  let incoming: JSX.Element | undefined;
+  let payment: JSX.Element | undefined;
+
+  const available = Amounts.parseOrThrow(entry.available);
+  const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
+  const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
+
+  if (!Amounts.isZero(pendingIncoming)) {
+    incoming = (
+      <span><i18n.Translate>
+        <span style={{ color: "darkgreen" }}>
+          {"+"}
+          {renderAmount(entry.pendingIncoming)}
+        </span>{" "}
+        incoming
+      </i18n.Translate></span>
+    );
+  }
+
+  const l = [incoming, payment].filter((x) => x !== undefined);
+  if (l.length === 0) {
+    return <span />;
+  }
+
+  if (l.length === 1) {
+    return <span>({l})</span>;
+  }
+  return (
+    <span>
+      ({l[0]}, {l[1]})
+    </span>
+  );
+}
+
+
+function ShowBalances({ wallet }: { wallet: BalancesResponse }) {
+  return <WalletBox>
+    <section>
+      <Centered>{wallet.balances.map((entry) => {
+        const av = Amounts.parseOrThrow(entry.available);
+        const v = av.value + av.fraction / amountFractionalBase;
+        return (
+          <p key={av.currency}>
+            <span>
+              <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" 
"}
+              <span>{av.currency}</span>
+            </span>
+            {formatPending(entry)}
+          </p>
+        );
+      })}</Centered>
+    </section>
+  </WalletBox>
+}
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx
 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx
new file mode 100644
index 00000000..d1e76c05
--- /dev/null
+++ 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx
@@ -0,0 +1,52 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { createExample } from '../test-utils';
+import { ConfirmProviderView as TestedComponent } from './ProviderAddPage';
+
+export default {
+  title: 'wallet/backup/confirm',
+  component: TestedComponent,
+  argTypes: {
+    onRetry: { action: 'onRetry' },
+    onDelete: { action: 'onDelete' },
+    onBack: { action: 'onBack' },
+  }
+};
+
+
+export const DemoService = createExample(TestedComponent, {
+  url: 'https://sync.demo.taler.net/',
+  provider: {
+    annual_fee: 'KUDOS:0.1',
+    storage_limit_in_megabytes: 20, 
+    supported_protocol_version: '1'
+  }
+});
+
+export const FreeService = createExample(TestedComponent, {
+  url: 'https://sync.taler:9667/',
+  provider: {
+    annual_fee: 'ARS:0',
+    storage_limit_in_megabytes: 20, 
+    supported_protocol_version: '1'
+  }
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
new file mode 100644
index 00000000..2b205ebe
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
@@ -0,0 +1,150 @@
+import { Amounts, BackupBackupProviderTerms, canonicalizeBaseUrl, i18n } from 
"@gnu-taler/taler-util";
+import { verify } from 
"@gnu-taler/taler-wallet-core/src/crypto/primitives/nacl-fast";
+import { VNode, h } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { Checkbox } from "../components/Checkbox";
+import { ErrorMessage } from "../components/ErrorMessage";
+import { Button, ButtonPrimary, Input, LightText, WalletBox, SmallTextLight } 
from "../components/styled/index";
+import * as wxApi from "../wxApi";
+
+interface Props {
+  currency: string;
+  onBack: () => void;
+}
+
+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({ onBack }: Props): VNode {
+  const [verifying, setVerifying] = useState<{ url: string, name: string, 
provider: BackupBackupProviderTerms } | undefined>(undefined)
+
+  async function getProviderInfo(url: string): 
Promise<BackupBackupProviderTerms> {
+    return fetch(`${url}config`)
+      .catch(e => { throw new Error(`Network error`) })
+      .then(getJsonIfOk)
+  }
+
+  if (!verifying) {
+    return <SetUrlView
+      onCancel={onBack}
+      onVerify={(url) => getProviderInfo(url)}
+      onConfirm={(url, name) => getProviderInfo(url)
+        .then((provider) => {
+          setVerifying({ url, name, provider });
+        })
+        .catch(e => e.message)
+      }
+    />
+  }
+  return <ConfirmProviderView
+    provider={verifying.provider}
+    url={verifying.url}
+    onCancel={() => {
+      setVerifying(undefined);
+    }}
+    onConfirm={() => {
+      wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack)
+    }}
+
+  />
+}
+
+
+export interface SetUrlViewProps {
+  initialValue?: string;
+  onCancel: () => void;
+  onVerify: (s: string) => Promise<BackupBackupProviderTerms | undefined>;
+  onConfirm: (url: string, name: string) => Promise<string | undefined>;
+  withError?: string;
+}
+
+export function SetUrlView({ initialValue, onCancel, onVerify, onConfirm, 
withError }: SetUrlViewProps) {
+  const [value, setValue] = useState<string>(initialValue || "")
+  const [urlError, setUrlError] = useState(false)
+  const [name, setName] = useState<string|undefined>(undefined)
+  const [error, setError] = useState<string | undefined>(withError)
+  useEffect(() => {
+    try {
+      const url = canonicalizeBaseUrl(value)
+      onVerify(url).then(r => {
+        setUrlError(false)
+        setName(new URL(url).hostname)
+      }).catch(() => {
+        setUrlError(true)
+        setName(undefined)
+      })
+    } catch {
+      setUrlError(true)
+      setName(undefined)
+    }
+  }, [value])
+  return <WalletBox>
+    <section>
+      <h1> Add backup provider</h1>
+      <ErrorMessage title={error && "Could not get provider information"} 
description={error} />
+      <LightText> Backup providers may charge for their service</LightText>
+      <p>
+        <Input invalid={urlError}>
+          <label>URL</label>
+          <input type="text" placeholder="https://"; value={value} 
onChange={(e) => setValue(e.currentTarget.value)} />
+        </Input>
+        <Input>
+          <label>Name</label>
+          <input type="text" disabled={name === undefined} value={name} 
onChange={e => setName(e.currentTarget.value)}/>
+        </Input>
+      </p>
+    </section>
+    <footer>
+      <Button onClick={onCancel}><i18n.Translate> &lt; 
Back</i18n.Translate></Button>
+      <ButtonPrimary
+        disabled={!value && !urlError}
+        onClick={() => {
+          const url = canonicalizeBaseUrl(value)
+          return onConfirm(url, name!).then(r => r ? setError(r) : undefined)
+        }}><i18n.Translate>Next</i18n.Translate></ButtonPrimary>
+    </footer>
+  </WalletBox>
+}
+
+export interface ConfirmProviderViewProps {
+  provider: BackupBackupProviderTerms,
+  url: string,
+  onCancel: () => void;
+  onConfirm: () => void;
+}
+export function ConfirmProviderView({ url, provider, onCancel, onConfirm }: 
ConfirmProviderViewProps) {
+  const [accepted, setAccepted] = useState(false);
+
+  return <WalletBox>
+    <section>
+      <h1>Review terms of service</h1>
+      <div>Provider URL: <a href={url} target="_blank">{url}</a></div>
+      <SmallTextLight>Please review and accept this provider's terms of 
service</SmallTextLight>
+      <h2>1. Pricing</h2>
+      <p>
+        {Amounts.isZero(provider.annual_fee) ? 'free of charge' : 
`${provider.annual_fee} per year of service`}
+      </p>
+      <h2>2. Storage</h2>
+      <p>
+        {provider.storage_limit_in_megabytes} megabytes of storage per year of 
service
+      </p>
+      <Checkbox label="Accept terms of service" name="terms" onToggle={() => 
setAccepted(old => !old)} enabled={accepted} />
+    </section>
+    <footer>
+      <Button onClick={onCancel}><i18n.Translate> &lt; 
Back</i18n.Translate></Button>
+      <ButtonPrimary
+        disabled={!accepted}
+        onClick={onConfirm}><i18n.Translate>Add 
provider</i18n.Translate></ButtonPrimary>
+    </footer>
+  </WalletBox>
+}
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx
new file mode 100644
index 00000000..4890e5e9
--- /dev/null
+++ 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx
@@ -0,0 +1,53 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { createExample } from '../test-utils';
+import { SetUrlView as TestedComponent } from './ProviderAddPage';
+
+export default {
+  title: 'wallet/backup/add',
+  component: TestedComponent,
+  argTypes: {
+    onRetry: { action: 'onRetry' },
+    onDelete: { action: 'onDelete' },
+    onBack: { action: 'onBack' },
+  }
+};
+
+
+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/wallet/ProviderDetail.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx
new file mode 100644
index 00000000..67ff8344
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx
@@ -0,0 +1,238 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { ProviderPaymentType } from '@gnu-taler/taler-wallet-core';
+import { createExample } from '../test-utils';
+import { ProviderView as TestedComponent } from './ProviderDetailPage';
+
+export default {
+  title: 'wallet/backup/details',
+  component: TestedComponent,
+  argTypes: {
+    onRetry: { action: 'onRetry' },
+    onDelete: { action: 'onDelete' },
+    onBack: { action: 'onBack' },
+  }
+};
+
+
+export const Active = createExample(TestedComponent, {
+  info: {
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": 1656599921000
+      }
+    },
+    "terms": {
+      "annualFee": "EUR:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+export const ActiveErrorSync = createExample(TestedComponent, {
+  info: {
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    lastAttemptedBackupTimestamp: {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": 1656599921000
+      }
+    },
+    lastError: {
+      code: 2002,
+      details: 'details',
+      hint: 'error hint from the server',
+      message: 'message'
+    },
+    "terms": {
+      "annualFee": "EUR:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
+  info: {
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": 1656599921000
+      }
+    },
+    backupProblem: {
+      type: 'backup-unreadable'
+    },
+    "terms": {
+      "annualFee": "EUR:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+export const ActiveBackupProblemDevice = createExample(TestedComponent, {
+  info: {
+    "active": true,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.taler:9967/";,
+    "lastSuccessfulBackupTimestamp": {
+      "t_ms": 1625063925078
+    },
+    "paymentProposalIds": [
+      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG"
+    ],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Paid,
+      "paidUntil": {
+        "t_ms": 1656599921000
+      }
+    },
+    backupProblem: {
+      type: 'backup-conflicting-device',
+      myDeviceId: 'my-device-id',
+      otherDeviceId: 'other-device-id',
+      backupTimestamp: {
+        "t_ms": 1656599921000
+      }
+    },
+    "terms": {
+      "annualFee": "EUR:1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+export const InactiveUnpaid = createExample(TestedComponent, {
+  info: {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Unpaid,
+    },
+    "terms": {
+      "annualFee": "EUR:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+export const InactiveInsufficientBalance = createExample(TestedComponent, {
+  info: {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.InsufficientBalance,
+    },
+    "terms": {
+      "annualFee": "EUR:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+export const InactivePending = createExample(TestedComponent, {
+  info: {
+    "active": false,
+    name:'sync.demo',
+    "syncProviderBaseUrl": "http://sync.demo.taler.net/";,
+    "paymentProposalIds": [],
+    "paymentStatus": {
+      "type": ProviderPaymentType.Pending,
+    },
+    "terms": {
+      "annualFee": "EUR:0.1",
+      "storageLimitInMegabytes": 16,
+      "supportedProtocolVersion": "0.0"
+    }
+  }
+});
+
+
+export const ActiveTermsChanged = createExample(TestedComponent, {
+  info: {
+    "active": true,
+    name:'sync.demo',
+    "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/wallet/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
new file mode 100644
index 00000000..fc361f62
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -0,0 +1,197 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+
+import { i18n, Timestamp } from "@gnu-taler/taler-util";
+import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType } from 
"@gnu-taler/taler-wallet-core";
+import { format, formatDuration, intervalToDuration } from "date-fns";
+import { Fragment, VNode, h } from "preact";
+import { ErrorMessage } from "../components/ErrorMessage";
+import { Button, ButtonDestructive, ButtonPrimary, PaymentStatus, WalletBox, 
SmallTextLight } from "../components/styled";
+import { useProviderStatus } from "../hooks/useProviderStatus";
+
+interface Props {
+  pid: string;
+  onBack: () => void;
+}
+
+export function ProviderDetailPage({ pid, onBack }: Props): VNode {
+  const status = useProviderStatus(pid)
+  if (!status) {
+    return <div><i18n.Translate>Loading...</i18n.Translate></div>
+  }
+  if (!status.info) {
+    onBack()
+    return <div />
+  }
+  return <ProviderView info={status.info}
+    onSync={status.sync}
+    onDelete={() => status.remove().then(onBack)}
+    onBack={onBack}
+    onExtend={() => { null }}
+  />;
+}
+
+export interface ViewProps {
+  info: ProviderInfo;
+  onDelete: () => void;
+  onSync: () => void;
+  onBack: () => void;
+  onExtend: () => void;
+}
+
+export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: 
ViewProps): VNode {
+  const lb = info?.lastSuccessfulBackupTimestamp
+  const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || 
info.paymentStatus.type === ProviderPaymentType.TermsChanged
+  return (
+    <WalletBox>
+      {info.backupProblem || info.lastError ? <header>
+        <Error info={info} />
+      </header> : undefined }
+      <header>
+        <h3>{info.name} 
<SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3>
+        <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 
60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus>
+      </header>
+      <section>
+        <p><b>Last backup:</b> {lb == null || lb.t_ms == "never" ? "never" : 
format(lb.t_ms, 'dd MMM yyyy')} </p>
+        <ButtonPrimary onClick={onSync}><i18n.Translate>Back 
up</i18n.Translate></ButtonPrimary>
+        {info.terms && <Fragment>
+          <p><b>Provider fee:</b> {info.terms && info.terms.annualFee} per 
year</p>
+        </Fragment>
+        }
+        <p>{descriptionByStatus(info.paymentStatus)}</p>
+        <ButtonPrimary disabled 
onClick={onExtend}><i18n.Translate>Extend</i18n.Translate></ButtonPrimary>
+
+        {info.paymentStatus.type === ProviderPaymentType.TermsChanged && <div>
+          <p><i18n.Translate>terms has changed, extending the service will 
imply accepting the new terms of service</i18n.Translate></p>
+          <table>
+            <thead>
+              <tr>
+                <td></td>
+                <td><i18n.Translate>old</i18n.Translate></td>
+                <td> -&gt;</td>
+                <td><i18n.Translate>new</i18n.Translate></td>
+              </tr>
+            </thead>
+            <tbody>
+
+              <tr>
+                <td><i18n.Translate>fee</i18n.Translate></td>
+                <td>{info.paymentStatus.oldTerms.annualFee}</td>
+                <td>-&gt;</td>
+                <td>{info.paymentStatus.newTerms.annualFee}</td>
+              </tr>
+              <tr>
+                <td><i18n.Translate>storage</i18n.Translate></td>
+                <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
+                <td>-&gt;</td>
+                <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>}
+
+      </section>
+      <footer>
+        <Button onClick={onBack}><i18n.Translate> &lt; 
back</i18n.Translate></Button>
+        <div>
+          <ButtonDestructive onClick={onDelete}><i18n.Translate>remove 
provider</i18n.Translate></ButtonDestructive>
+        </div>
+      </footer>
+    </WalletBox>
+  )
+}
+
+function daysSince(d?: Timestamp) {
+  if (!d || d.t_ms === 'never') return 'never synced'
+  const duration = intervalToDuration({
+    start: d.t_ms,
+    end: new Date(),
+  })
+  const str = formatDuration(duration, {
+    delimiter: ', ',
+    format: [
+      duration?.years ? i18n.str`years` : (
+        duration?.months ? i18n.str`months` : (
+          duration?.days ? i18n.str`days` : (
+            duration?.hours ? i18n.str`hours` : (
+              duration?.minutes ? i18n.str`minutes` : i18n.str`seconds`
+            )
+          )
+        )
+      )
+    ]
+  })
+  return `synced ${str} ago`
+}
+
+function Error({ info }: { info: ProviderInfo }) {
+  if (info.lastError) {
+    return <ErrorMessage title={info.lastError.hint} />
+  }
+  if (info.backupProblem) {
+    switch (info.backupProblem.type) {
+      case "backup-conflicting-device":
+        return <ErrorMessage title={<Fragment>
+          <i18n.Translate>There is conflict with another backup from 
<b>{info.backupProblem.otherDeviceId}</b></i18n.Translate>
+        </Fragment>} />
+      case "backup-unreadable":
+        return <ErrorMessage title="Backup is not readable" />
+      default:
+        return <ErrorMessage title={<Fragment>
+          <i18n.Translate>Unknown backup problem: 
{JSON.stringify(info.backupProblem)}</i18n.Translate>
+        </Fragment>} />
+    }
+  }
+  return null
+}
+
+function colorByStatus(status: ProviderPaymentType) {
+  switch (status) {
+    case ProviderPaymentType.InsufficientBalance:
+      return 'rgb(223, 117, 20)'
+    case ProviderPaymentType.Unpaid:
+      return 'rgb(202, 60, 60)'
+    case ProviderPaymentType.Paid:
+      return 'rgb(28, 184, 65)'
+    case ProviderPaymentType.Pending:
+      return 'gray'
+    case ProviderPaymentType.InsufficientBalance:
+      return 'rgb(202, 60, 60)'
+    case ProviderPaymentType.TermsChanged:
+      return 'rgb(202, 60, 60)'
+  }
+}
+
+function descriptionByStatus(status: ProviderPaymentStatus) {
+  switch (status.type) {
+    // return i18n.str`no enough balance to make the payment`
+    // return i18n.str`not paid yet`
+    case ProviderPaymentType.Paid:
+    case ProviderPaymentType.TermsChanged:
+      if (status.paidUntil.t_ms === 'never') {
+        return i18n.str`service paid`
+      } else {
+        return <Fragment>
+          <b>Backup valid until:</b> {format(status.paidUntil.t_ms, 'dd MMM 
yyyy')}
+        </Fragment>
+      }
+    case ProviderPaymentType.Unpaid:
+    case ProviderPaymentType.InsufficientBalance:
+    case ProviderPaymentType.Pending:
+      return ''
+  }
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
new file mode 100644
index 00000000..deb30e55
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
@@ -0,0 +1,43 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { createExample } from '../test-utils';
+import { SettingsView as TestedComponent } from './Settings';
+
+export default {
+  title: 'wallet/settings',
+  component: TestedComponent,
+  argTypes: {
+    setDeviceName: () => Promise.resolve(),
+  }
+};
+
+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/wallet/Settings.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
new file mode 100644
index 00000000..52e72ee2
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -0,0 +1,103 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+
+import { i18n } from "@gnu-taler/taler-util";
+import { VNode, h } from "preact";
+import { Checkbox } from "../components/Checkbox";
+import { EditableText } from "../components/EditableText";
+import { SelectList } from "../components/SelectList";
+import { useDevContext } from "../context/devContext";
+import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
+import { useLang } from "../hooks/useLang";
+
+export function SettingsPage(): VNode {
+  const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
+  const { devMode, toggleDevMode } = useDevContext()
+  const { name, update } = useBackupDeviceName()
+  const [lang, changeLang] = useLang()
+  return <SettingsView
+    lang={lang} changeLang={changeLang}
+    deviceName={name} setDeviceName={update}
+    permissionsEnabled={permissionsEnabled} 
togglePermissions={togglePermissions}
+    developerMode={devMode} toggleDeveloperMode={toggleDevMode}
+  />;
+}
+
+export interface ViewProps {
+  lang: string;
+  changeLang: (s: string) => void;
+  deviceName: string;
+  setDeviceName: (s: string) => Promise<void>;
+  permissionsEnabled: boolean;
+  togglePermissions: () => void;
+  developerMode: boolean;
+  toggleDeveloperMode: () => void;
+}
+
+import { strings as messages } from '../i18n/strings'
+
+type LangsNames = {
+  [P in keyof typeof messages]: string
+}
+
+const names: LangsNames = {
+  es: 'Español [es]',
+  en: 'English [en]',
+  fr: 'Français [fr]',
+  de: 'Deutsch [de]',
+  sv: 'Svenska [sv]',
+  it: 'Italiano [it]',
+}
+
+
+export function SettingsView({ lang, changeLang, deviceName, setDeviceName, 
permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: 
ViewProps): VNode {
+  return (
+    <div>
+      <section style={{ height: 300, overflow: 'auto' }}>
+        <h2><i18n.Translate>Wallet</i18n.Translate></h2>
+        <SelectList
+          value={lang}
+          onChange={changeLang}
+          name="lang"
+          list={names}
+          label={i18n.str`Language`}
+          description="(Choose your preferred lang)"
+        />
+        <EditableText
+          value={deviceName}
+          onChange={setDeviceName}
+          name="device-id"
+          label={i18n.str`Device name`}
+          description="(This is how you will recognize the wallet in the 
backup provider)"
+        />
+        <h2><i18n.Translate>Permissions</i18n.Translate></h2>
+        <Checkbox label="Automatically open wallet based on page content"
+          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/Transaction.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
similarity index 99%
rename from packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
rename to packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index 65302bab..0f7ea457 100644
--- a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -30,7 +30,7 @@ import { createExample } from '../test-utils';
 import { TransactionView as TestedComponent } from './Transaction';
 
 export default {
-  title: 'popup/history/details',
+  title: 'wallet/history/details',
   component: TestedComponent,
   argTypes: {
     onRetry: { action: 'onRetry' },
diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
similarity index 98%
rename from packages/taler-wallet-webextension/src/popup/Transaction.tsx
rename to packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 117a098b..d00abc16 100644
--- a/packages/taler-wallet-webextension/src/popup/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -22,7 +22,7 @@ import { useEffect, useState } from "preact/hooks";
 import * as wxApi from "../wxApi";
 import { Pages } from "../NavigationBar";
 import emptyImg from "../../static/img/empty.png"
-import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, 
Row, RowBorderGray, SmallTextLight } from "../components/styled";
+import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, 
Row, RowBorderGray, SmallTextLight, WalletBox } from "../components/styled";
 import { ErrorMessage } from "../components/ErrorMessage";
 
 export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
@@ -79,7 +79,7 @@ export function TransactionView({ transaction, onDelete, 
onRetry, onBack }: Wall
   }
 
   function TransactionTemplate({ upperRight, children }: { upperRight: VNode, 
children: VNode[] }) {
-    return <PopupBox>
+    return <WalletBox>
       <header>
         <SmallTextLight>
           {transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}
@@ -99,7 +99,7 @@ export function TransactionView({ transaction, onDelete, 
onRetry, onBack }: Wall
           <ButtonDestructive 
onClick={onDelete}><i18n.Translate>delete</i18n.Translate></ButtonDestructive>
         </div>
       </footer>
-    </PopupBox>
+    </WalletBox>
   }
 
   if (transaction.type === TransactionType.Withdrawal) {
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index 61df45e7..aa007786 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -20,24 +20,28 @@
  * @author Florian Dold <dold@taler.net>
  */
 
-import { Fragment, render, h } from "preact";
 import { setupI18n } from "@gnu-taler/taler-util";
-import { strings } from "./i18n/strings";
 import { createHashHistory } from 'history';
-
-import { WelcomePage } from "./wallet/Welcome";
-import { HistoryPage } from "./wallet/History";
-import { WithdrawPage } from "./cta/Withdraw";
+import { Fragment, h, render } from "preact";
+import Router, { route, Route } from "preact-router";
+import { useEffect } from "preact/hooks";
+import { LogoHeader } from "./components/LogoHeader";
+import { DevContextProvider } from "./context/devContext";
 import { PayPage } from "./cta/Pay";
 import { RefundPage } from "./cta/Refund";
 import { TipPage } from './cta/Tip';
-import Router, { route, Route } from "preact-router";
-import { DevContextProvider } from "./context/devContext";
-import { LogoHeader } from "./components/LogoHeader";
-import { useEffect } from "preact/hooks";
+import { WithdrawPage } from "./cta/Withdraw";
+import { strings } from "./i18n/strings";
 import {
   Pages, WalletNavBar
 } from "./NavigationBar";
+import { BalancePage } from "./wallet/BalancePage";
+import { HistoryPage } from "./wallet/History";
+import { SettingsPage } from "./wallet/Settings";
+import { TransactionPage } from './wallet/Transaction';
+import { WelcomePage } from "./wallet/Welcome";
+import { BackupPage } from './wallet/BackupPage';
+
 
 function main(): void {
   try {
@@ -76,7 +80,10 @@ function Application() {
         <Route path={Pages.welcome} component={withLogoAndNavBar(WelcomePage)} 
/>
 
         <Route path={Pages.history} component={withLogoAndNavBar(HistoryPage)} 
/>
-        <Route path={Pages.transaction} 
component={withLogoAndNavBar(HistoryPage)} />
+        <Route path={Pages.transaction} 
component={withLogoAndNavBar(TransactionPage)} />
+        <Route path={Pages.balance} component={withLogoAndNavBar(BalancePage)} 
/>
+        <Route path={Pages.settings} 
component={withLogoAndNavBar(SettingsPage)} />
+        <Route path={Pages.backup} component={withLogoAndNavBar(BackupPage)} />
 
         <Route path={Pages.reset_required} component={() => <div>no yet 
implemented</div>} />
         <Route path={Pages.payback} component={() => <div>no yet 
implemented</div>} />

-- 
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]