more ui: pagination

This commit is contained in:
Sebastian 2023-09-25 11:58:17 -03:00
parent 0b2c03dc5e
commit 4041a76a58
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
16 changed files with 151 additions and 106 deletions

View File

@ -17,7 +17,7 @@
import { createHashHistory } from "history"; import { createHashHistory } from "history";
import { VNode, h } from "preact"; import { VNode, h } from "preact";
import { Route, Router, route } from "preact-router"; import { Route, Router, route } from "preact-router";
import { useEffect } from "preact/hooks"; import { useEffect, useErrorBoundary } from "preact/hooks";
import { BankFrame } from "../pages/BankFrame.js"; import { BankFrame } from "../pages/BankFrame.js";
import { BusinessAccount } from "../pages/business/Home.js"; import { BusinessAccount } from "../pages/business/Home.js";
import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js"; import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
@ -27,6 +27,7 @@ import { useBackendContext } from "../context/backend.js";
import { LoginForm } from "../pages/LoginForm.js"; import { LoginForm } from "../pages/LoginForm.js";
import { AdminHome } from "../pages/admin/Home.js"; import { AdminHome } from "../pages/admin/Home.js";
import { bankUiSettings } from "../settings.js"; import { bankUiSettings } from "../settings.js";
import { notifyError } from "@gnu-taler/web-util/browser";
export function Routing(): VNode { export function Routing(): VNode {
const history = createHashHistory(); const history = createHashHistory();

View File

@ -46,6 +46,8 @@ export namespace State {
status: "ready"; status: "ready";
error: undefined; error: undefined;
transactions: Transaction[]; transactions: Transaction[];
onPrev?: () => void;
onNext?: () => void;
} }
} }

View File

@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util"; import { AbsoluteTime, Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
import { useTransactions } from "../../hooks/access.js"; import { useTransactions } from "../../hooks/access.js";
import { Props, State, Transaction } from "./index.js"; import { Props, State, Transaction } from "./index.js";
@ -34,45 +34,19 @@ export function useComponentState({ account }: Props): State {
} }
const transactions = result.data.transactions const transactions = result.data.transactions
.map((item: unknown) => { .map((tx) => {
if (
!item ||
typeof item !== "object" ||
!("direction" in item) ||
!("creditorIban" in item) ||
!("debtorIban" in item) ||
!("date" in item) ||
!("subject" in item) ||
!("currency" in item) ||
!("amount" in item)
) {
//not valid
return;
}
const anyItem = item as any;
if (
!(typeof anyItem.creditorIban === "string") ||
!(typeof anyItem.debtorIban === "string") ||
!(typeof anyItem.date === "string") ||
!(typeof anyItem.subject === "string") ||
!(typeof anyItem.currency === "string") ||
!(typeof anyItem.amount === "string")
) {
return;
}
const negative = anyItem.direction === "DEBIT"; const negative = tx.direction === "debit";
const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban; const cp = parsePaytoUri(negative ? tx.creditor_payto_uri : tx.debtor_payto_uri);
const counterpart = (cp === undefined || !cp.isKnown ? undefined :
cp.targetType === "iban" ? cp.iban :
cp.targetType === "x-taler-bank" ? cp.account :
cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 6)}...` : undefined) ??
"unkown";
let date = anyItem.date ? parseInt(anyItem.date, 10) : 0; const when = AbsoluteTime.fromMilliseconds(tx.date / 1000);
if (isNaN(date) || !isFinite(date)) { const amount = Amounts.parse(tx.amount);
date = 0; const subject = tx.subject;
}
const when: AbsoluteTime = !date
? AbsoluteTime.never()
: AbsoluteTime.fromMilliseconds(date);
const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`);
const subject = anyItem.subject;
return { return {
negative, negative,
counterpart, counterpart,
@ -87,5 +61,7 @@ export function useComponentState({ account }: Props): State {
status: "ready", status: "ready",
error: undefined, error: undefined,
transactions, transactions,
onNext: result.isReachingEnd ? undefined : result.loadMore,
onPrev: result.isReachingStart ? undefined : result.loadMorePrev,
}; };
} }

View File

@ -30,7 +30,7 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
); );
} }
export function ReadyView({ transactions }: State.Ready): VNode { export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
if (!transactions.length) return <div /> if (!transactions.length) return <div />
const txByDate = transactions.reduce((prev, cur) => { const txByDate = transactions.reduce((prev, cur) => {
@ -50,7 +50,7 @@ export function ReadyView({ transactions }: State.Ready): VNode {
<h1 class="text-base font-semibold leading-6 text-gray-900"><i18n.Translate>Latest transactions</i18n.Translate></h1> <h1 class="text-base font-semibold leading-6 text-gray-900"><i18n.Translate>Latest transactions</i18n.Translate></h1>
</div> </div>
</div> </div>
<div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 sm:rounded-lg min-w-fit bg-white"> <div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 rounded-lg min-w-fit bg-white">
<table class="min-w-full divide-y divide-gray-300"> <table class="min-w-full divide-y divide-gray-300">
<thead> <thead>
<tr> <tr>
@ -89,9 +89,30 @@ export function ReadyView({ transactions }: State.Ready): VNode {
</tr>) </tr>)
})} })}
</Fragment> </Fragment>
})} })}
</tbody> </tbody>
</table> </table>
<nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
<div class="flex flex-1 justify-between sm:justify-end">
<button
class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
disabled={!onPrev}
onClick={onPrev}
>
<i18n.Translate>First page</i18n.Translate>
</button>
<button
class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
disabled={!onNext}
onClick={onNext}
>
<i18n.Translate>Next</i18n.Translate>
</button>
</div>
</nav>
</div> </div>
</div> </div>
); );

