summaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/admin/AccountForm.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/admin/AccountForm.tsx')
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx536
1 files changed, 273 insertions, 263 deletions
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 5d4a5c5db..3aba99cea 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -39,11 +39,11 @@ import {
TanChannel,
undefinedIfEmpty,
validateIBAN,
+ validateTalerBank,
} from "../../utils.js";
-import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
+import { InputAmount, TextField, doAutoFocus } from "../PaytoWireTransferForm.js";
import { getRandomPassword } from "../rnd.js";
-const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
@@ -90,7 +90,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
onChange: ChangeByPurposeType[PurposeType];
purpose: PurposeType;
}): VNode {
- const { config, hints } = useBankCoreApiContext();
+ const { config, hints, url } = useBankCoreApiContext();
const { i18n } = useTranslationContext();
const { state: credentials } = useBackendState();
const [form, setForm] = useState<AccountFormData>({});
@@ -99,6 +99,9 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
ErrorMessageMappingFor<typeof defaultValue> | undefined
>(undefined);
+ const paytoType = config.wire_type === "X_TALER_BANK" ? "x-taler-bank" as const : "iban" as const;
+ const cashoutPaytoType: typeof paytoType = "iban" as const;
+
const defaultValue: AccountFormData = {
debit_threshold: Amounts.stringifyValue(
template?.debit_threshold ?? config.default_debit_threshold,
@@ -107,8 +110,8 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
isPublic: template?.is_public,
name: template?.name ?? "",
cashout_payto_uri:
- stringifyIbanPayto(template?.cashout_payto_uri) ?? ("" as PaytoString),
- payto_uri: stringifyIbanPayto(template?.payto_uri) ?? ("" as PaytoString),
+ getAccountId(cashoutPaytoType, template?.cashout_payto_uri) ?? ("" as PaytoString),
+ payto_uri: getAccountId(paytoType, template?.payto_uri) ?? ("" as PaytoString),
email: template?.contact_data?.email ?? "",
phone: template?.contact_data?.phone ?? "",
username: username ?? "",
@@ -117,10 +120,6 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
const OLD_CASHOUT_API = hints.indexOf(VersionHint.CASHOUT_BEFORE_2FA) !== -1;
- const showingCurrentUserInfo =
- credentials.status !== "loggedIn"
- ? false
- : username === credentials.username;
const userIsAdmin =
credentials.status !== "loggedIn" ? false : credentials.isUserAdministrator;
@@ -131,7 +130,6 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
const isCashoutEnabled = config.allow_conversion;
const editableCashout =
- showingCurrentUserInfo &&
(purpose === "create" ||
(purpose === "update" &&
(config.allow_edit_cashout_payto_uri || userIsAdmin)));
@@ -143,13 +141,6 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
const hasEmail = !!defaultValue.email || !!form.email;
function updateForm(newForm: typeof defaultValue): void {
- const cashoutParsed = !newForm.cashout_payto_uri
- ? undefined
- : buildPayto("iban", newForm.cashout_payto_uri, undefined);
-
- const internalParsed = !newForm.payto_uri
- ? undefined
- : buildPayto("iban", newForm.payto_uri, undefined);
const trimmedAmountStr = newForm.debit_threshold?.trim();
const parsedAmount = Amounts.parse(
@@ -163,24 +154,20 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
? undefined
: !editableCashout
? undefined
- : !cashoutParsed
- ? i18n.str`Doesn't have the pattern of an IBAN number`
- : !cashoutParsed.isKnown || cashoutParsed.targetType !== "iban"
- ? i18n.str`Only "IBAN" target are supported`
- : !IBAN_REGEX.test(cashoutParsed.iban)
- ? i18n.str`IBAN should have just uppercased letters and numbers`
- : validateIBAN(cashoutParsed.iban, i18n),
+ : !newForm.cashout_payto_uri ? undefined
+ : cashoutPaytoType === "iban" ? validateIBAN(newForm.cashout_payto_uri, i18n) :
+ cashoutPaytoType === "x-taler-bank" ? validateTalerBank(newForm.cashout_payto_uri, i18n) :
+ undefined,
+
payto_uri: !newForm.payto_uri
? undefined
: !editableAccount
? undefined
- : !internalParsed
- ? i18n.str`Doesn't have the pattern of an IBAN number`
- : !internalParsed.isKnown || internalParsed.targetType !== "iban"
- ? i18n.str`Only "IBAN" target are supported`
- : !IBAN_REGEX.test(internalParsed.iban)
- ? i18n.str`IBAN should have just uppercased letters and numbers`
- : validateIBAN(internalParsed.iban, i18n),
+ : !newForm.payto_uri ? undefined
+ : paytoType === "iban" ? validateIBAN(newForm.payto_uri, i18n) :
+ paytoType === "x-taler-bank" ? validateTalerBank(newForm.payto_uri, i18n) :
+ undefined,
+
email: !newForm.email
? undefined
: !EMAIL_REGEX.test(newForm.email)
@@ -219,14 +206,31 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
if (errors) {
onChange(undefined);
} else {
- const cashout = !newForm.cashout_payto_uri
- ? undefined
- : buildPayto("iban", newForm.cashout_payto_uri, undefined);
+ let cashout;
+ if (newForm.cashout_payto_uri) switch (cashoutPaytoType) {
+ case "x-taler-bank": {
+ cashout = buildPayto("x-taler-bank", url.host, newForm.cashout_payto_uri);
+ break;
+ }
+ case "iban": {
+ cashout = buildPayto("iban", newForm.cashout_payto_uri, undefined);
+ break;
+ }
+ default: assertUnreachable(cashoutPaytoType)
+ }
const cashoutURI = !cashout ? undefined : stringifyPaytoUri(cashout);
-
- const internal = !newForm.payto_uri
- ? undefined
- : buildPayto("iban", newForm.payto_uri, undefined);
+ let internal;
+ if (newForm.payto_uri) switch (paytoType) {
+ case "x-taler-bank": {
+ internal = buildPayto("x-taler-bank", url.host, newForm.payto_uri);
+ break;
+ }
+ case "iban": {
+ internal = buildPayto("iban", newForm.payto_uri, undefined);
+ break;
+ }
+ default: assertUnreachable(paytoType)
+ }
const internalURI = !internal ? undefined : stringifyPaytoUri(internal);
const threshold = !parsedAmount
@@ -328,7 +332,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
/>
</div>
<p class="mt-2 text-sm text-gray-500">
- <i18n.Translate>Account identification</i18n.Translate>
+ <i18n.Translate>Account id for authentication</i18n.Translate>
</p>
</div>
@@ -366,22 +370,26 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
</p>
</div>
- <PaytoField
- type="iban"
- name="internal-account"
- label={i18n.str`Internal IBAN`}
+ <TextField
+ id="internal-account"
+ label={i18n.str`Internal account`}
help={
purpose === "create"
- ? i18n.str`If empty a random account number will be assigned`
- : i18n.str`Account number for bank transfers`
+ ? i18n.str`If empty a random account id will be assigned`
+ : i18n.str`Share this id to receive bank transfers`
}
- value={(form.payto_uri ?? defaultValue.payto_uri) as PaytoString}
- disabled={!editableAccount}
+
error={errors?.payto_uri}
onChange={(e) => {
form.payto_uri = e as PaytoString;
updateForm(structuredClone(form));
}}
+ rightIcons={<CopyButton
+ class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+ getContent={() => form.payto_uri ?? defaultValue.payto_uri ?? ""}
+ />}
+ value={(form.payto_uri ?? defaultValue.payto_uri) as PaytoString}
+ disabled={!editableAccount}
/>
<div class="sm:col-span-5">
@@ -411,6 +419,9 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
isDirty={form.email !== undefined}
/>
</div>
+ <p class="mt-2 text-sm text-gray-500">
+ <i18n.Translate>To be used when second factor authentication is enabled</i18n.Translate>
+ </p>
</div>
<div class="sm:col-span-5">
@@ -440,102 +451,26 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
isDirty={form.phone !== undefined}
/>
</div>
+ <p class="mt-2 text-sm text-gray-500">
+ <i18n.Translate>To be used when second factor authentication is enabled</i18n.Translate>
+ </p>
</div>
- {showingCurrentUserInfo && isCashoutEnabled && (
- <PaytoField
- type="iban"
- name="cashout-account"
- label={i18n.str`Cashout IBAN`}
+ {isCashoutEnabled && (
+ <TextField
+ id="cashout-account"
+ label={i18n.str`Cashout account`}
help={i18n.str`External account number where the money is going to be sent when doing cashouts`}
- value={
- (form.cashout_payto_uri ??
- defaultValue.cashout_payto_uri) as PaytoString
- }
- disabled={!editableCashout}
error={errors?.cashout_payto_uri}
onChange={(e) => {
form.cashout_payto_uri = e as PaytoString;
updateForm(structuredClone(form));
}}
+ value={(form.cashout_payto_uri ?? defaultValue.cashout_payto_uri) as PaytoString}
+ disabled={!editableCashout}
/>
)}
- <div class="sm:col-span-5">
- <label
- for="debit"
- class="block text-sm font-medium leading-6 text-gray-900"
- >{i18n.str`Max debt`}</label>
- <InputAmount
- name="debit"
- left
- currency={config.currency}
- value={form.debit_threshold ?? defaultValue.debit_threshold}
- onChange={
- !editableThreshold
- ? undefined
- : (e) => {
- form.debit_threshold = e as AmountString;
- updateForm(structuredClone(form));
- }
- }
- />
- <ShowInputErrorLabel
- message={
- errors?.debit_threshold
- ? String(errors?.debit_threshold)
- : undefined
- }
- isDirty={form.debit_threshold !== undefined}
- />
- <p class="mt-2 text-sm text-gray-500">
- <i18n.Translate>
- How much is user able to transfer after zero balance
- </i18n.Translate>
- </p>
- </div>
-
- {purpose !== "create" || !userIsAdmin ? undefined : (
- <div class="sm:col-span-5">
- <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>Is this a payment provider?</i18n.Translate>
- </span>
- </span>
- <button
- type="button"
- data-enabled={
- form.isExchange ?? defaultValue.isExchange
- ? "true"
- : "false"
- }
- 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={() => {
- form.isExchange = !form.isExchange;
- updateForm(structuredClone(form));
- }}
- >
- <span
- aria-hidden="true"
- data-enabled={
- form.isExchange ?? defaultValue.isExchange
- ? "true"
- : "false"
- }
- 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>
- </div>
- )}
{/* channel, not shown if old cashout api */}
{OLD_CASHOUT_API ||
config.supported_tan_channels.length === 0 ? undefined : (
@@ -584,7 +519,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
</span>
{purpose !== "show" &&
!hasEmail &&
- i18n.str`Add a email in your profile to enable this option`}
+ i18n.str`Add an email in your profile to enable this option`}
</span>
</span>
<svg
@@ -669,6 +604,38 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
)}
<div class="sm:col-span-5">
+ <label
+ for="debit"
+ class="block text-sm font-medium leading-6 text-gray-900"
+ >{i18n.str`Max debt`}</label>
+ <InputAmount
+ name="debit"
+ left
+ currency={config.currency}
+ value={form.debit_threshold ?? defaultValue.debit_threshold}
+ onChange={
+ !editableThreshold
+ ? undefined
+ : (e) => {
+ form.debit_threshold = e as AmountString;
+ updateForm(structuredClone(form));
+ }
+ }
+ />
+ <ShowInputErrorLabel
+ message={
+ errors?.debit_threshold
+ ? String(errors?.debit_threshold)
+ : undefined
+ }
+ isDirty={form.debit_threshold !== undefined}
+ />
+ <p class="mt-2 text-sm text-gray-500">
+ <i18n.Translate>How much the balance can go below zero.</i18n.Translate>
+ </p>
+ </div>
+
+ <div class="sm:col-span-5">
<div class="flex items-center justify-between">
<span class="flex flex-grow flex-col">
<span
@@ -703,11 +670,51 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
</button>
</div>
<p class="mt-2 text-sm text-gray-500">
- <i18n.Translate>
- Public accounts have their balance publicly accessible
- </i18n.Translate>
+ <i18n.Translate>Public accounts have their balance publicly accessible</i18n.Translate>
</p>
</div>
+
+ {purpose !== "create" || !userIsAdmin ? undefined : (
+ <div class="sm:col-span-5">
+ <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>Is this account a payment provider?</i18n.Translate>
+ </span>
+ </span>
+ <button
+ type="button"
+ data-enabled={
+ form.isExchange ?? defaultValue.isExchange
+ ? "true"
+ : "false"
+ }
+ 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={() => {
+ form.isExchange = !form.isExchange;
+ updateForm(structuredClone(form));
+ }}
+ >
+ <span
+ aria-hidden="true"
+ data-enabled={
+ form.isExchange ?? defaultValue.isExchange
+ ? "true"
+ : "false"
+ }
+ 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>
+ </div>
+ )}
</div>
</div>
{children}
@@ -715,13 +722,14 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
);
}
-function stringifyIbanPayto(s: PaytoString | undefined): string | undefined {
+function getAccountId(type: "iban" | "x-taler-bank", s: PaytoString | undefined): string | undefined {
if (s === undefined) return undefined;
const p = parsePaytoUri(s);
if (p === undefined) return undefined;
- if (!p.isKnown) return undefined;
- if (p.targetType !== "iban") return undefined;
- return p.iban;
+ if (!p.isKnown) return "<unkown>";
+ if (type === "iban" && p.targetType === "iban") return p.iban;
+ if (type === "x-taler-bank" && p.targetType === "x-taler-bank") return p.account;
+ return "<unsupported>";
}
{
@@ -762,126 +770,128 @@ function stringifyIbanPayto(s: PaytoString | undefined): string | undefined {
</div> */
}
-function PaytoField({
- name,
- label,
- help,
- type,
- value,
- disabled,
- onChange,
- error,
-}: {
- error: TranslatedString | undefined;
- name: string;
- label: TranslatedString;
- help: TranslatedString;
- onChange: (s: string) => void;
- type: "iban" | "x-taler-bank" | "bitcoin";
- disabled?: boolean;
- value: string | undefined;
-}): VNode {
- if (type === "iban") {
- return (
- <div class="sm:col-span-5">
- <label
- class="block text-sm font-medium leading-6 text-gray-900"
- for={name}
- >
- {label}
- </label>
- <div class="mt-2">
- <div class="flex justify-between">
- <input
- type="text"
- class="mr-4 w-full block-inline 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={name}
- id={name}
- disabled={disabled}
- value={value ?? ""}
- onChange={(e) => {
- onChange(e.currentTarget.value);
- }}
- />
- <CopyButton
- class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
- getContent={() => value ?? ""}
- />
- </div>
- <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
- </div>
- <p class="mt-2 text-sm text-gray-500">{help}</p>
- </div>
- );
- }
- if (type === "x-taler-bank") {
- return (
- <div class="sm:col-span-5">
- <label
- class="block text-sm font-medium leading-6 text-gray-900"
- for={name}
- >
- {label}
- </label>
- <div class="mt-2">
- <div class="flex justify-between">
- <input
- type="text"
- class="mr-4 w-full block-inline 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={name}
- id={name}
- disabled={disabled}
- value={value ?? ""}
- />
- <CopyButton
- class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
- getContent={() => value ?? ""}
- />
- </div>
- <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
- </div>
- <p class="mt-2 text-sm text-gray-500">
- {/* <i18n.Translate>internal account id</i18n.Translate> */}
- {help}
- </p>
- </div>
- );
- }
- if (type === "bitcoin") {
- return (
- <div class="sm:col-span-5">
- <label
- class="block text-sm font-medium leading-6 text-gray-900"
- for={name}
- >
- {label}
- </label>
- <div class="mt-2">
- <div class="flex justify-between">
- <input
- type="text"
- class="mr-4 w-full block-inline 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={name}
- id={name}
- disabled={disabled}
- value={value ?? ""}
- />
- <CopyButton
- class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
- getContent={() => value ?? ""}
- />
- <ShowInputErrorLabel
- message={error}
- isDirty={value !== undefined}
- />
- </div>
- </div>
- <p class="mt-2 text-sm text-gray-500">
- {/* <i18n.Translate>bitcoin address</i18n.Translate> */}
- {help}
- </p>
- </div>
- );
- }
- assertUnreachable(type);
-}
+// function PaytoField({
+// name,
+// label,
+// help,
+// type,
+// value,
+// disabled,
+// onChange,
+// error,
+// }: {
+// error: TranslatedString | undefined;
+// name: string;
+// label: TranslatedString;
+// help: TranslatedString;
+// onChange: (s: string) => void;
+// type: "iban" | "x-taler-bank" | "bitcoin";
+// disabled?: boolean;
+// value: string | undefined;
+// }): VNode {
+// if (type === "iban") {
+// return (
+// <div class="sm:col-span-5">
+// <label
+// class="block text-sm font-medium leading-6 text-gray-900"
+// for={name}
+// >
+// {label}
+// </label>
+// <div class="mt-2">
+// <div class="flex justify-between">
+// <input
+// type="text"
+// class="mr-4 w-full block-inline 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={name}
+// id={name}
+// disabled={disabled}
+// value={value ?? ""}
+// onChange={(e) => {
+// onChange(e.currentTarget.value);
+// }}
+// />
+// <CopyButton
+// class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+// getContent={() => value ?? ""}
+// />
+// </div>
+// <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
+// </div>
+// <p class="mt-2 text-sm text-gray-500">{help}</p>
+// </div>
+// );
+// }
+// if (type === "x-taler-bank") {
+// return (
+// <div class="sm:col-span-5">
+// <label
+// class="block text-sm font-medium leading-6 text-gray-900"
+// for={name}
+// >
+// {label}
+// </label>
+// <div class="mt-2">
+// <div class="flex justify-between">
+// <input
+// type="text"
+// class="mr-4 w-full block-inline 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={name}
+// id={name}
+// disabled={disabled}
+// value={value ?? ""}
+// onChange={(e) => {
+// onChange(e.currentTarget.value);
+// }}
+// />
+// <CopyButton
+// class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+// getContent={() => value ?? ""}
+// />
+// </div>
+// <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
+// </div>
+// <p class="mt-2 text-sm text-gray-500">
+// {help}
+// </p>
+// </div>
+// );
+// }
+// if (type === "bitcoin") {
+// return (
+// <div class="sm:col-span-5">
+// <label
+// class="block text-sm font-medium leading-6 text-gray-900"
+// for={name}
+// >
+// {label}
+// </label>
+// <div class="mt-2">
+// <div class="flex justify-between">
+// <input
+// type="text"
+// class="mr-4 w-full block-inline 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={name}
+// id={name}
+// disabled={disabled}
+// value={value ?? ""}
+// />
+// <CopyButton
+// class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+// getContent={() => value ?? ""}
+// />
+// <ShowInputErrorLabel
+// message={error}
+// isDirty={value !== undefined}
+// />
+// </div>
+// </div>
+// <p class="mt-2 text-sm text-gray-500">
+// {/* <i18n.Translate>bitcoin address</i18n.Translate> */}
+// {help}
+// </p>
+// </div>
+// );
+// }
+// assertUnreachable(type);
+// }