more ui
This commit is contained in:
parent
c10f3f3ade
commit
1708d49a2d
@ -18,7 +18,7 @@
|
|||||||
import { serve } from "@gnu-taler/web-util/node";
|
import { serve } from "@gnu-taler/web-util/node";
|
||||||
import { initializeDev } from "@gnu-taler/web-util/build";
|
import { initializeDev } from "@gnu-taler/web-util/build";
|
||||||
|
|
||||||
const devEntryPoints = ["src/stories.tsx", "src/index.tsx"];
|
const devEntryPoints = ["src/stories.tsx", "src/index.tsx", "src/demobank-ui-settings.js"];
|
||||||
|
|
||||||
const build = initializeDev({
|
const build = initializeDev({
|
||||||
type: "development",
|
type: "development",
|
||||||
|
59
packages/demobank-ui/src/components/Attention.tsx
Normal file
59
packages/demobank-ui/src/components/Attention.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||||
|
import { ComponentChildren, Fragment, VNode, h } from "preact";
|
||||||
|
import { assertUnreachable } from "./Routing.js";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "info" | "success" | "warning" | "danger",
|
||||||
|
onClose?: () => void,
|
||||||
|
title: TranslatedString,
|
||||||
|
children?: ComponentChildren ,
|
||||||
|
}
|
||||||
|
export function Attention({ type = "info", title, children, onClose }: Props): VNode {
|
||||||
|
return <div class={`group attention-${type} mt-2`}>
|
||||||
|
<div class="rounded-md group-[.attention-info]:bg-blue-50 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 shadow">
|
||||||
|
<div class="flex">
|
||||||
|
<div >
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 group-[.attention-warning]:text-yellow-400 group-[.attention-danger]:text-red-400 group-[.attention-success]:text-green-400">
|
||||||
|
{(() => {
|
||||||
|
switch (type) {
|
||||||
|
case "info":
|
||||||
|
return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
|
||||||
|
case "warning":
|
||||||
|
return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
|
||||||
|
case "danger":
|
||||||
|
return <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
|
||||||
|
case "success":
|
||||||
|
return <path fill-rule="evenodd" d="M7.493 18.75c-.425 0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H14.23c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23h-.777zM2.331 10.977a11.969 11.969 0 00-.831 4.398 12 12 0 00.52 3.507c.26.85 1.084 1.368 1.973 1.368H4.9c.445 0 .72-.498.523-.898a8.963 8.963 0 01-.924-3.977c0-1.708.476-3.305 1.302-4.666.245-.403-.028-.959-.5-.959H4.25c-.832 0-1.612.453-1.918 1.227z" />
|
||||||
|
default:
|
||||||
|
assertUnreachable(type)
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 w-full">
|
||||||
|
<h3 class="text-sm group-hover:text-white font-bold group-[.attention-info]:text-blue-800 group-[.attention-success]:text-green-800 group-[.attention-warning]:text-yellow-800 group-[.attention-danger]:text-red-800">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 text-sm group-[.attention-info]:text-blue-700 group-[.attention-warning]:text-yellow-700 group-[.attention-danger]:text-red-700 group-[.attention-success]:text-green-700">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{onClose &&
|
||||||
|
<div>
|
||||||
|
<button type="button" class="font-semibold items-center rounded bg-transparent px-2 py-1 text-xs text-gray-900 hover:bg-gray-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
@ -17,25 +17,13 @@
|
|||||||
|
|
||||||
import { HttpError, useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { HttpError, useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
import { Attention } from "./Attention.js";
|
||||||
|
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
export function ErrorLoading({ error }: { error: HttpError<SandboxBackend.SandboxError> }): VNode {
|
export function ErrorLoading({ error }: { error: HttpError<SandboxBackend.SandboxError> }): VNode {
|
||||||
const { i18n } = useTranslationContext()
|
const { i18n } = useTranslationContext()
|
||||||
return (
|
return (<Attention type="danger" title={error.message as TranslatedString}>
|
||||||
<div><div class="rounded-md bg-red-50 p-4">
|
<p class="text-sm font-medium text-red-800">Got status "{error.info.status}" on {error.info.url}</p>
|
||||||
<div class="flex">
|
</Attention>
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
|
||||||
<p class="text-sm font-medium text-red-800">{error.message}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
|
||||||
<p class="text-sm font-medium text-red-800">Got status "{error.info.status}" on {error.info.url}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
|||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
import { format, isToday } from "date-fns";
|
import { format, isToday } from "date-fns";
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { Amounts } from "@gnu-taler/taler-util";
|
||||||
|
import { useEffect, useRef } from "preact/hooks";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -55,9 +56,9 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Date`}</th>
|
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Date`}</th>
|
||||||
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Amount`}</th>
|
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Amount`}</th>
|
||||||
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Counterpart`}</th>
|
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Counterpart`}</th>
|
||||||
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Subject`}</th>
|
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -69,22 +70,38 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{txs.map(item => {
|
{txs.map(item => {
|
||||||
|
const time = item.when.t_ms === "never" ? "" : format(item.when.t_ms, "HH:mm:ss")
|
||||||
|
const amount = <Fragment>
|
||||||
|
{item.negative ? "-" : ""}
|
||||||
|
{item.amount ? (
|
||||||
|
`${Amounts.stringifyValue(item.amount)} ${item.amount.currency
|
||||||
|
}`
|
||||||
|
) : (
|
||||||
|
<span style={{ color: "grey" }}><{i18n.str`invalid value`}></span>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
return (<tr key={idx}>
|
return (<tr key={idx}>
|
||||||
<td class="relative py-2 pl-2 pr-2 text-sm ">
|
<td class="relative py-2 pl-2 pr-2 text-sm ">
|
||||||
<div class="font-medium text-gray-900">{item.when.t_ms === "never"
|
<div class="font-medium text-gray-900">{time}</div>
|
||||||
? ""
|
<dl class="font-normal sm:hidden">
|
||||||
: format(item.when.t_ms, "HH:mm:ss")}</div>
|
<dt class="sr-only sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
|
||||||
|
<dd class="mt-1 truncate text-gray-700">
|
||||||
|
{item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? (
|
||||||
|
`${Amounts.stringifyValue(item.amount)}`
|
||||||
|
) : (
|
||||||
|
<span style={{ color: "grey" }}><{i18n.str`invalid value`}></span>
|
||||||
|
)}</dd>
|
||||||
|
<dt class="sr-only sm:hidden"><i18n.Translate>Counterpart</i18n.Translate></dt>
|
||||||
|
<dd class="mt-1 truncate text-gray-500 sm:hidden">
|
||||||
|
{item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
</td>
|
</td>
|
||||||
<td data-negative={item.negative ? "true" : "false"}
|
<td data-negative={item.negative ? "true" : "false"}
|
||||||
class="px-3 py-3.5 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600">
|
class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600">
|
||||||
{item.negative ? "-" : ""}
|
{amount}
|
||||||
{item.amount ? (
|
</td>
|
||||||
`${Amounts.stringifyValue(item.amount)} ${item.amount.currency
|
<td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500">{item.counterpart}</td>
|
||||||
}`
|
|
||||||
) : (
|
|
||||||
<span style={{ color: "grey" }}><{i18n.str`invalid value`}></span>
|
|
||||||
)}</td>
|
|
||||||
<td class="px-3 py-3.5 text-sm text-gray-500">{item.counterpart}</td>
|
|
||||||
<td class="px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">{item.subject}</td>
|
<td class="px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">{item.subject}</td>
|
||||||
</tr>)
|
</tr>)
|
||||||
})}
|
})}
|
||||||
@ -95,7 +112,7 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
|
|||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
|
<nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
|
||||||
<div class="flex flex-1 justify-between sm:justify-end">
|
<div class="flex flex-1 justify-between sm:justify-end">
|
||||||
<button
|
<button
|
||||||
class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
|
class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
|
||||||
|
21
packages/demobank-ui/src/demobank-ui-settings.js
Normal file
21
packages/demobank-ui/src/demobank-ui-settings.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Values for development environment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global settings for the demobank UI.
|
||||||
|
*/
|
||||||
|
localStorage.setItem("bank-base-url", "http://bank.taler.test/");
|
||||||
|
|
||||||
|
globalThis.talerDemobankSettings = {
|
||||||
|
backendBaseURL: "http://bank.taler.test/",
|
||||||
|
allowRegistrations: true,
|
||||||
|
showDemoNav: true,
|
||||||
|
simplePasswordForRandomAccounts: true,
|
||||||
|
allowRandomAccountCreation: true,
|
||||||
|
bankName: "Taler DEVELOPMENT Bank",
|
||||||
|
// Names and links for other demo sites to show in the navbar
|
||||||
|
demoSites: [
|
||||||
|
["Exchange", "https://Exchnage.taler.test/"],
|
||||||
|
["Bank", "https://bank-ui.taler.test/"],
|
||||||
|
["Merchant", "https://merchant.taler.test/"],
|
||||||
|
],
|
||||||
|
};
|
@ -70,7 +70,7 @@ export function useAccessAPI(): AccessAPI {
|
|||||||
contentType: "json",
|
contentType: "json",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await mutateAll(/.*accounts\/.*\/transactions.*/);
|
await mutateAll(/.*accounts\/.*/);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
|
const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
|
||||||
@ -382,7 +382,6 @@ export function useTransactions(
|
|||||||
loadMore: () => {
|
loadMore: () => {
|
||||||
if (!afterData || isReachingEnd) return;
|
if (!afterData || isReachingEnd) return;
|
||||||
// if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
|
// if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
|
||||||
// console.log("load more", page)
|
|
||||||
const l = afterData.data.transactions[afterData.data.transactions.length-1]
|
const l = afterData.data.transactions[afterData.data.transactions.length-1]
|
||||||
setStart(String(l.row_id));
|
setStart(String(l.row_id));
|
||||||
// }
|
// }
|
||||||
|
@ -435,7 +435,7 @@ export function useBusinessAccounts(
|
|||||||
HttpResponseOk<SandboxBackend.Circuit.CircuitAccounts>,
|
HttpResponseOk<SandboxBackend.Circuit.CircuitAccounts>,
|
||||||
RequestError<SandboxBackend.SandboxError>
|
RequestError<SandboxBackend.SandboxError>
|
||||||
>(
|
>(
|
||||||
[`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account],
|
[`accounts`, args?.page, PAGE_SIZE, args?.account],
|
||||||
sandboxAccountsFetcher,
|
sandboxAccountsFetcher,
|
||||||
{
|
{
|
||||||
refreshInterval: 0,
|
refreshInterval: 0,
|
||||||
|
@ -33,6 +33,7 @@ interface Settings {
|
|||||||
showInstallWallet: boolean;
|
showInstallWallet: boolean;
|
||||||
maxWithdrawalAmount: number;
|
maxWithdrawalAmount: number;
|
||||||
fastWithdrawal: boolean;
|
fastWithdrawal: boolean;
|
||||||
|
showDebugInfo: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForSettings = (): Codec<Settings> =>
|
export const codecForSettings = (): Codec<Settings> =>
|
||||||
@ -42,6 +43,7 @@ export const codecForSettings = (): Codec<Settings> =>
|
|||||||
.property("showDemoDescription", (codecForBoolean()))
|
.property("showDemoDescription", (codecForBoolean()))
|
||||||
.property("showInstallWallet", (codecForBoolean()))
|
.property("showInstallWallet", (codecForBoolean()))
|
||||||
.property("fastWithdrawal", (codecForBoolean()))
|
.property("fastWithdrawal", (codecForBoolean()))
|
||||||
|
.property("showDebugInfo", (codecForBoolean()))
|
||||||
.property("maxWithdrawalAmount", codecForNumber())
|
.property("maxWithdrawalAmount", codecForNumber())
|
||||||
.build("Settings");
|
.build("Settings");
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ const defaultSettings: Settings = {
|
|||||||
showInstallWallet: true,
|
showInstallWallet: true,
|
||||||
maxWithdrawalAmount: 25,
|
maxWithdrawalAmount: 25,
|
||||||
fastWithdrawal: false,
|
fastWithdrawal: false,
|
||||||
|
showDebugInfo: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEMOBANK_SETTINGS_KEY = buildStorageKey(
|
const DEMOBANK_SETTINGS_KEY = buildStorageKey(
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { Fragment, VNode, h } from "preact";
|
import { Fragment, VNode, h } from "preact";
|
||||||
|
import { Attention } from "../../components/Attention.js";
|
||||||
import { Transactions } from "../../components/Transactions/index.js";
|
import { Transactions } from "../../components/Transactions/index.js";
|
||||||
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
|
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
|
||||||
import { useSettings } from "../../hooks/settings.js";
|
import { useSettings } from "../../hooks/settings.js";
|
||||||
import { bankUiSettings } from "../../settings.js";
|
|
||||||
import { PaymentOptions } from "../PaymentOptions.js";
|
import { PaymentOptions } from "../PaymentOptions.js";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
@ -31,53 +31,27 @@ export function InvalidIbanView({ error }: State.InvalidIban) {
|
|||||||
|
|
||||||
const IS_PUBLIC_ACCOUNT_ENABLED = false
|
const IS_PUBLIC_ACCOUNT_ENABLED = false
|
||||||
|
|
||||||
|
|
||||||
function ShowDemoInfo(): VNode {
|
function ShowDemoInfo(): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const [settings, updateSettings] = useSettings();
|
const [settings, updateSettings] = useSettings();
|
||||||
if (!settings.showDemoDescription) return <Fragment />
|
if (!settings.showDemoDescription) return <Fragment />
|
||||||
return <div class="rounded-md bg-blue-50 p-4">
|
return <Attention title={i18n.str`This is a demo bank`} onClose={() => {
|
||||||
<div class="flex">
|
updateSettings("showDemoDescription", false);
|
||||||
<div class="flex-shrink-0">
|
}}>
|
||||||
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
{IS_PUBLIC_ACCOUNT_ENABLED ? (
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
|
<i18n.Translate>
|
||||||
</svg>
|
This part of the demo shows how a bank that supports Taler
|
||||||
</div>
|
directly would work. In addition to using your own bank
|
||||||
<div class="ml-3">
|
account, you can also see the transaction history of some{" "}
|
||||||
<h3 class="text-sm font-bold text-blue-800">
|
<a href="/public-accounts">Public Accounts</a>.
|
||||||
<i18n.Translate>This is a demo bank!</i18n.Translate>
|
</i18n.Translate>
|
||||||
</h3>
|
) : (
|
||||||
<div class="mt-2 text-sm text-blue-700">
|
<i18n.Translate>
|
||||||
{IS_PUBLIC_ACCOUNT_ENABLED ? (
|
This part of the demo shows how a bank that supports Taler
|
||||||
<i18n.Translate>
|
directly would work.
|
||||||
This part of the demo shows how a bank that supports Taler
|
</i18n.Translate>
|
||||||
directly would work. In addition to using your own bank
|
)}
|
||||||
account, you can also see the transaction history of some{" "}
|
</Attention>
|
||||||
<a href="/public-accounts">Public Accounts</a>.
|
|
||||||
</i18n.Translate>
|
|
||||||
) : (
|
|
||||||
<i18n.Translate>
|
|
||||||
This part of the demo shows how a bank that supports Taler
|
|
||||||
directly would work.
|
|
||||||
</i18n.Translate>
|
|
||||||
)}
|
|
||||||
<p class="mt-3 text-sm flex justify-end">
|
|
||||||
<button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
updateSettings("showDemoDescription", false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
|
export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
|
import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
|
||||||
import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { NotificationMessage, notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
|
import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
|
||||||
import { LangSelector } from "../components/LangSelector.js";
|
import { LangSelector } from "../components/LangSelector.js";
|
||||||
@ -26,6 +26,7 @@ import { useSettings } from "../hooks/settings.js";
|
|||||||
import { CopyButton, CopyIcon } from "../components/CopyButton.js";
|
import { CopyButton, CopyIcon } from "../components/CopyButton.js";
|
||||||
import logo from "../assets/logo-2021.svg";
|
import logo from "../assets/logo-2021.svg";
|
||||||
import { useAccountDetails } from "../hooks/access.js";
|
import { useAccountDetails } from "../hooks/access.js";
|
||||||
|
import { Attention } from "../components/Attention.js";
|
||||||
|
|
||||||
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
|
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
|
||||||
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
|
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
|
||||||
@ -108,7 +109,7 @@ export function BankFrame({
|
|||||||
setOpen(!open)
|
setOpen(!open)
|
||||||
}}>
|
}}>
|
||||||
<span class="absolute -inset-0.5"></span>
|
<span class="absolute -inset-0.5"></span>
|
||||||
<span class="sr-only">Open main menu</span>
|
<span class="sr-only">Open settings</span>
|
||||||
<svg class="block h-10 w-10" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
<svg class="block h-10 w-10" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
</svg>
|
</svg>
|
||||||
@ -227,6 +228,22 @@ export function BankFrame({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="mt-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="flex flex-grow flex-col">
|
||||||
|
<span class="text-sm text-black font-medium leading-6 " id="availability-label">
|
||||||
|
<i18n.Translate>Show debug info</i18n.Translate>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<button type="button" data-enabled={settings.showDebugInfo} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
|
||||||
|
|
||||||
|
onClick={() => {
|
||||||
|
updateSettings("showDebugInfo", !settings.showDebugInfo);
|
||||||
|
}}>
|
||||||
|
<span aria-hidden="true" data-enabled={settings.showDebugInfo} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li class="mt-2">
|
<li class="mt-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="flex flex-grow flex-col">
|
<span class="flex flex-grow flex-col">
|
||||||
@ -286,10 +303,10 @@ export function BankFrame({
|
|||||||
}
|
}
|
||||||
</div >
|
</div >
|
||||||
|
|
||||||
|
<StatusBanner />
|
||||||
<main class="-mt-32 flex-1">
|
<main class="-mt-32 flex-1">
|
||||||
<div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
|
||||||
<div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
|
<div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
|
||||||
<StatusBanner />
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -301,79 +318,46 @@ export function BankFrame({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MaybeShowDebugInfo({ info }: { info: any }): VNode {
|
||||||
|
const [settings] = useSettings()
|
||||||
|
if (settings.showDebugInfo) {
|
||||||
|
return <pre class="whitespace-break-spaces ">
|
||||||
|
{info}
|
||||||
|
</pre>
|
||||||
|
}
|
||||||
|
return <Fragment />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function StatusBanner(): VNode {
|
function StatusBanner(): VNode {
|
||||||
const notifs = useNotifications()
|
const notifs = useNotifications()
|
||||||
return <div
|
if (notifs.length === 0) return <Fragment />
|
||||||
class="fixed top-10 z-20 ml-4 mr-4"
|
return <div class="fixed z-20 w-full p-4"> {
|
||||||
> {
|
|
||||||
notifs.map(n => {
|
notifs.map(n => {
|
||||||
switch (n.message.type) {
|
switch (n.message.type) {
|
||||||
case "error":
|
case "error":
|
||||||
return <div class="rounded-md bg-red-50 p-4">
|
return <Attention type="danger" title={n.message.title} onClose={() => {
|
||||||
<div class="flex">
|
n.remove()
|
||||||
<div class="flex-shrink-0">
|
}}>
|
||||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
|
||||||
<p class="text-sm font-medium text-red-800">{n.message.title}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-sm">
|
|
||||||
<button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
n.remove()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{n.message.description &&
|
{n.message.description &&
|
||||||
<div class="mt-2 text-sm text-red-700">
|
<div class="mt-2 text-sm text-red-700">
|
||||||
{n.message.description}
|
{n.message.description}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<MaybeShowDebugInfo info={n.message.debug} />
|
||||||
|
{/* <a href="#" class="text-gray-500">
|
||||||
|
show debug info
|
||||||
|
</a>
|
||||||
{n.message.debug &&
|
{n.message.debug &&
|
||||||
<div class="mt-2 text-sm text-red-700 font-mono break-all">
|
<div class="mt-2 text-sm text-red-700 font-mono break-all">
|
||||||
{n.message.debug}
|
{n.message.debug}
|
||||||
</div>
|
</div>
|
||||||
}
|
} */}
|
||||||
</div>
|
</Attention>
|
||||||
case "info":
|
case "info":
|
||||||
return <div class="rounded-md bg-green-50 border-4 border-green-600 p-6">
|
return <Attention type="success" title={n.message.title} onClose={() => {
|
||||||
<div class="flex">
|
n.remove();
|
||||||
<div class="flex-shrink-0">
|
}} />
|
||||||
<svg class="h-8 w-8 text-green-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
|
||||||
<h3 class="text-lg font-medium text-green-800">{n.message.title}</h3>
|
|
||||||
|
|
||||||
<p class="mt-3 text-sm md:ml-6 md:mt-0">
|
|
||||||
<button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-md text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
n.remove();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg class="h-8 w-8" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,8 +137,8 @@ export function handleNotOkResult(
|
|||||||
const errorData = result.payload;
|
const errorData = result.payload;
|
||||||
notify({
|
notify({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: i18n.str`Could not load due to a client error`,
|
title: i18n.str`Could not load due to a request error`,
|
||||||
description: errorData?.error?.description as TranslatedString,
|
description: i18n.str`Request to url "${result.info.url}" returned ${result.info.status}`,
|
||||||
debug: JSON.stringify(result),
|
debug: JSON.stringify(result),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -174,7 +174,7 @@ export function handleNotOkResult(
|
|||||||
assertUnreachable(result);
|
assertUnreachable(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route("/")
|
// route("/")
|
||||||
return <div>error</div>;
|
return <div>error</div>;
|
||||||
}
|
}
|
||||||
return <div />;
|
return <div />;
|
||||||
|
@ -23,6 +23,7 @@ import { useBackendContext } from "../context/backend.js";
|
|||||||
import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
|
import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
|
||||||
import { bankUiSettings } from "../settings.js";
|
import { bankUiSettings } from "../settings.js";
|
||||||
import { undefinedIfEmpty } from "../utils.js";
|
import { undefinedIfEmpty } from "../utils.js";
|
||||||
|
import { doAutoFocus } from "./PaytoWireTransferForm.js";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,8 +99,8 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
saveError({
|
saveError({
|
||||||
title: i18n.str`Could not load due to a client error`,
|
title: i18n.str`Could not load due to a request error`,
|
||||||
// description: cause.payload.error.description,
|
description: i18n.str`Request to url "${cause.info.url}" returned ${cause.info.status}`,
|
||||||
debug: JSON.stringify(cause.payload),
|
debug: JSON.stringify(cause.payload),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -159,8 +160,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
|
|||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={doAutoFocus}
|
||||||
autoFocus
|
|
||||||
type="text"
|
type="text"
|
||||||
name="username"
|
name="username"
|
||||||
id="username"
|
id="username"
|
||||||
|
@ -118,7 +118,9 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
|
|||||||
try {
|
try {
|
||||||
setBusy({})
|
setBusy({})
|
||||||
await confirmWithdrawal(wid);
|
await confirmWithdrawal(wid);
|
||||||
notifyInfo(i18n.str`Wire transfer completed!`)
|
if (!settings.showWithdrawalSuccess) {
|
||||||
|
notifyInfo(i18n.str`Wire transfer completed!`)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof RequestError) {
|
if (error instanceof RequestError) {
|
||||||
notify(
|
notify(
|
||||||
|
@ -267,13 +267,12 @@ export function ConfirmedView({ error, onClose }: State.Confirmed) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-3 text-center sm:mt-5">
|
<div class="mt-3 text-center sm:mt-5">
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
|
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
|
||||||
<i18n.Translate>Withdrawal OK</i18n.Translate>
|
<i18n.Translate>Withdrawal confirmed</i18n.Translate>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
<i18n.Translate>
|
<i18n.Translate>
|
||||||
The wire transfer to the Taler exchange bank's account is completed, now the
|
The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.
|
||||||
exchange will send the requested amount into your GNU Taler wallet.
|
|
||||||
</i18n.Translate>
|
</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
|
|
||||||
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
|
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>("wire-transfer");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
@ -82,7 +82,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
|
|||||||
<i18n.Translate>another bank account</i18n.Translate>
|
<i18n.Translate>another bank account</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
<span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
|
<span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
|
||||||
<i18n.Translate>Make a wire transfer to an account which you know the address.</i18n.Translate>
|
<i18n.Translate>Make a wire transfer to an account which you know the bank account number</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -108,6 +108,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
|
|||||||
limit={limit}
|
limit={limit}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
notifyInfo(i18n.str`Wire transfer created!`);
|
notifyInfo(i18n.str`Wire transfer created!`);
|
||||||
|
setTab(undefined)
|
||||||
}}
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setTab(undefined)
|
setTab(undefined)
|
||||||
|
@ -55,10 +55,11 @@ export function PaytoWireTransferForm({
|
|||||||
onCancel: (() => void) | undefined;
|
onCancel: (() => void) | undefined;
|
||||||
limit: AmountJson;
|
limit: AmountJson;
|
||||||
}): VNode {
|
}): VNode {
|
||||||
const [isRawPayto, setIsRawPayto] = useState(false);
|
const [isRawPayto, setIsRawPayto] = useState(true);
|
||||||
const [iban, setIban] = useState<string | undefined>(undefined);
|
// FIXME: remove this
|
||||||
const [subject, setSubject] = useState<string | undefined>(undefined);
|
const [iban, setIban] = useState<string | undefined>("DE4745461198061");
|
||||||
const [amount, setAmount] = useState<string | undefined>(undefined);
|
const [subject, setSubject] = useState<string | undefined>("ASD");
|
||||||
|
const [amount, setAmount] = useState<string | undefined>("1.00001");
|
||||||
|
|
||||||
const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
|
const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
@ -76,17 +77,17 @@ export function PaytoWireTransferForm({
|
|||||||
|
|
||||||
const errorsWire = undefinedIfEmpty({
|
const errorsWire = undefinedIfEmpty({
|
||||||
iban: !iban
|
iban: !iban
|
||||||
? i18n.str`Missing IBAN`
|
? i18n.str`required`
|
||||||
: !IBAN_REGEX.test(iban)
|
: !IBAN_REGEX.test(iban)
|
||||||
? i18n.str`IBAN should have just uppercased letters and numbers`
|
? i18n.str`IBAN should have just uppercased letters and numbers`
|
||||||
: validateIBAN(iban, i18n),
|
: validateIBAN(iban, i18n),
|
||||||
subject: !subject ? i18n.str`Missing subject` : undefined,
|
subject: !subject ? i18n.str`required` : undefined,
|
||||||
amount: !trimmedAmountStr
|
amount: !trimmedAmountStr
|
||||||
? i18n.str`Missing amount`
|
? i18n.str`required`
|
||||||
: !parsedAmount
|
: !parsedAmount
|
||||||
? i18n.str`Amount is not valid`
|
? i18n.str`not valid`
|
||||||
: Amounts.isZero(parsedAmount)
|
: Amounts.isZero(parsedAmount)
|
||||||
? i18n.str`Should be greater than 0`
|
? i18n.str`should be greater than 0`
|
||||||
: Amounts.cmp(limit, parsedAmount) === -1
|
: Amounts.cmp(limit, parsedAmount) === -1
|
||||||
? i18n.str`balance is not enough`
|
? i18n.str`balance is not enough`
|
||||||
: undefined,
|
: undefined,
|
||||||
@ -101,14 +102,14 @@ export function PaytoWireTransferForm({
|
|||||||
? i18n.str`required`
|
? i18n.str`required`
|
||||||
: !parsed
|
: !parsed
|
||||||
? i18n.str`does not follow the pattern`
|
? i18n.str`does not follow the pattern`
|
||||||
: !parsed.params.amount
|
: !parsed.isKnown || parsed.targetType !== "iban"
|
||||||
? i18n.str`use the "amount" parameter to specify the amount to be transferred`
|
? i18n.str`only "IBAN" target are supported`
|
||||||
: Amounts.parse(parsed.params.amount) === undefined
|
: !parsed.params.amount
|
||||||
? i18n.str`the amount is not valid`
|
? i18n.str`use the "amount" parameter to specify the amount to be transferred`
|
||||||
: !parsed.params.message
|
: Amounts.parse(parsed.params.amount) === undefined
|
||||||
? i18n.str`use the "message" parameter to specify a reference text for the transfer`
|
? i18n.str`the amount is not valid`
|
||||||
: !parsed.isKnown || parsed.targetType !== "iban"
|
: !parsed.params.message
|
||||||
? i18n.str`only "IBAN" target are supported`
|
? i18n.str`use the "message" parameter to specify a reference text for the transfer`
|
||||||
: !IBAN_REGEX.test(parsed.iban)
|
: !IBAN_REGEX.test(parsed.iban)
|
||||||
? i18n.str`IBAN should have just uppercased letters and numbers`
|
? i18n.str`IBAN should have just uppercased letters and numbers`
|
||||||
: validateIBAN(parsed.iban, i18n),
|
: validateIBAN(parsed.iban, i18n),
|
||||||
@ -159,6 +160,9 @@ export function PaytoWireTransferForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
|
return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
|
||||||
|
{/**
|
||||||
|
* FIXME: Scan a qr code
|
||||||
|
*/}
|
||||||
<div class="px-4 sm:px-0">
|
<div class="px-4 sm:px-0">
|
||||||
<h2 class="text-base font-semibold leading-7 text-gray-900">
|
<h2 class="text-base font-semibold leading-7 text-gray-900">
|
||||||
{title}
|
{title}
|
||||||
@ -167,6 +171,17 @@ export function PaytoWireTransferForm({
|
|||||||
<div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 sm:gap-x-4">
|
<div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 sm:gap-x-4">
|
||||||
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
|
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
|
||||||
<input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => {
|
<input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => {
|
||||||
|
if (parsed && parsed.isKnown && parsed.targetType === "iban") {
|
||||||
|
setIban(parsed.iban)
|
||||||
|
const amount = Amounts.parse(parsed.params["amount"])
|
||||||
|
if (amount) {
|
||||||
|
setAmount(Amounts.stringifyValue(amount))
|
||||||
|
}
|
||||||
|
const subject = parsed.params["subject"]
|
||||||
|
if (subject) {
|
||||||
|
setSubject(subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
setIsRawPayto(false)
|
setIsRawPayto(false)
|
||||||
}} />
|
}} />
|
||||||
<span class="flex flex-1">
|
<span class="flex flex-1">
|
||||||
@ -180,12 +195,22 @@ export function PaytoWireTransferForm({
|
|||||||
|
|
||||||
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
|
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
|
||||||
<input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => {
|
<input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => {
|
||||||
|
if (iban) {
|
||||||
|
const payto = buildPayto("iban", iban, undefined)
|
||||||
|
if (parsedAmount) {
|
||||||
|
payto.params["amount"] = Amounts.stringify(parsedAmount)
|
||||||
|
}
|
||||||
|
if (subject) {
|
||||||
|
payto.params["message"] = subject
|
||||||
|
}
|
||||||
|
rawPaytoInputSetter(stringifyPaytoUri(payto))
|
||||||
|
}
|
||||||
setIsRawPayto(true)
|
setIsRawPayto(true)
|
||||||
}} />
|
}} />
|
||||||
<span class="flex flex-1">
|
<span class="flex flex-1">
|
||||||
<span class="flex flex-col">
|
<span class="flex flex-col">
|
||||||
<span class="block text-sm font-medium text-gray-900">
|
<span class="block text-sm font-medium text-gray-900">
|
||||||
<i18n.Translate>using the payto:// format</i18n.Translate>
|
<i18n.Translate>Import payto:// URI</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -195,7 +220,7 @@ export function PaytoWireTransferForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
|
class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2 w-fit mx-auto"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
onSubmit={e => {
|
onSubmit={e => {
|
||||||
@ -203,105 +228,106 @@ export function PaytoWireTransferForm({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="px-4 py-6 sm:p-8">
|
<div class="px-4 py-6 sm:p-8">
|
||||||
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
{!isRawPayto ?
|
||||||
{!isRawPayto ?
|
<div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
<Fragment>
|
|
||||||
|
|
||||||
<div class="sm:col-span-5">
|
<div class="sm:col-span-5">
|
||||||
<label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Account number`}</label>
|
<label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Recipient`}</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={focus ? doAutoFocus : undefined}
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
name="iban"
|
name="iban"
|
||||||
id="iban"
|
id="iban"
|
||||||
value={iban ?? ""}
|
value={iban ?? ""}
|
||||||
placeholder="CC0123456789"
|
placeholder="CC0123456789"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required
|
required
|
||||||
pattern={ibanRegex}
|
pattern={ibanRegex}
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
setIban(e.currentTarget.value);
|
setIban(e.currentTarget.value.toUpperCase());
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ShowInputErrorLabel
|
<ShowInputErrorLabel
|
||||||
message={errorsWire?.iban}
|
message={errorsWire?.iban}
|
||||||
isDirty={iban !== undefined}
|
isDirty={iban !== undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<p class="mt-2 text-sm text-gray-500" >the receiver of the money</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mt-2 text-sm text-gray-500" >
|
||||||
|
<i18n.Translate>IBAN of the recipient's account</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="sm:col-span-5">
|
<div class="sm:col-span-5">
|
||||||
<label for="subject" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Transfer subject`}</label>
|
<label for="subject" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Transfer subject`}</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
name="subject"
|
name="subject"
|
||||||
id="subject"
|
id="subject"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder="subject"
|
placeholder="subject"
|
||||||
value={subject ?? ""}
|
value={subject ?? ""}
|
||||||
required
|
required
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
setSubject(e.currentTarget.value);
|
setSubject(e.currentTarget.value);
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ShowInputErrorLabel
|
|
||||||
message={errorsWire?.subject}
|
|
||||||
isDirty={subject !== undefined}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sm:col-span-5">
|
|
||||||
<label for="amount" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Amount`}</label>
|
|
||||||
<Amount
|
|
||||||
name="amount"
|
|
||||||
currency={limit.currency}
|
|
||||||
value={trimmedAmountStr}
|
|
||||||
onChange={(d) => {
|
|
||||||
setAmount(d)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ShowInputErrorLabel
|
<ShowInputErrorLabel
|
||||||
message={errorsWire?.subject}
|
message={errorsWire?.subject}
|
||||||
isDirty={subject !== undefined}
|
isDirty={subject !== undefined}
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</Fragment> :
|
<div class="sm:col-span-5">
|
||||||
<Fragment>
|
<label for="amount" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Amount`}</label>
|
||||||
<div class="sm:col-span-6">
|
<Amount
|
||||||
<label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
|
name="amount"
|
||||||
<div class="mt-2">
|
left
|
||||||
<input
|
currency={limit.currency}
|
||||||
name="address"
|
value={trimmedAmountStr}
|
||||||
id="address"
|
onChange={(d) => {
|
||||||
type="text"
|
setAmount(d)
|
||||||
size={50}
|
}}
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" ref={ref}
|
/>
|
||||||
value={rawPaytoInput ?? ""}
|
<ShowInputErrorLabel
|
||||||
required
|
message={errorsWire?.amount}
|
||||||
placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
|
isDirty={subject !== undefined}
|
||||||
onInput={(e): void => {
|
/>
|
||||||
rawPaytoInputSetter(e.currentTarget.value);
|
<p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
|
||||||
}}
|
</div>
|
||||||
/>
|
|
||||||
<ShowInputErrorLabel
|
</div> :
|
||||||
message={errorsPayto?.rawPaytoInput}
|
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6 w-full">
|
||||||
isDirty={rawPaytoInput !== undefined}
|
<div class="sm:col-span-6">
|
||||||
/>
|
<label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
|
||||||
</div>
|
<div class="mt-2">
|
||||||
|
<textarea
|
||||||
|
ref={focus ? doAutoFocus : undefined}
|
||||||
|
name="address"
|
||||||
|
id="address"
|
||||||
|
type="textarea"
|
||||||
|
rows={3}
|
||||||
|
class="block overflow-hidden w-64 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
value={rawPaytoInput ?? ""}
|
||||||
|
required
|
||||||
|
placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
|
||||||
|
onInput={(e): void => {
|
||||||
|
rawPaytoInputSetter(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ShowInputErrorLabel
|
||||||
|
message={errorsPayto?.rawPaytoInput}
|
||||||
|
isDirty={rawPaytoInput !== undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Fragment>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
|
<div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
|
||||||
{onCancel ?
|
{onCancel ?
|
||||||
@ -328,17 +354,37 @@ export function PaytoWireTransferForm({
|
|||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the element when the load ended
|
||||||
|
* @param element
|
||||||
|
*/
|
||||||
|
export function doAutoFocus(element: HTMLElement | null) {
|
||||||
|
if (element) {
|
||||||
|
window.requestIdleCallback(() => {
|
||||||
|
element.focus()
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "center",
|
||||||
|
inline: "center"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function Amount(
|
export function Amount(
|
||||||
{
|
{
|
||||||
currency,
|
currency,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
error,
|
error,
|
||||||
|
left,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
error?: string;
|
error?: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
left?: boolean | undefined,
|
||||||
value: string | undefined;
|
value: string | undefined;
|
||||||
onChange?: (s: string) => void;
|
onChange?: (s: string) => void;
|
||||||
},
|
},
|
||||||
@ -346,13 +392,16 @@ export function Amount(
|
|||||||
): VNode {
|
): VNode {
|
||||||
return (
|
return (
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="relative rounded-md shadow-sm">
|
<div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
|
||||||
<div class="pointer-events-none absolute inset-y-0 flex items-center pl-3">
|
<div
|
||||||
|
class="pointer-events-none inset-y-0 flex items-center px-3"
|
||||||
|
>
|
||||||
<span class="text-gray-500 sm:text-sm">{currency}</span>
|
<span class="text-gray-500 sm:text-sm">{currency}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
class="text-right block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
data-left={left}
|
||||||
|
class="text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6"
|
||||||
placeholder="0.00" aria-describedby="price-currency"
|
placeholder="0.00" aria-describedby="price-currency"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
name={name}
|
name={name}
|
||||||
@ -371,3 +420,4 @@ export function Amount(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,6 @@ export function QrCodeSection({
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a href={talerWithdrawUri}
|
<a href={talerWithdrawUri}
|
||||||
// class="text-sm font-semibold leading-6 text-gray-900 btn "
|
|
||||||
class="inline-flex items-center disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
class="inline-flex items-center disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
>
|
>
|
||||||
<i18n.Translate>Click here to start</i18n.Translate>
|
<i18n.Translate>Click here to start</i18n.Translate>
|
||||||
|
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
|||||||
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
|
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
|
||||||
import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
|
import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
|
||||||
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
|
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
|
||||||
|
import { doAutoFocus } from "./PaytoWireTransferForm.js";
|
||||||
|
|
||||||
export function UpdateAccountPassword({
|
export function UpdateAccountPassword({
|
||||||
account,
|
account,
|
||||||
@ -27,11 +28,6 @@ export function UpdateAccountPassword({
|
|||||||
const [password, setPassword] = useState<string | undefined>();
|
const [password, setPassword] = useState<string | undefined>();
|
||||||
const [repeat, setRepeat] = useState<string | undefined>();
|
const [repeat, setRepeat] = useState<string | undefined>();
|
||||||
|
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (focus) ref.current?.focus();
|
|
||||||
}, [focus]);
|
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
if (result.loading || result.type === ErrorType.TIMEOUT) {
|
if (result.loading || result.type === ErrorType.TIMEOUT) {
|
||||||
return onLoadNotOk(result);
|
return onLoadNotOk(result);
|
||||||
@ -96,7 +92,7 @@ export function UpdateAccountPassword({
|
|||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={focus ? doAutoFocus : undefined}
|
||||||
type="password"
|
type="password"
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
name="password"
|
name="password"
|
||||||
|
@ -29,14 +29,15 @@ import {
|
|||||||
notifyError,
|
notifyError,
|
||||||
useTranslationContext,
|
useTranslationContext,
|
||||||
} from "@gnu-taler/web-util/browser";
|
} from "@gnu-taler/web-util/browser";
|
||||||
import { VNode, h } from "preact";
|
import { Fragment, VNode, h } from "preact";
|
||||||
import { forwardRef } from "preact/compat";
|
import { forwardRef } from "preact/compat";
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { useAccessAPI } from "../hooks/access.js";
|
import { useAccessAPI } from "../hooks/access.js";
|
||||||
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
|
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
|
||||||
import { Amount } from "./PaytoWireTransferForm.js";
|
import { Amount, doAutoFocus } from "./PaytoWireTransferForm.js";
|
||||||
import { useSettings } from "../hooks/settings.js";
|
import { useSettings } from "../hooks/settings.js";
|
||||||
import { OperationState } from "./OperationState/index.js";
|
import { OperationState } from "./OperationState/index.js";
|
||||||
|
import { Attention } from "../components/Attention.js";
|
||||||
|
|
||||||
const logger = new Logger("WalletWithdrawForm");
|
const logger = new Logger("WalletWithdrawForm");
|
||||||
const RefAmount = forwardRef(Amount);
|
const RefAmount = forwardRef(Amount);
|
||||||
@ -53,47 +54,13 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
|
|||||||
|
|
||||||
const { createWithdrawal } = useAccessAPI();
|
const { createWithdrawal } = useAccessAPI();
|
||||||
const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
|
const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (focus) ref.current?.focus();
|
|
||||||
}, [focus]);
|
|
||||||
|
|
||||||
if (!!settings.currentWithdrawalOperationId) {
|
if (!!settings.currentWithdrawalOperationId) {
|
||||||
return <div>
|
return <Attention type="warning" title={i18n.str`There is an operation already`}>
|
||||||
|
<i18n.Translate>
|
||||||
<div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
|
To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
|
||||||
<div class="flex">
|
</i18n.Translate>
|
||||||
<div class="flex-shrink-0">
|
</Attention>
|
||||||
<svg class="h-5 w-5 text-yellow-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-bold text-yellow-800">
|
|
||||||
<i18n.Translate>There is an operation already</i18n.Translate>
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 text-sm text-yellow-700">
|
|
||||||
<p>
|
|
||||||
<i18n.Translate>
|
|
||||||
To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
|
|
||||||
</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();
|
const trimmedAmountStr = amountStr?.trim();
|
||||||
@ -157,8 +124,8 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="px-4 py-6 sm:p-8">
|
<div class="px-4 py-6 ">
|
||||||
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
<div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
<div class="sm:col-span-5">
|
<div class="sm:col-span-5">
|
||||||
<label for="withdraw-amount">{i18n.str`Amount`}</label>
|
<label for="withdraw-amount">{i18n.str`Amount`}</label>
|
||||||
<RefAmount
|
<RefAmount
|
||||||
@ -169,51 +136,53 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
|
|||||||
setAmountStr(v);
|
setAmountStr(v);
|
||||||
}}
|
}}
|
||||||
error={errors?.amount}
|
error={errors?.amount}
|
||||||
ref={ref}
|
ref={focus ? doAutoFocus : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-5">
|
</div>
|
||||||
<span class="isolate inline-flex rounded-md shadow-sm">
|
<div class="mt-4">
|
||||||
<button type="button"
|
<div class="sm:inline">
|
||||||
class="relative inline-flex px-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setAmountStr("50.00")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
50.00
|
|
||||||
</button>
|
|
||||||
<button type="button"
|
|
||||||
class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setAmountStr("25.00")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
25.00
|
<button type="button"
|
||||||
</button>
|
class=" inline-flex px-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
||||||
<button type="button"
|
onClick={(e) => {
|
||||||
class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
e.preventDefault();
|
||||||
onClick={(e) => {
|
setAmountStr("50.00")
|
||||||
e.preventDefault();
|
}}
|
||||||
setAmountStr("10.00")
|
>
|
||||||
}}
|
50.00
|
||||||
>
|
</button>
|
||||||
10.00
|
<button type="button"
|
||||||
</button>
|
class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center rounded-r-md sm:rounded-none bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
||||||
<button type="button"
|
onClick={(e) => {
|
||||||
class="relative inline-flex px-6 py-4 text-sm items-center rounded-r-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
e.preventDefault();
|
||||||
onClick={(e) => {
|
setAmountStr("25.00")
|
||||||
e.preventDefault();
|
}}
|
||||||
setAmountStr("5.00")
|
>
|
||||||
}}
|
|
||||||
>
|
25.00
|
||||||
5.00
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</span>
|
<div class="mt-4 sm:inline">
|
||||||
|
<button type="button"
|
||||||
|
class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center rounded-l-md sm:rounded-none bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setAmountStr("10.00")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
10.00
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class=" inline-flex px-6 py-4 text-sm items-center rounded-r-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setAmountStr("5.00")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
5.00
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
|
<div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
|
||||||
@ -255,46 +224,20 @@ export function WalletWithdrawForm({
|
|||||||
<div class="px-4 sm:px-0">
|
<div class="px-4 sm:px-0">
|
||||||
<h2 class="text-base font-semibold leading-7 text-gray-900"><i18n.Translate>Prepare your wallet</i18n.Translate></h2>
|
<h2 class="text-base font-semibold leading-7 text-gray-900"><i18n.Translate>Prepare your wallet</i18n.Translate></h2>
|
||||||
<p class="mt-1 text-sm text-gray-500">
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
<i18n.Translate>After using your wallet you will confirm or cancel the operation.</i18n.Translate>
|
<i18n.Translate>After using your wallet you will need to confirm or cancel the operation on this site.</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
{settings.showInstallWallet && <div class="rounded-md bg-blue-50 ring-blue-2 ring-2 p-4">
|
{settings.showInstallWallet &&
|
||||||
<div class="flex">
|
<Attention title={i18n.str`You need a GNU Taler Wallet`} onClose={() => {
|
||||||
<div class="flex-shrink-0">
|
updateSettings("showInstallWallet", false);
|
||||||
<svg class="h-5 w-5 text-blue-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
}}>
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
|
<i18n.Translate>
|
||||||
</svg>
|
If you don't have one yet you can follow the instruction <a target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 hover:text-blue-600" href="https://taler.net/en/wallet.html">here</a>
|
||||||
</div>
|
</i18n.Translate>
|
||||||
<div class="ml-3">
|
</Attention>
|
||||||
<h3 class="text-sm font-bold text-blue-800">
|
}
|
||||||
<i18n.Translate>You need a GNU Taler Wallet</i18n.Translate>
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 text-sm text-blue-700">
|
|
||||||
<p>
|
|
||||||
<i18n.Translate>
|
|
||||||
If you dont have one yet you can follow the instruction <a target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 hover:text-blue-600" href="https://taler.net/en/wallet.html">here</a>
|
|
||||||
</i18n.Translate>
|
|
||||||
</p>
|
|
||||||
<p class="mt-3 text-sm flex justify-end">
|
|
||||||
<button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
updateSettings("showInstallWallet", false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
I know
|
|
||||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{!settings.fastWithdrawal ?
|
{!settings.fastWithdrawal ?
|
||||||
<OldWithdrawalForm
|
<OldWithdrawalForm
|
||||||
|
@ -37,6 +37,7 @@ import { useMemo, useState } from "preact/hooks";
|
|||||||
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
|
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
|
||||||
import { useAccessAnonAPI } from "../hooks/access.js";
|
import { useAccessAnonAPI } from "../hooks/access.js";
|
||||||
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
|
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
|
||||||
|
import { useSettings } from "../hooks/settings.js";
|
||||||
|
|
||||||
const logger = new Logger("WithdrawalConfirmationQuestion");
|
const logger = new Logger("WithdrawalConfirmationQuestion");
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ export function WithdrawalConfirmationQuestion({
|
|||||||
withdrawUri,
|
withdrawUri,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
const [settings, updateSettings] = useSettings()
|
||||||
|
|
||||||
const captchaNumbers = useMemo(() => {
|
const captchaNumbers = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -87,7 +89,9 @@ export function WithdrawalConfirmationQuestion({
|
|||||||
await confirmWithdrawal(
|
await confirmWithdrawal(
|
||||||
withdrawUri.withdrawalOperationId,
|
withdrawUri.withdrawalOperationId,
|
||||||
);
|
);
|
||||||
notifyInfo(i18n.str`Wire transfer completed!`)
|
if (!settings.showWithdrawalSuccess) {
|
||||||
|
notifyInfo(i18n.str`Wire transfer completed!`)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof RequestError) {
|
if (error instanceof RequestError) {
|
||||||
notify(
|
notify(
|
||||||
@ -203,7 +207,7 @@ export function WithdrawalConfirmationQuestion({
|
|||||||
|
|
||||||
<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
|
<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
|
||||||
<div class="px-4 sm:px-0">
|
<div class="px-4 sm:px-0">
|
||||||
<h2 class="text-base font-semibold text-gray-900"><i18n.Translate>Answer the next question to authorize the wire transfer</i18n.Translate></h2>
|
<h2 class="text-base font-semibold text-gray-900"><i18n.Translate>Answer the next question to authorize the wire transfer.</i18n.Translate></h2>
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
|
class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
|
||||||
@ -311,14 +315,10 @@ export function WithdrawalConfirmationQuestion({
|
|||||||
|
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900">Withdrawal identification</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0 break-words">{details.reserve}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
|
||||||
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
||||||
{Amounts.stringifyValue(details.amount)}
|
{Amounts.currencyOf(details.amount)} {Amounts.stringifyValue(details.amount)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
@ -94,20 +94,12 @@ export function WithdrawalQRCode({
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-3 text-center sm:mt-5">
|
<div class="mt-3 text-center sm:mt-5">
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
|
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
|
||||||
<i18n.Translate>Withdrawal OK</i18n.Translate>
|
<i18n.Translate>Withdrawal confirmed</i18n.Translate>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
<i18n.Translate>
|
<i18n.Translate>
|
||||||
The wire transfer to the Taler exchange bank's account is completed, now the
|
The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.
|
||||||
exchange will send the requested amount into your GNU Taler wallet.
|
|
||||||
</i18n.Translate>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2">
|
|
||||||
<p >
|
|
||||||
<i18n.Translate>
|
|
||||||
You can close this page now or continue to the account page.
|
|
||||||
</i18n.Translate>
|
</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty
|
|||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
|
import { buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
|
||||||
|
import { doAutoFocus } from "../PaytoWireTransferForm.js";
|
||||||
|
|
||||||
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
|
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
|
||||||
const EMAIL_REGEX =
|
const EMAIL_REGEX =
|
||||||
@ -37,10 +38,6 @@ export function AccountForm({
|
|||||||
RecursivePartial<typeof initial> | undefined
|
RecursivePartial<typeof initial> | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (focus) ref.current?.focus();
|
|
||||||
}, [focus]);
|
|
||||||
|
|
||||||
function updateForm(newForm: typeof initial): void {
|
function updateForm(newForm: typeof initial): void {
|
||||||
|
|
||||||
@ -97,7 +94,6 @@ export function AccountForm({
|
|||||||
<div class="px-4 py-6 sm:p-8">
|
<div class="px-4 py-6 sm:p-8">
|
||||||
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
|
|
||||||
|
|
||||||
<div class="sm:col-span-5">
|
<div class="sm:col-span-5">
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium leading-6 text-gray-900"
|
class="block text-sm font-medium leading-6 text-gray-900"
|
||||||
@ -108,7 +104,7 @@ export function AccountForm({
|
|||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={focus ? doAutoFocus : undefined}
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
name="username"
|
name="username"
|
||||||
|
@ -10,6 +10,7 @@ import { AdminAccount } from "./Account.js";
|
|||||||
import { AccountList } from "./AccountList.js";
|
import { AccountList } from "./AccountList.js";
|
||||||
import { CreateNewAccount } from "./CreateNewAccount.js";
|
import { CreateNewAccount } from "./CreateNewAccount.js";
|
||||||
import { RemoveAccount } from "./RemoveAccount.js";
|
import { RemoveAccount } from "./RemoveAccount.js";
|
||||||
|
import { Transactions } from "../../components/Transactions/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query account information and show QR code if there is pending withdrawal
|
* Query account information and show QR code if there is pending withdrawal
|
||||||
@ -141,6 +142,7 @@ export function AdminHome({ onRegister }: Props): VNode {
|
|||||||
|
|
||||||
<AdminAccount onRegister={onRegister} />
|
<AdminAccount onRegister={onRegister} />
|
||||||
|
|
||||||
|
<Transactions account="admin"/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -6,6 +6,8 @@ import { Amounts, HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util
|
|||||||
import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
|
import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
|
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
|
||||||
|
import { Attention } from "../../components/Attention.js";
|
||||||
|
import { doAutoFocus } from "../PaytoWireTransferForm.js";
|
||||||
|
|
||||||
export function RemoveAccount({
|
export function RemoveAccount({
|
||||||
account,
|
account,
|
||||||
@ -36,47 +38,15 @@ export function RemoveAccount({
|
|||||||
}
|
}
|
||||||
return onLoadNotOk(result);
|
return onLoadNotOk(result);
|
||||||
}
|
}
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (focus) ref.current?.focus();
|
|
||||||
}, [focus]);
|
|
||||||
|
|
||||||
const balance = Amounts.parse(result.data.balance.amount);
|
const balance = Amounts.parse(result.data.balance.amount);
|
||||||
if (!balance) {
|
if (!balance) {
|
||||||
return <div>there was an error reading the balance</div>;
|
return <div>there was an error reading the balance</div>;
|
||||||
}
|
}
|
||||||
const isBalanceEmpty = Amounts.isZero(balance);
|
const isBalanceEmpty = Amounts.isZero(balance);
|
||||||
if (!isBalanceEmpty) {
|
if (!isBalanceEmpty) {
|
||||||
return <div>
|
return <Attention type="warning" title={i18n.str`Can't delete the account`} onClose={onCancel}>
|
||||||
<div class="rounded-md bg-yellow-50 p-4">
|
<i18n.Translate>The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
|
||||||
<div class="flex">
|
</Attention>
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium text-yellow-800">
|
|
||||||
<i18n.Translate>Can't delete the account</i18n.Translate>
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 text-sm text-yellow-700">
|
|
||||||
<p>
|
|
||||||
<i18n.Translate>The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2 flex justify-end">
|
|
||||||
<button type="button" class="rounded-md ring-1 ring-gray-400 bg-white px-3 py-2 text-sm font-semibold shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
|
|
||||||
onClick={() => {
|
|
||||||
onCancel()
|
|
||||||
}}>
|
|
||||||
<i18n.Translate>Go back</i18n.Translate>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doRemove() {
|
async function doRemove() {
|
||||||
@ -117,26 +87,9 @@ export function RemoveAccount({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div class="rounded-md bg-yellow-50 p-4">
|
<Attention type="warning" title={i18n.str`You are going to remove the account`}>
|
||||||
<div class="flex">
|
<i18n.Translate>This step can't be undone.</i18n.Translate>
|
||||||
<div class="flex-shrink-0">
|
</Attention>
|
||||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-bold text-yellow-800">
|
|
||||||
<i18n.Translate>You are going to remove the account</i18n.Translate>
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 text-sm text-yellow-700">
|
|
||||||
<p>
|
|
||||||
<i18n.Translate>This step can't be undone.</i18n.Translate>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
|
<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
|
||||||
<div class="px-4 sm:px-0">
|
<div class="px-4 sm:px-0">
|
||||||
@ -164,7 +117,7 @@ export function RemoveAccount({
|
|||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={focus ? doAutoFocus : undefined}
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
name="password"
|
name="password"
|
||||||
|
Loading…
Reference in New Issue
Block a user