View File

@ -194,28 +194,25 @@ namespace SandboxBackend {
} }
interface BankAccountTransactionInfo { interface BankAccountTransactionInfo {
creditorIban: string; creditor_payto_uri: string;
creditorBic: string; // Optional debtor_payto_uri: string;
creditorName: string;
debtorIban: string; amount: Amount;
debtorBic: string; direction: "debit" | "credit";
debtorName: string;
amount: number;
currency: string;
subject: string; subject: string;
// Transaction unique ID. Matches // Transaction unique ID. Matches
// $transaction_id from the URI. // $transaction_id from the URI.
uid: string; row_id: number;
direction: "DBIT" | "CRDT"; date: number;
date: string; // milliseconds since the Unix epoch // date: Timestamp;
} }
interface CreateBankAccountTransactionCreate { interface CreateBankAccountTransactionCreate {
// Address in the Payto format of the wire transfer receiver. // Address in the Payto format of the wire transfer receiver.
// It needs at least the 'message' query string parameter. // It needs at least the 'message' query string parameter.
paytoUri: string; payto_uri: string;
// Transaction amount (in the $currency:x.y format), optional. // Transaction amount (in the $currency:x.y format), optional.
// However, when not given, its value must occupy the 'amount' // However, when not given, its value must occupy the 'amount'
@ -326,32 +323,32 @@ namespace SandboxBackend {
interface AccountMinimalData { interface AccountMinimalData {
// Username // Username
username: string; username: string;
// Legal name of the account owner. // Legal name of the account owner.
name: string; name: string;
// current balance of the account // current balance of the account
balance: Balance; balance: Balance;
// Number indicating the max debit allowed for the requesting user. // Number indicating the max debit allowed for the requesting user.
debit_threshold: Amount; debit_threshold: Amount;
} }
interface AccountData { interface AccountData {
// Legal name of the account owner. // Legal name of the account owner.
name: string; name: string;
// Available balance on the account. // Available balance on the account.
balance: Balance; balance: Balance;
// payto://-URI of the account. // payto://-URI of the account.
payto_uri: string; payto_uri: string;
// Number indicating the max debit allowed for the requesting user. // Number indicating the max debit allowed for the requesting user.
debit_threshold: Amount; debit_threshold: Amount;
contact_data?: ChallengeContactData; contact_data?: ChallengeContactData;
// 'payto' address pointing the bank account // 'payto' address pointing the bank account
// where to send cashouts. This field is optional // where to send cashouts. This field is optional
// because not all the accounts are required to participate // because not all the accounts are required to participate
@ -360,7 +357,7 @@ namespace SandboxBackend {
// be done via the access API. // be done via the access API.
cashout_payto_uri?: string; cashout_payto_uri?: string;
} }
} }
namespace Circuit { namespace Circuit {

View File

@ -255,7 +255,7 @@ export function useTransactionDetails(
} }
interface PaginationFilter { interface PaginationFilter {
page: number; // page: number;
} }
export function usePublicAccounts( export function usePublicAccounts(
@ -275,7 +275,7 @@ export function usePublicAccounts(
} = useSWR< } = useSWR<
HttpResponseOk<SandboxBackend.CoreBank.PublicAccountsResponse>, HttpResponseOk<SandboxBackend.CoreBank.PublicAccountsResponse>,
RequestError<SandboxBackend.SandboxError> RequestError<SandboxBackend.SandboxError>
>([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher); >([`public-accounts`, page, PAGE_SIZE], paginatedFetcher);
const [lastAfter, setLastAfter] = useState< const [lastAfter, setLastAfter] = useState<
HttpResponse< HttpResponse<
@ -334,7 +334,7 @@ export function useTransactions(
> { > {
const { paginatedFetcher } = useAuthenticatedBackend(); const { paginatedFetcher } = useAuthenticatedBackend();
const [page, setPage] = useState(1); const [start, setStart] = useState<string>();
const { const {
data: afterData, data: afterData,
@ -344,7 +344,7 @@ export function useTransactions(
HttpResponseOk<SandboxBackend.CoreBank.BankAccountTransactionsResponse>, HttpResponseOk<SandboxBackend.CoreBank.BankAccountTransactionsResponse>,
RequestError<SandboxBackend.SandboxError> RequestError<SandboxBackend.SandboxError>
>( >(
[`accounts/${account}/transactions`, args?.page, PAGE_SIZE], [`accounts/${account}/transactions`, start, PAGE_SIZE],
paginatedFetcher, { paginatedFetcher, {
refreshInterval: 0, refreshInterval: 0,
refreshWhenHidden: false, refreshWhenHidden: false,
@ -374,19 +374,24 @@ export function useTransactions(
// if the query returns less that we ask, then we have reach the end or beginning // if the query returns less that we ask, then we have reach the end or beginning
const isReachingEnd = const isReachingEnd =
afterData && afterData.data.transactions.length < PAGE_SIZE; afterData && afterData.data.transactions.length < PAGE_SIZE;
const isReachingStart = false; const isReachingStart = start == undefined;
const pagination = { const pagination = {
isReachingEnd, isReachingEnd,
isReachingStart, isReachingStart,
loadMore: () => { loadMore: () => {
if (!afterData || isReachingEnd) return; if (!afterData || isReachingEnd) return;
if (afterData.data.transactions.length < MAX_RESULT_SIZE) { // if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
setPage(page + 1); // console.log("load more", page)
} const l = afterData.data.transactions[afterData.data.transactions.length-1]
setStart(String(l.row_id));
// }
}, },
loadMorePrev: () => { loadMorePrev: () => {
null; if (!afterData || isReachingStart) return;
// if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
setStart(undefined)
// }
}, },
}; };

View File

@ -159,7 +159,7 @@ interface useBackendType {
fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>; fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
multiFetcher: <T>(endpoint: string[][]) => Promise<HttpResponseOk<T>[]>; multiFetcher: <T>(endpoint: string[][]) => Promise<HttpResponseOk<T>[]>;
paginatedFetcher: <T>( paginatedFetcher: <T>(
args: [string, number, number], args: [string, string | undefined, number],
) => Promise<HttpResponseOk<T>>; ) => Promise<HttpResponseOk<T>>;
sandboxAccountsFetcher: <T>( sandboxAccountsFetcher: <T>(
args: [string, number, number, string], args: [string, number, number, string],
@ -188,13 +188,15 @@ export function usePublicBackend(): useBackendType {
[baseUrl], [baseUrl],
); );
const paginatedFetcher = useCallback( const paginatedFetcher = useCallback(
function fetcherImpl<T>([endpoint, page, size]: [ function fetcherImpl<T>([endpoint, start, size]: [
string, string,
number, string | undefined,
number, number,
]): Promise<HttpResponseOk<T>> { ]): Promise<HttpResponseOk<T>> {
const delta = -1 * size //descending order
const params = start ? { delta, start } : {delta}
return requestHandler<T>(baseUrl, endpoint, { return requestHandler<T>(baseUrl, endpoint, {
params: { page: page || 1, size }, params,
}); });
}, },
[baseUrl], [baseUrl],
@ -280,14 +282,16 @@ export function useAuthenticatedBackend(): useBackendType {
[baseUrl, creds], [baseUrl, creds],
); );
const paginatedFetcher = useCallback( const paginatedFetcher = useCallback(
function fetcherImpl<T>([endpoint, page = 1, size]: [ function fetcherImpl<T>([endpoint, start, size]: [
string, string,
number, string | undefined,
number, number,
]): Promise<HttpResponseOk<T>> { ]): Promise<HttpResponseOk<T>> {
const delta = -1 * size //descending order
const params = start ? { delta, start } : {delta}
return requestHandler<T>(baseUrl, endpoint, { return requestHandler<T>(baseUrl, endpoint, {
token: creds, token: creds,
params: { delta: size, start: size * page }, params,
}); });
}, },
[baseUrl, creds], [baseUrl, creds],

View File

@ -49,7 +49,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
}; };
} }
if (result.status === HttpStatusCode.Unauthorized) { if (result.status === HttpStatusCode.Unauthorized) {
notifyError(i18n.str`Require login`, undefined); notifyError(i18n.str`Authorization denied`, i18n.str`Maybe the session has expired, login again.`);
return { return {
status: "error-user-not-found", status: "error-user-not-found",
error: result, error: result,

View File

@ -15,9 +15,9 @@
*/ */
import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser"; import { notifyError, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import { StateUpdater, useEffect, useState } from "preact/hooks"; import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js"; import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useBusinessAccountDetails } from "../hooks/circuit.js"; import { useBusinessAccountDetails } from "../hooks/circuit.js";
@ -50,6 +50,15 @@ export function BankFrame({
const [settings, updateSettings] = useSettings(); const [settings, updateSettings] = useSettings();
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [error, resetError] = useErrorBoundary();
useEffect(() => {
if (error) {
notifyError(i18n.str`Internal error, please report.`, (error instanceof Error ? error.message : String(error)) as TranslatedString)
resetError()
}
}, [error])
const demo_sites = []; const demo_sites = [];
for (const i in bankUiSettings.demoSites) for (const i in bankUiSettings.demoSites)
demo_sites.push( demo_sites.push(
@ -355,7 +364,9 @@ function StatusBanner(): VNode {
</div> </div>
<div class="ml-3 flex-1 md:flex md:justify-between"> <div class="ml-3 flex-1 md:flex md:justify-between">
<p class="text-sm font-medium text-red-800">{n.message.title}</p> <p class="text-sm font-medium text-red-800">{n.message.title}</p>
<p class="mt-3 text-sm md:ml-6 md:mt-0"> </div>
<div>
<p class="text-sm">
<button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" <button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -68,7 +68,7 @@ export function HomePage({
account={account} account={account}
goToConfirmOperation={goToConfirmOperation} goToConfirmOperation={goToConfirmOperation}
goToBusinessAccount={goToBusinessAccount} goToBusinessAccount={goToBusinessAccount}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
/> />
); );
} }
@ -112,7 +112,6 @@ export function WithdrawalOperationPage({
export function handleNotOkResult( export function handleNotOkResult(
i18n: ReturnType<typeof useTranslationContext>["i18n"], i18n: ReturnType<typeof useTranslationContext>["i18n"],
onRegister?: () => void,
): <T>( ): <T>(
result: result:
| HttpResponsePaginated<T, SandboxBackend.SandboxError> | HttpResponsePaginated<T, SandboxBackend.SandboxError>
@ -133,7 +132,7 @@ export function handleNotOkResult(
case ErrorType.CLIENT: { case ErrorType.CLIENT: {
if (result.status === HttpStatusCode.Unauthorized) { if (result.status === HttpStatusCode.Unauthorized) {
notifyError(i18n.str`Wrong credentials`, undefined); notifyError(i18n.str`Wrong credentials`, undefined);
return <LoginForm onRegister={onRegister} />; return <LoginForm />;
} }
const errorData = result.payload; const errorData = result.payload;
notify({ notify({

View File

@ -24,6 +24,7 @@ import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
import { bankUiSettings } from "../settings.js"; import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty } from "../utils.js"; import { undefinedIfEmpty } from "../utils.js";
/** /**
* Collect and submit login data. * Collect and submit login data.
*/ */
@ -35,7 +36,6 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker(); const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker();
// const testLogin = useCredentialsCheckerOld();
const ref = useRef<HTMLInputElement>(null); const ref = useRef<HTMLInputElement>(null);
useEffect(function focusInput() { useEffect(function focusInput() {
ref.current?.focus(); ref.current?.focus();
@ -120,6 +120,13 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
setBusy(undefined) setBusy(undefined)
} }
/**
* Register form may be shown in the initialization step.
* If this is an error when usgin the app the registration
* callback is not set
*/
const isSessionExpired = !onRegister
return ( return (
<Fragment> <Fragment>
<h1 class="nav"></h1> <h1 class="nav"></h1>
@ -128,9 +135,9 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
)} */} )} */}
<div class="flex min-h-full flex-col justify-center"> <div class="flex min-h-full flex-col justify-center">
<div class="sm:mx-auto sm:w-full sm:max-w-sm"> {/* <div class="sm:mx-auto sm:w-full sm:max-w-sm">
<h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h2> <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h2>
</div> </div> */}
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<form class="space-y-6" noValidate <form class="space-y-6" noValidate
@ -151,8 +158,9 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
type="text" type="text"
name="username" name="username"
id="username" id="username"
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" class="block w-full disabled:bg-gray-200 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={username ?? ""} value={username ?? ""}
disabled={isSessionExpired}
enterkeyhint="next" enterkeyhint="next"
placeholder="identification" placeholder="identification"
autocomplete="username" autocomplete="username"
@ -194,7 +202,28 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
</div> </div>
</div> </div>
<div> {isSessionExpired ? <div class="flex justify-between">
<button type="submit"
class="rounded-md bg-white-600 px-3 py-1.5 text-sm font-semibold leading-6 text-black shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600"
onClick={(e) => {
e.preventDefault()
doLogin()
}}
>
<i18n.Translate>Cancel</i18n.Translate>
</button>
<button type="submit"
class="rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
disabled={!!errors}
onClick={(e) => {
e.preventDefault()
doLogin()
}}
>
<i18n.Translate>Renew session</i18n.Translate>
</button>
</div> : <div>
<button type="submit" <button type="submit"
class="flex w-full justify-center rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" class="flex w-full justify-center rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
disabled={!!errors} disabled={!!errors}
@ -205,7 +234,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
> >
<i18n.Translate>Log in</i18n.Translate> <i18n.Translate>Log in</i18n.Translate>
</button> </button>
</div> </div>}
</form> </form>
{bankUiSettings.allowRegistrations && onRegister && {bankUiSettings.allowRegistrations && onRegister &&

View File

@ -317,8 +317,9 @@ export function WithdrawalConfirmationQuestion({
</div> </div>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt> <dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">To be added</dd> <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
{/* Amounts.stringifyValue(details.amount) */} {Amounts.stringifyValue(details.amount)}
</dd>
</div> </div>
</dl> </dl>
</div> </div>

View File

@ -13,7 +13,7 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode
const result = useAccountDetails(account); const result = useAccountDetails(account);
if (!result.ok) { if (!result.ok) {
return handleNotOkResult(i18n, onRegister)(result); return handleNotOkResult(i18n)(result);
} }
const { data } = result; const { data } = result;

View File

@ -8,17 +8,16 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
interface Props { interface Props {
onAction: (type: AccountAction, account: string) => void; onAction: (type: AccountAction, account: string) => void;
account: string | undefined; account: string | undefined;
onRegister: () => void;
onCreateAccount: () => void; onCreateAccount: () => void;
} }
export function AccountList({ account, onAction, onCreateAccount, onRegister }: Props): VNode { export function AccountList({ account, onAction, onCreateAccount }: Props): VNode {
const result = useBusinessAccounts({ account }); const result = useBusinessAccounts({ account });
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
if (result.loading) return <div />; if (result.loading) return <div />;
if (!result.ok) { if (!result.ok) {
return handleNotOkResult(i18n, onRegister)(result); return handleNotOkResult(i18n)(result);
} }
const { customers } = result.data; const { customers } = result.data;

View File

@ -37,7 +37,7 @@ export function AdminHome({ onRegister }: Props): VNode {
switch (action.type) { switch (action.type) {
case "show-cashouts-details": return <ShowCashoutDetails case "show-cashouts-details": return <ShowCashoutDetails
id={action.account} id={action.account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onCancel={() => { onCancel={() => {
setAction(undefined); setAction(undefined);
}} }}
@ -73,7 +73,7 @@ export function AdminHome({ onRegister }: Props): VNode {
) )
case "update-password": return <UpdateAccountPassword case "update-password": return <UpdateAccountPassword
account={action.account} account={action.account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => { onUpdateSuccess={() => {
notifyInfo(i18n.str`Password changed`); notifyInfo(i18n.str`Password changed`);
setAction(undefined); setAction(undefined);
@ -84,7 +84,7 @@ export function AdminHome({ onRegister }: Props): VNode {
/> />
case "remove-account": return <RemoveAccount case "remove-account": return <RemoveAccount
account={action.account} account={action.account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => { onUpdateSuccess={() => {
notifyInfo(i18n.str`Account removed`); notifyInfo(i18n.str`Account removed`);
setAction(undefined); setAction(undefined);
@ -95,7 +95,7 @@ export function AdminHome({ onRegister }: Props): VNode {
/> />
case "show-details": return <ShowAccountDetails case "show-details": return <ShowAccountDetails
account={action.account} account={action.account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onChangePassword={() => { onChangePassword={() => {
setAction({ setAction({
type: "update-password", type: "update-password",

View File

@ -75,7 +75,7 @@ export function BusinessAccount({
return ( return (
<CreateCashout <CreateCashout
account={account} account={account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onCancel={() => { onCancel={() => {
setNewcashout(false); setNewcashout(false);
}} }}
@ -93,7 +93,7 @@ export function BusinessAccount({
return ( return (
<ShowCashoutDetails <ShowCashoutDetails
id={showCashoutDetails} id={showCashoutDetails}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onCancel={() => { onCancel={() => {
setShowCashoutDetails(undefined); setShowCashoutDetails(undefined);
}} }}
@ -104,7 +104,7 @@ export function BusinessAccount({
return ( return (
<UpdateAccountPassword <UpdateAccountPassword
account={account} account={account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => { onUpdateSuccess={() => {
notifyInfo(i18n.str`Password changed`); notifyInfo(i18n.str`Password changed`);
setUpdatePassword(false); setUpdatePassword(false);
@ -119,7 +119,7 @@ export function BusinessAccount({
<div> <div>
<ShowAccountDetails <ShowAccountDetails
account={account} account={account}
onLoadNotOk={handleNotOkResult(i18n, onRegister)} onLoadNotOk={handleNotOkResult(i18n)}
onUpdateSuccess={() => { onUpdateSuccess={() => {
notifyInfo(i18n.str`Account updated`); notifyInfo(i18n.str`Account updated`);
}} }}