diff options
Diffstat (limited to 'packages/demobank-ui/src')
-rw-r--r-- | packages/demobank-ui/src/pages/home/index.tsx | 120 | ||||
-rw-r--r-- | packages/demobank-ui/src/scss/bank.scss | 5 |
2 files changed, 97 insertions, 28 deletions
diff --git a/packages/demobank-ui/src/pages/home/index.tsx b/packages/demobank-ui/src/pages/home/index.tsx index 6fbb00ce4..8ab815efd 100644 --- a/packages/demobank-ui/src/pages/home/index.tsx +++ b/packages/demobank-ui/src/pages/home/index.tsx @@ -31,6 +31,7 @@ import { QR } from "../../components/QR.js"; import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js"; import { Translate, useTranslator } from "../../i18n/index.js"; import "../../scss/main.scss"; +import { parsePaytoUri } from "@gnu-taler/taler-util"; /** * If the first argument does not look like a placeholder, return it. @@ -113,6 +114,7 @@ interface TransactionRequestType { interface CredentialsRequestType { username: string; password: string; + repeatPassword: string; } /** @@ -336,14 +338,16 @@ type RawPaytoInputType = string; type RawPaytoInputTypeOpt = RawPaytoInputType | undefined; function useRawPaytoInputType( state?: RawPaytoInputType, -): [RawPaytoInputTypeOpt, StateUpdater<RawPaytoInputTypeOpt>] { +): [RawPaytoInputTypeOpt, StateUpdater<RawPaytoInputTypeOpt>, boolean] { const ret = useLocalStorage("raw-payto-input-state", state); + const [dirty, setDirty] = useState(false); const retObj: RawPaytoInputTypeOpt = ret[0]; const retSetter: StateUpdater<RawPaytoInputTypeOpt> = function (val) { const newVal = val instanceof Function ? val(retObj) : val; + setDirty(true); ret[1](newVal); }; - return [retObj, retSetter]; + return [retObj, retSetter, dirty]; } /** @@ -1012,12 +1016,24 @@ function BankFrame(Props: any): VNode { </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 { const currency = useContext(CurrencyContext); const [pageState, pageStateSetter] = useContext(PageContext); // NOTE: used for go-back button? const [submitData, submitDataSetter] = useWireTransferRequestType(); - const [rawPaytoInput, rawPaytoInputSetter] = useRawPaytoInputType(); + const [rawPaytoInput, rawPaytoInputSetter, rawPaytoInputDirty] = + useRawPaytoInputType(); const i18n = useTranslator(); const { focus, backendState } = Props; const amountRegex = "^[0-9]+(.[0-9]+)?$"; @@ -1159,6 +1175,14 @@ function PaytoWireTransfer(Props: any): VNode { </div> ); + const errors = undefinedIfEmpty({ + rawPaytoInput: !rawPaytoInput + ? i18n`Missing payto address` + : !parsePaytoUri(rawPaytoInput) + ? i18n`Payto does not follow the pattern` + : undefined, + }); + return ( <div> <p>{i18n`Transfer money to account identified by payto:// URI:`}</p> @@ -1168,17 +1192,21 @@ function PaytoWireTransfer(Props: any): VNode { <input name="address" type="text" - size={90} + size={50} ref={ref} id="address" value={rawPaytoInput} required 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 => { rawPaytoInputSetter(e.currentTarget.value); }} /> + <ShowInputErrorLabel + message={errors?.rawPaytoInput} + isDirty={rawPaytoInputDirty} + /> <br /> <div class="hint"> Hint: @@ -1192,6 +1220,7 @@ function PaytoWireTransfer(Props: any): VNode { <input class="pure-button pure-button-primary" type="submit" + disabled={!!errors} value={i18n`Send`} onClick={async () => { // empty string evaluates to false. @@ -1593,6 +1622,11 @@ function RegistrationButton(Props: any): VNode { 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. */ @@ -1604,6 +1638,16 @@ function LoginForm(Props: any): VNode { useEffect(() => { ref.current?.focus(); }, []); + + const errors = undefinedIfEmpty({ + username: !(submitData && submitData.username) + ? i18n`Missing username` + : undefined, + password: !(submitData && submitData.password) + ? i18n`Missing password` + : undefined, + }); + return ( <div class="login-div"> <form action="javascript:void(0);" class="login-form"> @@ -1649,6 +1693,7 @@ function LoginForm(Props: any): VNode { <button type="submit" class="pure-button pure-button-primary" + disabled={!!errors} onClick={() => { if (typeof submitData === "undefined") { console.log("login data is undefined", submitData); @@ -1691,7 +1736,19 @@ function RegistrationForm(Props: any): VNode { const [pageState, pageStateSetter] = useContext(PageContext); const [submitData, submitDataSetter] = useCredentialsRequestType(); 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 ( <Fragment> <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 /> {/* <label for="phone">{i18n`Phone number:`}</label> @@ -1755,28 +1830,10 @@ function RegistrationForm(Props: any): VNode { */} <button class="pure-button pure-button-primary btn-register" + disabled={!!errors} onClick={() => { console.log("maybe submitting the registration.."); - console.log(submitData); - 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.."); + if (!submitData) return; registrationCall( { ...submitData }, 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 * after setting to undefined. */ - submitDataSetter({ username: "", password: "" }); + submitDataSetter({ + username: "", + password: "", + repeatPassword: "", + }); }} > {i18n`Register`} @@ -1799,6 +1860,11 @@ function RegistrationForm(Props: any): VNode { <button class="pure-button pure-button-secondary btn-cancel" onClick={() => { + submitDataSetter({ + username: "", + password: "", + repeatPassword: "", + }); pageStateSetter((prevState: PageStateType) => ({ ...prevState, tryRegister: false, diff --git a/packages/demobank-ui/src/scss/bank.scss b/packages/demobank-ui/src/scss/bank.scss index 55ac21588..fec0ea5c9 100644 --- a/packages/demobank-ui/src/scss/bank.scss +++ b/packages/demobank-ui/src/scss/bank.scss @@ -120,7 +120,7 @@ input { .large-amount { font-weight: bold; - font-size: x-large; + font-size: xxx-large; } .currency { @@ -259,3 +259,6 @@ html { .hint { scale: 0.7; } +h1.nav { + text-align: center; +} |