aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/business/CreateCashout.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/business/CreateCashout.tsx')
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx174
1 files changed, 151 insertions, 23 deletions
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index c5f4ebc4e..2f77f3960 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -26,6 +26,7 @@ import {
Loading,
LocalNotificationBanner,
ShowInputErrorLabel,
+ notifyInfo,
useLocalNotification,
useTranslationContext
} from "@gnu-taler/web-util/browser";
@@ -46,12 +47,13 @@ import {
import { LoginForm } from "../LoginForm.js";
import { InputAmount, RenderAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { getRandomPassword, getRandomUsername } from "../rnd.js";
interface Props {
account: string;
focus?: boolean,
onComplete: (id: string) => void;
- onCancel: () => void;
+ onCancel?: () => void;
}
type FormType = {
@@ -77,7 +79,10 @@ export function CreateCashout({
estimateByCredit: calculateFromCredit,
estimateByDebit: calculateFromDebit,
} = useEstimator();
- const { config } = useBankCoreApiContext()
+ const { state: credentials } = useBackendState();
+ const creds = credentials.status !== "loggedIn" ? undefined : credentials
+
+ const { api, config } = useBankCoreApiContext()
const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, amount: "2" });
const [notification, notify, handleError] = useLocalNotification()
const info = useConversionInfo();
@@ -119,7 +124,7 @@ export function CreateCashout({
debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold)
}
- const {fiat_currency, regional_currency} = info.body
+ const { fiat_currency, regional_currency, fiat_currency_specification, regional_currency_specification } = info.body
const regionalZero = Amounts.zeroOfCurrency(regional_currency);
const fiatZero = Amounts.zeroOfCurrency(fiat_currency);
const limit = account.balanceIsDebit
@@ -128,7 +133,6 @@ export function CreateCashout({
const zeroCalc = { debit: regionalZero, credit: fiatZero, beforeFee: fiatZero };
const [calc, setCalc] = useState(zeroCalc);
- console.log(calc)
const sellFee = Amounts.parseOrThrow(conversionInfo.cashout_fee);
const sellRate = conversionInfo.cashout_ratio
/**
@@ -159,6 +163,7 @@ export function CreateCashout({
setForm(newForm);
}
const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
+ subject: !form.subject ? i18n.str`required` : undefined,
amount: !form.amount
? i18n.str`required`
: !inputAmount
@@ -174,6 +179,70 @@ export function CreateCashout({
});
const trimmedAmountStr = form.amount?.trim();
+ async function createCashout() {
+ const request_uid = encodeCrock(getRandomBytes(32))
+ await handleError(async () => {
+ if (!creds || !form.subject || !form.channel) return;
+
+ const resp = await api.createCashout(creds, {
+ request_uid,
+ amount_credit: Amounts.stringify(calc.credit),
+ amount_debit: Amounts.stringify(calc.debit),
+ subject: form.subject,
+ tan_channel: form.channel,
+ })
+ if (resp.type === "ok") {
+ notifyInfo(i18n.str`Cashout created`)
+ } else {
+ switch (resp.case) {
+ case "account-not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "request-already-used": return notify({
+ type: "error",
+ title: i18n.str`Duplicated request detected, check if the operation succeded or try again.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "incorrect-exchange-rate": return notify({
+ type: "error",
+ title: i18n.str`The exchange rate was incorrectly applied`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-contact-info": return notify({
+ type: "error",
+ title: i18n.str`Missing contact info before to create the cashout`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "no-enough-balance": return notify({
+ type: "error",
+ title: i18n.str`The account does not have sufficient funds`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "cashout-not-supported": return notify({
+ type: "error",
+ title: i18n.str`Cashouts are not supported`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "tan-failed": return notify({
+ type: "error",
+ title: i18n.str`Sending the confirmation code failed.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ }
+ assertUnreachable(resp)
+ }
+ })
+ }
+
return (
<div>
<LocalNotificationBanner notification={notification} />
@@ -192,18 +261,18 @@ export function CreateCashout({
<div class="flex items-center justify-between border-t-2 afu pt-4">
<dt class="flex items-center text-sm text-gray-600">
- <span><i18n.Translate>Current balance</i18n.Translate></span>
+ <span><i18n.Translate>Balance</i18n.Translate></span>
</dt>
<dd class="text-sm text-gray-900">
- <RenderAmount value={account.balance} />
+ <RenderAmount value={account.balance} spec={regional_currency_specification} />
</dd>
</div>
<div class="flex items-center justify-between border-t-2 afu pt-4">
<dt class="flex items-center text-sm text-gray-600">
- <span><i18n.Translate>Cashout fee</i18n.Translate></span>
+ <span><i18n.Translate>Fee</i18n.Translate></span>
</dt>
<dd class="text-sm text-gray-900">
- <RenderAmount value={sellFee} />
+ <RenderAmount value={sellFee} spec={fiat_currency_specification} />
</dd>
</div>
</dl>
@@ -226,7 +295,7 @@ export function CreateCashout({
class="block text-sm font-medium leading-6 text-gray-900"
for="subject"
>
- {i18n.str`Subject`}
+ {i18n.str`Transfer subject`}
</label>
<div class="mt-2">
<input
@@ -253,14 +322,24 @@ export function CreateCashout({
{/* amount */}
<div class="sm:col-span-5">
- <label
- class="block text-sm font-medium leading-6 text-gray-900"
- for="amount"
- >
- {form.isDebit
- ? i18n.str`Amount to send`
- : i18n.str`Amount to receive`}
- </label>
+ <div class="flex justify-between">
+ <label
+ class="block text-sm font-medium leading-6 text-gray-900"
+ for="amount"
+ >
+ {form.isDebit
+ ? i18n.str`Amount to send`
+ : i18n.str`Amount to receive`}
+ </label>
+ <button type="button" data-enabled={form.isDebit} 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.isDebit = !form.isDebit
+ updateForm(structuredClone(form))
+ }}>
+ <span aria-hidden="true" data-enabled={form.isDebit} 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 class="mt-2">
<InputAmount
name="amount"
@@ -287,7 +366,7 @@ export function CreateCashout({
<div class="justify-between items-center flex ">
<dt class="text-sm text-gray-600"><i18n.Translate>Total cost</i18n.Translate></dt>
<dd class="text-sm text-gray-900">
- <RenderAmount value={calc.debit} negative withColor />
+ <RenderAmount value={calc.debit} negative withColor spec={regional_currency_specification} />
</dd>
</div>
@@ -302,7 +381,7 @@ export function CreateCashout({
</a> */}
</dt>
<dd class="text-sm text-gray-900">
- <RenderAmount value={balanceAfter} />
+ <RenderAmount value={balanceAfter} spec={regional_currency_specification} />
</dd>
</div>
{Amounts.isZero(sellFee) || Amounts.isZero(calc.beforeFee) ? undefined : (
@@ -316,14 +395,14 @@ export function CreateCashout({
</a> */}
</dt>
<dd class="text-sm text-gray-900">
- <RenderAmount value={calc.beforeFee} />
+ <RenderAmount value={calc.beforeFee} spec={fiat_currency_specification} />
</dd>
</div>
)}
<div class="flex justify-between items-center border-t-2 afu pt-4">
<dt class="text-lg text-gray-900 font-medium"><i18n.Translate>Total cashout transfer</i18n.Translate></dt>
<dd class="text-lg text-gray-900 font-medium">
- <RenderAmount value={calc.credit} withColor />
+ <RenderAmount value={calc.credit} withColor spec={fiat_currency_specification} />
</dd>
</div>
</dl>
@@ -331,6 +410,55 @@ export function CreateCashout({
)}
{/* channel */}
+ <div class="sm:col-span-5">
+ <label
+ class="block text-sm font-medium leading-6 text-gray-900"
+ for="channel"
+ >
+ {i18n.str`Confirmation the operation using`}
+ </label>
+
+ <div class="mt-2 max-w-xl text-sm text-gray-500">
+ <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
+
+ <label onClick={()=>{
+ form.channel = TanChannel.EMAIL
+ updateForm(structuredClone(form))
+ }} data-selected={form.channel === TanChannel.EMAIL} class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600">
+ <input type="radio" name="channel" value="Newsletter" class="sr-only" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-0-label" class="block text-sm font-medium text-gray-900 ">
+ <i18n.Translate>Email</i18n.Translate>
+ </span>
+ </span>
+ </span>
+ <svg data-selected={form.channel === TanChannel.EMAIL} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" 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>
+ </label>
+
+ <label onClick={()=>{
+ form.channel = TanChannel.SMS
+ updateForm(structuredClone(form))
+ }} data-selected={form.channel === TanChannel.SMS} class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600">
+ <input type="radio" name="channel" value="Existing Customers" class="sr-only" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
+ <i18n.Translate>SMS</i18n.Translate>
+ </span>
+ </span>
+ </span>
+ <svg data-selected={form.channel === TanChannel.SMS} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" 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>
+ </label>
+
+ </div>
+ </div>
+
+ </div>
</div>
</div>
@@ -348,10 +476,10 @@ export function CreateCashout({
disabled={!!errors}
onClick={(e) => {
e.preventDefault()
- // doChangePassword()
+ createCashout()
}}
>
- <i18n.Translate>Change</i18n.Translate>
+ <i18n.Translate>Cashout</i18n.Translate>
</button>
</div>
</form>