diff options
Diffstat (limited to 'packages')
-rw-r--r-- | packages/demobank-ui/src/declaration.d.ts | 13 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/AdminPage.tsx | 473 |
2 files changed, 259 insertions, 227 deletions
diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index 03ab8f2a8..8dc5fd8b2 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -99,6 +99,11 @@ type Amount = string; type UUID = string; type Integer = number; +interface Balance { + amount: Amount; + credit_debit_indicator: "credit" | "debit"; +} + namespace SandboxBackend { export interface Config { // Name of this API, always "circuit". @@ -161,10 +166,7 @@ namespace SandboxBackend { interface BankAccountBalanceResponse { // Available balance on the account. - balance: { - amount: Amount; - credit_debit_indicator: "credit" | "debit"; - }; + balance: Balance; // payto://-URI of the account. (New) paytoUri: string; } @@ -304,6 +306,9 @@ namespace SandboxBackend { // Legal subject owning the account. name: string; + + // current balance of the account + balance: Balance; } interface CircuitAccountData { diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx index 3d0c09cbf..58b048b83 100644 --- a/packages/demobank-ui/src/pages/AdminPage.tsx +++ b/packages/demobank-ui/src/pages/AdminPage.tsx @@ -230,7 +230,10 @@ export function AdminPage({ onLoadNotOk }: Props): VNode { </div> </p> - <section id="main"> + <section + id="main" + style={{ width: 600, marginLeft: "auto", marginRight: "auto" }} + > {!customers.length ? ( <div></div> ) : ( @@ -242,12 +245,18 @@ export function AdminPage({ onLoadNotOk }: Props): VNode { <tr> <th>{i18n.str`Username`}</th> <th>{i18n.str`Name`}</th> - <th></th> - <th></th> + <th>{i18n.str`Balance`}</th> + <th>{i18n.str`Actions`}</th> </tr> </thead> <tbody> {customers.map((item, idx) => { + const balance = !item.balance + ? undefined + : Amounts.parse(item.balance.amount); + const balanceIsDebit = + item.balance && + item.balance.credit_debit_indicator == "debit"; return ( <tr key={idx}> <td> @@ -263,6 +272,20 @@ export function AdminPage({ onLoadNotOk }: Props): VNode { </td> <td>{item.name}</td> <td> + {!balance ? ( + i18n.str`unknown` + ) : ( + <span class="amount"> + {balanceIsDebit ? <b>-</b> : null} + <span class="value">{`${Amounts.stringifyValue( + balance, + )}`}</span> + + <span class="currency">{`${balance.currency}`}</span> + </span> + )} + </td> + <td> <a href="#" onClick={(e) => { @@ -272,8 +295,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode { > change password </a> - </td> - <td> + <a href="#" onClick={(e) => { @@ -283,8 +305,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode { > cashouts </a> - </td> - <td> + <a href="#" onClick={(e) => { @@ -384,82 +405,84 @@ export function UpdateAccountPassword({ <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} /> )} - <form class="pure-form"> - <fieldset> - <label>{i18n.str`Password`}</label> - <input - type="password" - value={password ?? ""} - onChange={(e) => { - setPassword(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.password} - isDirty={password !== undefined} - /> - </fieldset> - <fieldset> - <label>{i18n.str`Repeast password`}</label> - <input - type="password" - value={repeat ?? ""} - onChange={(e) => { - setRepeat(e.currentTarget.value); - }} - /> - <ShowInputErrorLabel - message={errors?.repeat} - isDirty={repeat !== undefined} - /> - </fieldset> - </form> - <p> - <div style={{ display: "flex", justifyContent: "space-between" }}> - <div> + <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}> + <form class="pure-form"> + <fieldset> + <label>{i18n.str`Password`}</label> <input - class="pure-button" - type="submit" - value={i18n.str`Close`} - onClick={async (e) => { - e.preventDefault(); - onClear(); + type="password" + value={password ?? ""} + onChange={(e) => { + setPassword(e.currentTarget.value); }} /> - </div> - <div> + <ShowInputErrorLabel + message={errors?.password} + isDirty={password !== undefined} + /> + </fieldset> + <fieldset> + <label>{i18n.str`Repeast password`}</label> <input - id="select-exchange" - class="pure-button pure-button-primary content" - disabled={!!errors} - type="submit" - value={i18n.str`Confirm`} - onClick={async (e) => { - e.preventDefault(); - if (!!errors || !password) return; - try { - const r = await changePassword(account, { - new_password: password, - }); - onUpdateSuccess(); - } catch (error) { - if (error instanceof RequestError) { - saveError(buildRequestErrorMessage(i18n, error.cause)); - } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); - } - } + type="password" + value={repeat ?? ""} + onChange={(e) => { + setRepeat(e.currentTarget.value); }} /> + <ShowInputErrorLabel + message={errors?.repeat} + isDirty={repeat !== undefined} + /> + </fieldset> + </form> + <p> + <div style={{ display: "flex", justifyContent: "space-between" }}> + <div> + <input + class="pure-button" + type="submit" + value={i18n.str`Close`} + onClick={async (e) => { + e.preventDefault(); + onClear(); + }} + /> + </div> + <div> + <input + id="select-exchange" + class="pure-button pure-button-primary content" + disabled={!!errors} + type="submit" + value={i18n.str`Confirm`} + onClick={async (e) => { + e.preventDefault(); + if (!!errors || !password) return; + try { + const r = await changePassword(account, { + new_password: password, + }); + onUpdateSuccess(); + } catch (error) { + if (error instanceof RequestError) { + saveError(buildRequestErrorMessage(i18n, error.cause)); + } else { + saveError({ + title: i18n.str`Operation failed, please report`, + description: + error instanceof Error + ? error.message + : JSON.stringify(error), + }); + } + } + }} + /> + </div> </div> - </div> - </p> + </p> + </div> </div> ); } @@ -488,81 +511,83 @@ function CreateNewAccount({ <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} /> )} - <AccountForm - template={undefined} - purpose="create" - onChange={(a) => { - console.log(a); - setSubmitAccount(a); - }} - /> + <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}> + <AccountForm + template={undefined} + purpose="create" + onChange={(a) => { + console.log(a); + setSubmitAccount(a); + }} + /> - <p> - <div style={{ display: "flex", justifyContent: "space-between" }}> - <div> - <input - class="pure-button" - type="submit" - value={i18n.str`Close`} - onClick={async (e) => { - e.preventDefault(); - onClose(); - }} - /> - </div> - <div> - <input - id="select-exchange" - class="pure-button pure-button-primary content" - disabled={!submitAccount} - type="submit" - value={i18n.str`Confirm`} - onClick={async (e) => { - e.preventDefault(); + <p> + <div style={{ display: "flex", justifyContent: "space-between" }}> + <div> + <input + class="pure-button" + type="submit" + value={i18n.str`Close`} + onClick={async (e) => { + e.preventDefault(); + onClose(); + }} + /> + </div> + <div> + <input + id="select-exchange" + class="pure-button pure-button-primary content" + disabled={!submitAccount} + type="submit" + value={i18n.str`Confirm`} + onClick={async (e) => { + e.preventDefault(); - if (!submitAccount) return; - try { - const account: SandboxBackend.Circuit.CircuitAccountRequest = - { - cashout_address: submitAccount.cashout_address, - contact_data: submitAccount.contact_data, - internal_iban: submitAccount.iban, - name: submitAccount.name, - username: submitAccount.username, - password: randomPassword(), - }; - - await createAccount(account); - onCreateSuccess(account.password); - } catch (error) { - if (error instanceof RequestError) { - saveError( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Forbidden - ? i18n.str`The rights to perform the operation are not sufficient` - : status === HttpStatusCode.BadRequest - ? i18n.str`Input data was invalid` - : status === HttpStatusCode.Conflict - ? i18n.str`At least one registration detail was not available` - : undefined, - }), - ); - } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), - }); + if (!submitAccount) return; + try { + const account: SandboxBackend.Circuit.CircuitAccountRequest = + { + cashout_address: submitAccount.cashout_address, + contact_data: submitAccount.contact_data, + internal_iban: submitAccount.iban, + name: submitAccount.name, + username: submitAccount.username, + password: randomPassword(), + }; + + await createAccount(account); + onCreateSuccess(account.password); + } catch (error) { + if (error instanceof RequestError) { + saveError( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The rights to perform the operation are not sufficient` + : status === HttpStatusCode.BadRequest + ? i18n.str`Input data was invalid` + : status === HttpStatusCode.Conflict + ? i18n.str`At least one registration detail was not available` + : undefined, + }), + ); + } else { + saveError({ + title: i18n.str`Operation failed, please report`, + description: + error instanceof Error + ? error.message + : JSON.stringify(error), + }); + } } - } - }} - /> + }} + /> + </div> </div> - </div> - </p> + </p> + </div> </div> ); } @@ -608,90 +633,92 @@ export function ShowAccountDetails({ {error && ( <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} /> )} - <AccountForm - template={result.data} - purpose={update ? "update" : "show"} - onChange={(a) => setSubmitAccount(a)} - /> + <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}> + <AccountForm + template={result.data} + purpose={update ? "update" : "show"} + onChange={(a) => setSubmitAccount(a)} + /> - <p> - <div style={{ display: "flex", justifyContent: "space-between" }}> - <div> - {onClear ? ( - <input - class="pure-button" - type="submit" - value={i18n.str`Close`} - onClick={async (e) => { - e.preventDefault(); - onClear(); - }} - /> - ) : undefined} - </div> - <div style={{ display: "flex" }}> + <p> + <div style={{ display: "flex", justifyContent: "space-between" }}> <div> - <input - id="select-exchange" - class="pure-button pure-button-primary content" - disabled={update && !submitAccount} - type="submit" - value={i18n.str`Change password`} - onClick={async (e) => { - e.preventDefault(); - onChangePassword(); - }} - /> + {onClear ? ( + <input + class="pure-button" + type="submit" + value={i18n.str`Close`} + onClick={async (e) => { + e.preventDefault(); + onClear(); + }} + /> + ) : undefined} </div> - <div> - <input - id="select-exchange" - class="pure-button pure-button-primary content" - disabled={update && !submitAccount} - type="submit" - value={update ? i18n.str`Confirm` : i18n.str`Update`} - onClick={async (e) => { - e.preventDefault(); - - if (!update) { - setUpdate(true); - } else { - if (!submitAccount) return; - try { - await updateAccount(account, { - cashout_address: submitAccount.cashout_address, - contact_data: submitAccount.contact_data, - }); - onUpdateSuccess(); - } catch (error) { - if (error instanceof RequestError) { - saveError( - buildRequestErrorMessage(i18n, error.cause, { - onClientError: (status) => - status === HttpStatusCode.Forbidden - ? i18n.str`The rights to change the account are not sufficient` - : status === HttpStatusCode.NotFound - ? i18n.str`The username was not found` - : undefined, - }), - ); - } else { - saveError({ - title: i18n.str`Operation failed, please report`, - description: - error instanceof Error - ? error.message - : JSON.stringify(error), + <div style={{ display: "flex" }}> + <div> + <input + id="select-exchange" + class="pure-button pure-button-primary content" + disabled={update && !submitAccount} + type="submit" + value={i18n.str`Change password`} + onClick={async (e) => { + e.preventDefault(); + onChangePassword(); + }} + /> + </div> + <div> + <input + id="select-exchange" + class="pure-button pure-button-primary content" + disabled={update && !submitAccount} + type="submit" + value={update ? i18n.str`Confirm` : i18n.str`Update`} + onClick={async (e) => { + e.preventDefault(); + + if (!update) { + setUpdate(true); + } else { + if (!submitAccount) return; + try { + await updateAccount(account, { + cashout_address: submitAccount.cashout_address, + contact_data: submitAccount.contact_data, }); + onUpdateSuccess(); + } catch (error) { + if (error instanceof RequestError) { + saveError( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The rights to change the account are not sufficient` + : status === HttpStatusCode.NotFound + ? i18n.str`The username was not found` + : undefined, + }), + ); + } else { + saveError({ + title: i18n.str`Operation failed, please report`, + description: + error instanceof Error + ? error.message + : JSON.stringify(error), + }); + } } } - } - }} - /> + }} + /> + </div> </div> </div> - </div> - </p> + </p> + </div> </div> ); } |