using util to parse uri, some more fixs
This commit is contained in:
parent
b4bad2deaf
commit
af29a02e5b
@ -14,6 +14,7 @@
|
|||||||
"jed": "1.1.1",
|
"jed": "1.1.1",
|
||||||
"preact": "10.6.5",
|
"preact": "10.6.5",
|
||||||
"preact-router": "3.2.1",
|
"preact-router": "3.2.1",
|
||||||
|
"@gnu-taler/taler-util": "workspace:*",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"react": "npm:@preact/compat@^17.1.2",
|
"react": "npm:@preact/compat@^17.1.2",
|
||||||
"swr": "1.3.0"
|
"swr": "1.3.0"
|
||||||
|
@ -31,6 +31,7 @@ import { QR } from "../../components/QR.js";
|
|||||||
import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js";
|
import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js";
|
||||||
import { Translate, useTranslator } from "../../i18n/index.js";
|
import { Translate, useTranslator } from "../../i18n/index.js";
|
||||||
import "../../scss/main.scss";
|
import "../../scss/main.scss";
|
||||||
|
import { parsePaytoUri } from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the first argument does not look like a placeholder, return it.
|
* If the first argument does not look like a placeholder, return it.
|
||||||
@ -113,6 +114,7 @@ interface TransactionRequestType {
|
|||||||
interface CredentialsRequestType {
|
interface CredentialsRequestType {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
repeatPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -336,14 +338,16 @@ type RawPaytoInputType = string;
|
|||||||
type RawPaytoInputTypeOpt = RawPaytoInputType | undefined;
|
type RawPaytoInputTypeOpt = RawPaytoInputType | undefined;
|
||||||
function useRawPaytoInputType(
|
function useRawPaytoInputType(
|
||||||
state?: RawPaytoInputType,
|
state?: RawPaytoInputType,
|
||||||
): [RawPaytoInputTypeOpt, StateUpdater<RawPaytoInputTypeOpt>] {
|
): [RawPaytoInputTypeOpt, StateUpdater<RawPaytoInputTypeOpt>, boolean] {
|
||||||
const ret = useLocalStorage("raw-payto-input-state", state);
|
const ret = useLocalStorage("raw-payto-input-state", state);
|
||||||
|
const [dirty, setDirty] = useState(false);
|
||||||
const retObj: RawPaytoInputTypeOpt = ret[0];
|
const retObj: RawPaytoInputTypeOpt = ret[0];
|
||||||
const retSetter: StateUpdater<RawPaytoInputTypeOpt> = function (val) {
|
const retSetter: StateUpdater<RawPaytoInputTypeOpt> = function (val) {
|
||||||
const newVal = val instanceof Function ? val(retObj) : val;
|
const newVal = val instanceof Function ? val(retObj) : val;
|
||||||
|
setDirty(true);
|
||||||
ret[1](newVal);
|
ret[1](newVal);
|
||||||
};
|
};
|
||||||
return [retObj, retSetter];
|
return [retObj, retSetter, dirty];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1012,12 +1016,24 @@ function BankFrame(Props: any): VNode {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
function ShowInputErrorLabel({
|
||||||
|
isDirty,
|
||||||
|
message,
|
||||||
|
}: {
|
||||||
|
message: string | undefined;
|
||||||
|
isDirty: boolean;
|
||||||
|
}): VNode {
|
||||||
|
if (message && isDirty)
|
||||||
|
return <p class="informational informational-fail">{message}</p>;
|
||||||
|
return <Fragment />;
|
||||||
|
}
|
||||||
|
|
||||||
function PaytoWireTransfer(Props: any): VNode {
|
function PaytoWireTransfer(Props: any): VNode {
|
||||||
const currency = useContext(CurrencyContext);
|
const currency = useContext(CurrencyContext);
|
||||||
const [pageState, pageStateSetter] = useContext(PageContext); // NOTE: used for go-back button?
|
const [pageState, pageStateSetter] = useContext(PageContext); // NOTE: used for go-back button?
|
||||||
const [submitData, submitDataSetter] = useWireTransferRequestType();
|
const [submitData, submitDataSetter] = useWireTransferRequestType();
|
||||||
const [rawPaytoInput, rawPaytoInputSetter] = useRawPaytoInputType();
|
const [rawPaytoInput, rawPaytoInputSetter, rawPaytoInputDirty] =
|
||||||
|
useRawPaytoInputType();
|
||||||
const i18n = useTranslator();
|
const i18n = useTranslator();
|
||||||
const { focus, backendState } = Props;
|
const { focus, backendState } = Props;
|
||||||
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
||||||
@ -1159,6 +1175,14 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const errors = undefinedIfEmpty({
|
||||||
|
rawPaytoInput: !rawPaytoInput
|
||||||
|
? i18n`Missing payto address`
|
||||||
|
: !parsePaytoUri(rawPaytoInput)
|
||||||
|
? i18n`Payto does not follow the pattern`
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{i18n`Transfer money to account identified by payto:// URI:`}</p>
|
<p>{i18n`Transfer money to account identified by payto:// URI:`}</p>
|
||||||
@ -1168,17 +1192,21 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
<input
|
<input
|
||||||
name="address"
|
name="address"
|
||||||
type="text"
|
type="text"
|
||||||
size={90}
|
size={50}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id="address"
|
id="address"
|
||||||
value={rawPaytoInput}
|
value={rawPaytoInput}
|
||||||
required
|
required
|
||||||
placeholder={i18n`payto address`}
|
placeholder={i18n`payto address`}
|
||||||
pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
|
// pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
rawPaytoInputSetter(e.currentTarget.value);
|
rawPaytoInputSetter(e.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ShowInputErrorLabel
|
||||||
|
message={errors?.rawPaytoInput}
|
||||||
|
isDirty={rawPaytoInputDirty}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
Hint:
|
Hint:
|
||||||
@ -1192,6 +1220,7 @@ function PaytoWireTransfer(Props: any): VNode {
|
|||||||
<input
|
<input
|
||||||
class="pure-button pure-button-primary"
|
class="pure-button pure-button-primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
disabled={!!errors}
|
||||||
value={i18n`Send`}
|
value={i18n`Send`}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// empty string evaluates to false.
|
// empty string evaluates to false.
|
||||||
@ -1593,6 +1622,11 @@ function RegistrationButton(Props: any): VNode {
|
|||||||
return <span />;
|
return <span />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
|
||||||
|
return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
|
||||||
|
? obj
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Collect and submit login data.
|
* Collect and submit login data.
|
||||||
*/
|
*/
|
||||||
@ -1604,6 +1638,16 @@ function LoginForm(Props: any): VNode {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ref.current?.focus();
|
ref.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const errors = undefinedIfEmpty({
|
||||||
|
username: !(submitData && submitData.username)
|
||||||
|
? i18n`Missing username`
|
||||||
|
: undefined,
|
||||||
|
password: !(submitData && submitData.password)
|
||||||
|
? i18n`Missing password`
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="login-div">
|
<div class="login-div">
|
||||||
<form action="javascript:void(0);" class="login-form">
|
<form action="javascript:void(0);" class="login-form">
|
||||||
@ -1649,6 +1693,7 @@ function LoginForm(Props: any): VNode {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="pure-button pure-button-primary"
|
class="pure-button pure-button-primary"
|
||||||
|
disabled={!!errors}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (typeof submitData === "undefined") {
|
if (typeof submitData === "undefined") {
|
||||||
console.log("login data is undefined", submitData);
|
console.log("login data is undefined", submitData);
|
||||||
@ -1691,7 +1736,19 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
const [pageState, pageStateSetter] = useContext(PageContext);
|
const [pageState, pageStateSetter] = useContext(PageContext);
|
||||||
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
||||||
const i18n = useTranslator();
|
const i18n = useTranslator();
|
||||||
// https://stackoverflow.com/questions/36683770/how-to-get-the-value-of-an-input-field-using-reactjs
|
|
||||||
|
const errors = !submitData
|
||||||
|
? undefined
|
||||||
|
: undefinedIfEmpty({
|
||||||
|
username: !submitData.username ? i18n`Missing username` : undefined,
|
||||||
|
password: !submitData.password ? i18n`Missing password` : undefined,
|
||||||
|
repeatPassword: !submitData.repeatPassword
|
||||||
|
? i18n`Missing password`
|
||||||
|
: submitData.repeatPassword !== submitData.password
|
||||||
|
? i18n`Password don't match`
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h1 class="nav">{i18n`Welcome to ${UI_BANK_NAME}!`}</h1>
|
<h1 class="nav">{i18n`Welcome to ${UI_BANK_NAME}!`}</h1>
|
||||||
@ -1735,6 +1792,24 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<p class="unameFieldLabel registerFieldLabel formFieldLabel">
|
||||||
|
<label for="register-repeat">{i18n`Repeat Password:`}</label>
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
name="register-repeat"
|
||||||
|
id="register-repeat"
|
||||||
|
placeholder="Same password"
|
||||||
|
value={submitData && submitData.repeatPassword}
|
||||||
|
required
|
||||||
|
onInput={(e): void => {
|
||||||
|
submitDataSetter((submitData: any) => ({
|
||||||
|
...submitData,
|
||||||
|
repeatPassword: e.currentTarget.value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
{/*
|
{/*
|
||||||
<label for="phone">{i18n`Phone number:`}</label>
|
<label for="phone">{i18n`Phone number:`}</label>
|
||||||
@ -1755,28 +1830,10 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
*/}
|
*/}
|
||||||
<button
|
<button
|
||||||
class="pure-button pure-button-primary btn-register"
|
class="pure-button pure-button-primary btn-register"
|
||||||
|
disabled={!!errors}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("maybe submitting the registration..");
|
console.log("maybe submitting the registration..");
|
||||||
console.log(submitData);
|
if (!submitData) return;
|
||||||
if (typeof submitData === "undefined") {
|
|
||||||
console.log(`submit data ${submitData} is undefined`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof submitData.password === "undefined" ||
|
|
||||||
typeof submitData.username === "undefined"
|
|
||||||
) {
|
|
||||||
console.log("username or password is undefined");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
submitData.password.length === 0 ||
|
|
||||||
submitData.username.length === 0
|
|
||||||
) {
|
|
||||||
console.log("username or password are the empty string");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("submitting the registration..");
|
|
||||||
registrationCall(
|
registrationCall(
|
||||||
{ ...submitData },
|
{ ...submitData },
|
||||||
Props.backendStateSetter, // will store BE URL, if OK.
|
Props.backendStateSetter, // will store BE URL, if OK.
|
||||||
@ -1790,7 +1847,11 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
* strings due to a non lively update of the <input> fields
|
* strings due to a non lively update of the <input> fields
|
||||||
* after setting to undefined.
|
* after setting to undefined.
|
||||||
*/
|
*/
|
||||||
submitDataSetter({ username: "", password: "" });
|
submitDataSetter({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
repeatPassword: "",
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n`Register`}
|
{i18n`Register`}
|
||||||
@ -1799,6 +1860,11 @@ function RegistrationForm(Props: any): VNode {
|
|||||||
<button
|
<button
|
||||||
class="pure-button pure-button-secondary btn-cancel"
|
class="pure-button pure-button-secondary btn-cancel"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
submitDataSetter({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
repeatPassword: "",
|
||||||
|
});
|
||||||
pageStateSetter((prevState: PageStateType) => ({
|
pageStateSetter((prevState: PageStateType) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
tryRegister: false,
|
tryRegister: false,
|
||||||
|
@ -120,7 +120,7 @@ input {
|
|||||||
|
|
||||||
.large-amount {
|
.large-amount {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: x-large;
|
font-size: xxx-large;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency {
|
.currency {
|
||||||
@ -259,3 +259,6 @@ html {
|
|||||||
.hint {
|
.hint {
|
||||||
scale: 0.7;
|
scale: 0.7;
|
||||||
}
|
}
|
||||||
|
h1.nav {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@ -96,6 +96,7 @@ importers:
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@creativebulma/bulma-tooltip': ^1.2.0
|
'@creativebulma/bulma-tooltip': ^1.2.0
|
||||||
'@gnu-taler/pogen': ^0.0.5
|
'@gnu-taler/pogen': ^0.0.5
|
||||||
|
'@gnu-taler/taler-util': workspace:*
|
||||||
'@typescript-eslint/eslint-plugin': ^5.41.0
|
'@typescript-eslint/eslint-plugin': ^5.41.0
|
||||||
'@typescript-eslint/parser': ^5.41.0
|
'@typescript-eslint/parser': ^5.41.0
|
||||||
bulma: ^0.9.4
|
bulma: ^0.9.4
|
||||||
@ -116,6 +117,7 @@ importers:
|
|||||||
swr: 1.3.0
|
swr: 1.3.0
|
||||||
typescript: ^4.4.4
|
typescript: ^4.4.4
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@gnu-taler/taler-util': link:../taler-util
|
||||||
date-fns: 2.29.3
|
date-fns: 2.29.3
|
||||||
jed: 1.1.1
|
jed: 1.1.1
|
||||||
preact: 10.6.5
|
preact: 10.6.5
|
||||||
|
Loading…
Reference in New Issue
Block a user