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