diff options
15 files changed, 149 insertions, 107 deletions
diff --git a/packages/taler-wallet-webextension/src/components/AmountField.tsx b/packages/taler-wallet-webextension/src/components/AmountField.tsx index 79c510d2f..1c57be0df 100644 --- a/packages/taler-wallet-webextension/src/components/AmountField.tsx +++ b/packages/taler-wallet-webextension/src/components/AmountField.tsx @@ -45,23 +45,20 @@ export function AmountField({ return handler.value; } return ( - <Fragment> - <TextField - label={label} - type="number" - min="0" - step="0.1" - variant="filled" - error={!!handler.error} - required={required} - startAdornment={ - <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div> - } - value={handler.value} - disabled={!handler.onInput} - onInput={positiveAmount} - /> - {handler.error && <ErrorText>{handler.error}</ErrorText>} - </Fragment> + <TextField + label={label} + type="number" + min="0" + step="0.1" + variant="filled" + error={handler.error} + required={required} + startAdornment={ + <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div> + } + value={handler.value} + disabled={!handler.onInput} + onInput={positiveAmount} + /> ); } diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx index 603392b60..4970f590f 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx @@ -78,7 +78,7 @@ export function ReadyView({ <TextField label="Subject" variant="filled" - error={!!subject.error} + error={subject.error} required fullWidth value={subject.value} diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx index 93ae343e9..bca806c5d 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx @@ -68,7 +68,7 @@ export function ReadyView({ <TextField label="Subject" variant="filled" - error={!!subject.error} + error={subject.error} required fullWidth value={subject.value} diff --git a/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx b/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx index a409f09f0..7db6b2964 100644 --- a/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx +++ b/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx @@ -56,13 +56,13 @@ const Input = (variant: Props["variant"]): VNode => { value="disabled" /> <TextField - error + error={"Error"} variant={variant} label="Something" {...{ value, onChange }} /> <TextField - error + error={"Error"} disabled variant={variant} label="Disabled and Error" diff --git a/packages/taler-wallet-webextension/src/mui/TextField.tsx b/packages/taler-wallet-webextension/src/mui/TextField.tsx index 7c6eb40a2..ba05158fa 100644 --- a/packages/taler-wallet-webextension/src/mui/TextField.tsx +++ b/packages/taler-wallet-webextension/src/mui/TextField.tsx @@ -30,7 +30,7 @@ export interface Props { autoFocus?: boolean; color?: Colors; disabled?: boolean; - error?: boolean; + error?: string; fullWidth?: boolean; helperText?: VNode | string; id?: string; @@ -85,7 +85,12 @@ export function TextField({ <FormControl {...props}> {label && <InputLabel>{label}</InputLabel>} <Input {...props}>{children}</Input> - {helperText && <FormHelperText>{helperText}</FormHelperText>} + {helperText && ( + <FormHelperText error={props.error}>{helperText}</FormHelperText> + )} + {props.error ? ( + <FormHelperText error="true">{props.error}</FormHelperText> + ) : undefined} </FormControl> ); } diff --git a/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx b/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx index 8454b0fad..e80e7f8d8 100644 --- a/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx @@ -22,7 +22,7 @@ import { Colors } from "../style"; export interface Props { color: Colors; disabled: boolean; - error: boolean; + error?: string; focused: boolean; fullWidth: boolean; hiddenLabel: boolean; @@ -64,7 +64,7 @@ export const FormControlContext = createContext<FCCProps | null>(null); export function FormControl({ color = "primary", disabled = false, - error = false, + error = undefined, focused: visuallyFocused, fullWidth = false, hiddenLabel = false, @@ -75,9 +75,9 @@ export function FormControl({ children, }: Partial<Props>): VNode { const [filled, setFilled] = useState(false); - const [focusedState, setFocused] = useState(false); + const [focusedState, setFocused] = useState(visuallyFocused); const focused = - visuallyFocused !== undefined && !disabled ? visuallyFocused : focusedState; + focusedState !== undefined && !disabled ? focusedState : false; const value: FCCProps = { color, @@ -124,7 +124,7 @@ export interface FCCProps { // setAdornedStart, color: Colors; disabled: boolean; - error: boolean; + error: string | undefined; filled: boolean; focused: boolean; fullWidth: boolean; @@ -142,7 +142,7 @@ export interface FCCProps { const defaultContextValue: FCCProps = { color: "primary", disabled: false, - error: false, + error: undefined, filled: false, focused: false, fullWidth: false, @@ -159,7 +159,7 @@ const defaultContextValue: FCCProps = { function withoutUndefinedProperties(obj: any): any { return Object.keys(obj).reduce((acc, key) => { const _acc: any = acc; - if (obj[key] !== undefined) _acc[key] = obj[key]; + if (obj[key] !== undefined && obj[key] !== false) _acc[key] = obj[key]; return _acc; }, {}); } diff --git a/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx b/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx index 739b41e32..5e40ba616 100644 --- a/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx @@ -43,7 +43,7 @@ const containedStyle = css` interface Props { disabled?: boolean; - error?: boolean; + error?: string; filled?: boolean; focused?: boolean; margin?: "dense"; diff --git a/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx b/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx index 1d4b5eff5..11404b5c1 100644 --- a/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx @@ -22,7 +22,7 @@ import { useFormControl } from "./FormControl.js"; export interface Props { class?: string; disabled?: boolean; - error?: boolean; + error?: string; filled?: boolean; focused?: boolean; required?: boolean; @@ -67,9 +67,9 @@ export function FormLabel({ }); return ( <label - data-focused={fcs.focused} + data-focused={!fcs.focused ? undefined : true} data-error={!fcs.error ? undefined : true} - data-disabled={fcs.disabled} + data-disabled={!fcs.disabled ? undefined : true} class={[_class, root, theme.typography.body1].join(" ")} {...rest} style={{ diff --git a/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx b/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx index e1c6e7af1..94304f16b 100644 --- a/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx @@ -60,8 +60,8 @@ export function InputBaseRoot({ const fcs = useFormControl({}); return ( <div - data-disabled={disabled} - data-focused={focused} + data-disabled={!disabled ? undefined : true} + data-focused={!focused ? undefined : true} data-multiline={multiline} data-hasStart={!!startAdornment} data-hasEnd={!!endAdornment} @@ -116,6 +116,12 @@ const componentStyle = css` duration: theme.transitions.duration.shorter, })}; } + &:not(focus)::placeholder { + opacity: 0; + } + &:focus::placeholder { + opacity: ${theme.palette.mode === "light" ? 0.42 : 0.5}; + } &:focus { outline: 0; } @@ -292,11 +298,11 @@ export function InputBase({ <Root {...fcs} onClick={handleClick}> <FormControlContext.Provider value={null}> <Input - aria-invalid={fcs.error} + aria-invalid={fcs.error ? true : undefined} // aria-describedby={} - disabled={fcs.disabled} + disabled={fcs.disabled ? true : undefined} name={name} - placeholder={placeholder} + placeholder={!placeholder ? undefined : placeholder} readOnly={readOnly} required={fcs.required} rows={rows} diff --git a/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx b/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx index fa5144ca3..9ab91e7fd 100644 --- a/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx @@ -27,7 +27,7 @@ export interface Props { defaultValue?: string; disabled?: boolean; disableUnderline?: boolean; - error?: boolean; + error?: string; fullWidth?: boolean; id?: string; margin?: "dense" | "normal" | "none"; @@ -176,7 +176,7 @@ function Root({ return ( <InputBaseRoot disabled={disabled} - focused={focused} + focused={focused ? true : undefined} fullWidth={fullWidth} multiline={multiline} error={error} diff --git a/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx b/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx index 469047afb..35cbd7a41 100644 --- a/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx @@ -90,7 +90,7 @@ interface InputLabelProps { color: Colors; disableAnimation: boolean; disabled: boolean; - error: boolean; + error?: string; focused: boolean; margin: boolean; required: boolean; @@ -104,8 +104,8 @@ export function InputLabel(props: Partial<InputLabelProps>): VNode { <FormLabel data-form-control={!!fcs} data-size={fcs.size} - data-shrink={props.shrink || fcs.filled || fcs.focused} - data-disable-animation={props.disableAnimation} + data-shrink={props.shrink || fcs.filled || fcs.focused ? true : undefined} + data-disable-animation={props.disableAnimation ? true : undefined} data-variant={fcs.variant} class={root} {...props} diff --git a/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx b/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx index b1152ebec..45614f618 100644 --- a/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx @@ -28,7 +28,7 @@ export interface Props { disabled?: boolean; disableUnderline?: boolean; endAdornment?: VNode; - error?: boolean; + error?: string; fullWidth?: boolean; id?: string; margin?: "dense" | "normal" | "none"; @@ -128,7 +128,7 @@ function Root({ fullWidth, disabled, focused, error, children }: any): VNode { return ( <InputBaseRoot disabled={disabled} - focused={focused} + focused={focused ? true : undefined} fullWidth={fullWidth} error={error} class={[rootStyle, formControlStyle, underlineStyle].join(" ")} diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/stories.tsx b/packages/taler-wallet-webextension/src/wallet/ManageAccount/stories.tsx index c0d3a38b0..875dec227 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/stories.tsx @@ -194,8 +194,9 @@ export const AddingIbanAccount = createExample(ReadyView, { uri: { targetType: "iban", iban: "ASDQWEQWE", + bic: "SANDBOX", isKnown: true, - targetPath: "/ASDQWEQWE", + targetPath: "SANDBOX/ASDQWEQWE", params: {}, }, }, diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx index 326e078f4..3af0d5505 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx @@ -23,7 +23,6 @@ import { import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { ErrorMessage } from "../../components/ErrorMessage.js"; import { LoadingError } from "../../components/LoadingError.js"; import { SelectList } from "../../components/SelectList.js"; import { @@ -41,6 +40,7 @@ import checkIcon from "../../svg/check_24px.svg"; import warningIcon from "../../svg/warning_24px.svg"; import deleteIcon from "../../svg/delete_24px.svg"; import { State } from "./index.js"; +import { ErrorMessage } from "../../components/ErrorMessage.js"; type AccountType = "bitcoin" | "x-taler-bank" | "iban"; type ComponentFormByAccountType = { @@ -143,9 +143,9 @@ export function ReadyView({ </p> <p> <TextField - label="Account alias" - variant="standard" - required + label="Alias" + variant="filled" + placeholder="Easy to remember description" fullWidth disabled={accountType.value === ""} value={alias.value} @@ -202,6 +202,9 @@ function IbanTable({ <i18n.Translate>Alias</i18n.Translate> </th> <th> + <i18n.Translate>Bank Id</i18n.Translate> + </th> + <th> <i18n.Translate>Int. Account Number</i18n.Translate> </th> <th> @@ -219,7 +222,8 @@ function IbanTable({ return ( <tr key={account.alias}> <td>{account.alias}</td> - <td>{p.targetPath}</td> + <td>{p.bic}</td> + <td>{p.iban}</td> <td>{p.params["receiver-name"]}</td> <td class="kyc"> {account.kyc_completed ? ( @@ -415,7 +419,7 @@ function BitcoinAddressAccount({ field }: { field: TextFieldHandler }): VNode { variant="standard" fullWidth value={value} - error={value !== undefined && !!errors?.value} + error={value !== undefined ? errors?.value : undefined} disabled={!field.onInput} onChange={(v) => { setValue(v); @@ -424,9 +428,6 @@ function BitcoinAddressAccount({ field }: { field: TextFieldHandler }): VNode { } }} /> - {value !== undefined && errors?.value && ( - <ErrorMessage title={<span>{errors?.value}</span>} /> - )} </Fragment> ); } @@ -456,7 +457,7 @@ function TalerBankAddressAccount({ variant="standard" fullWidth value={host} - error={host !== undefined && !!errors?.host} + error={host !== undefined ? errors?.host : undefined} disabled={!field.onInput} onChange={(v) => { setHost(v); @@ -464,77 +465,109 @@ function TalerBankAddressAccount({ field.onInput(`payto://x-taler-bank/${v}/${account}`); } }} - />{" "} - {host !== undefined && errors?.host && ( - <ErrorMessage title={<span>{errors?.host}</span>} /> - )} + /> <TextField label="Bank account" variant="standard" fullWidth disabled={!field.onInput} value={account} - error={account !== undefined && !!errors?.account} + error={account !== undefined ? errors?.account : undefined} onChange={(v) => { setAccount(v || ""); if (!errors && field.onInput) { field.onInput(`payto://x-taler-bank/${host}/${v}`); } }} - />{" "} - {account !== undefined && errors?.account && ( - <ErrorMessage title={<span>{errors?.account}</span>} /> - )} + /> </Fragment> ); } +//Taken from libeufin and libeufin took it from the ISO20022 XSD schema +const bicRegex = /^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/; +const ibanRegex = /^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{1,30}$/; + function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode { const { i18n } = useTranslationContext(); - const [number, setNumber] = useState<string | undefined>(undefined); + const [bic, setBic] = useState<string | undefined>(undefined); + const [iban, setIban] = useState<string | undefined>(undefined); const [name, setName] = useState<string | undefined>(undefined); const errors = undefinedIfEmpty({ - number: !number ? i18n.str`Can't be empty` : undefined, + bic: !bic + ? undefined + : !bicRegex.test(bic) + ? i18n.str`Invalid bic` + : undefined, + iban: !iban + ? i18n.str`Can't be empty` + : !ibanRegex.test(iban) + ? i18n.str`Invalid iban` + : undefined, name: !name ? i18n.str`Can't be empty` : undefined, }); + + function sendUpdateIfNoErrors( + bic: string | undefined, + iban: string, + name: string, + ): void { + if (!errors && field.onInput) { + const path = bic === undefined ? iban : `${bic}/${iban}`; + field.onInput( + `payto://iban/${path}?receiver-name=${encodeURIComponent(name)}`, + ); + } + } return ( <Fragment> - <TextField - label="IBAN number" - variant="standard" - fullWidth - value={number} - error={number !== undefined && !!errors?.number} - disabled={!field.onInput} - onChange={(v) => { - setNumber(v); - if (!errors && field.onInput) { - field.onInput(`payto://iban/${v}?receiver-name=${name}`); - } - }} - /> - {number !== undefined && errors?.number && ( - <ErrorMessage title={<span>{errors?.number}</span>} /> - )} - <TextField - label="Account name" - variant="standard" - fullWidth - value={name} - error={name !== undefined && !!errors?.name} - disabled={!field.onInput} - onChange={(v) => { - setName(v); - if (!errors && field.onInput) { - field.onInput( - `payto://iban/${number}?receiver-name=${encodeURIComponent(v)}`, - ); - } - }} - /> - {name !== undefined && errors?.name && ( - <ErrorMessage title={<span>{errors?.name}</span>} /> - )} + <p> + <TextField + label="BIC" + variant="filled" + placeholder="BANKID" + fullWidth + value={bic} + error={bic !== undefined ? errors?.bic : undefined} + disabled={!field.onInput} + onChange={(v) => { + setBic(v); + sendUpdateIfNoErrors(v, iban || "", name || ""); + }} + /> + </p> + <p> + <TextField + label="IBAN" + variant="filled" + placeholder="XX123456" + fullWidth + required + value={iban} + error={iban !== undefined ? errors?.iban : undefined} + disabled={!field.onInput} + onChange={(v) => { + setIban(v); + sendUpdateIfNoErrors(bic, v, name || ""); + }} + /> + </p> + <p> + <TextField + label="Receiver name" + variant="filled" + placeholder="Name of the target bank account owner" + fullWidth + required + value={name} + error={name !== undefined ? errors?.name : undefined} + disabled={!field.onInput} + onChange={(v) => { + setName(v); + sendUpdateIfNoErrors(bic, iban || "", v); + }} + /> + </p> </Fragment> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index 746d127cf..76bfe014b 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -1343,7 +1343,7 @@ function DepositDetails({ const { i18n } = useTranslationContext(); const r = Amounts.parseOrThrow(transaction.amountRaw); const e = Amounts.parseOrThrow(transaction.amountEffective); - const fee = Amounts.sub(r, e).amount; + const fee = Amounts.sub(e, r).amount; const maxFrac = [r, e, fee] .map((a) => Amounts.maxFractionalDigits(a)) @@ -1366,7 +1366,7 @@ function DepositDetails({ <i18n.Translate>Transaction fees</i18n.Translate> </td> <td> - <Amount value={fee} negative maxFracSize={maxFrac} /> + <Amount value={fee} maxFracSize={maxFrac} /> </td> </tr> )} |