towards new core bank api
This commit is contained in:
parent
5640f0a67d
commit
15af6c619d
@ -27,6 +27,7 @@ import { Test } from "../pages/Test.js";
|
||||
import { useBackendContext } from "../context/backend.js";
|
||||
import { LoginForm } from "../pages/LoginForm.js";
|
||||
import { AdminHome } from "../pages/admin/Home.js";
|
||||
import { bankUiSettings } from "../settings.js";
|
||||
|
||||
export function Routing(): VNode {
|
||||
const history = createHashHistory();
|
||||
@ -45,6 +46,10 @@ export function Routing(): VNode {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/public-accounts"
|
||||
component={() => <PublicHistoriesPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/operation/:wopid"
|
||||
component={({ wopid }: { wopid: string }) => (
|
||||
@ -53,12 +58,10 @@ export function Routing(): VNode {
|
||||
onContinue={() => {
|
||||
route("/account");
|
||||
}}
|
||||
// onLoadNotOk={() => {
|
||||
// route("/account");
|
||||
// }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{bankUiSettings.allowRegistrations &&
|
||||
<Route
|
||||
path="/register"
|
||||
component={() => (
|
||||
@ -69,6 +72,7 @@ export function Routing(): VNode {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
<Route default component={Redirect} to="/login" />
|
||||
</Router>
|
||||
</BankFrame>
|
||||
@ -93,16 +97,6 @@ export function Routing(): VNode {
|
||||
path="/public-accounts"
|
||||
component={() => <PublicHistoriesPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/register"
|
||||
component={() => (
|
||||
<RegistrationPage
|
||||
onComplete={() => {
|
||||
route("/account");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/account"
|
||||
component={() => {
|
||||
|
166
packages/demobank-ui/src/declaration.d.ts
vendored
166
packages/demobank-ui/src/declaration.d.ts
vendored
@ -99,11 +99,6 @@ type Amount = string;
|
||||
type UUID = string;
|
||||
type Integer = number;
|
||||
|
||||
interface Balance {
|
||||
amount: Amount;
|
||||
credit_debit_indicator: "credit" | "debit";
|
||||
}
|
||||
|
||||
namespace SandboxBackend {
|
||||
export interface Config {
|
||||
// Name of this API, always "circuit".
|
||||
@ -126,7 +121,7 @@ namespace SandboxBackend {
|
||||
}
|
||||
|
||||
export interface SandboxError {
|
||||
error: SandboxErrorDetail;
|
||||
error?: SandboxErrorDetail;
|
||||
}
|
||||
interface SandboxErrorDetail {
|
||||
// String enum classifying the error.
|
||||
@ -152,26 +147,12 @@ namespace SandboxBackend {
|
||||
UtilError = "util-error",
|
||||
}
|
||||
|
||||
namespace Access {
|
||||
interface PublicAccountsResponse {
|
||||
publicAccounts: PublicAccount[];
|
||||
}
|
||||
interface PublicAccount {
|
||||
iban: string;
|
||||
balance: string;
|
||||
// The account name _and_ the username of the
|
||||
// Sandbox customer that owns such a bank account.
|
||||
accountLabel: string;
|
||||
}
|
||||
|
||||
interface BankAccountBalanceResponse {
|
||||
// Available balance on the account.
|
||||
balance: Balance;
|
||||
// payto://-URI of the account. (New)
|
||||
paytoUri: string;
|
||||
// Number indicating the max debit allowed for the requesting user.
|
||||
debitThreshold: Amount;
|
||||
}
|
||||
type EmailAddress = string;
|
||||
type PhoneNumber = string;
|
||||
|
||||
namespace CoreBank {
|
||||
|
||||
interface BankAccountCreateWithdrawalRequest {
|
||||
// Amount to withdraw.
|
||||
amount: Amount;
|
||||
@ -243,11 +224,144 @@ namespace SandboxBackend {
|
||||
amount?: string;
|
||||
}
|
||||
|
||||
interface BankRegistrationRequest {
|
||||
interface RegisterAccountRequest {
|
||||
// Username
|
||||
username: string;
|
||||
|
||||
// Password.
|
||||
password: string;
|
||||
|
||||
// Legal name of the account owner
|
||||
name: string;
|
||||
|
||||
// Defaults to false.
|
||||
is_public?: boolean;
|
||||
|
||||
// Is this a taler exchange account?
|
||||
// If true:
|
||||
// - incoming transactions to the account that do not
|
||||
// have a valid reserve public key are automatically
|
||||
// - the account provides the taler-wire-gateway-api endpoints
|
||||
// Defaults to false.
|
||||
is_taler_exchange?: boolean;
|
||||
|
||||
// Addresses where to send the TAN for transactions.
|
||||
// Currently only used for cashouts.
|
||||
// If missing, cashouts will fail.
|
||||
// In the future, might be used for other transactions
|
||||
// as well.
|
||||
challenge_contact_data?: ChallengeContactData;
|
||||
|
||||
// 'payto' address pointing a bank account
|
||||
// external to the libeufin-bank.
|
||||
// Payments will be sent to this bank account
|
||||
// when the user wants to convert the local currency
|
||||
// back to fiat currency outside libeufin-bank.
|
||||
cashout_payto_uri?: string;
|
||||
|
||||
// Internal payto URI of this bank account.
|
||||
// Used mostly for testing.
|
||||
internal_payto_uri?: string;
|
||||
}
|
||||
interface ChallengeContactData {
|
||||
|
||||
// E-Mail address
|
||||
email?: EmailAddress;
|
||||
|
||||
// Phone number.
|
||||
phone?: PhoneNumber;
|
||||
}
|
||||
|
||||
interface AccountReconfiguration {
|
||||
|
||||
// Addresses where to send the TAN for transactions.
|
||||
// Currently only used for cashouts.
|
||||
// If missing, cashouts will fail.
|
||||
// In the future, might be used for other transactions
|
||||
// as well.
|
||||
challenge_contact_data?: ChallengeContactData;
|
||||
|
||||
// 'payto' address pointing a bank account
|
||||
// external to the libeufin-bank.
|
||||
// Payments will be sent to this bank account
|
||||
// when the user wants to convert the local currency
|
||||
// back to fiat currency outside libeufin-bank.
|
||||
cashout_address?: string;
|
||||
|
||||
// Legal name associated with $username.
|
||||
// When missing, the old name is kept.
|
||||
name?: string;
|
||||
|
||||
// If present, change the is_exchange configuration.
|
||||
// See RegisterAccountRequest
|
||||
is_exchange?: boolean;
|
||||
}
|
||||
|
||||
|
||||
interface AccountPasswordChange {
|
||||
|
||||
// New password.
|
||||
new_password: string;
|
||||
}
|
||||
interface PublicAccountsResponse {
|
||||
public_accounts: PublicAccount[];
|
||||
}
|
||||
interface PublicAccount {
|
||||
payto_uri: string;
|
||||
|
||||
balance: Balance;
|
||||
|
||||
// The account name (=username) of the
|
||||
// libeufin-bank account.
|
||||
account_name: string;
|
||||
}
|
||||
|
||||
interface ListBankAccountsResponse {
|
||||
accounts: AccountMinimalData[];
|
||||
}
|
||||
// interface Balance {
|
||||
// amount: Amount;
|
||||
// credit_debit_indicator: "credit" | "debit";
|
||||
// }
|
||||
type Balance = Amount
|
||||
interface AccountMinimalData {
|
||||
// Username
|
||||
username: string;
|
||||
|
||||
// Legal name of the account owner.
|
||||
name: string;
|
||||
|
||||
// current balance of the account
|
||||
balance: Balance;
|
||||
|
||||
// Number indicating the max debit allowed for the requesting user.
|
||||
debit_threshold: Amount;
|
||||
}
|
||||
|
||||
interface AccountData {
|
||||
// Legal name of the account owner.
|
||||
name: string;
|
||||
|
||||
// Available balance on the account.
|
||||
balance: Balance;
|
||||
|
||||
// payto://-URI of the account.
|
||||
payto_uri: string;
|
||||
|
||||
// Number indicating the max debit allowed for the requesting user.
|
||||
debit_threshold: Amount;
|
||||
|
||||
contact_data?: ChallengeContactData;
|
||||
|
||||
// 'payto' address pointing the bank account
|
||||
// where to send cashouts. This field is optional
|
||||
// because not all the accounts are required to participate
|
||||
// in the merchants' circuit. One example is the exchange:
|
||||
// that never cashouts. Registering these accounts can
|
||||
// be done via the access API.
|
||||
cashout_payto_uri?: string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Circuit {
|
||||
|
@ -44,13 +44,13 @@ export function useAccessAPI(): AccessAPI {
|
||||
const account = state.username;
|
||||
|
||||
const createWithdrawal = async (
|
||||
data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
|
||||
data: SandboxBackend.CoreBank.BankAccountCreateWithdrawalRequest,
|
||||
): Promise<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
|
||||
HttpResponseOk<SandboxBackend.CoreBank.BankAccountCreateWithdrawalResponse>
|
||||
> => {
|
||||
const res =
|
||||
await request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(
|
||||
`access-api/accounts/${account}/withdrawals`,
|
||||
await request<SandboxBackend.CoreBank.BankAccountCreateWithdrawalResponse>(
|
||||
`accounts/${account}/withdrawals`,
|
||||
{
|
||||
method: "POST",
|
||||
data,
|
||||
@ -60,10 +60,10 @@ export function useAccessAPI(): AccessAPI {
|
||||
return res;
|
||||
};
|
||||
const createTransaction = async (
|
||||
data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
|
||||
data: SandboxBackend.CoreBank.CreateBankAccountTransactionCreate,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(
|
||||
`access-api/accounts/${account}/transactions`,
|
||||
`accounts/${account}/transactions`,
|
||||
{
|
||||
method: "POST",
|
||||
data,
|
||||
@ -74,7 +74,7 @@ export function useAccessAPI(): AccessAPI {
|
||||
return res;
|
||||
};
|
||||
const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/accounts/${account}`, {
|
||||
const res = await request<void>(`accounts/${account}`, {
|
||||
method: "DELETE",
|
||||
contentType: "json",
|
||||
});
|
||||
@ -94,7 +94,7 @@ export function useAccessAnonAPI(): AccessAnonAPI {
|
||||
const { request } = useAuthenticatedBackend();
|
||||
|
||||
const abortWithdrawal = async (id: string): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/withdrawals/${id}/abort`, {
|
||||
const res = await request<void>(`accounts/withdrawals/${id}/abort`, {
|
||||
method: "POST",
|
||||
contentType: "json",
|
||||
});
|
||||
@ -104,7 +104,7 @@ export function useAccessAnonAPI(): AccessAnonAPI {
|
||||
const confirmWithdrawal = async (
|
||||
id: string,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`access-api/withdrawals/${id}/confirm`, {
|
||||
const res = await request<void>(`withdrawals/${id}/confirm`, {
|
||||
method: "POST",
|
||||
contentType: "json",
|
||||
});
|
||||
@ -122,10 +122,10 @@ export function useTestingAPI(): TestingAPI {
|
||||
const mutateAll = useMatchMutate();
|
||||
const { request: noAuthRequest } = usePublicBackend();
|
||||
const register = async (
|
||||
data: SandboxBackend.Access.BankRegistrationRequest,
|
||||
data: SandboxBackend.CoreBank.RegisterAccountRequest,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
// FIXME: This API is deprecated. The normal account registration API should be used instead.
|
||||
const res = await noAuthRequest<void>(`access-api/testing/register`, {
|
||||
const res = await noAuthRequest<void>(`accounts`, {
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json",
|
||||
@ -139,18 +139,18 @@ export function useTestingAPI(): TestingAPI {
|
||||
|
||||
export interface TestingAPI {
|
||||
register: (
|
||||
data: SandboxBackend.Access.BankRegistrationRequest,
|
||||
data: SandboxBackend.CoreBank.RegisterAccountRequest,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
|
||||
export interface AccessAPI {
|
||||
createWithdrawal: (
|
||||
data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
|
||||
data: SandboxBackend.CoreBank.BankAccountCreateWithdrawalRequest,
|
||||
) => Promise<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
|
||||
HttpResponseOk<SandboxBackend.CoreBank.BankAccountCreateWithdrawalResponse>
|
||||
>;
|
||||
createTransaction: (
|
||||
data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
|
||||
data: SandboxBackend.CoreBank.CreateBankAccountTransactionCreate,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
deleteAccount: () => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
@ -167,15 +167,15 @@ export interface InstanceTemplateFilter {
|
||||
export function useAccountDetails(
|
||||
account: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Access.BankAccountBalanceResponse,
|
||||
SandboxBackend.CoreBank.AccountData,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountBalanceResponse>,
|
||||
HttpResponseOk<SandboxBackend.CoreBank.AccountData>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/accounts/${account}`], fetcher, {
|
||||
>([`accounts/${account}`], fetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
@ -187,29 +187,9 @@ export function useAccountDetails(
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
//FIXME: remove optional when libeufin sandbox has implemented the feature
|
||||
if (data && typeof data.data.debitThreshold === "undefined") {
|
||||
data.data.debitThreshold = "0";
|
||||
}
|
||||
//FIXME: sandbox server should return amount string
|
||||
if (data) {
|
||||
const isAmount = Amounts.parse(data.data.debitThreshold);
|
||||
if (isAmount) {
|
||||
//server response with correct format
|
||||
return data;
|
||||
}
|
||||
const { currency } = Amounts.parseOrThrow(data.data.balance.amount);
|
||||
const clone = structuredClone(data);
|
||||
|
||||
const theNumber = Number.parseInt(data.data.debitThreshold, 10);
|
||||
const value = Number.isNaN(theNumber) ? 0 : theNumber;
|
||||
clone.data.debitThreshold = Amounts.stringify({
|
||||
currency,
|
||||
value: value,
|
||||
fraction: 0,
|
||||
});
|
||||
return clone;
|
||||
}
|
||||
if (error) return error.cause;
|
||||
return { loading: true };
|
||||
}
|
||||
@ -218,15 +198,15 @@ export function useAccountDetails(
|
||||
export function useWithdrawalDetails(
|
||||
wid: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Access.BankAccountGetWithdrawalResponse,
|
||||
SandboxBackend.CoreBank.BankAccountGetWithdrawalResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountGetWithdrawalResponse>,
|
||||
HttpResponseOk<SandboxBackend.CoreBank.BankAccountGetWithdrawalResponse>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/withdrawals/${wid}`], fetcher, {
|
||||
>([`withdrawals/${wid}`], fetcher, {
|
||||
refreshInterval: 1000,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
@ -248,15 +228,15 @@ export function useTransactionDetails(
|
||||
account: string,
|
||||
tid: string,
|
||||
): HttpResponse<
|
||||
SandboxBackend.Access.BankAccountTransactionInfo,
|
||||
SandboxBackend.CoreBank.BankAccountTransactionInfo,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { fetcher } = useAuthenticatedBackend();
|
||||
const { paginatedFetcher } = useAuthenticatedBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountTransactionInfo>,
|
||||
HttpResponseOk<SandboxBackend.CoreBank.BankAccountTransactionInfo>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/accounts/${account}/transactions/${tid}`], fetcher, {
|
||||
>([`accounts/${account}/transactions/${tid}`], paginatedFetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
@ -281,7 +261,7 @@ interface PaginationFilter {
|
||||
export function usePublicAccounts(
|
||||
args?: PaginationFilter,
|
||||
): HttpResponsePaginated<
|
||||
SandboxBackend.Access.PublicAccountsResponse,
|
||||
SandboxBackend.CoreBank.PublicAccountsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { paginatedFetcher } = usePublicBackend();
|
||||
@ -293,13 +273,13 @@ export function usePublicAccounts(
|
||||
error: afterError,
|
||||
isValidating: loadingAfter,
|
||||
} = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.PublicAccountsResponse>,
|
||||
HttpResponseOk<SandboxBackend.CoreBank.PublicAccountsResponse>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`access-api/public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher);
|
||||
>([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher);
|
||||
|
||||
const [lastAfter, setLastAfter] = useState<
|
||||
HttpResponse<
|
||||
SandboxBackend.Access.PublicAccountsResponse,
|
||||
SandboxBackend.CoreBank.PublicAccountsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
>
|
||||
>({ loading: true });
|
||||
@ -312,7 +292,7 @@ export function usePublicAccounts(
|
||||
|
||||
// if the query returns less that we ask, then we have reach the end or beginning
|
||||
const isReachingEnd =
|
||||
afterData && afterData.data.publicAccounts.length < PAGE_SIZE;
|
||||
afterData && afterData.data.public_accounts.length < PAGE_SIZE;
|
||||
const isReachingStart = false;
|
||||
|
||||
const pagination = {
|
||||
@ -320,7 +300,7 @@ export function usePublicAccounts(
|
||||
isReachingStart,
|
||||
loadMore: () => {
|
||||
if (!afterData || isReachingEnd) return;
|
||||
if (afterData.data.publicAccounts.length < MAX_RESULT_SIZE) {
|
||||
if (afterData.data.public_accounts.length < MAX_RESULT_SIZE) {
|
||||
setPage(page + 1);
|
||||
}
|
||||
},
|
||||
@ -329,12 +309,12 @@ export function usePublicAccounts(
|
||||
},
|
||||
};
|
||||
|
||||
const publicAccounts = !afterData
|
||||
const public_accounts = !afterData
|
||||
? []
|
||||
: (afterData || lastAfter).data.publicAccounts;
|
||||
if (loadingAfter) return { loading: true, data: { publicAccounts } };
|
||||
: (afterData || lastAfter).data.public_accounts;
|
||||
if (loadingAfter) return { loading: true, data: { public_accounts } };
|
||||
if (afterData) {
|
||||
return { ok: true, data: { publicAccounts }, ...pagination };
|
||||
return { ok: true, data: { public_accounts }, ...pagination };
|
||||
}
|
||||
return { loading: true };
|
||||
}
|
||||
@ -349,7 +329,7 @@ export function useTransactions(
|
||||
account: string,
|
||||
args?: PaginationFilter,
|
||||
): HttpResponsePaginated<
|
||||
SandboxBackend.Access.BankAccountTransactionsResponse,
|
||||
SandboxBackend.CoreBank.BankAccountTransactionsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
> {
|
||||
const { paginatedFetcher } = useAuthenticatedBackend();
|
||||
@ -361,10 +341,10 @@ export function useTransactions(
|
||||
error: afterError,
|
||||
isValidating: loadingAfter,
|
||||
} = useSWR<
|
||||
HttpResponseOk<SandboxBackend.Access.BankAccountTransactionsResponse>,
|
||||
HttpResponseOk<SandboxBackend.CoreBank.BankAccountTransactionsResponse>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>(
|
||||
[`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE],
|
||||
[`accounts/${account}/transactions`, args?.page, PAGE_SIZE],
|
||||
paginatedFetcher, {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
@ -378,7 +358,7 @@ export function useTransactions(
|
||||
|
||||
const [lastAfter, setLastAfter] = useState<
|
||||
HttpResponse<
|
||||
SandboxBackend.Access.BankAccountTransactionsResponse,
|
||||
SandboxBackend.CoreBank.BankAccountTransactionsResponse,
|
||||
SandboxBackend.SandboxError
|
||||
>
|
||||
>({ loading: true });
|
||||
|
@ -310,7 +310,7 @@ export function useAuthenticatedBackend(): useBackendType {
|
||||
]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
basicAuth: creds,
|
||||
params: { page, size },
|
||||
params: { delta: size, start: size * page },
|
||||
});
|
||||
},
|
||||
[baseUrl, creds],
|
||||
|
@ -9,20 +9,25 @@ export function useCredentialsChecker() {
|
||||
//while merchant backend doesn't have a login endpoint
|
||||
async function requestNewLoginToken(
|
||||
username: string,
|
||||
password: AccessToken,
|
||||
password: string,
|
||||
): Promise<LoginResult> {
|
||||
const data: LoginTokenRequest = {
|
||||
scope: "write",
|
||||
scope: "readwrite" as "write", //FIX: different than merchant
|
||||
duration: {
|
||||
d_us: "forever"
|
||||
// d_us: "forever" //FIX: should return shortest
|
||||
d_us: 1000 * 60 * 60 * 23
|
||||
},
|
||||
refreshable: true,
|
||||
}
|
||||
try {
|
||||
const response = await request<LoginTokenSuccessResponse>(baseUrl, `accounts/${username}/token`, {
|
||||
method: "POST",
|
||||
token: password,
|
||||
data
|
||||
basicAuth: {
|
||||
username: username,
|
||||
password,
|
||||
},
|
||||
data,
|
||||
contentType: "json"
|
||||
});
|
||||
return { valid: true, token: response.data.token, expiration: response.data.expiration };
|
||||
} catch (error) {
|
||||
|
@ -60,7 +60,7 @@ export namespace State {
|
||||
|
||||
export interface InvalidIban {
|
||||
status: "invalid-iban",
|
||||
error: HttpResponseOk<SandboxBackend.Access.BankAccountBalanceResponse>;
|
||||
error: HttpResponseOk<SandboxBackend.CoreBank.AccountData>;
|
||||
}
|
||||
|
||||
export interface UserNotFound {
|
||||
|
@ -40,7 +40,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
|
||||
};
|
||||
}
|
||||
//logout if there is any error, not if loading
|
||||
backend.logOut();
|
||||
// backend.logOut();
|
||||
if (result.status === HttpStatusCode.NotFound) {
|
||||
notifyError(i18n.str`Username or account label "${account}" not found`, undefined);
|
||||
return {
|
||||
@ -55,9 +55,13 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
|
||||
}
|
||||
|
||||
const { data } = result;
|
||||
const balance = Amounts.parseOrThrow(data.balance.amount);
|
||||
const debitThreshold = Amounts.parseOrThrow(data.debitThreshold);
|
||||
const payto = parsePaytoUri(data.paytoUri);
|
||||
|
||||
// FIXME: balance
|
||||
// const balance = Amounts.parseOrThrow(data.balance.amount);
|
||||
const balance = Amounts.parseOrThrow(data.balance);
|
||||
|
||||
const debitThreshold = Amounts.parseOrThrow(data.debit_threshold);
|
||||
const payto = parsePaytoUri(data.payto_uri);
|
||||
|
||||
if (!payto || !payto.isKnown || (payto.targetType !== "iban" && payto.targetType !== "x-taler-bank")) {
|
||||
return {
|
||||
@ -66,7 +70,9 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
|
||||
};
|
||||
}
|
||||
|
||||
const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
|
||||
// FIXME: balance
|
||||
const balanceIsDebit = true;
|
||||
// data.balance.credit_debit_indicator == "debit";
|
||||
const limit = balanceIsDebit
|
||||
? Amounts.sub(debitThreshold, balance).amount
|
||||
: Amounts.add(balance, debitThreshold).amount;
|
||||
|
@ -27,7 +27,7 @@ import { useSettings } from "../../hooks/settings.js";
|
||||
|
||||
export function InvalidIbanView({ error }: State.InvalidIban) {
|
||||
return (
|
||||
<div>Payto from server is not valid "{error.data.paytoUri}"</div>
|
||||
<div>Payto from server is not valid "{error.data.payto_uri}"</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -458,15 +458,14 @@ function WelcomeAccount({ account }: { account: string }): VNode {
|
||||
const result = useAccountDetails(account);
|
||||
if (!result.ok) return <div />
|
||||
|
||||
// const account = "Sebastian"
|
||||
const payto = parsePaytoUri(result.data.paytoUri)
|
||||
const payto = parsePaytoUri(result.data.payto_uri)
|
||||
if (!payto) return <div />
|
||||
|
||||
const accountNumber = !payto.isKnown ? undefined : payto.targetType === "iban" ? payto.iban : payto.targetType === "x-taler-bank" ? payto.account : undefined;
|
||||
return <i18n.Translate>
|
||||
Welcome, {account} {accountNumber !== undefined ?
|
||||
<span>
|
||||
(<a href={result.data.paytoUri}>{accountNumber}</a> <CopyButton getContent={() => result.data.paytoUri} />)
|
||||
(<a href={result.data.payto_uri}>{accountNumber}</a> <CopyButton getContent={() => result.data.payto_uri} />)
|
||||
</span>
|
||||
: <Fragment />}!
|
||||
</i18n.Translate>
|
||||
@ -477,9 +476,12 @@ function AccountBalance({ account }: { account: string }): VNode {
|
||||
const result = useAccountDetails(account);
|
||||
if (!result.ok) return <div />
|
||||
|
||||
// FIXME: balance
|
||||
return <div>
|
||||
{Amounts.currencyOf(result.data.balance.amount)}
|
||||
{Amounts.currencyOf(result.data.balance)}
|
||||
{Amounts.stringifyValue(result.data.balance)}
|
||||
{/* {Amounts.currencyOf(result.data.balance.amount)}
|
||||
{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
|
||||
{Amounts.stringifyValue(result.data.balance.amount)}
|
||||
{Amounts.stringifyValue(result.data.balance.amount)} */}
|
||||
</div>
|
||||
}
|
||||
|
@ -120,8 +120,8 @@ export function handleNotOkResult(
|
||||
) => VNode {
|
||||
return function handleNotOkResult2<T>(
|
||||
result:
|
||||
| HttpResponsePaginated<T, SandboxBackend.SandboxError>
|
||||
| HttpResponse<T, SandboxBackend.SandboxError>,
|
||||
| HttpResponsePaginated<T, SandboxBackend.SandboxError | undefined>
|
||||
| HttpResponse<T, SandboxBackend.SandboxError| undefined>,
|
||||
): VNode {
|
||||
if (result.loading) return <Loading />;
|
||||
if (!result.ok) {
|
||||
@ -139,7 +139,7 @@ export function handleNotOkResult(
|
||||
notify({
|
||||
type: "error",
|
||||
title: i18n.str`Could not load due to a client error`,
|
||||
description: errorData.error.description as TranslatedString,
|
||||
description: errorData?.error?.description as TranslatedString,
|
||||
debug: JSON.stringify(result),
|
||||
});
|
||||
break;
|
||||
@ -148,7 +148,7 @@ export function handleNotOkResult(
|
||||
notify({
|
||||
type: "error",
|
||||
title: i18n.str`Server returned with error`,
|
||||
description: result.payload.error.description as TranslatedString,
|
||||
description: result.payload?.error?.description as TranslatedString,
|
||||
debug: JSON.stringify(result.payload),
|
||||
});
|
||||
break;
|
||||
|
@ -31,12 +31,13 @@ import { useCredentialsCheckerOld } from "../hooks/backend.js";
|
||||
*/
|
||||
export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||
const backend = useBackendContext();
|
||||
const [username, setUsername] = useState<string | undefined>();
|
||||
const currentUser = backend.state.status === "loggedIn" ? backend.state.username : undefined
|
||||
const [username, setUsername] = useState<string | undefined>(currentUser);
|
||||
const [password, setPassword] = useState<string | undefined>();
|
||||
const { i18n } = useTranslationContext();
|
||||
// const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker();
|
||||
const { requestNewLoginToken, refreshLoginToken } = useCredentialsChecker();
|
||||
|
||||
const testLogin = useCredentialsCheckerOld();
|
||||
// const testLogin = useCredentialsCheckerOld();
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
useEffect(function focusInput() {
|
||||
ref.current?.focus();
|
||||
@ -46,8 +47,8 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||
const errors = undefinedIfEmpty({
|
||||
username: !username
|
||||
? i18n.str`Missing username`
|
||||
: !USERNAME_REGEX.test(username)
|
||||
? i18n.str`Use letters and numbers only, and start with a lowercase letter`
|
||||
// : !USERNAME_REGEX.test(username)
|
||||
// ? i18n.str`Use letters and numbers only, and start with a lowercase letter`
|
||||
: undefined,
|
||||
password: !password ? i18n.str`Missing password` : undefined,
|
||||
}) ?? busy;
|
||||
@ -59,12 +60,11 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||
async function doLogin() {
|
||||
if (!username || !password) return;
|
||||
setBusy({})
|
||||
const testResult = await testLogin(username, password);
|
||||
if (testResult.valid) {
|
||||
const result = await requestNewLoginToken(username, password);
|
||||
if (result.valid) {
|
||||
backend.logIn({ username, password });
|
||||
} else {
|
||||
if (testResult.requestError) {
|
||||
const { cause } = testResult;
|
||||
const { cause } = result;
|
||||
switch (cause.type) {
|
||||
case ErrorType.CLIENT: {
|
||||
if (cause.status === HttpStatusCode.Unauthorized) {
|
||||
@ -79,7 +79,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||
} else {
|
||||
saveError({
|
||||
title: i18n.str`Could not load due to a client error`,
|
||||
description: cause.payload.error.description,
|
||||
// description: cause.payload.error.description,
|
||||
debug: JSON.stringify(cause.payload),
|
||||
});
|
||||
}
|
||||
@ -88,7 +88,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||
case ErrorType.SERVER: {
|
||||
saveError({
|
||||
title: i18n.str`Server had a problem, try again later or report.`,
|
||||
description: cause.payload.error.description,
|
||||
// description: cause.payload.error.description,
|
||||
debug: JSON.stringify(cause.payload),
|
||||
});
|
||||
break;
|
||||
@ -116,13 +116,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
saveError({
|
||||
title: i18n.str`Unexpected error, please report.`,
|
||||
debug: JSON.stringify(testResult.error),
|
||||
});
|
||||
}
|
||||
backend.logOut();
|
||||
// backend.logOut();
|
||||
}
|
||||
setPassword(undefined);
|
||||
setBusy(undefined)
|
||||
|
@ -36,8 +36,8 @@ export function PublicHistoriesPage({}: Props): VNode {
|
||||
const result = usePublicAccounts();
|
||||
|
||||
const [showAccount, setShowAccount] = useState(
|
||||
result.ok && result.data.publicAccounts.length > 0
|
||||
? result.data.publicAccounts[0].accountLabel
|
||||
result.ok && result.data.public_accounts.length > 0
|
||||
? result.data.public_accounts[0].account_name
|
||||
: undefined,
|
||||
);
|
||||
|
||||
@ -51,9 +51,9 @@ export function PublicHistoriesPage({}: Props): VNode {
|
||||
const accountsBar = [];
|
||||
|
||||
// Ask story of all the public accounts.
|
||||
for (const account of data.publicAccounts) {
|
||||
logger.trace("Asking transactions for", account.accountLabel);
|
||||
const isSelected = account.accountLabel == showAccount;
|
||||
for (const account of data.public_accounts) {
|
||||
logger.trace("Asking transactions for", account.account_name);
|
||||
const isSelected = account.account_name == showAccount;
|
||||
accountsBar.push(
|
||||
<li
|
||||
class={
|
||||
@ -65,13 +65,13 @@ export function PublicHistoriesPage({}: Props): VNode {
|
||||
<a
|
||||
href="#"
|
||||
class="pure-menu-link"
|
||||
onClick={() => setShowAccount(account.accountLabel)}
|
||||
onClick={() => setShowAccount(account.account_name)}
|
||||
>
|
||||
{account.accountLabel}
|
||||
{account.account_name}
|
||||
</a>
|
||||
</li>,
|
||||
);
|
||||
txs[account.accountLabel] = <Transactions account={account.accountLabel} />;
|
||||
txs[account.account_name] = <Transactions account={account.account_name} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -53,6 +53,7 @@ export const USERNAME_REGEX = /^[a-z][a-zA-Z0-9-]*$/;
|
||||
function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
const backend = useBackendContext();
|
||||
const [username, setUsername] = useState<string | undefined>();
|
||||
const [name, setName] = useState<string | undefined>();
|
||||
const [password, setPassword] = useState<string | undefined>();
|
||||
const [repeatPassword, setRepeatPassword] = useState<string | undefined>();
|
||||
|
||||
@ -60,6 +61,9 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
const errors = undefinedIfEmpty({
|
||||
name: !name
|
||||
? i18n.str`Missing name`
|
||||
: undefined,
|
||||
username: !username
|
||||
? i18n.str`Missing username`
|
||||
: !USERNAME_REGEX.test(username)
|
||||
@ -74,9 +78,9 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
});
|
||||
|
||||
async function doRegistrationStep() {
|
||||
if (!username || !password) return;
|
||||
if (!username || !password || !name) return;
|
||||
try {
|
||||
await register({ username, password });
|
||||
await register({ name, username, password });
|
||||
setUsername(undefined);
|
||||
backend.logIn({ username, password });
|
||||
onComplete();
|
||||
@ -97,13 +101,13 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
? error.message
|
||||
: JSON.stringify(error)) as TranslatedString
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
setPassword(undefined);
|
||||
setRepeatPassword(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
async function delay(ms: number):Promise<void> {
|
||||
async function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(undefined);
|
||||
@ -117,14 +121,15 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
setUsername(undefined);
|
||||
setPassword(undefined);
|
||||
setRepeatPassword(undefined);
|
||||
await register({ username: user, password: pass });
|
||||
backend.logIn({ username: user, password: pass });
|
||||
const username = `_${user.first}-${user.second}_`
|
||||
await register({ username, name: `${user.first} ${user.second}`, password: pass });
|
||||
backend.logIn({ username, password: pass });
|
||||
onComplete();
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError) {
|
||||
if (tries > 0) {
|
||||
await delay(200)
|
||||
await doRandomRegistration(tries-1)
|
||||
await doRandomRegistration(tries - 1)
|
||||
} else {
|
||||
notify(
|
||||
buildRequestErrorMessage(i18n, error.cause, {
|
||||
@ -142,7 +147,7 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
? error.message
|
||||
: JSON.stringify(error)) as TranslatedString
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,6 +262,7 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
|
||||
</form>
|
||||
|
||||
{bankUiSettings.allowRandomAccountCreation &&
|
||||
<p class="mt-10 text-center text-sm text-gray-500 border-t">
|
||||
<button type="submit"
|
||||
class="flex mt-4 w-full justify-center rounded-md bg-green-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
|
||||
@ -268,6 +274,7 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
|
||||
<i18n.Translate>Create a random user</i18n.Translate>
|
||||
</button>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -59,7 +59,9 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
|
||||
}, [focus]);
|
||||
|
||||
if (!!settings.currentWithdrawalOperationId) {
|
||||
return <div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
|
||||
return <div>
|
||||
|
||||
<div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
@ -77,9 +79,21 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
|
||||
</i18n.Translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
<div class="flex justify-end gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8 " >
|
||||
<button type="button" class="text-sm font-semibold leading-6 text-gray-900 bg-white p-2 rounded-sm"
|
||||
onClick={() => {
|
||||
updateSettings("currentWithdrawalOperationId", undefined)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<i18n.Translate>Cancel</i18n.Translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
const trimmedAmountStr = amountStr?.trim();
|
||||
|
@ -16,9 +16,14 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode
|
||||
return handleNotOkResult(i18n, onRegister)(result);
|
||||
}
|
||||
const { data } = result;
|
||||
const balance = Amounts.parseOrThrow(data.balance.amount);
|
||||
const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
|
||||
const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
|
||||
|
||||
//FIXME: libeufin does not follow the spec
|
||||
const balance = Amounts.parseOrThrow(data.balance);
|
||||
const balanceIsDebit = true;
|
||||
// const balance = Amounts.parseOrThrow(data.balance.amount);
|
||||
// const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
|
||||
|
||||
const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold);
|
||||
const limit = balanceIsDebit
|
||||
? Amounts.sub(debitThreshold, balance).amount
|
||||
: Amounts.add(balance, debitThreshold).amount;
|
||||
|
@ -41,7 +41,9 @@ export function RemoveAccount({
|
||||
if (focus) ref.current?.focus();
|
||||
}, [focus]);
|
||||
|
||||
const balance = Amounts.parse(result.data.balance.amount);
|
||||
//FIXME: libeufin does not follow the spec
|
||||
const balance = Amounts.parse(result.data.balance);
|
||||
// const balance = Amounts.parse(result.data.balance.amount);
|
||||
if (!balance) {
|
||||
return <div>there was an error reading the balance</div>;
|
||||
}
|
||||
|
@ -236,10 +236,14 @@ function CreateCashout({
|
||||
if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
|
||||
const config = ratiosResult.data;
|
||||
|
||||
const balance = Amounts.parseOrThrow(result.data.balance.amount);
|
||||
const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
|
||||
//FIXME: libeufin does not follow the spec
|
||||
const balance = Amounts.parseOrThrow(result.data.balance);
|
||||
const balanceIsDebit = true;
|
||||
// const balance = Amounts.parseOrThrow(result.data.balance.amount);
|
||||
// const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
|
||||
|
||||
const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold);
|
||||
const zero = Amounts.zeroOfCurrency(balance.currency);
|
||||
const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
|
||||
const limit = balanceIsDebit
|
||||
? Amounts.sub(debitThreshold, balance).amount
|
||||
: Amounts.add(balance, debitThreshold).amount;
|
||||
@ -257,8 +261,7 @@ function CreateCashout({
|
||||
if (!sellRate || sellRate < 0) return <div>error rate</div>;
|
||||
|
||||
const amount = Amounts.parseOrThrow(
|
||||
`${!form.isDebit ? fiatCurrency : balance.currency}:${
|
||||
!form.amount ? "0" : form.amount
|
||||
`${!form.isDebit ? fiatCurrency : balance.currency}:${!form.amount ? "0" : form.amount
|
||||
}`,
|
||||
);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { createEddsaKeyPair, encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"
|
||||
import { bankUiSettings } from "../settings.js"
|
||||
|
||||
|
||||
const noun = [
|
||||
@ -2879,12 +2880,16 @@ const adj = [
|
||||
"zigzag",
|
||||
]
|
||||
|
||||
export function getRandomUsername(): string {
|
||||
const n = Math.random() * noun.length
|
||||
const a = Math.random() * adj.length
|
||||
return `tmp-${a}-${n}`
|
||||
export function getRandomUsername(): { first: string, second: string } {
|
||||
const n = Math.floor(Math.random() * noun.length)
|
||||
const a = Math.floor(Math.random() * adj.length)
|
||||
return {
|
||||
first: adj[a],
|
||||
second: noun[n]
|
||||
}
|
||||
}
|
||||
|
||||
export function getRandomPassword(): string {
|
||||
if (bankUiSettings.simplePasswordForRandomAccounts) return "123"
|
||||
return encodeCrock(getRandomBytes(16))
|
||||
}
|
@ -18,6 +18,8 @@ export interface BankUiSettings {
|
||||
backendBaseURL: string;
|
||||
allowRegistrations: boolean;
|
||||
showDemoNav: boolean;
|
||||
simplePasswordForRandomAccounts: boolean;
|
||||
allowRandomAccountCreation: boolean;
|
||||
bankName: string;
|
||||
demoSites: [string, string][];
|
||||
}
|
||||
@ -30,6 +32,8 @@ const defaultSettings: BankUiSettings = {
|
||||
allowRegistrations: true,
|
||||
bankName: "Taler Bank",
|
||||
showDemoNav: true,
|
||||
simplePasswordForRandomAccounts: true,
|
||||
allowRandomAccountCreation: true,
|
||||
demoSites: [
|
||||
["Landing", "https://demo.taler.net/"],
|
||||
["Bank", "https://bank.demo.taler.net/"],
|
||||
|
Loading…
Reference in New Issue
Block a user