aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx393
1 files changed, 376 insertions, 17 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
index 187a2412f..5f9c5065c 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
@@ -14,17 +14,22 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { Amounts } from "@gnu-taler/taler-util";
import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import {
+ InputWithLabel,
+ LightText,
+ SvgIcon,
+} from "../components/styled/index.js";
+import { useTranslationContext } from "../context/translation.js";
+import { Button } from "../mui/Button.js";
+import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js";
-
-const QrVideo = styled.video`
- width: 80%;
- margin-left: auto;
- margin-right: auto;
- padding: 8px;
- background-color: black;
-`;
+import { TextField } from "../mui/TextField.js";
+import arrowIcon from "../svg/chevron-down.svg";
+import bankIcon from "../svg/ri-bank-line.svg";
const Container = styled.div`
display: flex;
@@ -36,25 +41,379 @@ const Container = styled.div`
interface Props {
action: "send" | "get";
- currency?: string;
+ amount?: string;
+ goToWalletManualWithdraw: (amount: string) => void;
}
-export function DestinationSelectionGetCash({ currency }: Props): VNode {
+type Contact = {
+ icon: string;
+ name: string;
+ description: string;
+};
+
+const ContactTable = styled.table`
+ width: 100%;
+ & > tr > td {
+ padding: 8px;
+ & > div:not([data-disabled]):hover {
+ background-color: lightblue;
+ }
+ color: black;
+ div[data-disabled] > * {
+ color: gray;
+ }
+ }
+
+ & > tr:nth-child(2n) {
+ background: #ebebeb;
+ }
+`;
+
+const MediaExample = styled.div`
+ text-size-adjust: 100%;
+ color: inherit;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ text-transform: none;
+ text-align: left;
+ box-sizing: border-box;
+ align-items: center;
+ display: flex;
+ padding: 8px 8px;
+
+ &[data-disabled]:hover {
+ cursor: inherit;
+ }
+ cursor: pointer;
+`;
+
+const MediaLeft = styled.div`
+ text-size-adjust: 100%;
+
+ color: inherit;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ text-transform: none;
+ text-align: left;
+ box-sizing: border-box;
+ padding-right: 8px;
+ display: block;
+`;
+
+const MediaBody = styled.div`
+ text-size-adjust: 100%;
+
+ font-family: inherit;
+ text-transform: none;
+ text-align: left;
+ box-sizing: border-box;
+ flex: 1 1;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 1.42857;
+`;
+const MediaRight = styled.div`
+ text-size-adjust: 100%;
+
+ color: inherit;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ text-transform: none;
+ text-align: left;
+ box-sizing: border-box;
+ padding-left: 8px;
+`;
+
+const CircleDiv = styled.div`
+ box-sizing: border-box;
+ align-items: center;
+ background-position: 50%;
+ background-repeat: no-repeat;
+ background-size: cover;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ margin-left: auto;
+ margin-right: auto;
+ overflow: hidden;
+ text-align: center;
+ text-decoration: none;
+ text-transform: uppercase;
+ transition: background-color 0.15s ease, border-color 0.15s ease,
+ color 0.15s ease;
+ font-size: 16px;
+ background-color: #86a7bd1a;
+ height: 40px;
+ line-height: 40px;
+ width: 40px;
+ border: none;
+`;
+
+function RowExample({
+ info,
+ disabled,
+}: {
+ info: Contact;
+ disabled?: boolean;
+}): VNode {
+ return (
+ <MediaExample data-disabled={disabled}>
+ <MediaLeft>
+ <CircleDiv>
+ <SvgIcon
+ title={info.name}
+ dangerouslySetInnerHTML={{ __html: info.icon }}
+ color="currentColor"
+ />
+ </CircleDiv>
+ </MediaLeft>
+ <MediaBody>
+ <span>{info.name}</span>
+ <LightText>{info.description}</LightText>
+ </MediaBody>
+ <MediaRight>
+ <SvgIcon
+ title="Select this contact"
+ dangerouslySetInnerHTML={{ __html: arrowIcon }}
+ color="currentColor"
+ transform="rotate(-90deg)"
+ />
+ </MediaRight>
+ </MediaExample>
+ );
+}
+
+export function DestinationSelectionGetCash({
+ amount: initialAmount,
+ goToWalletManualWithdraw,
+}: Props): VNode {
+ const parsedInitialAmount = !initialAmount
+ ? undefined
+ : Amounts.parse(initialAmount);
+ const parsedInitialAmountValue = !parsedInitialAmount
+ ? ""
+ : Amounts.stringifyValue(parsedInitialAmount);
+ const currency = parsedInitialAmount?.currency;
+
+ const [amount, setAmount] = useState(parsedInitialAmountValue);
+ const { i18n } = useTranslationContext();
+ const previous1: Contact[] = [];
+ const previous2: Contact[] = [
+ {
+ name: "International Bank",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ {
+ name: "Max",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ {
+ name: "Alex",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ ];
+
+ if (!currency) {
+ return <div>currency not provided</div>;
+ }
+ const currencyAndAmount = `${currency}:${amount}`;
+ const parsedAmount = Amounts.parse(currencyAndAmount);
+ // const dirty = parsedInitialAmountValue !== amount;
+ const invalid = !parsedAmount || Amounts.isZero(parsedAmount);
return (
<Container>
- <p>Request {currency} from:</p>
- <Paper style={{ padding: 8 }}>Bank account</Paper>
- <Paper style={{ padding: 8 }}>Another person</Paper>
+ <h1>
+ <i18n.Translate>Specify the amount and the origin</i18n.Translate>
+ </h1>
+ <TextField
+ label="Amount"
+ type="number"
+ variant="filled"
+ error={invalid}
+ required
+ startAdornment={
+ <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
+ }
+ value={amount}
+ onChange={(e) => {
+ setAmount(e);
+ }}
+ />
+
+ <Grid container spacing={1} columns={1}>
+ {previous2.length > 0 ? (
+ <Fragment>
+ <p>Previous origins:</p>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <ContactTable>
+ {previous2.map((info, i) => (
+ <tr key={i}>
+ <td>
+ <RowExample info={info} disabled={invalid} />
+ </td>
+ </tr>
+ ))}
+ </ContactTable>
+ </Paper>
+ </Grid>
+ </Fragment>
+ ) : undefined}
+ <Grid item>
+ <p>Create new origin for the money</p>
+ </Grid>
+ <Grid item container columns={3} spacing={1}>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>From my bank account</p>
+ <Button
+ disabled={invalid}
+ onClick={async () =>
+ goToWalletManualWithdraw(currencyAndAmount)
+ }
+ >
+ Withdraw
+ </Button>
+ </Paper>
+ </Grid>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>From someone else</p>
+ <Button disabled>Request</Button>
+ </Paper>
+ </Grid>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>From a business or charity</p>
+ <Button disabled>Invoice</Button>
+ </Paper>
+ </Grid>
+ </Grid>
+ <Grid item columns={1} spacing={1} xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>From a exchange reserve or purse</p>
+ <Button disabled>Create</Button>
+ </Paper>
+ </Grid>
+ </Grid>
</Container>
);
}
-export function DestinationSelectionSendCash({ currency }: Props): VNode {
+export function DestinationSelectionSendCash({
+ amount: initialAmount,
+}: Props): VNode {
+ const parsedInitialAmount = !initialAmount
+ ? undefined
+ : Amounts.parse(initialAmount);
+ const parsedInitialAmountValue = !parsedInitialAmount
+ ? ""
+ : Amounts.stringifyValue(parsedInitialAmount);
+ const currency = parsedInitialAmount?.currency;
+
+ const [amount, setAmount] = useState(parsedInitialAmountValue);
+ const { i18n } = useTranslationContext();
+ const previous1: Contact[] = [];
+ const previous2: Contact[] = [
+ {
+ name: "International Bank",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ {
+ name: "Max",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ {
+ name: "Alex",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ ];
+
+ if (!currency) {
+ return <div>currency not provided</div>;
+ }
+ const currencyAndAmount = `${currency}:${amount}`;
+ const parsedAmount = Amounts.parse(currencyAndAmount);
+ const invalid = !parsedAmount || Amounts.isZero(parsedAmount);
return (
<Container>
- <p>Sending {currency} to:</p>
- <Paper style={{ padding: 8 }}>Bank account</Paper>
- <Paper style={{ padding: 8 }}>Another person</Paper>
+ <h1>
+ <i18n.Translate>Specify the amount and the destination</i18n.Translate>
+ </h1>
+
+ <TextField
+ label="Amount"
+ type="number"
+ variant="filled"
+ required
+ error={invalid}
+ startAdornment={
+ <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
+ }
+ value={amount}
+ onChange={(e) => {
+ setAmount(e);
+ }}
+ />
+
+ <Grid container spacing={1} columns={1}>
+ {previous2.length > 0 ? (
+ <Fragment>
+ <p>Previous destinations:</p>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <ContactTable>
+ {previous2.map((info, i) => (
+ <tr key={i}>
+ <td>
+ <RowExample info={info} disabled={invalid} />
+ </td>
+ </tr>
+ ))}
+ </ContactTable>
+ </Paper>
+ </Grid>
+ </Fragment>
+ ) : undefined}
+ <Grid item>
+ <p>Create a destination for the money</p>
+ </Grid>
+ <Grid item container columns={3} spacing={1}>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>To my bank account</p>
+ <Button disabled={invalid}>Deposit</Button>
+ </Paper>
+ </Grid>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>To someone else</p>
+ <Button disabled>Send</Button>
+ </Paper>
+ </Grid>
+ <Grid item xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>To a business or charity</p>
+ <Button disabled>Pay</Button>
+ </Paper>
+ </Grid>
+ </Grid>
+ <Grid item columns={1} spacing={1} xs={1}>
+ <Paper style={{ padding: 8 }}>
+ <p>To an exchange reserve or purse</p>
+ <Button disabled>Create</Button>
+ </Paper>
+ </Grid>
+ </Grid>
</Container>
);
}