gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: added example of withdrawal u


From: gnunet
Subject: [taler-wallet-core] branch master updated: added example of withdrawal use cases
Date: Tue, 12 Oct 2021 21:32:31 +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 b8d03b6b added example of withdrawal use cases
b8d03b6b is described below

commit b8d03b6b2aef630c0fafd7f6ab0fe317abfe1d93
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Oct 12 15:18:29 2021 -0300

    added example of withdrawal use cases
---
 build-system/Makefile                              |   2 +-
 .../src/cta/Withdraw.stories.tsx                   | 544 ++++++++++++++++++++-
 .../taler-wallet-webextension/src/cta/Withdraw.tsx | 127 +++--
 .../src/hooks/useTalerActionURL.ts                 |  48 +-
 .../TalerActionFound.stories.tsx}                  |  33 +-
 .../src/popup/TalerActionFound.tsx                 |  98 ++++
 .../src/popupEntryPoint.tsx                        |  29 +-
 .../src/wallet/ManualWithdrawPage.tsx              |   6 +-
 .../src/wallet/Settings.stories.tsx                |  10 +
 .../src/wallet/Settings.tsx                        |  17 +-
 .../taler-wallet-webextension/static/popup.html    |  28 +-
 11 files changed, 805 insertions(+), 137 deletions(-)

diff --git a/build-system/Makefile b/build-system/Makefile
index 24b86c15..bbe4bc03 100644
--- a/build-system/Makefile
+++ b/build-system/Makefile
@@ -51,7 +51,7 @@ check: compile
 webextension: compile
        cd ./packages/taler-wallet-webextension/ && ./pack.sh
 
-.PHONY: dev-view
+.PHONY: webextension-dev-view
 webextension-dev-view: compile
        pnpm run --filter @gnu-taler/taler-wallet-webextension storybook
 
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx 
b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx
index 69073f50..a3c46c78 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx
@@ -35,6 +35,405 @@ export default {
   },
 };
 
