default exchange and select currency

This commit is contained in:
Sebastian 2022-08-15 23:01:05 -03:00
parent 0798aa5ced
commit fb8372dfbf
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
5 changed files with 207 additions and 65 deletions

View File

@ -1,9 +1,25 @@
/*
This file is part of GNU Taler
(C) 2022 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/>
*/
import { css } from "@linaria/core"; import { css } from "@linaria/core";
import { h, JSX, VNode, ComponentChildren } from "preact"; import { h, JSX, VNode, ComponentChildren } from "preact";
import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import { useCallback, useEffect, useRef, useState } from "preact/hooks";
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
import { alpha } from "./colors/manipulation"; import { alpha } from "./colors/manipulation";
import { ModalManager } from "./ModalManager"; import { ModalManager } from "./ModalManager.js";
import { Portal } from "./Portal.js"; import { Portal } from "./Portal.js";
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
import { theme } from "./style"; import { theme } from "./style";
@ -22,12 +38,16 @@ interface Props {
children: ComponentChildren; children: ComponentChildren;
open?: boolean; open?: boolean;
exited?: boolean; exited?: boolean;
container?: HTMLElement; container?: VNode;
} }
const defaultManager = new ModalManager(); const defaultManager = new ModalManager();
const manager = defaultManager; const manager = defaultManager;
function getModal(): any {
return null; //TODO: fix
}
export function Modal({ export function Modal({
open, open,
// exited, // exited,
@ -52,18 +72,18 @@ export function Modal({
return; return;
} }
if (open && isTopModal()) { // if (open && isTopModal()) {
handleMounted(); // handleMounted();
} else { // } else {
ariaHidden(modalRef.current, true); // ariaHidden(modalRef.current, true);
} // }
}); });
return ( return (
<Portal <Portal
ref={handlePortalRef} ref={handlePortalRef}
container={container} container={container}
disablePortal={disablePortal} // disablePortal={disablePortal}
> >
<div <div
class={[_class, baseStyle].join(" ")} class={[_class, baseStyle].join(" ")}
@ -105,7 +125,7 @@ function getOffsetLeft(rect: any, horizontal: any): number {
return offset; return offset;
} }
function getTransformOriginValue(transformOrigin): string { function getTransformOriginValue(transformOrigin: any): string {
return [transformOrigin.horizontal, transformOrigin.vertical] return [transformOrigin.horizontal, transformOrigin.vertical]
.map((n) => (typeof n === "number" ? `${n}px` : n)) .map((n) => (typeof n === "number" ? `${n}px` : n))
.join(" "); .join(" ");

View File

@ -1,3 +1,19 @@
/*
This file is part of GNU Taler
(C) 2022 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/>
*/
//////////////////// ////////////////////
function ownerDocument(node: Node | null | undefined): Document { function ownerDocument(node: Node | null | undefined): Document {
return (node && node.ownerDocument) || document; return (node && node.ownerDocument) || document;

View File

@ -1,3 +1,19 @@
/*
This file is part of GNU Taler
(C) 2022 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/>
*/
import { css } from "@linaria/core"; import { css } from "@linaria/core";
import { h, JSX, VNode, ComponentChildren } from "preact"; import { h, JSX, VNode, ComponentChildren } from "preact";
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
@ -48,7 +64,7 @@ function getOffsetLeft(rect: any, horizontal: any): number {
return offset; return offset;
} }
function getTransformOriginValue(transformOrigin): string { function getTransformOriginValue(transformOrigin: any): string {
return [transformOrigin.horizontal, transformOrigin.vertical] return [transformOrigin.horizontal, transformOrigin.vertical]
.map((n) => (typeof n === "number" ? `${n}px` : n)) .map((n) => (typeof n === "number" ? `${n}px` : n))
.join(" "); .join(" ");

View File

@ -1,3 +1,19 @@
/*
This file is part of GNU Taler
(C) 2022 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/>
*/
import { css } from "@linaria/core"; import { css } from "@linaria/core";
import { createPortal, forwardRef } from "preact/compat"; import { createPortal, forwardRef } from "preact/compat";
import { import {
@ -26,7 +42,7 @@ const baseStyle = css`
`; `;
interface Props { interface Props {
class: string; // class: string;
children: ComponentChildren; children: ComponentChildren;
disablePortal?: boolean; disablePortal?: boolean;
container?: VNode; container?: VNode;
@ -39,10 +55,11 @@ export const Portal = forwardRef(function Portal(
const [mountNode, setMountNode] = useState<HTMLElement | undefined>( const [mountNode, setMountNode] = useState<HTMLElement | undefined>(
undefined, undefined,
); );
const handleRef = useForkRef( const handleRef = null;
isValidElement(children) ? children.ref : null, // useForkRef(
ref, // isValidElement(children) ? children.ref : null,
); // ref,
// );
useEffect(() => { useEffect(() => {
if (!disablePortal) { if (!disablePortal) {
@ -81,25 +98,25 @@ function getContainer(container: any): any {
return typeof container === "function" ? container() : container; return typeof container === "function" ? container() : container;
} }
function useForkRef<Instance>( // function useForkRef<Instance>(
refA: React.Ref<Instance> | null | undefined, // refA: React.Ref<Instance> | null | undefined,
refB: React.Ref<Instance> | null | undefined, // refB: React.Ref<Instance> | null | undefined,
): React.Ref<Instance> | null { // ): React.Ref<Instance> | null {
/** // /**
* This will create a new function if the ref props change and are defined. // * This will create a new function if the ref props change and are defined.
* This means react will call the old forkRef with `null` and the new forkRef // * This means react will call the old forkRef with `null` and the new forkRef
* with the ref. Cleanup naturally emerges from this behavior. // * with the ref. Cleanup naturally emerges from this behavior.
*/ // */
return useMemo(() => { // return useMemo(() => {
if (refA == null && refB == null) { // if (refA == null && refB == null) {
return null; // return null;
} // }
return (refValue) => { // return (refValue) => {
setRef(refA, refValue); // setRef(refA, refValue);
setRef(refB, refValue); // setRef(refB, refValue);
}; // };
}, [refA, refB]); // }, [refA, refB]);
} // }
function setRef<T>( function setRef<T>(
ref: RefObject<T | null> | ((instance: T | null) => void) | null | undefined, ref: RefObject<T | null> | ((instance: T | null) => void) | null | undefined,

View File

@ -18,18 +18,29 @@ import { Amounts } from "@gnu-taler/taler-util";
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { ErrorMessage } from "../components/ErrorMessage.js";
import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { SelectList } from "../components/SelectList.js";
import { import {
Input,
InputWithLabel, InputWithLabel,
LightText, LightText,
LinkPrimary,
SubTitle,
SvgIcon, SvgIcon,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Alert } from "../mui/Alert.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { Grid } from "../mui/Grid.js"; import { Grid } from "../mui/Grid.js";
import { Paper } from "../mui/Paper.js"; import { Paper } from "../mui/Paper.js";
import { TextField } from "../mui/TextField.js"; import { TextField } from "../mui/TextField.js";
import { Pages } from "../NavigationBar.js";
import arrowIcon from "../svg/chevron-down.svg"; import arrowIcon from "../svg/chevron-down.svg";
import bankIcon from "../svg/ri-bank-line.svg"; import bankIcon from "../svg/ri-bank-line.svg";
import * as wxApi from "../wxApi.js";
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
@ -152,6 +163,57 @@ const CircleDiv = styled.div`
border: none; border: none;
`; `;
export function SelectCurrency({
onChange,
}: {
onChange: (s: string) => void;
}): VNode {
const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(wxApi.listExchanges);
if (!hook) {
return <Loading />;
}
if (hook.hasError) {
return (
<LoadingError
error={hook}
title={<i18n.Translate>Could not load list of exchange</i18n.Translate>}
/>
);
}
const list: Record<string, string> = {};
hook.response.exchanges.forEach((e) => (list[e.currency] = e.currency));
list[""] = "Select a currency";
return (
<Fragment>
<h1>
<i18n.Translate>Specify the amount and the origin</i18n.Translate>
</h1>
<Alert severity="warning">
Choose a currency to proceed or add more exchanges in the settings tab
</Alert>
<Input>
<SelectList
label={<i18n.Translate>Known currencies</i18n.Translate>}
list={list}
name="lang"
value={""}
onChange={(v) => onChange(v)}
/>
</Input>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div />
<LinkPrimary href={Pages.settingsExchangeAdd({})}>
<i18n.Translate>Add an exchange</i18n.Translate>
</LinkPrimary>
</div>
</Fragment>
);
}
function RowExample({ function RowExample({
info, info,
disabled, disabled,
@ -194,9 +256,9 @@ export function DestinationSelectionGetCash({
? undefined ? undefined
: Amounts.parse(initialAmount); : Amounts.parse(initialAmount);
const parsedInitialAmountValue = !parsedInitialAmount const parsedInitialAmountValue = !parsedInitialAmount
? "" ? "0"
: Amounts.stringifyValue(parsedInitialAmount); : Amounts.stringifyValue(parsedInitialAmount);
const currency = parsedInitialAmount?.currency; const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
const [amount, setAmount] = useState(parsedInitialAmountValue); const [amount, setAmount] = useState(parsedInitialAmountValue);
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -220,7 +282,11 @@ export function DestinationSelectionGetCash({
]; ];
if (!currency) { if (!currency) {
return <div>currency not provided</div>; return (
<div>
<SelectCurrency onChange={(c) => setCurrency(c)} />
</div>
);
} }
const currencyAndAmount = `${currency}:${amount}`; const currencyAndAmount = `${currency}:${amount}`;
const parsedAmount = Amounts.parse(currencyAndAmount); const parsedAmount = Amounts.parse(currencyAndAmount);
@ -231,20 +297,25 @@ export function DestinationSelectionGetCash({
<h1> <h1>
<i18n.Translate>Specify the amount and the origin</i18n.Translate> <i18n.Translate>Specify the amount and the origin</i18n.Translate>
</h1> </h1>
<TextField <Grid container columns={2} justifyContent="space-between">
label="Amount" <TextField
type="number" label="Amount"
variant="filled" type="number"
error={invalid} variant="filled"
required error={invalid}
startAdornment={ required
<div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div> startAdornment={
} <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
value={amount} }
onChange={(e) => { value={amount}
setAmount(e); onChange={(e) => {
}} setAmount(e);
/> }}
/>
<Button onClick={async () => setCurrency(undefined)}>
Change currency
</Button>
</Grid>
<Grid container spacing={1} columns={1}> <Grid container spacing={1} columns={1}>
{previous2.length > 0 ? ( {previous2.length > 0 ? (
@ -350,20 +421,22 @@ export function DestinationSelectionSendCash({
<i18n.Translate>Specify the amount and the destination</i18n.Translate> <i18n.Translate>Specify the amount and the destination</i18n.Translate>
</h1> </h1>
<TextField <div>
label="Amount" <TextField
type="number" label="Amount"
variant="filled" type="number"
required variant="filled"
error={invalid} required
startAdornment={ error={invalid}
<div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div> startAdornment={
} <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
value={amount} }
onChange={(e) => { value={amount}
setAmount(e); onChange={(e) => {
}} setAmount(e);
/> }}
/>
</div>
<Grid container spacing={1} columns={1}> <Grid container spacing={1} columns={1}>
{previous2.length > 0 ? ( {previous2.length > 0 ? (