commit 867049abeacecfbec73094d9963ee57889887260
parent ec9d6255e6b6113e60802a643bc68d9b6c0d8d44
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 20 Aug 2024 01:43:44 -0300
first form test
Diffstat:
6 files changed, 389 insertions(+), 315 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx b/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx
@@ -110,7 +110,7 @@ export function ExchangeAmlFrame({
children,
officer,
}: {
- officer?: OfficerState,
+ officer?: OfficerState;
children?: ComponentChildren;
}): VNode {
const { i18n } = useTranslationContext();
@@ -133,7 +133,7 @@ export function ExchangeAmlFrame({
}, [error]);
const [preferences, updatePreferences] = usePreferences();
- const settings = useUiSettingsContext()
+ const settings = useUiSettingsContext();
return (
<div
diff --git a/packages/kyc-ui/src/Routing.tsx b/packages/kyc-ui/src/Routing.tsx
@@ -17,7 +17,7 @@
import {
urlPattern,
useCurrentLocation,
- useNavigationContext
+ useNavigationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -44,10 +44,6 @@ const publicPages = {
/\/info\/(?<token>[0-9A-Za-z]+)/,
({ token }) => `#/start/${token}`,
),
- fillForm: urlPattern<{ token: string, formId: string }>(
- /\/fill-form\/(?<token>[0-9A-Za-z]+)\/(?<formId>[0-9A-Za-z\-]+)/,
- ({ token, formId }) => `#/fill-form/${token}/${formId}`,
- ),
};
function safeGetParam(
@@ -76,33 +72,20 @@ function PublicRounting(): VNode {
switch (location.name) {
case undefined: {
- return <div>not found</div>
+ return <div>not found</div>;
}
case "start": {
- const toFillForm = urlPattern<{ formId: string }>(
- /\/fill-form\/(?<token>[0-9A-Za-z]+)\/(?<formId>[0-9A-Za-z]+)/,
- ({ formId }) => `#/fill-form/${location.values.token}/${formId}`,
- )
return (
<Start
//FIX: validate token
token={location.values.token as AccessToken}
- routeFillForm={toFillForm}
- onCreated={() => {
- navigateTo(publicPages.completed.url({}));
- }}
- />
- );
- }
- case "fillForm": {
- return (
- <FillForm
- //FIX: validate token
- token={location.values.token as AccessToken}
- formId={location.values.formId}
+ // onCreated={() => {
+ // navigateTo(publicPages.completed.url({}));
+ // }}
/>
);
}
+
case "completed": {
return <CallengeCompleted />;
}
diff --git a/packages/kyc-ui/src/forms/index.ts b/packages/kyc-ui/src/forms/index.ts
@@ -13,21 +13,26 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { FormMetadata, InternationalizationAPI } from "@gnu-taler/web-util/browser";
+import {
+ FormMetadata,
+ InternationalizationAPI,
+} from "@gnu-taler/web-util/browser";
import { simplest } from "./simplest.js";
+import { personalInfo } from "./personal-info.js";
-export const preloadedForms: (i18n: InternationalizationAPI) => Array<FormMetadata> = (i18n) => [
- {
- label: i18n.str`Simple comment`,
- id: "__simple_comment",
- version: 1,
- config: simplest(i18n),
- },
- {
- label: i18n.str`Personal info`,
- id: "personal-info",
- version: 1,
- config: simplest(i18n),
- },
-
-]
-\ No newline at end of file
+export const preloadedForms: (
+ i18n: InternationalizationAPI,
+) => Array<FormMetadata> = (i18n) => [
+ {
+ label: i18n.str`Simple comment`,
+ id: "__simple_comment",
+ version: 1,
+ config: simplest(i18n),
+ },
+ {
+ label: i18n.str`Personal info`,
+ id: "personal-info",
+ version: 1,
+ config: personalInfo(i18n),
+ },
+];
diff --git a/packages/kyc-ui/src/forms/personal-info.ts b/packages/kyc-ui/src/forms/personal-info.ts
@@ -14,34 +14,56 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
- import type {
- DoubleColumnForm,
- DoubleColumnFormSection,
- InternationalizationAPI,
- UIHandlerId,
- } from "@gnu-taler/web-util/browser";
-
- export const personalInfo = (i18n: InternationalizationAPI): DoubleColumnForm => ({
- type: "double-column" as const,
- design: [
- {
- title: i18n.str`Simple form`,
- fields: [
- {
- type: "textArea",
- id: ".full_name" as UIHandlerId,
- name: "full_name",
- label: i18n.str`Full Name`,
- },
- {
- type: "textArea",
- id: ".birthdate" as UIHandlerId,
- name: "birthdate",
- label: i18n.str`Birthdate`,
- },
- ],
- },
- ],
- });
-
-
-\ No newline at end of file
+import type {
+ DoubleColumnForm,
+ DoubleColumnFormSection,
+ InternationalizationAPI,
+ UIHandlerId,
+} from "@gnu-taler/web-util/browser";
+
+export const personalInfo = (
+ i18n: InternationalizationAPI,
+): DoubleColumnForm => ({
+ type: "double-column" as const,
+ design: [
+ {
+ title: i18n.str`Simple form`,
+ fields: [
+ // {
+ // type: "absoluteTimeText",
+ // name: "dateOfDeath",
+ // label: i18n.str`Date of death`,
+ // pattern: "dd/MM/yyyy",
+ // // help: i18n.str`if deceased. format 'dd/MM/yyyy'`,
+ // help: i18n.str`if deceased'`,
+ // id: ".birthdate" as UIHandlerId,
+ // },
+ {
+ type: "choiceStacked",
+ name: "trucker",
+ id: ".trucker" as UIHandlerId,
+ required: true,
+ label: i18n.str`Are you a cross-border truck driver?`,
+ choices: [
+ {
+ label: i18n.str`Yes`,
+ value: "yes",
+ },
+ {
+ label: i18n.str`No`,
+ value: "no",
+ },
+ ],
+ },
+ {
+ type: "amount",
+ id: ".money" as UIHandlerId,
+ currency: "YEIN",
+ name: "money",
+ converterId: "Taler.Amount",
+ label: i18n.str`How much is in your pockets?`,
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/kyc-ui/src/pages/FillForm.tsx b/packages/kyc-ui/src/pages/FillForm.tsx
@@ -16,56 +16,74 @@
import {
AbsoluteTime,
AccessToken,
- AmountJson,
- Amounts
+ HttpStatusCode,
+ KycRequirementInformation,
+ assertUnreachable,
} from "@gnu-taler/taler-util";
import {
Button,
- convertUiField,
FormMetadata,
- getConverterById,
InternationalizationAPI,
LocalNotificationBanner,
RenderAllFieldsByUiConfig,
UIFormElementConfig,
UIHandlerId,
+ convertUiField,
+ getConverterById,
useExchangeApiContext,
useLocalNotificationHandler,
- useTranslationContext
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { preloadedForms } from "../forms/index.js";
-import { FormErrors, useFormState, validateRequiredFields } from "../hooks/form.js";
+import {
+ FormErrors,
+ useFormState,
+ validateRequiredFields,
+} from "../hooks/form.js";
import { undefinedIfEmpty } from "./Start.js";
type Props = {
token: AccessToken;
formId: string;
+ requirement: KycRequirementInformation;
+ onComplete: () => void;
};
type FormType = {
- when: AbsoluteTime;
// state: TalerExchangeApi.AmlState;
- threshold: AmountJson;
- comment: string;
};
-export function FillForm({token, formId}:Props):VNode {
+type KycFormMetadata = {
+ id: string;
+ version: number;
+ when: AbsoluteTime;
+};
+
+type KycForm = {
+ header: KycFormMetadata;
+ payload: object;
+};
+
+export function FillForm({
+ token,
+ formId,
+ requirement,
+ onComplete,
+}: Props): VNode {
const { i18n } = useTranslationContext();
- const { config } = useExchangeApiContext();
+ const { config, lib } = useExchangeApiContext();
// const { forms } = useUiFormsContext();
const [notification, withErrorHandler] = useLocalNotificationHandler();
- const initial: FormType = {
- when: AbsoluteTime.now(),
- threshold: Amounts.zeroOfCurrency(config.currency),
- comment: "",
- };
-
const theForm = searchForm(i18n, [], formId);
if (!theForm) {
return <div>form with id {formId} not found</div>;
}
+ const reqId = requirement.id;
+ if (!reqId) {
+ return <div>no id for this form, can't upload</div>;
+ }
const shape: Array<UIHandlerId> = [];
const requiredFields: Array<UIHandlerId> = [];
@@ -76,11 +94,8 @@ export function FillForm({token, formId}:Props):VNode {
getRequiredFields(section.fields),
);
});
- const [form, state] = useFormState<FormType>(shape, initial, (st) => {
- const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({
- threshold: !st.threshold ? i18n.str`required` : undefined,
- when: !st.when ? i18n.str`required` : undefined,
- });
+ const [form, state] = useFormState<FormType>(shape, {}, (st) => {
+ const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({});
const errors = undefinedIfEmpty<FormErrors<FormType> | undefined>(
validateRequiredFields(partialErrors, st, requiredFields),
@@ -103,58 +118,40 @@ export function FillForm({token, formId}:Props):VNode {
const validatedForm = state.status !== "ok" ? undefined : state.result;
const submitHandler =
- validatedForm === undefined
- ? undefined
- : withErrorHandler(
- async () => {
- // const justification: Justification = {
- // id: theForm.id,
- // label: theForm.label,
- // version: theForm.version,
- // value: validatedForm,
- // };
+ validatedForm === undefined
+ ? undefined
+ : withErrorHandler(
+ async () => {
+ const information: KycForm = {
+ header: {
+ id: theForm.id,
+ version: theForm.version,
+ when: AbsoluteTime.now(),
+ },
+ payload: validatedForm,
+ };
- // const decision: Omit<TalerExchangeApi.AmlDecisionRequest, "officer_sig"> =
- // {
- // justification: JSON.stringify(justification),
- // decision_time: TalerProtocolTimestamp.now(),
- // h_payto: account,
- // keep_investigating: false,
- // new_rules: {
- // custom_measures: {},
- // expiration_time: {
- // t_s: "never"
- // },
- // rules: [],
- // successor_measure: undefined
- // },
- // properties: {},
- // new_measure: undefined,
- // };
+ return lib.exchange.uploadKycForm(reqId, information);
+ },
+ (res) => {
+ onComplete();
+ },
+ (fail) => {
+ switch (fail.case) {
+ case HttpStatusCode.PayloadTooLarge:
+ return i18n.str`The form is too big for the server, try uploading smaller files.`;
+ case HttpStatusCode.NotFound:
+ return i18n.str`The account was not found`;
+ case HttpStatusCode.Conflict:
+ return i18n.str`Officer disabled or more recent decision was already submitted.`;
+ default:
+ assertUnreachable(fail);
+ }
+ },
+ );
- return {
- type: "ok",
- body: {}
- }
- },
- () => {
- // window.location.href = privatePages.cases.url({});
- },
- // (fail) => {
- // switch (fail.case) {
- // case HttpStatusCode.Forbidden:
- // return i18n.str`Wrong credentials for "${officer.account}"`;
- // case HttpStatusCode.NotFound:
- // return i18n.str`The account was not found`;
- // case HttpStatusCode.Conflict:
- // return i18n.str`Officer disabled or more recent decision was already submitted.`;
- // default:
- // assertUnreachable(fail);
- // }
- // },
- );
-
- return <Fragment>
+ return (
+ <div class="rounded-lg bg-white px-5 py-6 shadow m-4">
<LocalNotificationBanner notification={notification} />
<div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
{theForm.config.design.map((section, i) => {
@@ -195,12 +192,12 @@ export function FillForm({token, formId}:Props):VNode {
</div>
<div class="mt-6 flex items-center justify-end gap-x-6">
- <a
- // href={privatePages.caseDetails.url({ cid: account })}
+ <button
+ onClick={onComplete}
class="text-sm font-semibold leading-6 text-gray-900"
>
<i18n.Translate>Cancel</i18n.Translate>
- </a>
+ </button>
<Button
type="submit"
handler={submitHandler}
@@ -210,13 +207,11 @@ export function FillForm({token, formId}:Props):VNode {
<i18n.Translate>Confirm</i18n.Translate>
</Button>
</div>
- </Fragment>
-
+ </div>
+ );
}
-function getRequiredFields(
- fields: UIFormElementConfig[],
-): Array<UIHandlerId> {
+function getRequiredFields(fields: UIFormElementConfig[]): Array<UIHandlerId> {
const shape: Array<UIHandlerId> = [];
fields.forEach((field) => {
if ("id" in field) {
@@ -230,17 +225,12 @@ function getRequiredFields(
}
shape.push(field.id);
} else if (field.type === "group") {
- Array.prototype.push.apply(
- shape,
- getRequiredFields(field.fields),
- );
+ Array.prototype.push.apply(shape, getRequiredFields(field.fields));
}
});
return shape;
}
-function getShapeFromFields(
- fields: UIFormElementConfig[],
-): Array<UIHandlerId> {
+function getShapeFromFields(fields: UIFormElementConfig[]): Array<UIHandlerId> {
const shape: Array<UIHandlerId> = [];
fields.forEach((field) => {
if ("id" in field) {
@@ -251,10 +241,7 @@ function getShapeFromFields(
}
shape.push(field.id);
} else if (field.type === "group") {
- Array.prototype.push.apply(
- shape,
- getShapeFromFields(field.fields),
- );
+ Array.prototype.push.apply(shape, getShapeFromFields(field.fields));
}
});
return shape;
@@ -275,4 +262,3 @@ function searchForm(
}
return undefined;
}
-
diff --git a/packages/kyc-ui/src/pages/Start.tsx b/packages/kyc-ui/src/pages/Start.tsx
@@ -18,40 +18,37 @@ import {
HttpStatusCode,
KycRequirementInformation,
TalerError,
- assertUnreachable
+ assertUnreachable,
} from "@gnu-taler/taler-util";
import {
Attention,
- FormMetadata,
- InternationalizationAPI,
Loading,
LocalNotificationBanner,
- RouteDefinition,
useLocalNotificationHandler,
- useTranslationContext
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
-import { preloadedForms } from "../forms/index.js";
import { useKycInfo } from "../hooks/kyc.js";
+import { FillForm } from "./FillForm.js";
type Props = {
token: AccessToken;
- onCreated: () => void;
- routeFillForm: RouteDefinition<{ formId: string }>;
};
-export function Start({
+function ShowReqList({
token,
- routeFillForm,
- onCreated,
-}: Props): VNode {
+ onFormSelected,
+}: {
+ token: AccessToken;
+ onFormSelected: (r: KycRequirementInformation) => void;
+}): VNode {
const { i18n } = useTranslationContext();
const [notification, withErrorHandler] = useLocalNotificationHandler();
// const { lib } = useExchangeApiContext();
// const { state, start } = useSessionState();
- const result = useKycInfo({ accessToken: token })
-
+ const result = useKycInfo({ accessToken: token });
if (!result) {
return <Loading />;
}
@@ -82,37 +79,37 @@ export function Start({
// : undefined,
});
- const onStart =
- !!errors
- ? undefined
- : withErrorHandler(
- async () => {
- return {
- type: "ok",
- body: {},
- }
- // return lib.exchange.uploadKycForm(
- // "clientId",
- // createRFC8959AccessTokenEncoded(password),
- // );
- },
- (ok) => {
- // start({
- // nonce: ok.body.nonce,
- // clientId,
- // redirectURL: url,
- // state: encodeCrock(randomBytes(32)),
- // });
+ // const onStart =
+ // !!errors
+ // ? undefined
+ // : withErrorHandler(
+ // async () => {
+ // return {
+ // type: "ok",
+ // body: {},
+ // }
+ // // return lib.exchange.uploadKycForm(
+ // // "clientId",
+ // // createRFC8959AccessTokenEncoded(password),
+ // // );
+ // },
+ // (ok) => {
+ // // start({
+ // // nonce: ok.body.nonce,
+ // // clientId,
+ // // redirectURL: url,
+ // // state: encodeCrock(randomBytes(32)),
+ // // });
- onCreated();
- },
- // () => {
- // // switch (fail.case) {
- // // case HttpStatusCode.NotFound:
- // // return i18n.str`Client doesn't exist.`;
- // // }
- // },
- );
+ // onCreated();
+ // },
+ // // () => {
+ // // // switch (fail.case) {
+ // // // case HttpStatusCode.NotFound:
+ // // // return i18n.str`Client doesn't exist.`;
+ // // // }
+ // // },
+ // );
// const requirements: typeof result.body.requirements = [{
// description: "this is the form description, click to show the form field bla bla bla",
@@ -132,30 +129,23 @@ export function Start({
// }]
const requirements = result.body.requirements;
if (!result.body.requirements.length) {
- return <Fragment>
- <LocalNotificationBanner notification={notification} />
- <div class="isolate bg-white px-6 py-12">
- <div class="mx-auto max-w-2xl text-center">
- <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
- <i18n.Translate>
- No requirements for this account
- </i18n.Translate>
- </h2>
- </div>
- <div class="m-8">
- <Attention
- title={i18n.str`Kyc completed`}
- type="success"
- >
- <i18n.Translate>
- You can close this now
- </i18n.Translate>
- </Attention>
+ return (
+ <Fragment>
+ <LocalNotificationBanner notification={notification} />
+ <div class="isolate bg-white px-6 py-12">
+ <div class="mx-auto max-w-2xl text-center">
+ <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
+ <i18n.Translate>No requirements for this account</i18n.Translate>
+ </h2>
+ </div>
+ <div class="m-8">
+ <Attention title={i18n.str`Kyc completed`} type="success">
+ <i18n.Translate>You can close this now</i18n.Translate>
+ </Attention>
+ </div>
</div>
- </div>
-
-
- </Fragment>
+ </Fragment>
+ );
}
return (
<Fragment>
@@ -171,107 +161,195 @@ export function Start({
</div>
<div class="mt-8">
- <ul role="list" class=" divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl">
+ <ul
+ role="list"
+ class=" divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl"
+ >
{requirements.map((req, idx) => {
- return <li key={idx} class="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6">
- <RequirementRow requirement={req} routeFillForm={routeFillForm} />
- </li>
+ return (
+ <li
+ key={idx}
+ class="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6"
+ >
+ <RequirementRow
+ requirement={req}
+ onFormSelected={() => {
+ onFormSelected(req);
+ }}
+ />
+ </li>
+ );
})}
</ul>
</div>
-
</div>
</Fragment>
);
}
+export function Start({ token }: Props): VNode {
+ const [req, setReq] = useState<KycRequirementInformation>();
+ if (!req) {
+ return <ShowReqList token={token} onFormSelected={(r) => setReq(r)} />;
+ }
+ return (
+ <FillForm
+ formId={req.form}
+ requirement={req}
+ token={token}
+ onComplete={() => {
+ setReq(undefined);
+ }}
+ />
+ );
+}
-
-function RequirementRow({ requirement: req, routeFillForm }: { requirement: KycRequirementInformation, routeFillForm: RouteDefinition<{ formId: string }> }): VNode {
- const { i18n } = useTranslationContext()
+function RequirementRow({
+ requirement: req,
+ onFormSelected,
+}: {
+ requirement: KycRequirementInformation;
+ onFormSelected: () => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
switch (req.form) {
case "INFO": {
- return <Fragment>
- <div class="flex min-w-0 gap-x-4">
- <div class="inline-block h-10 w-10 rounded-full">
-
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
- <path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
- </svg>
- </div>
- <div class="min-w-0 flex-auto">
- <p class="text-sm font-semibold leading-6 text-gray-900">
- <span class="absolute inset-x-0 -top-px bottom-0"></span>
- <i18n.Translate>Information</i18n.Translate>
- </p>
- <p class="mt-1 flex text-xs leading-5 text-gray-500">
- {req.description}
- </p>
+ return (
+ <Fragment>
+ <div class="flex min-w-0 gap-x-4">
+ <div class="inline-block h-10 w-10 rounded-full">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
+ />
+ </svg>
+ </div>
+ <div class="min-w-0 flex-auto">
+ <p class="text-sm font-semibold leading-6 text-gray-900">
+ <span class="absolute inset-x-0 -top-px bottom-0"></span>
+ <i18n.Translate>Information</i18n.Translate>
+ </p>
+ <p class="mt-1 flex text-xs leading-5 text-gray-500">
+ {req.description}
+ </p>
+ </div>
</div>
- </div>
- </Fragment>
+ </Fragment>
+ );
}
case "LINK": {
- return <Fragment>
- <div class="flex min-w-0 gap-x-4">
- <div class="inline-block h-10 w-10 rounded-full">
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
- <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
- </svg>
+ return (
+ <Fragment>
+ <div class="flex min-w-0 gap-x-4">
+ <div class="inline-block h-10 w-10 rounded-full">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
+ />
+ </svg>
+ </div>
+ <div class="min-w-0 flex-auto">
+ <p class="text-sm font-semibold leading-6 text-gray-900">
+ <a href="#">
+ <span class="absolute inset-x-0 -top-px bottom-0"></span>
+ <i18n.Translate>Begin KYC process</i18n.Translate>
+ </a>
+ </p>
+ <p class="mt-1 flex text-xs leading-5 text-gray-500">
+ {req.description}
+ </p>
+ </div>
</div>
- <div class="min-w-0 flex-auto">
- <p class="text-sm font-semibold leading-6 text-gray-900">
- <a href="#">
- <span class="absolute inset-x-0 -top-px bottom-0"></span>
- <i18n.Translate>Begin KYC process</i18n.Translate>
- </a>
- </p>
- <p class="mt-1 flex text-xs leading-5 text-gray-500">
- {req.description}
- </p>
- </div>
- </div>
- <div class="flex shrink-0 items-center gap-x-4">
- <div class="hidden sm:flex sm:flex-col sm:items-end">
- <p class="text-sm leading-6 text-gray-900">
- <i18n.Translate>Start</i18n.Translate>
- </p>
+ <div class="flex shrink-0 items-center gap-x-4">
+ <div class="hidden sm:flex sm:flex-col sm:items-end">
+ <p class="text-sm leading-6 text-gray-900">
+ <i18n.Translate>Start</i18n.Translate>
+ </p>
+ </div>
+ <svg
+ class="h-5 w-5 flex-none text-gray-400"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ aria-hidden="true"
+ >
+ <path
+ fill-rule="evenodd"
+ d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
+ clip-rule="evenodd"
+ />
+ </svg>
</div>
- <svg class="h-5 w-5 flex-none text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
- </svg>
- </div>
- </Fragment>
+ </Fragment>
+ );
}
default: {
- return <Fragment>
- <div class="flex min-w-0 gap-x-4">
- <div class="inline-block h-10 w-10 rounded-full">
-
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
- <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
- </svg>
+ return (
+ <Fragment>
+ <div class="flex min-w-0 gap-x-4">
+ <div class="inline-block h-10 w-10 rounded-full">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
+ />
+ </svg>
+ </div>
+ <div class="min-w-0 flex-auto">
+ <p class="text-sm font-semibold leading-6 text-gray-900">
+ <button onClick={onFormSelected}>
+ <span class="absolute inset-x-0 -top-px bottom-0"></span>
+ <i18n.Translate>Form</i18n.Translate>
+ </button>
+ </p>
+ <p class="mt-1 flex text-xs leading-5 text-gray-500">
+ {req.description}
+ </p>
+ </div>
</div>
- <div class="min-w-0 flex-auto">
- <p class="text-sm font-semibold leading-6 text-gray-900">
- <a href={routeFillForm.url({ formId: req.form })}>
- <span class="absolute inset-x-0 -top-px bottom-0"></span>
- <i18n.Translate>Form</i18n.Translate>
- </a>
- </p>
- <p class="mt-1 flex text-xs leading-5 text-gray-500">
- {req.description}
- </p>
- </div>
- </div>
- <div class="flex shrink-0 items-center gap-x-4">
- <div class="hidden sm:flex sm:flex-col sm:items-end">
- <p class="text-sm leading-6 text-gray-900">Fill form</p>
+ <div class="flex shrink-0 items-center gap-x-4">
+ <div class="hidden sm:flex sm:flex-col sm:items-end">
+ <p class="text-sm leading-6 text-gray-900">Fill form</p>
+ </div>
+ <svg
+ class="h-5 w-5 flex-none text-gray-400"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ aria-hidden="true"
+ >
+ <path
+ fill-rule="evenodd"
+ d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
+ clip-rule="evenodd"
+ />
+ </svg>
</div>
- <svg class="h-5 w-5 flex-none text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
- </svg>
- </div>
- </Fragment>
+ </Fragment>
+ );
}
}
}
@@ -293,7 +371,9 @@ export function doAutoFocus(element: HTMLElement | null): void {
}
}
-export function undefinedIfEmpty<T extends object | undefined>(obj: T): T | undefined {
+export function undefinedIfEmpty<T extends object | undefined>(
+ obj: T,
+): T | undefined {
if (obj === undefined) return undefined;
return Object.keys(obj).some(
(k) => (obj as Record<string, T>)[k] !== undefined,