+const termsHtml = `<html xmlns="http://www.w3.org/1999/xhtml"; lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <title>Terms Of Service &#8212; Taler Terms of Service</title>
+</head><body>
+  <div>
+    Terms of service
+  </div>
+  <div>
+    A complete separated html with it's own design
+  </div>
+</body>
+</html>
+`
+const termsPlain = `
+Terms Of Service
+****************
+
+Last Updated: 12.4.2019
+
+Welcome! Taler Systems SA (“we,” “our,” or “us”) provides a payment
+service through our Internet presence (collectively the “Services”).
+Before using our Services, please read the Terms of Service (the
+“Terms” or the “Agreement”) carefully.
+
+
+Overview
+========
+
+This section provides a brief summary of the highlights of this
+Agreement. Please note that when you accept this Agreement, you are
+accepting all of the terms and conditions and not just this section.
+We and possibly other third parties provide Internet services which
+interact with the Taler Wallet’s self-hosted personal payment
+application. When using the Taler Wallet to interact with our
+Services, you are agreeing to our Terms, so please read carefully.
+
+
+Highlights:
+-----------
+
+   * You are responsible for keeping the data in your Taler Wallet at
+     all times under your control. Any losses arising from you not
+     being in control of your private information are your problem.
+
+   * We will try to transfer funds we hold in escrow for our users to
+     any legal recipient to the best of our ability within the
+     limitations of the law and our implementation. However, the
+     Services offered today are highly experimental and the set of
+     recipients of funds is severely restricted.
+
+   * For our Services, we may charge transaction fees. The specific
+     fee structure is provided based on the Taler protocol and should
+     be shown to you when you withdraw electronic coins using a Taler
+     Wallet. You agree and understand that the Taler protocol allows
+     for the fee structure to change.
+
+   * You agree to not intentionally overwhelm our systems with
+     requests and follow responsible disclosure if you find security
+     issues in our services.
+
+   * We cannot be held accountable for our Services not being
+     available due to circumstances beyond our control. If we modify
+     or terminate our services, we will try to give you the
+     opportunity to recover your funds. However, given the
+     experimental state of the Services today, this may not be
+     possible. You are strongly advised to limit your use of the
+     Service to small-scale experiments expecting total loss of all
+     funds.
+
+These terms outline approved uses of our Services. The Services and
+these Terms are still at an experimental stage. If you have any
+questions or comments related to this Agreement, please send us a
+message to legal@taler-systems.com. If you do not agree to this
+Agreement, you must not use our Services.
+
+
+How you accept this policy
+==========================
+
+By sending funds to us (to top-up your Taler Wallet), you acknowledge
+that you have read, understood, and agreed to these Terms. We reserve
+the right to change these Terms at any time. If you disagree with the
+change, we may in the future offer you with an easy option to recover
+your unspent funds. However, in the current experimental period you
+acknowledge that this feature is not yet available, resulting in your
+funds being lost unless you accept the new Terms. If you continue to
+use our Services other than to recover your unspent funds, your
+continued use of our Services following any such change will signify
+your acceptance to be bound by the then current Terms. Please check
+the effective date above to determine if there have been any changes
+since you have last reviewed these Terms.
+
+
+Services
+========
+
+We will try to transfer funds that we hold in escrow for our users to
+any legal recipient to the best of our ability and within the
+limitations of the law and our implementation. However, the Services
+offered today are highly experimental and the set of recipients of
+funds is severely restricted.  The Taler Wallet can be loaded by
+exchanging fiat currencies against electronic coins. We are providing
+this exchange service. Once your Taler Wallet is loaded with
+electronic coins they can be spent for purchases if the seller is
+accepting Taler as a means of payment. We are not guaranteeing that
+any seller is accepting Taler at all or a particular seller.  The
+seller or recipient of deposits of electronic coins must specify the
+target account, as per the design of the Taler protocol. They are
+responsible for following the protocol and specifying the correct bank
+account, and are solely liable for any losses that may arise from
+specifying the wrong account. We will allow the government to link
+wire transfers to the underlying contract hash. It is the
+responsibility of recipients to preserve the full contracts and to pay
+whatever taxes and charges may be applicable. Technical issues may
+lead to situations where we are unable to make transfers at all or
+lead to incorrect transfers that cannot be reversed. We will only
+refuse to execute transfers if the transfers are prohibited by a
+competent legal authority and we are ordered to do so.
+
+
+Fees
+====
+
+You agree to pay the fees for exchanges and withdrawals completed via
+the Taler Wallet ("Fees") as defined by us, which we may change from
+time to time. With the exception of wire transfer fees, Taler
+transaction fees are set for any electronic coin at the time of
+withdrawal and fixed throughout the validity period of the respective
+electronic coin. Your wallet should obtain and display applicable fees
+when withdrawing funds. Fees for coins obtained as change may differ
+from the fees applicable to the original coin. Wire transfer fees that
+are independent from electronic coins may change annually.  You
+authorize us to charge or deduct applicable fees owed in connection
+with deposits, exchanges and withdrawals following the rules of the
+Taler protocol. We reserve the right to provide different types of
+rewards to users either in the form of discount for our Services or in
+any other form at our discretion and without prior notice to you.
+
+
+Eligibility
+===========
+
+To be eligible to use our Services, you must be able to form legally
+binding contracts or have the permission of your legal guardian. By
+using our Services, you represent and warrant that you meet all
+eligibility requirements that we outline in these Terms.
+
+
+Financial self-responsibility
+=============================
+
+You will be responsible for maintaining the availability, integrity
+and confidentiality of the data stored in your wallet. When you setup
+a Taler Wallet, you are strongly advised to follow the precautionary
+measures offered by the software to minimize the chances to losse
+access to or control over your Wallet data. We will not be liable for
+any loss or damage arising from your failure to comply with this
+paragraph.
+
+
+Copyrights and trademarks
+=========================
+
+The Taler Wallet is released under the terms of the GNU General Public
+License (GNU GPL). You have the right to access, use, and share the
+Taler Wallet, in modified or unmodified form. However, the GPL is a
+strong copyleft license, which means that any derivative works must be
+distributed under the same license terms as the original software. If
+you have any questions, you should review the GNU GPL’s full terms and
+conditions at https://www.gnu.org/licenses/gpl-3.0.en.html.  “Taler”
+itself is a trademark of Taler Systems SA. You are welcome to use the
+name in relation to processing payments using the Taler protocol,
+assuming your use is compatible with an official release from the GNU
+Project that is not older than two years.
+
+
+Your use of our services
+========================
+
+When using our Services, you agree to not take any action that
+intentionally imposes an unreasonable load on our infrastructure. If
+you find security problems in our Services, you agree to first report
+them to security@taler-systems.com and grant us the right to publish
+your report. We warrant that we will ourselves publicly disclose any
+issues reported within 3 months, and that we will not prosecute anyone
+reporting security issues if they did not exploit the issue beyond a
+proof-of-concept, and followed the above responsible disclosure
+practice.
+
+
+Limitation of liability & disclaimer of warranties
+==================================================
+
+You understand and agree that we have no control over, and no duty to
+take any action regarding: Failures, disruptions, errors, or delays in
+processing that you may experience while using our Services; The risk
+of failure of hardware, software, and Internet connections; The risk
+of malicious software being introduced or found in the software
+underlying the Taler Wallet; The risk that third parties may obtain
+unauthorized access to information stored within your Taler Wallet,
+including, but not limited to your Taler Wallet coins or backup
+encryption keys.  You release us from all liability related to any
+losses, damages, or claims arising from:
+
+1. user error such as forgotten passwords, incorrectly constructed
+   transactions;
+
+2. server failure or data loss;
+
+3. unauthorized access to the Taler Wallet application;
+
+4. bugs or other errors in the Taler Wallet software; and
+
+5. any unauthorized third party activities, including, but not limited
+   to, the use of viruses, phishing, brute forcing, or other means of
+   attack against the Taler Wallet. We make no representations
+   concerning any Third Party Content contained in or accessed through
+   our Services.
+
+Any other terms, conditions, warranties, or representations associated
+with such content, are solely between you and such organizations
+and/or individuals.
+
+
+Limitation of liability
+=======================
+
+To the fullest extent permitted by applicable law, in no event will we
+or any of our officers, directors, representatives, agents, servants,
+counsel, employees, consultants, lawyers, and other personnel
+authorized to act, acting, or purporting to act on our behalf
+(collectively the “Taler Parties”) be liable to you under contract,
+tort, strict liability, negligence, or any other legal or equitable
+theory, for:
+
+1. any lost profits, data loss, cost of procurement of substitute
+   goods or services, or direct, indirect, incidental, special,
+   punitive, compensatory, or consequential damages of any kind
+   whatsoever resulting from:
+
+   1. your use of, or conduct in connection with, our services;
+
+   2. any unauthorized use of your wallet and/or private key due to
+      your failure to maintain the confidentiality of your wallet;
+
+   3. any interruption or cessation of transmission to or from the
+      services; or
+
+   4. any bugs, viruses, trojan horses, or the like that are found in
+      the Taler Wallet software or that may be transmitted to or
+      through our services by any third party (regardless of the
+      source of origination), or
+
+2. any direct damages.
+
+These limitations apply regardless of legal theory, whether based on
+tort, strict liability, breach of contract, breach of warranty, or any
+other legal theory, and whether or not we were advised of the
+possibility of such damages. Some jurisdictions do not allow the
+exclusion or limitation of liability for consequential or incidental
+damages, so the above limitation may not apply to you.
+
+
+Warranty disclaimer
+===================
+
+Our services are provided "as is" and without warranty of any kind. To
+the maximum extent permitted by law, we disclaim all representations
+and warranties, express or implied, relating to the services and
+underlying software or any content on the services, whether provided
+or owned by us or by any third party, including without limitation,
+warranties of merchantability, fitness for a particular purpose,
+title, non-infringement, freedom from computer virus, and any implied
+warranties arising from course of dealing, course of performance, or
+usage in trade, all of which are expressly disclaimed. In addition, we
+do not represent or warrant that the content accessible via the
+services is accurate, complete, available, current, free of viruses or
+other harmful components, or that the results of using the services
+will meet your requirements. Some states do not allow the disclaimer
+of implied warranties, so the foregoing disclaimers may not apply to
+you. This paragraph gives you specific legal rights and you may also
+have other legal rights that vary from state to state.
+
+
+Indemnity
+=========
+
+To the extent permitted by applicable law, you agree to defend,
+indemnify, and hold harmless the Taler Parties from and against any
+and all claims, damages, obligations, losses, liabilities, costs or
+debt, and expenses (including, but not limited to, attorney’s fees)
+arising from: (a) your use of and access to the Services; (b) any
+feedback or submissions you provide to us concerning the Taler Wallet;
+(c) your violation of any term of this Agreement; or (d) your
+violation of any law, rule, or regulation, or the rights of any third
+party.
+
+
+Time limitation on claims
+=========================
+
+You agree that any claim you may have arising out of or related to
+your relationship with us must be filed within one year after such
+claim arises, otherwise, your claim in permanently barred.
+
+
+Governing law
+=============
+
+No matter where you’re located, the laws of Switzerland will govern
+these Terms. If any provisions of these Terms are inconsistent with
+any applicable law, those provisions will be superseded or modified
+only to the extent such provisions are inconsistent. The parties agree
+to submit to the ordinary courts in Zurich, Switzerland for exclusive
+jurisdiction of any dispute arising out of or related to your use of
+the Services or your breach of these Terms.
+
+
+Termination
+===========
+
+In the event of termination concerning your use of our Services, your
+obligations under this Agreement will still continue.
+
+
+Discontinuance of services
+==========================
+
+We may, in our sole discretion and without cost to you, with or
+without prior notice, and at any time, modify or discontinue,
+temporarily or permanently, any portion of our Services. We will use
+the Taler protocol’s provisions to notify Wallets if our Services are
+to be discontinued. It is your responsibility to ensure that the Taler
+Wallet is online at least once every three months to observe these
+notifications. We shall not be held responsible or liable for any loss
+of funds in the event that we discontinue or depreciate the Services
+and your Taler Wallet fails to transfer out the coins within a three
+months notification period.
+
+
+No waiver
+=========
+
+Our failure to exercise or delay in exercising any right, power, or
+privilege under this Agreement shall not operate as a waiver; nor
+shall any single or partial exercise of any right, power, or privilege
+preclude any other or further exercise thereof.
+
+
+Severability
+============
+
+If it turns out that any part of this Agreement is invalid, void, or
+for any reason unenforceable, that term will be deemed severable and
+limited or eliminated to the minimum extent necessary.
+
+
+Force majeure
+=============
+
+We shall not be held liable for any delays, failure in performance, or
+interruptions of service which result directly or indirectly from any
+cause or condition beyond our reasonable control, including but not
+limited to: any delay or failure due to any act of God, act of civil
+or military authorities, act of terrorism, civil disturbance, war,
+strike or other labor dispute, fire, interruption in
+telecommunications or Internet services or network provider services,
+failure of equipment and/or software, other catastrophe, or any other
+occurrence which is beyond our reasonable control and shall not affect
+the validity and enforceability of any remaining provisions.
+
+
+Assignment
+==========
+
+You agree that we may assign any of our rights and/or transfer, sub-
+contract, or delegate any of our obligations under these Terms.
+
+
+Entire agreement
+================
+
+This Agreement sets forth the entire understanding and agreement as to
+the subject matter hereof and supersedes any and all prior
+discussions, agreements, and understandings of any kind (including,
+without limitation, any prior versions of this Agreement) and every
+nature between us. Except as provided for above, any modification to
+this Agreement must be in writing and must be signed by both parties.
+
+
+Questions or comments
+=====================
+
+We welcome comments, questions, concerns, or suggestions. Please send
+us a message on our contact page at legal@taler-systems.com.
+
+`
+
 const termsXml = `<?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE document PUBLIC "+//IDN docutils.sourceforge.net//DTD Docutils 
Generic//EN//XML" "http://docutils.sourceforge.net/docs/ref/docutils.dtd";>
 <!-- Generated by Docutils 0.14 -->
@@ -381,7 +780,7 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?>
 </document>
 `;
 
