business account
This commit is contained in:
parent
53af8b486f
commit
ba8b40c915
@ -23,7 +23,7 @@ import { useComponentState } from "./state.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
|
||||
export interface Props {
|
||||
account: string;
|
||||
empty?: boolean;
|
||||
}
|
||||
|
||||
export type State = State.Loading | State.LoadingUriError | State.Ready;
|
||||
|
@ -18,27 +18,24 @@ import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util";
|
||||
import { useCashouts } from "../../hooks/circuit.js";
|
||||
import { Props, State, Transaction } from "./index.js";
|
||||
|
||||
export function useComponentState({
|
||||
account,
|
||||
}: Props): State {
|
||||
const result = useCashouts()
|
||||
export function useComponentState({ empty }: Props): State {
|
||||
const result = useCashouts();
|
||||
if (result.loading) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined
|
||||
}
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
if (!result.ok) {
|
||||
return {
|
||||
status: "loading-error",
|
||||
error: result
|
||||
}
|
||||
error: result,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
error: undefined,
|
||||
cashout: result.data,
|
||||
cashouts: result.data,
|
||||
};
|
||||
}
|
||||
|
@ -26,20 +26,4 @@ export default {
|
||||
title: "transaction list",
|
||||
};
|
||||
|
||||
export const Ready = tests.createExample(ReadyView, {
|
||||
transactions: [
|
||||
{
|
||||
amount: {
|
||||
currency: "USD",
|
||||
fraction: 0,
|
||||
value: 1,
|
||||
},
|
||||
counterpart: "ASD",
|
||||
negative: false,
|
||||
subject: "Some",
|
||||
when: {
|
||||
t_ms: new Date().getTime(),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
export const Ready = tests.createExample(ReadyView, {});
|
||||
|
@ -39,7 +39,7 @@ export function ReadyView({ cashouts }: State.Ready): VNode {
|
||||
<tr>
|
||||
<th>{i18n.str`Created`}</th>
|
||||
<th>{i18n.str`Confirmed`}</th>
|
||||
<th>{i18n.str`Counterpart`}</th>
|
||||
<th>{i18n.str`Status`}</th>
|
||||
<th>{i18n.str`Subject`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -53,8 +53,9 @@ export function ReadyView({ cashouts }: State.Ready): VNode {
|
||||
? format(item.confirmation_time, "dd/MM/yyyy HH:mm:ss")
|
||||
: "-"}
|
||||
</td>
|
||||
<td>{Amounts.stringifyValue(item.amount_debit)}</td>
|
||||
<td>{Amounts.stringifyValue(item.amount_credit)}</td>
|
||||
<td>{item.counterpart}</td>
|
||||
<td>{item.status}</td>
|
||||
<td>{item.subject}</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -18,21 +18,19 @@ import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util";
|
||||
import { useTransactions } from "../../hooks/access.js";
|
||||
import { Props, State, Transaction } from "./index.js";
|
||||
|
||||
export function useComponentState({
|
||||
account,
|
||||
}: Props): State {
|
||||
const result = useTransactions(account)
|
||||
export function useComponentState({ account }: Props): State {
|
||||
const result = useTransactions(account);
|
||||
if (result.loading) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined
|
||||
}
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
if (!result.ok) {
|
||||
return {
|
||||
status: "loading-error",
|
||||
error: result
|
||||
}
|
||||
error: result,
|
||||
};
|
||||
}
|
||||
// if (error) {
|
||||
// switch (error.status) {
|
||||
@ -73,53 +71,57 @@ export function useComponentState({
|
||||
// };
|
||||
// }
|
||||
|
||||
const transactions = result.data.transactions.map((item: unknown) => {
|
||||
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 transactions = result.data.transactions
|
||||
.map((item: unknown) => {
|
||||
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 === "DBIT";
|
||||
const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban;
|
||||
const negative = anyItem.direction === "DBIT";
|
||||
const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban;
|
||||
|
||||
let date = anyItem.date ? parseInt(anyItem.date, 10) : 0
|
||||
if (isNaN(date) || !isFinite(date)) {
|
||||
date = 0
|
||||
}
|
||||
const when: AbsoluteTime = !date ? AbsoluteTime.never() : {
|
||||
t_ms: date,
|
||||
};
|
||||
const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`);
|
||||
const subject = anyItem.subject;
|
||||
return {
|
||||
negative,
|
||||
counterpart,
|
||||
when,
|
||||
amount,
|
||||
subject,
|
||||
};
|
||||
}).filter((x): x is Transaction => x !== undefined);
|
||||
let date = anyItem.date ? parseInt(anyItem.date, 10) : 0;
|
||||
if (isNaN(date) || !isFinite(date)) {
|
||||
date = 0;
|
||||
}
|
||||
const when: AbsoluteTime = !date
|
||||
? AbsoluteTime.never()
|
||||
: {
|
||||
t_ms: date,
|
||||
};
|
||||
const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`);
|
||||
const subject = anyItem.subject;
|
||||
return {
|
||||
negative,
|
||||
counterpart,
|
||||
when,
|
||||
amount,
|
||||
subject,
|
||||
};
|
||||
})
|
||||
.filter((x): x is Transaction => x !== undefined);
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
|
@ -95,7 +95,7 @@ export type ErrorMessage = {
|
||||
description?: string;
|
||||
title: TranslatedString;
|
||||
debug?: string;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Track page state.
|
||||
*/
|
||||
@ -110,5 +110,4 @@ export interface PageStateType {
|
||||
* be moved in a future "withdrawal state" object.
|
||||
*/
|
||||
withdrawalId?: string;
|
||||
|
||||
}
|
||||
|
22
packages/demobank-ui/src/declaration.d.ts
vendored
22
packages/demobank-ui/src/declaration.d.ts
vendored
@ -70,7 +70,6 @@ interface WireTransferRequestType {
|
||||
amount?: string;
|
||||
}
|
||||
|
||||
|
||||
type HashCode = string;
|
||||
type EddsaPublicKey = string;
|
||||
type EddsaSignature = string;
|
||||
@ -101,7 +100,6 @@ type UUID = string;
|
||||
type Integer = number;
|
||||
|
||||
namespace SandboxBackend {
|
||||
|
||||
export interface Config {
|
||||
// Name of this API, always "circuit".
|
||||
name: string;
|
||||
@ -126,7 +124,6 @@ namespace SandboxBackend {
|
||||
error: SandboxErrorDetail;
|
||||
}
|
||||
interface SandboxErrorDetail {
|
||||
|
||||
// String enum classifying the error.
|
||||
type: ErrorType;
|
||||
|
||||
@ -147,13 +144,12 @@ namespace SandboxBackend {
|
||||
* Sandbox and Nexus, therefore the actual meaning
|
||||
* must be carried by the error 'message' field.
|
||||
*/
|
||||
UtilError = "util-error"
|
||||
UtilError = "util-error",
|
||||
}
|
||||
|
||||
namespace Access {
|
||||
|
||||
interface PublicAccountsResponse {
|
||||
publicAccounts: PublicAccount[]
|
||||
publicAccounts: PublicAccount[];
|
||||
}
|
||||
interface PublicAccount {
|
||||
iban: string;
|
||||
@ -213,7 +209,6 @@ namespace SandboxBackend {
|
||||
}
|
||||
|
||||
interface BankAccountTransactionInfo {
|
||||
|
||||
creditorIban: string;
|
||||
creditorBic: string; // Optional
|
||||
creditorName: string;
|
||||
@ -233,7 +228,6 @@ namespace SandboxBackend {
|
||||
date: string; // milliseconds since the Unix epoch
|
||||
}
|
||||
interface CreateBankAccountTransactionCreate {
|
||||
|
||||
// Address in the Payto format of the wire transfer receiver.
|
||||
// It needs at least the 'message' query string parameter.
|
||||
paytoUri: string;
|
||||
@ -250,7 +244,6 @@ namespace SandboxBackend {
|
||||
|
||||
password: string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Circuit {
|
||||
@ -281,7 +274,6 @@ namespace SandboxBackend {
|
||||
internal_iban?: string;
|
||||
}
|
||||
interface CircuitContactData {
|
||||
|
||||
// E-Mail address
|
||||
email?: string;
|
||||
|
||||
@ -289,7 +281,6 @@ namespace SandboxBackend {
|
||||
phone?: string;
|
||||
}
|
||||
interface CircuitAccountReconfiguration {
|
||||
|
||||
// Addresses where to send the TAN.
|
||||
contact_data: CircuitContactData;
|
||||
|
||||
@ -300,7 +291,6 @@ namespace SandboxBackend {
|
||||
cashout_address: string;
|
||||
}
|
||||
interface AccountPasswordChange {
|
||||
|
||||
// New password.
|
||||
new_password: string;
|
||||
}
|
||||
@ -314,7 +304,6 @@ namespace SandboxBackend {
|
||||
|
||||
// Legal subject owning the account.
|
||||
name: string;
|
||||
|
||||
}
|
||||
|
||||
interface CircuitAccountData {
|
||||
@ -336,10 +325,9 @@ namespace SandboxBackend {
|
||||
enum TanChannel {
|
||||
SMS = "sms",
|
||||
EMAIL = "email",
|
||||
FILE = "file"
|
||||
FILE = "file",
|
||||
}
|
||||
interface CashoutRequest {
|
||||
|
||||
// Optional subject to associate to the
|
||||
// cashout operation. This data will appear
|
||||
// as the incoming wire transfer subject in
|
||||
@ -370,7 +358,6 @@ namespace SandboxBackend {
|
||||
uuid: string;
|
||||
}
|
||||
interface CashoutConfirm {
|
||||
|
||||
// the TAN that confirms $cashoutId.
|
||||
tan: string;
|
||||
}
|
||||
@ -398,7 +385,6 @@ namespace SandboxBackend {
|
||||
cashouts: string[];
|
||||
}
|
||||
interface CashoutStatusResponse {
|
||||
|
||||
status: CashoutStatus;
|
||||
// Amount debited to the circuit bank account.
|
||||
amount_debit: Amount;
|
||||
@ -415,7 +401,6 @@ namespace SandboxBackend {
|
||||
confirmation_time?: number | null; // milliseconds since the Unix epoch
|
||||
}
|
||||
enum CashoutStatus {
|
||||
|
||||
// The payment was initiated after a valid
|
||||
// TAN was received by the bank.
|
||||
CONFIRMED = "confirmed",
|
||||
@ -425,5 +410,4 @@ namespace SandboxBackend {
|
||||
PENDING = "pending",
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,91 +14,113 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import useSWR from "swr";
|
||||
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import {
|
||||
HttpError,
|
||||
HttpResponse,
|
||||
HttpResponseOk,
|
||||
HttpResponsePaginated,
|
||||
RequestError,
|
||||
} from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { useAuthenticatedBackend, useMatchMutate, usePublicBackend } from "./backend.js";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import useSWR from "swr";
|
||||
import { useBackendContext } from "../context/backend.js";
|
||||
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
|
||||
import {
|
||||
useAuthenticatedBackend,
|
||||
useMatchMutate,
|
||||
usePublicBackend,
|
||||
} from "./backend.js";
|
||||
|
||||
export function useAccessAPI(): AccessAPI {
|
||||
const mutateAll = useMatchMutate();
|
||||
const { request } = useAuthenticatedBackend();
|
||||
const { state } = useBackendContext()
|
||||
const { state } = useBackendContext();
|
||||
if (state.status === "loggedOut") {
|
||||
throw Error("access-api can't be used when the user is not logged In")
|
||||
throw Error("access-api can't be used when the user is not logged In");
|
||||
}
|
||||
const account = state.username
|
||||
const account = state.username;
|
||||
|
||||
const createWithdrawal = async (
|
||||
data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
|
||||
): Promise<HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>> => {
|
||||
const res = await request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(`access-api/accounts/${account}/withdrawals`, {
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json"
|
||||
});
|
||||
): Promise<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
|
||||
> => {
|
||||
const res =
|
||||
await request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(
|
||||
`access-api/accounts/${account}/withdrawals`,
|
||||
{
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json",
|
||||
},
|
||||
);
|
||||
return res;
|
||||
};
|
||||
const abortWithdrawal = async (
|
||||
id: string,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/accounts/${account}/withdrawals/${id}`, {
|
||||
method: "POST",
|
||||
contentType: "json"
|
||||
});
|
||||
const abortWithdrawal = async (id: string): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(
|
||||
`access-api/accounts/${account}/withdrawals/${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
contentType: "json",
|
||||
},
|
||||
);
|
||||
await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
|
||||
return res;
|
||||
};
|
||||
const confirmWithdrawal = async (
|
||||
id: string,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/accounts/${account}/withdrawals/${id}`, {
|
||||
method: "POST",
|
||||
contentType: "json"
|
||||
});
|
||||
const res = await request<void>(
|
||||
`access-api/accounts/${account}/withdrawals/${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
contentType: "json",
|
||||
},
|
||||
);
|
||||
await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
|
||||
return res;
|
||||
};
|
||||
const createTransaction = async (
|
||||
data: SandboxBackend.Access.CreateBankAccountTransactionCreate
|
||||
data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/accounts/${account}/transactions`, {
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json"
|
||||
});
|
||||
const res = await request<void>(
|
||||
`access-api/accounts/${account}/transactions`,
|
||||
{
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json",
|
||||
},
|
||||
);
|
||||
await mutateAll(/.*accounts\/.*\/transactions.*/);
|
||||
return res;
|
||||
};
|
||||
const deleteAccount = async (
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/accounts/${account}`, {
|
||||
method: "DELETE",
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*accounts\/.*/);
|
||||
return res;
|
||||
};
|
||||
|
||||
return { abortWithdrawal, confirmWithdrawal, createWithdrawal, createTransaction, deleteAccount };
|
||||
return {
|
||||
abortWithdrawal,
|
||||
confirmWithdrawal,
|
||||
createWithdrawal,
|
||||
createTransaction,
|
||||
deleteAccount,
|
||||
};
|
||||
}
|
||||
|
||||
export function useTestingAPI(): TestingAPI {
|
||||
const mutateAll = useMatchMutate();
|
||||
const { request: noAuthRequest } = usePublicBackend();
|
||||
const register = async (
|
||||
data: SandboxBackend.Access.BankRegistrationRequest
|
||||
data: SandboxBackend.Access.BankRegistrationRequest,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await noAuthRequest<void>(`access-api/testing/register`, {
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*accounts\/.*/);
|
||||
return res;
|
||||
@ -107,25 +129,22 @@ export function useTestingAPI(): TestingAPI {
|
||||
return { register };
|
||||
}
|
||||
|
||||
|
||||
export interface TestingAPI {
|
||||
register: (
|
||||
data: SandboxBackend.Access.BankRegistrationRequest
|
||||
data: SandboxBackend.Access.BankRegistrationRequest,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
|
||||
export interface AccessAPI {
|
||||
createWithdrawal: (
|
||||
data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
|
||||
) => Promise<HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>>;
|
||||
abortWithdrawal: (
|
||||
wid: string,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
confirmWithdrawal: (
|
||||
wid: string
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
) => Promise<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
|
||||
>;
|
||||
abortWithdrawal: (wid: string) => Promise<HttpResponseOk<void>>;
|
||||
confirmWithdrawal: (wid: string) => Promise<HttpResponseOk<void>>;
|
||||
createTransaction: (
|
||||
data: SandboxBackend.Access.CreateBankAccountTransactionCreate
|
||||
data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
deleteAccount: () => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
@ -135,13 +154,17 @@ export interface InstanceTemplateFilter {
|
||||
position?: string;
|
||||
}
|
||||
|
||||
|
||||
export function useAccountDetails(account: string): HttpResponse<SandboxBackend.Access.BankAccountBalanceResponse, SandboxBackend.SandboxError> {
|
||||
export function useAccountDetails(
|
||||
account: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Access.BankAccountBalanceResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountBalanceResponse>,
|
||||
HttpError<SandboxBackend.SandboxError>
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/accounts/${account}`], fetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
@ -155,17 +178,23 @@ export function useAccountDetails(account: string): HttpResponse<SandboxBackend.
|
||||
});
|
||||
|
||||
if (data) return data;
|
||||
if (error) return error;
|
||||
if (error) return error.info;
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
// FIXME: should poll
|
||||
export function useWithdrawalDetails(account: string, wid: string): HttpResponse<SandboxBackend.Access.BankAccountGetWithdrawalResponse, SandboxBackend.SandboxError> {
|
||||
export function useWithdrawalDetails(
|
||||
account: string,
|
||||
wid: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Access.BankAccountGetWithdrawalResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountGetWithdrawalResponse>,
|
||||
HttpError<SandboxBackend.SandboxError>
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/accounts/${account}/withdrawals/${wid}`], fetcher, {
|
||||
refreshInterval: 1000,
|
||||
refreshWhenHidden: false,
|
||||
@ -176,21 +205,26 @@ export function useWithdrawalDetails(account: string, wid: string): HttpResponse
|
||||
errorRetryInterval: 1,
|
||||
shouldRetryOnError: false,
|
||||
keepPreviousData: true,
|
||||
|
||||
});
|
||||
|
||||
// if (isValidating) return { loading: true, data: data?.data };
|
||||
if (data) return data;
|
||||
if (error) return error;
|
||||
if (error) return error.info;
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
export function useTransactionDetails(account: string, tid: string): HttpResponse<SandboxBackend.Access.BankAccountTransactionInfo, SandboxBackend.SandboxError> {
|
||||
export function useTransactionDetails(
|
||||
account: string,
|
||||
tid: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Access.BankAccountTransactionInfo,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountTransactionInfo>,
|
||||
HttpError<SandboxBackend.SandboxError>
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/accounts/${account}/transactions/${tid}`], fetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
@ -205,17 +239,20 @@ export function useTransactionDetails(account: string, tid: string): HttpRespons
|
||||
|
||||
// if (isValidating) return { loading: true, data: data?.data };
|
||||
if (data) return data;
|
||||
if (error) return error;
|
||||
if (error) return error.info;
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
interface PaginationFilter {
|
||||
page: number,
|
||||
page: number;
|
||||
}
|
||||
|
||||
export function usePublicAccounts(
|
||||
args?: PaginationFilter,
|
||||
): HttpResponsePaginated<SandboxBackend.Access.PublicAccountsResponse, SandboxBackend.SandboxError> {
|
||||
): HttpResponsePaginated<
|
||||
SandboxBackend.Access.PublicAccountsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { paginatedFetcher } = usePublicBackend();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
@ -226,18 +263,21 @@ export function usePublicAccounts(
|
||||
isValidating: loadingAfter,
|
||||
} = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.PublicAccountsResponse>,
|
||||
HttpError<SandboxBackend.SandboxError>
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher);
|
||||
|
||||
const [lastAfter, setLastAfter] = useState<
|
||||
HttpResponse<SandboxBackend.Access.PublicAccountsResponse, SandboxBackend.SandboxError>
|
||||
HttpResponse<
|
||||
SandboxBackend.Access.PublicAccountsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
>
|
||||
>({ loading: true });
|
||||
|
||||
useEffect(() => {
|
||||
if (afterData) setLastAfter(afterData);
|
||||
}, [afterData]);
|
||||
|
||||
if (afterError) return afterError;
|
||||
if (afterError) return afterError.info;
|
||||
|
||||
// if the query returns less that we ask, then we have reach the end or beginning
|
||||
const isReachingEnd =
|
||||
@ -254,30 +294,33 @@ export function usePublicAccounts(
|
||||
}
|
||||
},
|
||||
loadMorePrev: () => {
|
||||
null
|
||||
null;
|
||||
},
|
||||
};
|
||||
|
||||
const publicAccounts = !afterData ? [] : (afterData || lastAfter).data.publicAccounts;
|
||||
if (loadingAfter)
|
||||
return { loading: true, data: { publicAccounts } };
|
||||
const publicAccounts = !afterData
|
||||
? []
|
||||
: (afterData || lastAfter).data.publicAccounts;
|
||||
if (loadingAfter) return { loading: true, data: { publicAccounts } };
|
||||
if (afterData) {
|
||||
return { ok: true, data: { publicAccounts }, ...pagination };
|
||||
}
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FIXME: mutate result when balance change (transaction )
|
||||
* @param account
|
||||
* @param args
|
||||
* @returns
|
||||
* @param account
|
||||
* @param args
|
||||
* @returns
|
||||
*/
|
||||
export function useTransactions(
|
||||
account: string,
|
||||
args?: PaginationFilter,
|
||||
): HttpResponsePaginated<SandboxBackend.Access.BankAccountTransactionsResponse, SandboxBackend.SandboxError> {
|
||||
): HttpResponsePaginated<
|
||||
SandboxBackend.Access.BankAccountTransactionsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { paginatedFetcher } = useAuthenticatedBackend();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
@ -288,18 +331,24 @@ export function useTransactions(
|
||||
isValidating: loadingAfter,
|
||||
} = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountTransactionsResponse>,
|
||||
HttpError<SandboxBackend.SandboxError>
|
||||
>([`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE], paginatedFetcher);
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>(
|
||||
[`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE],
|
||||
paginatedFetcher,
|
||||
);
|
||||
|
||||
const [lastAfter, setLastAfter] = useState<
|
||||
HttpResponse<SandboxBackend.Access.BankAccountTransactionsResponse, SandboxBackend.SandboxError>
|
||||
HttpResponse<
|
||||
SandboxBackend.Access.BankAccountTransactionsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
>
|
||||
>({ loading: true });
|
||||
|
||||
useEffect(() => {
|
||||
if (afterData) setLastAfter(afterData);
|
||||
}, [afterData]);
|
||||
|
||||
if (afterError) return afterError;
|
||||
if (afterError) return afterError.info;
|
||||
|
||||
// if the query returns less that we ask, then we have reach the end or beginning
|
||||
const isReachingEnd =
|
||||
@ -316,13 +365,14 @@ export function useTransactions(
|
||||
}
|
||||
},
|
||||
loadMorePrev: () => {
|
||||
null
|
||||
null;
|
||||
},
|
||||
};
|
||||
|
||||
const transactions = !afterData ? [] : (afterData || lastAfter).data.transactions;
|
||||
if (loadingAfter)
|
||||
return { loading: true, data: { transactions } };
|
||||
const transactions = !afterData
|
||||
? []
|
||||
: (afterData || lastAfter).data.transactions;
|
||||
if (loadingAfter) return { loading: true, data: { transactions } };
|
||||
if (afterData) {
|
||||
return { ok: true, data: { transactions }, ...pagination };
|
||||
}
|
||||
|
@ -15,7 +15,10 @@
|
||||
*/
|
||||
|
||||
import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
|
||||
import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import {
|
||||
RequestError,
|
||||
useLocalStorage,
|
||||
} from "@gnu-taler/web-util/lib/index.browser";
|
||||
import {
|
||||
HttpResponse,
|
||||
HttpResponseOk,
|
||||
@ -57,7 +60,7 @@ export function getInitialBackendBaseURL(): string {
|
||||
|
||||
export const defaultState: BackendState = {
|
||||
status: "loggedOut",
|
||||
url: getInitialBackendBaseURL()
|
||||
url: getInitialBackendBaseURL(),
|
||||
};
|
||||
|
||||
export interface BackendStateHandler {
|
||||
@ -91,7 +94,12 @@ export function useBackendState(): BackendStateHandler {
|
||||
},
|
||||
logIn(info) {
|
||||
//admin is defined by the username
|
||||
const nextState: BackendState = { status: "loggedIn", url: state.url, ...info, isUserAdministrator: info.username === "admin" };
|
||||
const nextState: BackendState = {
|
||||
status: "loggedIn",
|
||||
url: state.url,
|
||||
...info,
|
||||
isUserAdministrator: info.username === "admin",
|
||||
};
|
||||
update(JSON.stringify(nextState));
|
||||
},
|
||||
};
|
||||
@ -103,24 +111,25 @@ interface useBackendType {
|
||||
options?: RequestOptions,
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
|
||||
multiFetcher: <T>(endpoint: string[]) => Promise<HttpResponseOk<T>[]>;
|
||||
paginatedFetcher: <T>(args: [string, number, number]) => Promise<HttpResponseOk<T>>;
|
||||
sandboxAccountsFetcher: <T>(args: [string, number, number, string]) => Promise<HttpResponseOk<T>>;
|
||||
multiFetcher: <T>(endpoint: string[][]) => Promise<HttpResponseOk<T>[]>;
|
||||
paginatedFetcher: <T>(
|
||||
args: [string, number, number],
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
sandboxAccountsFetcher: <T>(
|
||||
args: [string, number, number, string],
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
}
|
||||
|
||||
|
||||
export function usePublicBackend(): useBackendType {
|
||||
const { state } = useBackendContext();
|
||||
const { request: requestHandler } = useApiContext();
|
||||
|
||||
const baseUrl = state.url
|
||||
const baseUrl = state.url;
|
||||
|
||||
const request = useCallback(
|
||||
function requestImpl<T>(
|
||||
path: string,
|
||||
options: RequestOptions = {},
|
||||
): Promise<HttpResponseOk<T>> {
|
||||
|
||||
return requestHandler<T>(baseUrl, path, options);
|
||||
},
|
||||
[baseUrl],
|
||||
@ -133,15 +142,21 @@ export function usePublicBackend(): useBackendType {
|
||||
[baseUrl],
|
||||
);
|
||||
const paginatedFetcher = useCallback(
|
||||
function fetcherImpl<T>([endpoint, page, size]: [string, number, number]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, { params: { page: page || 1, size } });
|
||||
function fetcherImpl<T>([endpoint, page, size]: [
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
params: { page: page || 1, size },
|
||||
});
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
const multiFetcher = useCallback(
|
||||
function multiFetcherImpl<T>(
|
||||
endpoints: string[],
|
||||
): Promise<HttpResponseOk<T>[]> {
|
||||
function multiFetcherImpl<T>([endpoints]: string[][]): Promise<
|
||||
HttpResponseOk<T>[]
|
||||
> {
|
||||
return Promise.all(
|
||||
endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint)),
|
||||
);
|
||||
@ -149,27 +164,39 @@ export function usePublicBackend(): useBackendType {
|
||||
[baseUrl],
|
||||
);
|
||||
const sandboxAccountsFetcher = useCallback(
|
||||
function fetcherImpl<T>([endpoint, page, size, account]: [string, number, number, string]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, { params: { page: page || 1, size } });
|
||||
function fetcherImpl<T>([endpoint, page, size, account]: [
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
string,
|
||||
]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
params: { page: page || 1, size },
|
||||
});
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
return { request, fetcher, paginatedFetcher, multiFetcher, sandboxAccountsFetcher };
|
||||
return {
|
||||
request,
|
||||
fetcher,
|
||||
paginatedFetcher,
|
||||
multiFetcher,
|
||||
sandboxAccountsFetcher,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAuthenticatedBackend(): useBackendType {
|
||||
const { state } = useBackendContext();
|
||||
const { request: requestHandler } = useApiContext();
|
||||
|
||||
const creds = state.status === "loggedIn" ? state : undefined
|
||||
const baseUrl = state.url
|
||||
const creds = state.status === "loggedIn" ? state : undefined;
|
||||
const baseUrl = state.url;
|
||||
|
||||
const request = useCallback(
|
||||
function requestImpl<T>(
|
||||
path: string,
|
||||
options: RequestOptions = {},
|
||||
): Promise<HttpResponseOk<T>> {
|
||||
|
||||
return requestHandler<T>(baseUrl, path, { basicAuth: creds, ...options });
|
||||
},
|
||||
[baseUrl, creds],
|
||||
@ -182,36 +209,66 @@ export function useAuthenticatedBackend(): useBackendType {
|
||||
[baseUrl, creds],
|
||||
);
|
||||
const paginatedFetcher = useCallback(
|
||||
function fetcherImpl<T>([endpoint, page = 0, size]: [string, number, number]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds, params: { page, size } });
|
||||
function fetcherImpl<T>([endpoint, page = 0, size]: [
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
basicAuth: creds,
|
||||
params: { page, size },
|
||||
});
|
||||
},
|
||||
[baseUrl, creds],
|
||||
);
|
||||
const multiFetcher = useCallback(
|
||||
function multiFetcherImpl<T>(
|
||||
endpoints: string[],
|
||||
): Promise<HttpResponseOk<T>[]> {
|
||||
function multiFetcherImpl<T>([endpoints]: string[][]): Promise<
|
||||
HttpResponseOk<T>[]
|
||||
> {
|
||||
console.log("list size", endpoints.length, endpoints);
|
||||
return Promise.all(
|
||||
endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint, { basicAuth: creds })),
|
||||
endpoints.map((endpoint) =>
|
||||
requestHandler<T>(baseUrl, endpoint, { basicAuth: creds }),
|
||||
),
|
||||
);
|
||||
},
|
||||
[baseUrl, creds],
|
||||
);
|
||||
const sandboxAccountsFetcher = useCallback(
|
||||
function fetcherImpl<T>([endpoint, page, size, account]: [string, number, number, string]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds, params: { page: page || 1, size } });
|
||||
function fetcherImpl<T>([endpoint, page, size, account]: [
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
string,
|
||||
]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
basicAuth: creds,
|
||||
params: { page: page || 1, size },
|
||||
});
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
return { request, fetcher, paginatedFetcher, multiFetcher, sandboxAccountsFetcher };
|
||||
|
||||
return {
|
||||
request,
|
||||
fetcher,
|
||||
paginatedFetcher,
|
||||
multiFetcher,
|
||||
sandboxAccountsFetcher,
|
||||
};
|
||||
}
|
||||
|
||||
export function useBackendConfig(): HttpResponse<SandboxBackend.Config, SandboxBackend.SandboxError> {
|
||||
export function useBackendConfig(): HttpResponse<
|
||||
SandboxBackend.Config,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { request } = usePublicBackend();
|
||||
|
||||
type Type = SandboxBackend.Config;
|
||||
|
||||
const [result, setResult] = useState<HttpResponse<Type, SandboxBackend.SandboxError>>({ loading: true });
|
||||
const [result, setResult] = useState<
|
||||
HttpResponse<Type, SandboxBackend.SandboxError>
|
||||
>({ loading: true });
|
||||
|
||||
useEffect(() => {
|
||||
request<Type>(`/config`)
|
||||
@ -238,10 +295,8 @@ export function useMatchMutate(): (
|
||||
const allKeys = Array.from(cache.keys());
|
||||
const keys = allKeys.filter((key) => re.test(key));
|
||||
const mutations = keys.map((key) => {
|
||||
mutate(key, value, true);
|
||||
return mutate(key, value, true);
|
||||
});
|
||||
return Promise.all(mutations);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,23 +15,24 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
HttpError,
|
||||
HttpResponse,
|
||||
HttpResponseOk,
|
||||
HttpResponsePaginated,
|
||||
RequestError
|
||||
RequestError,
|
||||
useApiContext,
|
||||
} from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import useSWR from "swr";
|
||||
import { useBackendContext } from "../context/backend.js";
|
||||
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
|
||||
import { useAuthenticatedBackend } from "./backend.js";
|
||||
import { useAuthenticatedBackend, useMatchMutate } from "./backend.js";
|
||||
|
||||
export function useAdminAccountAPI(): AdminAccountAPI {
|
||||
const { request } = useAuthenticatedBackend();
|
||||
const { state } = useBackendContext()
|
||||
const mutateAll = useMatchMutate();
|
||||
const { state } = useBackendContext();
|
||||
if (state.status === "loggedOut") {
|
||||
throw Error("access-api can't be used when the user is not logged In")
|
||||
throw Error("access-api can't be used when the user is not logged In");
|
||||
}
|
||||
|
||||
const createAccount = async (
|
||||
@ -40,8 +41,9 @@ export function useAdminAccountAPI(): AdminAccountAPI {
|
||||
const res = await request<void>(`circuit-api/accounts`, {
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*circuit-api\/accounts.*/);
|
||||
return res;
|
||||
};
|
||||
|
||||
@ -52,8 +54,9 @@ export function useAdminAccountAPI(): AdminAccountAPI {
|
||||
const res = await request<void>(`circuit-api/accounts/${account}`, {
|
||||
method: "PATCH",
|
||||
data,
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*circuit-api\/accounts.*/);
|
||||
return res;
|
||||
};
|
||||
const deleteAccount = async (
|
||||
@ -61,8 +64,9 @@ export function useAdminAccountAPI(): AdminAccountAPI {
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`circuit-api/accounts/${account}`, {
|
||||
method: "DELETE",
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*circuit-api\/accounts.*/);
|
||||
return res;
|
||||
};
|
||||
const changePassword = async (
|
||||
@ -72,7 +76,7 @@ export function useAdminAccountAPI(): AdminAccountAPI {
|
||||
const res = await request<void>(`circuit-api/accounts/${account}/auth`, {
|
||||
method: "PATCH",
|
||||
data,
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
return res;
|
||||
};
|
||||
@ -82,9 +86,10 @@ export function useAdminAccountAPI(): AdminAccountAPI {
|
||||
|
||||
export function useCircuitAccountAPI(): CircuitAccountAPI {
|
||||
const { request } = useAuthenticatedBackend();
|
||||
const { state } = useBackendContext()
|
||||
const mutateAll = useMatchMutate();
|
||||
const { state } = useBackendContext();
|
||||
if (state.status === "loggedOut") {
|
||||
throw Error("access-api can't be used when the user is not logged In")
|
||||
throw Error("access-api can't be used when the user is not logged In");
|
||||
}
|
||||
const account = state.username;
|
||||
|
||||
@ -94,8 +99,9 @@ export function useCircuitAccountAPI(): CircuitAccountAPI {
|
||||
const res = await request<void>(`circuit-api/accounts/${account}`, {
|
||||
method: "PATCH",
|
||||
data,
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*circuit-api\/accounts.*/);
|
||||
return res;
|
||||
};
|
||||
const changePassword = async (
|
||||
@ -104,7 +110,7 @@ export function useCircuitAccountAPI(): CircuitAccountAPI {
|
||||
const res = await request<void>(`circuit-api/accounts/${account}/auth`, {
|
||||
method: "PATCH",
|
||||
data,
|
||||
contentType: "json"
|
||||
contentType: "json",
|
||||
});
|
||||
return res;
|
||||
};
|
||||
@ -120,57 +126,72 @@ export interface AdminAccountAPI {
|
||||
|
||||
updateAccount: (
|
||||
account: string,
|
||||
data: SandboxBackend.Circuit.CircuitAccountReconfiguration
|
||||
data: SandboxBackend.Circuit.CircuitAccountReconfiguration,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
changePassword: (
|
||||
account: string,
|
||||
data: SandboxBackend.Circuit.AccountPasswordChange
|
||||
data: SandboxBackend.Circuit.AccountPasswordChange,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
|
||||
export interface CircuitAccountAPI {
|
||||
updateAccount: (
|
||||
data: SandboxBackend.Circuit.CircuitAccountReconfiguration
|
||||
data: SandboxBackend.Circuit.CircuitAccountReconfiguration,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
changePassword: (
|
||||
data: SandboxBackend.Circuit.AccountPasswordChange
|
||||
data: SandboxBackend.Circuit.AccountPasswordChange,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
|
||||
|
||||
export interface InstanceTemplateFilter {
|
||||
//FIXME: add filter to the template list
|
||||
position?: string;
|
||||
}
|
||||
|
||||
|
||||
export function useMyAccountDetails(): HttpResponse<SandboxBackend.Circuit.CircuitAccountData, SandboxBackend.SandboxError> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
const { state } = useBackendContext()
|
||||
if (state.status === "loggedOut") {
|
||||
throw Error("can't access my-account-details when logged out")
|
||||
async function getBusinessStatus(
|
||||
request: ReturnType<typeof useApiContext>["request"],
|
||||
url: string,
|
||||
basicAuth: { username: string; password: string },
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const result = await request<
|
||||
HttpResponseOk<SandboxBackend.Circuit.CircuitAccountData>
|
||||
>(url, `circuit-api/accounts/${basicAuth.username}`, { basicAuth });
|
||||
return result.ok;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Circuit.CircuitAccountData>,
|
||||
HttpError<SandboxBackend.SandboxError>
|
||||
>([`accounts/${state.username}`], fetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshWhenOffline: false,
|
||||
errorRetryCount: 0,
|
||||
errorRetryInterval: 1,
|
||||
shouldRetryOnError: false,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
if (data) return data;
|
||||
if (error) return error;
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
export function useAccountDetails(account: string): HttpResponse<SandboxBackend.Circuit.CircuitAccountData, SandboxBackend.SandboxError> {
|
||||
export function useBusinessAccountFlag(): boolean | undefined {
|
||||
const [isBusiness, setIsBusiness] = useState<boolean | undefined>();
|
||||
const { state } = useBackendContext();
|
||||
const { request } = useApiContext();
|
||||
const creds =
|
||||
state.status === "loggedOut"
|
||||
? undefined
|
||||
: { username: state.username, password: state.password };
|
||||
|
||||
useEffect(() => {
|
||||
if (!creds) return;
|
||||
getBusinessStatus(request, state.url, creds)
|
||||
.then((result) => {
|
||||
setIsBusiness(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
setIsBusiness(false);
|
||||
});
|
||||
});
|
||||
|
||||
return isBusiness;
|
||||
}
|
||||
|
||||
export function useBusinessAccountDetails(
|
||||
account: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Circuit.CircuitAccountData,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
@ -188,20 +209,22 @@ export function useAccountDetails(account: string): HttpResponse<SandboxBackend.
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
// if (isValidating) return { loading: true, data: data?.data };
|
||||
if (data) return data;
|
||||
if (error) return error.info;
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
interface PaginationFilter {
|
||||
account?: string,
|
||||
page?: number,
|
||||
account?: string;
|
||||
page?: number;
|
||||
}
|
||||
|
||||
export function useAccounts(
|
||||
export function useBusinessAccounts(
|
||||
args?: PaginationFilter,
|
||||
): HttpResponsePaginated<SandboxBackend.Circuit.CircuitAccounts, SandboxBackend.SandboxError> {
|
||||
): HttpResponsePaginated<
|
||||
SandboxBackend.Circuit.CircuitAccounts,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { sandboxAccountsFetcher } = useAuthenticatedBackend();
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
@ -212,17 +235,21 @@ export function useAccounts(
|
||||
} = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Circuit.CircuitAccounts>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account], sandboxAccountsFetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshWhenOffline: false,
|
||||
errorRetryCount: 0,
|
||||
errorRetryInterval: 1,
|
||||
shouldRetryOnError: false,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
>(
|
||||
[`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account],
|
||||
sandboxAccountsFetcher,
|
||||
{
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshWhenOffline: false,
|
||||
errorRetryCount: 0,
|
||||
errorRetryInterval: 1,
|
||||
shouldRetryOnError: false,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
|
||||
// const [lastAfter, setLastAfter] = useState<
|
||||
// HttpResponse<SandboxBackend.Circuit.CircuitAccounts, SandboxBackend.SandboxError>
|
||||
@ -247,18 +274,18 @@ export function useAccounts(
|
||||
}
|
||||
},
|
||||
loadMorePrev: () => {
|
||||
null
|
||||
null;
|
||||
},
|
||||
};
|
||||
|
||||
const result = useMemo(() => {
|
||||
const customers = !afterData ? [] : (afterData)?.data?.customers ?? [];
|
||||
return { ok: true as const, data: { customers }, ...pagination }
|
||||
}, [afterData?.data])
|
||||
const customers = !afterData ? [] : afterData?.data?.customers ?? [];
|
||||
return { ok: true as const, data: { customers }, ...pagination };
|
||||
}, [afterData?.data]);
|
||||
|
||||
if (afterError) return afterError.info;
|
||||
if (afterData) {
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
// if (loadingAfter)
|
||||
|
@ -104,49 +104,48 @@ export function AccountPage({ account, onLoadNotOk }: Props): VNode {
|
||||
)}
|
||||
|
||||
<section style={{ marginTop: "2em" }}>
|
||||
<Moves account={account} />
|
||||
<div class="active">
|
||||
<h3>{i18n.str`Latest transactions`}</h3>
|
||||
<Transactions account={account} />
|
||||
</div>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function Moves({ account }: { account: string }): VNode {
|
||||
const [tab, setTab] = useState<"transactions" | "cashouts">("transactions");
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<article>
|
||||
<div class="payments">
|
||||
<div class="tab">
|
||||
<button
|
||||
class={tab === "transactions" ? "tablinks active" : "tablinks"}
|
||||
onClick={(): void => {
|
||||
setTab("transactions");
|
||||
}}
|
||||
>
|
||||
{i18n.str`Transactions`}
|
||||
</button>
|
||||
<button
|
||||
class={tab === "cashouts" ? "tablinks active" : "tablinks"}
|
||||
onClick={(): void => {
|
||||
setTab("cashouts");
|
||||
}}
|
||||
>
|
||||
{i18n.str`Cashouts`}
|
||||
</button>
|
||||
</div>
|
||||
{tab === "transactions" && (
|
||||
<div class="active">
|
||||
<h3>{i18n.str`Latest transactions`}</h3>
|
||||
<Transactions account={account} />
|
||||
</div>
|
||||
)}
|
||||
{tab === "cashouts" && (
|
||||
<div class="active">
|
||||
<h3>{i18n.str`Latest cashouts`}</h3>
|
||||
<Cashouts account={account} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
// function Moves({ account }: { account: string }): VNode {
|
||||
// const [tab, setTab] = useState<"transactions" | "cashouts">("transactions");
|
||||
// const { i18n } = useTranslationContext();
|
||||
// return (
|
||||
// <article>
|
||||
// <div class="payments">
|
||||
// <div class="tab">
|
||||
// <button
|
||||
// class={tab === "transactions" ? "tablinks active" : "tablinks"}
|
||||
// onClick={(): void => {
|
||||
// setTab("transactions");
|
||||
// }}
|
||||
// >
|
||||
// {i18n.str`Transactions`}
|
||||
// </button>
|
||||
// <button
|
||||
// class={tab === "cashouts" ? "tablinks active" : "tablinks"}
|
||||
// onClick={(): void => {
|
||||
// setTab("cashouts");
|
||||
// }}
|
||||
// >
|
||||
// {i18n.str`Cashouts`}
|
||||
// </button>
|
||||
// </div>
|
||||
// {tab === "transactions" && (
|
||||
// )}
|
||||
// {tab === "cashouts" && (
|
||||
// <div class="active">
|
||||
// <h3>{i18n.str`Latest cashouts`}</h3>
|
||||
// <Cashouts account={account} />
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// </article>
|
||||
// );
|
||||
// }
|
||||
|
@ -24,8 +24,8 @@ import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { ErrorMessage, usePageContext } from "../context/pageState.js";
|
||||
import {
|
||||
useAccountDetails,
|
||||
useAccounts,
|
||||
useBusinessAccountDetails,
|
||||
useBusinessAccounts,
|
||||
useAdminAccountAPI,
|
||||
} from "../hooks/circuit.js";
|
||||
import {
|
||||
@ -71,7 +71,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
|
||||
}));
|
||||
}
|
||||
|
||||
const result = useAccounts({ account });
|
||||
const result = useBusinessAccounts({ account });
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
if (result.loading) return <div />;
|
||||
@ -86,6 +86,10 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
|
||||
<ShowAccountDetails
|
||||
account={showDetails}
|
||||
onLoadNotOk={onLoadNotOk}
|
||||
onChangePassword={() => {
|
||||
setUpdatePassword(showDetails);
|
||||
setShowDetails(undefined);
|
||||
}}
|
||||
onUpdateSuccess={() => {
|
||||
showInfoMessage(i18n.str`Account updated`);
|
||||
setShowDetails(undefined);
|
||||
@ -230,7 +234,7 @@ function initializeFromTemplate(
|
||||
return initial as any;
|
||||
}
|
||||
|
||||
function UpdateAccountPassword({
|
||||
export function UpdateAccountPassword({
|
||||
account,
|
||||
onClear,
|
||||
onUpdateSuccess,
|
||||
@ -242,7 +246,7 @@ function UpdateAccountPassword({
|
||||
account: string;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const result = useAccountDetails(account);
|
||||
const result = useBusinessAccountDetails(account);
|
||||
const { changePassword } = useAdminAccountAPI();
|
||||
const [password, setPassword] = useState<string | undefined>();
|
||||
const [repeat, setRepeat] = useState<string | undefined>();
|
||||
@ -268,7 +272,7 @@ function UpdateAccountPassword({
|
||||
<div>
|
||||
<div>
|
||||
<h1 class="nav welcome-text">
|
||||
<i18n.Translate>Admin panel</i18n.Translate>
|
||||
<i18n.Translate>Update password for {account}</i18n.Translate>
|
||||
</h1>
|
||||
</div>
|
||||
{error && (
|
||||
@ -276,10 +280,6 @@ function UpdateAccountPassword({
|
||||
)}
|
||||
|
||||
<form class="pure-form">
|
||||
<fieldset>
|
||||
<label for="username">{i18n.str`Username`}</label>
|
||||
<input name="username" type="text" readOnly value={account} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label>{i18n.str`Password`}</label>
|
||||
<input
|
||||
@ -366,7 +366,7 @@ function CreateNewAccount({
|
||||
<div>
|
||||
<div>
|
||||
<h1 class="nav welcome-text">
|
||||
<i18n.Translate>Admin panel</i18n.Translate>
|
||||
<i18n.Translate>New account</i18n.Translate>
|
||||
</h1>
|
||||
</div>
|
||||
{error && (
|
||||
@ -428,19 +428,21 @@ function CreateNewAccount({
|
||||
);
|
||||
}
|
||||
|
||||
function ShowAccountDetails({
|
||||
export function ShowAccountDetails({
|
||||
account,
|
||||
onClear,
|
||||
onUpdateSuccess,
|
||||
onLoadNotOk,
|
||||
onChangePassword,
|
||||
}: {
|
||||
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
|
||||
onClear: () => void;
|
||||
onClear?: () => void;
|
||||
onChangePassword: () => void;
|
||||
onUpdateSuccess: () => void;
|
||||
account: string;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const result = useAccountDetails(account);
|
||||
const result = useBusinessAccountDetails(account);
|
||||
const { updateAccount } = useAdminAccountAPI();
|
||||
const [update, setUpdate] = useState(false);
|
||||
const [submitAccount, setSubmitAccount] = useState<
|
||||
@ -459,7 +461,7 @@ function ShowAccountDetails({
|
||||
<div>
|
||||
<div>
|
||||
<h1 class="nav welcome-text">
|
||||
<i18n.Translate>Admin panel</i18n.Translate>
|
||||
<i18n.Translate>Business account details</i18n.Translate>
|
||||
</h1>
|
||||
</div>
|
||||
{error && (
|
||||
@ -474,42 +476,59 @@ function ShowAccountDetails({
|
||||
<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();
|
||||
}}
|
||||
/>
|
||||
{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();
|
||||
<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) {
|
||||
handleError(error, saveError, i18n);
|
||||
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) {
|
||||
handleError(error, saveError, i18n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Logger } from "@gnu-taler/taler-util";
|
||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||
import talerLogo from "../assets/logo-white.svg";
|
||||
import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js";
|
||||
@ -24,41 +25,46 @@ import {
|
||||
PageStateType,
|
||||
usePageContext,
|
||||
} from "../context/pageState.js";
|
||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { useBusinessAccountDetails } from "../hooks/circuit.js";
|
||||
import { bankUiSettings } from "../settings.js";
|
||||
|
||||
const logger = new Logger("BankFrame");
|
||||
|
||||
function MaybeBusinessButton({
|
||||
account,
|
||||
onClick,
|
||||
}: {
|
||||
account: string;
|
||||
onClick: () => void;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const result = useBusinessAccountDetails(account);
|
||||
if (!result.ok) return <Fragment />;
|
||||
return (
|
||||
<div class="some-space">
|
||||
<a
|
||||
href="#"
|
||||
class="pure-button pure-button-primary"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}}
|
||||
>{i18n.str`Business Profile`}</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BankFrame({
|
||||
children,
|
||||
goToBusinessAccount,
|
||||
}: {
|
||||
children: ComponentChildren;
|
||||
goToBusinessAccount?: () => void;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const backend = useBackendContext();
|
||||
const { pageState, pageStateSetter } = usePageContext();
|
||||
logger.trace("state", pageState);
|
||||
const logOut = (
|
||||
<div class="logout">
|
||||
<a
|
||||
href="#"
|
||||
class="pure-button logout-button"
|
||||
onClick={() => {
|
||||
pageStateSetter((prevState: PageStateType) => {
|
||||
const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
|
||||
backend.logOut();
|
||||
return {
|
||||
...rest,
|
||||
withdrawalInProgress: false,
|
||||
error: undefined,
|
||||
info: undefined,
|
||||
isRawPayto: false,
|
||||
};
|
||||
});
|
||||
}}
|
||||
>{i18n.str`Logout`}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
const demo_sites = [];
|
||||
for (const i in bankUiSettings.demoSites)
|
||||
@ -120,7 +126,36 @@ export function BankFrame({
|
||||
/>
|
||||
)}
|
||||
<StatusBanner />
|
||||
{backend.state.status === "loggedIn" ? logOut : null}
|
||||
{backend.state.status === "loggedIn" ? (
|
||||
<div class="top-right">
|
||||
{goToBusinessAccount ? (
|
||||
<MaybeBusinessButton
|
||||
account={backend.state.username}
|
||||
onClick={goToBusinessAccount}
|
||||
/>
|
||||
) : undefined}
|
||||
<div class="some-space">
|
||||
<a
|
||||
href="#"
|
||||
class="pure-button logout-button"
|
||||
onClick={() => {
|
||||
pageStateSetter((prevState: PageStateType) => {
|
||||
const { talerWithdrawUri, withdrawalId, ...rest } =
|
||||
prevState;
|
||||
backend.logOut();
|
||||
return {
|
||||
...rest,
|
||||
withdrawalInProgress: false,
|
||||
error: undefined,
|
||||
info: undefined,
|
||||
isRawPayto: false,
|
||||
};
|
||||
});
|
||||
}}
|
||||
>{i18n.str`Logout`}</a>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{children}
|
||||
</section>
|
||||
<section id="footer" class="footer">
|
||||
|
90
packages/demobank-ui/src/pages/BusinessAccount.tsx
Normal file
90
packages/demobank-ui/src/pages/BusinessAccount.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
HttpResponsePaginated,
|
||||
useTranslationContext,
|
||||
} from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { Cashouts } from "../components/Cashouts/index.js";
|
||||
import { useBackendContext } from "../context/backend.js";
|
||||
import { usePageContext } from "../context/pageState.js";
|
||||
import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
|
||||
import { LoginForm } from "./LoginForm.js";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onRegister: () => void;
|
||||
onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
|
||||
}
|
||||
export function BusinessAccount({
|
||||
onClose,
|
||||
onLoadNotOk,
|
||||
onRegister,
|
||||
}: Props): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const { pageStateSetter } = usePageContext();
|
||||
const backend = useBackendContext();
|
||||
const [updatePassword, setUpdatePassword] = useState(false);
|
||||
function showInfoMessage(info: TranslatedString): void {
|
||||
pageStateSetter((prev) => ({
|
||||
...prev,
|
||||
info,
|
||||
}));
|
||||
}
|
||||
|
||||
if (backend.state.status === "loggedOut") {
|
||||
return <LoginForm onRegister={onRegister} />;
|
||||
}
|
||||
|
||||
if (updatePassword) {
|
||||
return (
|
||||
<UpdateAccountPassword
|
||||
account={backend.state.username}
|
||||
onLoadNotOk={onLoadNotOk}
|
||||
onUpdateSuccess={() => {
|
||||
showInfoMessage(i18n.str`Password changed`);
|
||||
setUpdatePassword(false);
|
||||
}}
|
||||
onClear={() => {
|
||||
setUpdatePassword(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<ShowAccountDetails
|
||||
account={backend.state.username}
|
||||
onLoadNotOk={onLoadNotOk}
|
||||
onUpdateSuccess={() => {
|
||||
showInfoMessage(i18n.str`Account updated`);
|
||||
}}
|
||||
onChangePassword={() => {
|
||||
setUpdatePassword(true);
|
||||
}}
|
||||
onClear={onClose}
|
||||
/>
|
||||
<section style={{ marginTop: "2em" }}>
|
||||
<div class="active">
|
||||
<h3>{i18n.str`Latest cashouts`}</h3>
|
||||
<Cashouts />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -50,6 +50,7 @@ export function HomePage({ onRegister }: { onRegister: () => void }): VNode {
|
||||
}
|
||||
|
||||
function saveErrorAndLogout(error: PageStateType["error"]): void {
|
||||
console.log("rrot", error);
|
||||
saveError(error);
|
||||
backend.logOut();
|
||||
}
|
||||
@ -123,6 +124,7 @@ function handleNotOkResult(
|
||||
return function handleNotOkResult2<T, E>(
|
||||
result: HttpResponsePaginated<T, E>,
|
||||
): VNode {
|
||||
console.log("qweqwe", JSON.stringify(result, undefined, 2));
|
||||
if (result.clientError && result.isUnauthorized) {
|
||||
onErrorHandler({
|
||||
title: i18n.str`Wrong credentials for "${account}"`,
|
||||
@ -139,7 +141,7 @@ function handleNotOkResult(
|
||||
if (!result.ok) {
|
||||
onErrorHandler({
|
||||
title: i18n.str`The backend reported a problem: HTTP status #${result.status}`,
|
||||
description: `Diagnostic from ${result.info?.url.href} is "${result.message}"`,
|
||||
description: `Diagnostic from ${result.info?.url} is "${result.message}"`,
|
||||
debug: JSON.stringify(result.error),
|
||||
});
|
||||
return <LoginForm onRegister={onRegister} />;
|
||||
|
@ -28,6 +28,7 @@ import { HomePage } from "./HomePage.js";
|
||||
import { BankFrame } from "./BankFrame.js";
|
||||
import { PublicHistoriesPage } from "./PublicHistoriesPage.js";
|
||||
import { RegistrationPage } from "./RegistrationPage.js";
|
||||
import { BusinessAccount } from "./BusinessAccount.js";
|
||||
|
||||
function handleNotOkResult(
|
||||
safe: string,
|
||||
@ -96,7 +97,11 @@ export function Routing(): VNode {
|
||||
<Route
|
||||
path="/account"
|
||||
component={() => (
|
||||
<BankFrame>
|
||||
<BankFrame
|
||||
goToBusinessAccount={() => {
|
||||
route("/business");
|
||||
}}
|
||||
>
|
||||
<HomePage
|
||||
onRegister={() => {
|
||||
route("/register");
|
||||
@ -105,6 +110,22 @@ export function Routing(): VNode {
|
||||
</BankFrame>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/business"
|
||||
component={() => (
|
||||
<BankFrame>
|
||||
<BusinessAccount
|
||||
onClose={() => {
|
||||
route("/account");
|
||||
}}
|
||||
onRegister={() => {
|
||||
route("/register");
|
||||
}}
|
||||
onLoadNotOk={handleNotOkResult("/account", saveError, i18n)}
|
||||
/>
|
||||
</BankFrame>
|
||||
)}
|
||||
/>
|
||||
<Route default component={Redirect} to="/account" />
|
||||
</Router>
|
||||
);
|
||||
|
@ -51,8 +51,11 @@ input[type="number"]::-webkit-inner-spin-button {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logout {
|
||||
.top-right {
|
||||
float: right;
|
||||
}
|
||||
.some-space {
|
||||
display: inline-block;
|
||||
border: 20px;
|
||||
margin-right: 15px;
|
||||
margin-top: 15px;
|
||||
|
@ -54,8 +54,10 @@ export type PartialButDefined<T> = {
|
||||
};
|
||||
|
||||
export type WithIntermediate<Type extends object> = {
|
||||
[prop in keyof Type]: Type[prop] extends object ? WithIntermediate<Type[prop]> : (Type[prop] | undefined);
|
||||
}
|
||||
[prop in keyof Type]: Type[prop] extends object
|
||||
? WithIntermediate<Type[prop]>
|
||||
: Type[prop] | undefined;
|
||||
};
|
||||
|
||||
// export function partialWithObjects<T extends object>(obj: T | undefined, () => complete): WithIntermediate<T> {
|
||||
// const root = obj === undefined ? {} : obj;
|
||||
|
Loading…
Reference in New Issue
Block a user