-export const WithdrawNewTermsXML = createExample(TestedComponent, {
+export const NewTerms = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -417,7 +816,134 @@ export const WithdrawNewTermsXML = 
createExample(TestedComponent, {
   },
 })
 
-export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, {
+export const TermsReviewingPLAIN = createExample(TestedComponent, {
+  knownExchanges: [{
+    currency: 'USD',
+    exchangeBaseUrl: 'exchange.demo.taler.net',
+    paytoUris: ['asd'],
+  },{
+    currency: 'USD',
+    exchangeBaseUrl: 'exchange.test.taler.net',
+    paytoUris: ['asd'],
+  }],
+  details: {
+    exchangeInfo: {
+      baseUrl: 'exchange.demo.taler.net'
+    } as ExchangeRecord,
+    withdrawFee: {
+      currency: 'USD',
+      fraction: 0,
+      value: 0
+    },
+  } as ExchangeWithdrawDetails,
+  amount: {
+    currency: 'USD',
+    value: 2,
+    fraction: 10000000
+  },
+
+  onSwitchExchange: async () => { },
+  terms: {
+    value: {
+      type: 'plain',
+      content: termsPlain
+    },
+    status: 'new'
+  },
+  reviewing: true
+})
+
+export const TermsReviewingHTML = createExample(TestedComponent, {
+  knownExchanges: [{
+    currency: 'USD',
+    exchangeBaseUrl: 'exchange.demo.taler.net',
+    paytoUris: ['asd'],
+  },{
+    currency: 'USD',
+    exchangeBaseUrl: 'exchange.test.taler.net',
+    paytoUris: ['asd'],
+  }],
+  details: {
+    exchangeInfo: {
+      baseUrl: 'exchange.demo.taler.net'
+    } as ExchangeRecord,
+    withdrawFee: {
+      currency: 'USD',
+      fraction: 0,
+      value: 0
+    },
+  } as ExchangeWithdrawDetails,
+  amount: {
+    currency: 'USD',
+    value: 2,
+    fraction: 10000000
+  },
+
+  onSwitchExchange: async () => { },
+  terms: {
+    value: {
+      type: 'html',
+      href: new 
URL(`data:text/html;base64,${Buffer.from(termsHtml).toString('base64')}`),
+    },
+    status: 'new'
+  },
+  reviewing: true
+})
+
+const termsPdf = `
+%PDF-1.2 
+9 0 obj << >> 
+stream
+BT/ 9 Tf(This is the Exchange TERMS OF SERVICE)' ET
+endstream
+endobj
+4 0 obj << /Type /Page /Parent 5 0 R /Contents 9 0 R >> endobj 
+5 0 obj << /Kids [4 0 R ] /Count 1 /Type /Pages /MediaBox [ 0 0 180 20 ] >> 
endobj
+3 0 obj << /Pages 5 0 R /Type /Catalog >> endobj
+trailer
+<< /Root 3 0 R >>
+%%EOF
+`
+
+export const TermsReviewingPDF = createExample(TestedComponent, {
+  knownExchanges: [{
+    currency: 'USD',
+    exchangeBaseUrl: 'exchange.demo.taler.net',
+    paytoUris: ['asd'],
+  },{
+    currency: 'USD',
+    exchangeBaseUrl: 'exchange.test.taler.net',
+    paytoUris: ['asd'],
+  }],
+  details: {
+    exchangeInfo: {
+      baseUrl: 'exchange.demo.taler.net'
+    } as ExchangeRecord,
+    withdrawFee: {
+      currency: 'USD',
+      fraction: 0,
+      value: 0
+    },
+  } as ExchangeWithdrawDetails,
+  amount: {
+    currency: 'USD',
+    value: 2,
+    fraction: 10000000
+  },
+
+  onSwitchExchange: async () => { },
+  terms: {
+    value: {
+      type: 'pdf',
+      location: new 
URL(`data:text/html;base64,${Buffer.from(termsPdf).toString('base64')}`),
+    },
+    status: 'new'
+  },
+  reviewing: true
+})
+
+
+export const TermsReviewingXML = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -454,7 +980,7 @@ export const WithdrawNewTermsReviewingXML = 
createExample(TestedComponent, {
   reviewing: true
 })
 
-export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, {
+export const NewTermsAccepted = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -487,10 +1013,10 @@ export const WithdrawNewTermsAcceptedXML = 
createExample(TestedComponent, {
     },
     status: 'new'
   },
-  accepted: true
+  reviewed: true
 })
 
-export const WithdrawNewTermsShowAfterAcceptedXML = 
createExample(TestedComponent, {
+export const TermsShowAgainXML = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -528,7 +1054,7 @@ export const WithdrawNewTermsShowAfterAcceptedXML = 
createExample(TestedComponen
   reviewing: true,
 })
 
-export const WithdrawChangedTermsXML = createExample(TestedComponent, {
+export const TermsChanged = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -564,7 +1090,7 @@ export const WithdrawChangedTermsXML = 
createExample(TestedComponent, {
   },
 })
 
-export const WithdrawNotFoundTermsXML = createExample(TestedComponent, {
+export const TermsNotFound = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -596,7 +1122,7 @@ export const WithdrawNotFoundTermsXML = 
createExample(TestedComponent, {
   },
 })
 
-export const WithdrawAcceptedTermsXML = createExample(TestedComponent, {
+export const TermsAlreadyAccepted = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
@@ -629,7 +1155,7 @@ export const WithdrawAcceptedTermsXML = 
createExample(TestedComponent, {
 })
 
 
-export const WithdrawAcceptedTermsWithoutFee = createExample(TestedComponent, {
+export const WithoutFee = createExample(TestedComponent, {
   knownExchanges: [{
     currency: 'USD',
     exchangeBaseUrl: 'exchange.demo.taler.net',
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx 
b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
index 52295f1a..94f217fc 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
@@ -49,7 +49,7 @@ export interface ViewProps {
   onReview: (b: boolean) => void;
   onAccept: (b: boolean) => void;
   reviewing: boolean;
-  accepted: boolean;
+  reviewed: boolean;
   confirmed: boolean;
   terms: {
     value?: TermsDocument;
@@ -61,7 +61,7 @@ export interface ViewProps {
 
 type TermsStatus = 'new' | 'accepted' | 'changed' | 'notfound';
 
-type TermsDocument = TermsDocumentXml | TermsDocumentHtml;
+type TermsDocument = TermsDocumentXml | TermsDocumentHtml | TermsDocumentPlain 
| TermsDocumentJson | TermsDocumentPdf;
 
 interface TermsDocumentXml {
   type: 'xml',
@@ -70,7 +70,22 @@ interface TermsDocumentXml {
 
 interface TermsDocumentHtml {
   type: 'html',
-  href: string,
+  href: URL,
+}
+
+interface TermsDocumentPlain {
+  type: 'plain',
+  content: string,
+}
+
+interface TermsDocumentJson {
+  type: 'json',
+  data: any,
+}
+
+interface TermsDocumentPdf {
+  type: 'pdf',
+  location: URL,
 }
 
 function amountToString(text: AmountJson) {
@@ -79,7 +94,7 @@ function amountToString(text: AmountJson) {
   return `${amount} ${aj.currency}`
 }
 
-export function View({ details, knownExchanges, amount, onWithdraw, 
onSwitchExchange, terms, reviewing, onReview, onAccept, accepted, confirmed }: 
ViewProps) {
+export function View({ details, knownExchanges, amount, onWithdraw, 
onSwitchExchange, terms, reviewing, onReview, onAccept, reviewed, confirmed }: 
ViewProps) {
   const needsReview = terms.status === 'changed' || terms.status === 'new'
 
   const [switchingExchange, setSwitchingExchange] = useState<string | 
undefined>(undefined)
@@ -105,9 +120,6 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
             <div>
               <SelectList label="Known exchanges" list={exchanges} name="" 
onChange={onSwitchExchange} />
             </div>
-            <p>
-              This is the list of known exchanges
-            </p>
             <LinkSuccess upperCased onClick={() => 
onSwitchExchange(switchingExchange)}>
               {i18n.str`Confirm exchange selection`}
             </LinkSuccess>
@@ -118,7 +130,7 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
 
         </section>
       }
-      {!reviewing && accepted &&
+      {!reviewing && reviewed &&
         <section>
           <LinkSuccess
             upperCased
@@ -130,11 +142,24 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
       }
       {reviewing &&
         <section>
-          <TermsOfService>
-            {terms.status !== 'accepted' && terms.value && terms.value.type 
=== 'xml' && <ExchangeXmlTos doc={terms.value.document} />}
-          </TermsOfService>
+          {terms.status !== 'accepted' && terms.value && terms.value.type === 
'xml' &&
+            <TermsOfService>
+              <ExchangeXmlTos doc={terms.value.document} />
+            </TermsOfService>
+          }
+          {terms.status !== 'accepted' && terms.value && terms.value.type === 
'plain' &&
+            <div style={{ textAlign: 'left' }}>
+              <pre>{terms.value.content}</pre>
+            </div>
+          }
+          {terms.status !== 'accepted' && terms.value && terms.value.type === 
'html' &&
+            <iframe src={terms.value.href.toString()} />
+          }
+          {terms.status !== 'accepted' && terms.value && terms.value.type === 
'pdf' &&
+            <a href={terms.value.location.toString()} download="tos.pdf" 
>Download Terms of Service</a>
+          }
         </section>}
-      {reviewing && accepted &&
+      {reviewing && reviewed &&
         <section>
           <LinkSuccess
             upperCased
@@ -144,14 +169,14 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
           </LinkSuccess>
         </section>
       }
-      {(reviewing || accepted) &&
+      {(reviewing || reviewed) &&
         <section>
           <CheckboxOutlined
             name="terms"
-            enabled={accepted}
+            enabled={reviewed}
             label={i18n.str`I accept the exchange terms of service`}
             onToggle={() => {
-              onAccept(!accepted)
+              onAccept(!reviewed)
               onReview(false)
             }}
           />
@@ -162,7 +187,7 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
        * Main action section
        */}
       <section>
-        {terms.status === 'new' && !accepted && !reviewing &&
+        {terms.status === 'new' && !reviewed && !reviewing &&
           <ButtonSuccess
             upperCased
             disabled={!details.exchangeInfo.baseUrl}
@@ -171,7 +196,7 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
             {i18n.str`Review exchange terms of service`}
           </ButtonSuccess>
         }
-        {terms.status === 'changed' && !accepted &&
+        {terms.status === 'changed' && !reviewed && !reviewing &&
           <ButtonWarning
             upperCased
             disabled={!details.exchangeInfo.baseUrl}
@@ -180,7 +205,7 @@ export function View({ details, knownExchanges, amount, 
onWithdraw, onSwitchExch
             {i18n.str`Review new version of terms of service`}
           </ButtonWarning>
         }
-        {(terms.status === 'accepted' || (needsReview && accepted)) &&
+        {(terms.status === 'accepted' || (needsReview && reviewed)) &&
           <ButtonSuccess
             upperCased
             disabled={!details.exchangeInfo.baseUrl || confirmed}
@@ -204,7 +229,7 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: 
{ uri: string, uriIn
   const [errorAccepting, setErrorAccepting] = useState<string | 
undefined>(undefined)
 
   const [reviewing, setReviewing] = useState<boolean>(false)
-  const [accepted, setAccepted] = useState<boolean>(false)
+  const [reviewed, setReviewed] = useState<boolean>(false)
   const [confirmed, setConfirmed] = useState<boolean>(false)
 
   const knownExchangesHook = useAsyncAsHook(() => listExchanges())
@@ -219,7 +244,7 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: 
{ uri: string, uriIn
     return getExchangeWithdrawalInfo({
       exchangeBaseUrl: exchange,
       amount: withdrawAmount,
-      tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
+      tosAcceptedFormat: ['text/xml']
     })
   })
 
@@ -235,7 +260,7 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: 
{ uri: string, uriIn
   const onAccept = async (): Promise<void> => {
     try {
       await setExchangeTosAccepted(details.exchangeInfo.baseUrl, 
details.tosRequested?.tosEtag)
-      setAccepted(true)
+      setReviewed(true)
     } catch (e) {
       if (e instanceof Error) {
         setErrorAccepting(e.message)
@@ -257,18 +282,7 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo 
}: { uri: string, uriIn
     }
   };
 
-  let termsContent: TermsDocument | undefined = undefined;
-  if (details.tosRequested) {
-    if (details.tosRequested.tosContentType === 'text/xml') {
-      try {
-        const document = new 
DOMParser().parseFromString(details.tosRequested.tosText, "text/xml")
-        termsContent = { type: 'xml', document }
-      } catch (e) {
-        console.log(e)
-        debugger;
-      }
-    }
-  }
+  const termsContent: TermsDocument | undefined = !details.tosRequested ? 
undefined : parseTermsOfServiceContent(details.tosRequested.tosContentType, 
details.tosRequested.tosText);
 
   const status: TermsStatus = !termsContent ? 'notfound' : (
     !details.exchangeDetails.termsOfServiceAcceptedEtag ? 'new' : (
@@ -285,7 +299,7 @@ export function WithdrawPageWithParsedURI({ uri, uriInfo }: 
{ uri: string, uriIn
     onSwitchExchange={setCustomExchange}
     knownExchanges={knownExchanges}
     confirmed={confirmed}
-    accepted={accepted} onAccept={onAccept}
+    reviewed={reviewed} onAccept={onAccept}
     reviewing={reviewing} onReview={setReviewing}
   // terms={[]}
   />
@@ -307,3 +321,48 @@ export function WithdrawPage({ talerWithdrawUri }: Props): 
JSX.Element {
   return <WithdrawPageWithParsedURI uri={talerWithdrawUri} 
uriInfo={uriInfoHook.response} />
 }
 
+function parseTermsOfServiceContent(type: string, text: string): TermsDocument 
| undefined {
+  if (type === 'text/xml') {
+    try {
+      const document = new DOMParser().parseFromString(text, "text/xml")
+      return { type: 'xml', document }
+    } catch (e) {
+      console.log(e)
+      debugger;
+    }
+  } else if (type === 'text/html') {
+    try {
+      const href = new URL(text)
+      return { type: 'html', href }
+    } catch (e) {
+      console.log(e)
+      debugger;
+    }
+  } else if (type === 'text/json') {
+    try {
+      const data = JSON.parse(text)
+      return { type: 'json', data }
+    } catch (e) {
+      console.log(e)
+      debugger;
+    }
+  } else if (type === 'text/pdf') {
+    try {
+      const location = new URL(text)
+      return { type: 'pdf', location }
+    } catch (e) {
+      console.log(e)
+      debugger;
+    }
+  } else if (type === 'text/plain') {
+    try {
+      const content = text
+      return { type: 'plain', content }
+    } catch (e) {
+      console.log(e)
+      debugger;
+    }
+  }
+  return undefined
+}
+
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts 
b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
index 3178374f..ff9cc029 100644
--- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
@@ -25,10 +25,7 @@ export function useTalerActionURL(): [string | undefined, 
(s: boolean) => void]
   useEffect(() => {
     async function check(): Promise<void> {
       const talerUri = await findTalerUriInActiveTab();
-      if (talerUri) {
-        const actionUrl = actionForTalerUri(talerUri);
-        setTalerActionUrl(actionUrl);
-      }
+      setTalerActionUrl(talerUri)
     }
     check();
   }, []);
@@ -36,49 +33,6 @@ export function useTalerActionURL(): [string | undefined, 
(s: boolean) => void]
   return [url, setDismissed];
 }
 
-function actionForTalerUri(talerUri: string): string | undefined {
-  const uriType = classifyTalerUri(talerUri);
-  switch (uriType) {
-    case TalerUriType.TalerWithdraw:
-      return makeExtensionUrlWithParams("static/wallet.html#/withdraw", {
-        talerWithdrawUri: talerUri,
-      });
-    case TalerUriType.TalerPay:
-      return makeExtensionUrlWithParams("static/wallet.html#/pay", {
-        talerPayUri: talerUri,
-      });
-    case TalerUriType.TalerTip:
-      return makeExtensionUrlWithParams("static/wallet.html#/tip", {
-        talerTipUri: talerUri,
-      });
-    case TalerUriType.TalerRefund:
-      return makeExtensionUrlWithParams("static/wallet.html#/refund", {
-        talerRefundUri: talerUri,
-      });
-    case TalerUriType.TalerNotifyReserve:
-      // FIXME: implement
-      break;
-    default:
-      console.warn(
-        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
-      );
-      break;
-  }
-  return undefined;
-}
-
-function makeExtensionUrlWithParams(
-  url: string,
-  params?: { [name: string]: string | undefined },
-): string {
-  const innerUrl = new URL(chrome.extension.getURL("/" + url));
-  if (params) {
-    const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&')
-    innerUrl.hash = innerUrl.hash + '?' + hParams
-  }
-  return innerUrl.href;
-}
-
 async function findTalerUriInActiveTab(): Promise<string | undefined> {
   return new Promise((resolve, reject) => {
     chrome.tabs.executeScript(
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx
similarity index 54%
copy from packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
copy to 
packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx
index deb30e55..88c7c725 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.stories.tsx
@@ -20,24 +20,33 @@
 */
 
 import { createExample } from '../test-utils';
-import { SettingsView as TestedComponent } from './Settings';
+import { TalerActionFound as TestedComponent } from './TalerActionFound';
 
 export default {
-  title: 'wallet/settings',
+  title: 'popup/TalerActionFound',
   component: TestedComponent,
-  argTypes: {
-    setDeviceName: () => Promise.resolve(),
-  }
 };
 
-export const AllOff = createExample(TestedComponent, {
-  deviceName: 'this-is-the-device-name',
-  setDeviceName: () => Promise.resolve(),
+export const PayAction = createExample(TestedComponent, {
+  url: 'taler://pay/something'
 });
 
-export const OneChecked = createExample(TestedComponent, {
-  deviceName: 'this-is-the-device-name',
-  permissionsEnabled: true,
-  setDeviceName: () => Promise.resolve(),
+export const WithdrawalAction = createExample(TestedComponent, {
+  url: 'taler://withdraw/something'
 });
 
+export const TipAction = createExample(TestedComponent, {
+  url: 'taler://tip/something'
+});
+
+export const NotifyAction = createExample(TestedComponent, {
+  url: 'taler://notify-reserve/something'
+});
+
+export const RefundAction = createExample(TestedComponent, {
+  url: 'taler://refund/something'
+});
+
+export const InvalidAction = createExample(TestedComponent, {
+  url: 'taler://something/asd'
+});
diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx 
b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
new file mode 100644
index 00000000..4e3c7168
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
@@ -0,0 +1,98 @@
+import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
+import { ButtonPrimary, ButtonSuccess, PopupBox } from 
"../components/styled/index";
+
+export interface Props {
+  url: string;
+  onDismiss: (s: boolean) => void;
+}
+
+export function TalerActionFound({ url, onDismiss }: Props) {
+  const uriType = classifyTalerUri(url);
+  return <PopupBox>
+    <section>
+      <h1>Taler Action </h1>
+      {uriType === TalerUriType.TalerPay && <div>
+        <p>This page has pay action.</p>
+        <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": 
actionForTalerUri(uriType, url) }); }}>
+          Open pay page
+        </ButtonSuccess>
+      </div>}
+      {uriType === TalerUriType.TalerWithdraw && <div>
+        <p>This page has a withdrawal action.</p>
+        <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": 
actionForTalerUri(uriType, url) }); }}>
+          Open withdraw page
+        </ButtonSuccess>
+      </div>}
+      {uriType === TalerUriType.TalerTip && <div>
+        <p>This page has a tip action.</p>
+        <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": 
actionForTalerUri(uriType, url) }); }}>
+          Open tip page
+        </ButtonSuccess>
+      </div>}
+      {uriType === TalerUriType.TalerNotifyReserve && <div>
+        <p>This page has a notify reserve action.</p>
+        <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": 
actionForTalerUri(uriType, url) }); }}>
+          Notify
+        </ButtonSuccess>
+      </div>}
+      {uriType === TalerUriType.TalerRefund && <div>
+        <p>This page has a refund action.</p>
+        <ButtonSuccess onClick={() => { chrome.tabs.create({ "url": 
actionForTalerUri(uriType, url) }); }}>
+          Open refund page
+        </ButtonSuccess>
+      </div>}
+      {uriType === TalerUriType.Unknown && <div>
+        <p>This page has a malformed taler uri.</p>
+        <p>{url}</p>
+      </div>}
+
+    </section>
+    <footer>
+      <div />
+      <ButtonPrimary onClick={() => onDismiss(true)}> Dismiss </ButtonPrimary>
+    </footer>
+  </PopupBox>;
+
+}
+
+function actionForTalerUri(uriType: TalerUriType, talerUri: string): string | 
undefined {
+  switch (uriType) {
+    case TalerUriType.TalerWithdraw:
+      return makeExtensionUrlWithParams("static/wallet.html#/withdraw", {
+        talerWithdrawUri: talerUri,
+      });
+    case TalerUriType.TalerPay:
+      return makeExtensionUrlWithParams("static/wallet.html#/pay", {
+        talerPayUri: talerUri,
+      });
+    case TalerUriType.TalerTip:
+      return makeExtensionUrlWithParams("static/wallet.html#/tip", {
+        talerTipUri: talerUri,
+      });
+    case TalerUriType.TalerRefund:
+      return makeExtensionUrlWithParams("static/wallet.html#/refund", {
+        talerRefundUri: talerUri,
+      });
+    case TalerUriType.TalerNotifyReserve:
+      // FIXME: implement
+      break;
+    default:
+      console.warn(
+        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
+      );
+      break;
+  }
+  return undefined;
+}
+
+function makeExtensionUrlWithParams(
+  url: string,
+  params?: { [name: string]: string | undefined },
+): string {
+  const innerUrl = new URL(chrome.extension.getURL("/" + url));
+  if (params) {
+    const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&')
+    innerUrl.hash = innerUrl.hash + '?' + hParams
+  }
+  return innerUrl.href;
+}
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index 4bdc2d88..33a46214 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -38,6 +38,7 @@ import {
 import { ProviderAddPage } from "./popup/ProviderAddPage";
 import { ProviderDetailPage } from "./popup/ProviderDetailPage";
 import { SettingsPage } from "./popup/Settings";
+import { TalerActionFound } from "./popup/TalerActionFound";
 
 function main(): void {
   try {
@@ -62,32 +63,16 @@ if (document.readyState === "loading") {
   main();
 }
 
-interface Props {
-  url: string;
-  onDismiss: (s: boolean) => void;
-}
-
-function TalerActionFound({ url, onDismiss }: Props) {
-  return <div style={{ padding: "1em", width: 400 }}>
-    <h1>Taler Action </h1>
-    <p>This page has a Taler action.</p>
-    <p>
-      <button onClick={() => { chrome.tabs.create({ "url": url }); }}>
-        Open
-      </button>
-    </p>
-    <p>
-      <button onClick={() => onDismiss(true)}> Dismiss </button>
-    </p>
-  </div>
-
-}
-
 function Application() {
   const [talerActionUrl, setDismissed] = useTalerActionURL()
 
   if (talerActionUrl) {
-    return <TalerActionFound url={talerActionUrl} onDismiss={setDismissed} />
+    return <div>
+      <WalletNavBar />
+      <div style={{ width: 400, height: 290 }}>
+        <TalerActionFound url={talerActionUrl} onDismiss={setDismissed} />
+      </div>
+    </div>
   }
 
   return (
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
index d4daefc2..35cea2b9 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
@@ -21,6 +21,8 @@ import { CreateManualWithdraw } from "./CreateManualWithdraw";
 import * as wxApi from '../wxApi'
 import { AcceptManualWithdrawalResult, AmountJson, Amounts } from 
"@gnu-taler/taler-util";
 import { ReserveCreated } from "./ReserveCreated.js";
+import { route } from 'preact-router';
+import { Pages } from "../NavigationBar.js";
 
 interface Props {
 
@@ -58,7 +60,9 @@ export function ManualWithdrawPage({ }: Props): VNode {
   }
 
   if (success) {
-    return <ReserveCreated reservePub={success.reservePub} 
paytos={success.exchangePaytoUris} onBack={() => {}}/>
+    return <ReserveCreated reservePub={success.reservePub} 
paytos={success.exchangePaytoUris} onBack={() => {
+      route(Pages.balance)
+    }}/>
   }
 
   return <CreateManualWithdraw
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
index deb30e55..a04a0b4f 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
@@ -41,3 +41,13 @@ export const OneChecked = createExample(TestedComponent, {
   setDeviceName: () => Promise.resolve(),
 });
 
+export const WithOneExchange = createExample(TestedComponent, {
+  deviceName: 'this-is-the-device-name',
+  permissionsEnabled: true,
+  setDeviceName: () => Promise.resolve(),
+  knownExchanges: [{
+    currency: 'USD',
+    exchangeBaseUrl: 'http://exchange.taler',
+    paytoUris: ['payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator']
+  }]
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index d1eb012f..8d18586b 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -81,15 +81,14 @@ export function SettingsView({ knownExchanges, lang, 
changeLang, deviceName, set
         {!knownExchanges || !knownExchanges.length ? <div>
           No exchange yet!
         </div> :
-          <dl>
-            {knownExchanges.map(e => <Fragment>
-              <dt>{e.currency}</dt>
-              <dd>{e.exchangeBaseUrl}</dd>
-              <dd>{e.paytoUris}</dd>
-            </Fragment>)}
-          </dl>
+          <table>
+            {knownExchanges.map(e => <tr>
+              <td>{e.currency}</td>
+              <td><a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a></td>
+            </tr>)}
+          </table>
         }
-        <ButtonPrimary>add exchange</ButtonPrimary>
+        
         <h2><i18n.Translate>Permissions</i18n.Translate></h2>
         <Checkbox label="Automatically open wallet based on page content"
           name="perm"
@@ -105,4 +104,4 @@ export function SettingsView({ knownExchanges, lang, 
changeLang, deviceName, set
       </section>
     </WalletBox>
   )
-}
\ No newline at end of file
+}
diff --git a/packages/taler-wallet-webextension/static/popup.html 
b/packages/taler-wallet-webextension/static/popup.html
index 9c124645..e3c0b158 100644
--- a/packages/taler-wallet-webextension/static/popup.html
+++ b/packages/taler-wallet-webextension/static/popup.html
@@ -2,8 +2,32 @@
 <html>
   <head>
     <meta charset="utf-8" />
-    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/static/style/popup.css" />
+    <style>
+      html {
+        font-family: sans-serif; /* 1 */
+      }
+      body {
+        margin: 0;
+      }
+      </style>
+      <style>
+      html {
+      }
+      h1 {
+        font-size: 2em;
+      }
+      input {
+        font: inherit;
+      }
+      body {
+        margin: 0;
+        font-size: 100%;
+        padding: 0;
+        overflow: hidden;
+        background-color: #f8faf7;
+        font-family: Arial, Helvetica, sans-serif;
+      }
+      </style>
     <link rel="stylesheet" type="text/css" href="/dist/popupEntryPoint.css" />
     <link rel="icon" href="/static/img/icon.png" />
     <script src="/dist/popupEntryPoint.js"></script>

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