aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-11-14 13:37:49 -0300
committerSebastian <sebasjm@gmail.com>2024-11-14 13:37:49 -0300
commitcd09d8c37cabf70358c3e02c64f83c16375d44a7 (patch)
tree5bfbf4781d028c0d959fb90743801e8e37c7a742
parent567a9617cee2dbb5001040f06a5e40db222d4666 (diff)
ui issues
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseDetails.tsx993
1 files changed, 514 insertions, 479 deletions
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index d07c991a8..ed29fc067 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -184,11 +184,22 @@ export function getEventsFromAmlHistory(
return ke.sort(selectSooner);
}
-type NewDecision = { request: Omit<Omit<AmlDecisionRequest, "justification">, "officer_sig">, askInformation: boolean }
+type NewDecision = {
+ request: Omit<Omit<AmlDecisionRequest, "justification">, "officer_sig">;
+ askInformation: boolean;
+};
-export function CaseDetails({ account, paytoString }: { account: string, paytoString?: PaytoString }) {
+export function CaseDetails({
+ account,
+ paytoString,
+}: {
+ account: string;
+ paytoString?: PaytoString;
+}) {
const [selected, setSelected] = useState<AbsoluteTime>(AbsoluteTime.now());
- const [request, setDesicionRequest] = useState<NewDecision | undefined>(undefined)
+ const [request, setDesicionRequest] = useState<NewDecision | undefined>(
+ undefined,
+ );
const { config } = useExchangeApiContext();
const { i18n } = useTranslationContext();
@@ -237,12 +248,15 @@ export function CaseDetails({ account, paytoString }: { account: string, paytoSt
const events = getEventsFromAmlHistory(accountDetails, i18n, allForms);
-
-
if (request) {
- return <SubmitNewDecision decision={request} onComplete={() => {
- setDesicionRequest(undefined)
- }} />
+ return (
+ <SubmitNewDecision
+ decision={request}
+ onComplete={() => {
+ setDesicionRequest(undefined);
+ }}
+ />
+ );
}
return (
@@ -287,7 +301,7 @@ export function CaseDetails({ account, paytoString }: { account: string, paytoSt
},
},
askInformation: false,
- })
+ });
}}
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
>
@@ -358,7 +372,7 @@ export function CaseDetails({ account, paytoString }: { account: string, paytoSt
h_payto: account,
keep_investigating: false,
properties: {
- "pepe": "text",
+ pepe: "text",
},
new_measures: "ask",
new_rules: {
@@ -382,8 +396,8 @@ export function CaseDetails({ account, paytoString }: { account: string, paytoSt
{!activeDecision ? (
<Attention title={i18n.str`No active rules found`} type="warning" />
) : (
- <Fragment>
- <h1 class="my-4 text-base font-semibold leading-6 text-black">
+ <div class="my-4">
+ <h1 class="mb-4 text-base font-semibold leading-6 text-black">
<i18n.Translate>Current active rules</i18n.Translate>
</h1>
<ShowDecisionLimitInfo
@@ -395,16 +409,23 @@ export function CaseDetails({ account, paytoString }: { account: string, paytoSt
)}
justification={activeDecision.justification}
ruleSet={activeDecision.limits}
-
- startOpen />
- </Fragment>
+ startOpen
+ />
+ </div>
)}
- <h1 class="my-4 text-base font-semibold leading-6 text-black">
- <i18n.Translate>KYC collection events</i18n.Translate>
- </h1>
- {events.length === 0 ?
- <Attention title={i18n.str`No events found`} type="warning" />
- :
+ <div class="px-4 sm:px-0">
+ <h1 class="text-base font-semibold leading-6 text-black">
+ <i18n.Translate>KYC collection events</i18n.Translate>
+ </h1>
+ <p class="mt-1 text-sm leading-6 text-gray-600">
+ <i18n.Translate>
+ Every event when the user was asked information.
+ </i18n.Translate>
+ </p>
+ </div>
+ {events.length === 0 ? (
+ <Attention title={i18n.str`The event list is empty`} type="warning" />
+ ) : (
<ShowTimeline
history={events}
onSelect={(e) => {
@@ -423,88 +444,101 @@ export function CaseDetails({ account, paytoString }: { account: string, paytoSt
}
}}
/>
- }
+ )}
{/* {selected && <ShowEventDetails event={selected} />} */}
{selected && <ShowConsolidated history={events} until={selected} />}
{restDecisions.length > 0 ? (
- <Fragment>
- <h1 class="my-4 text-base font-semibold leading-6 text-black">
+ <div class="my-4 grid gap-y-4">
+ <h1 class="text-base font-semibold leading-6 text-black">
<i18n.Translate>Previous AML decisions</i18n.Translate>
</h1>
{restDecisions.map((d) => {
- return <ShowDecisionLimitInfo since={AbsoluteTime.fromProtocolTimestamp(
- d.decision_time,
- )}
- until={AbsoluteTime.fromProtocolTimestamp(
- d.limits.expiration_time,
- )}
- justification={d.justification}
- ruleSet={d.limits}
- />;
+ return (
+ <ShowDecisionLimitInfo
+ since={AbsoluteTime.fromProtocolTimestamp(d.decision_time)}
+ until={AbsoluteTime.fromProtocolTimestamp(
+ d.limits.expiration_time,
+ )}
+ justification={d.justification}
+ ruleSet={d.limits}
+ />
+ );
})}
- </Fragment>
- ) : (
- !activeDecision ?
- <div class="ty-4">
- <Attention title={i18n.str`No aml history found`} type="warning" />
- </div> : undefined
- )}
+ </div>
+ ) : !activeDecision ? (
+ <div class="ty-4">
+ <Attention title={i18n.str`No aml history found`} type="warning" />
+ </div>
+ ) : undefined}
</div>
);
}
-
-function SubmitNewDecision({ decision, onComplete }: { onComplete: () => void; decision: NewDecision }): VNode {
+function SubmitNewDecision({
+ decision,
+ onComplete,
+}: {
+ onComplete: () => void;
+ decision: NewDecision;
+}): VNode {
const { i18n } = useTranslationContext();
const { lib } = useExchangeApiContext();
const [notification, withErrorHandler] = useLocalNotificationHandler();
- const formDesign: UIFormElementConfig[] = [{
- id: "justification" as UIHandlerId,
- type: "textArea",
- required: true,
- label: i18n.str`Justification`,
- }]
+ const formDesign: UIFormElementConfig[] = [
+ {
+ id: "justification" as UIHandlerId,
+ type: "textArea",
+ required: true,
+ label: i18n.str`Justification`,
+ },
+ ];
if (decision.askInformation) {
- formDesign.push({
+ formDesign.push({
type: "caption",
label: i18n.str`Form definition`,
help: i18n.str`The user will need to complete this form.`,
- })
+ });
formDesign.push({
id: "fields" as UIHandlerId,
type: "array",
required: true,
- label: i18n.str`Fields`,
- fields: [{
- id: "name" as UIHandlerId,
- type: "text",
- required: true,
- label: i18n.str`Name`,
- help:i18n.str`Name of the field in the form`,
- },{
- id: "type" as UIHandlerId,
- type: "choiceStacked",
- required: true,
- label: i18n.str`Type`,
- help:i18n.str`Type of information being asked`,
- choices: [{
- value: "number",
- label: i18n.str`Number`,
- description:i18n.str`Numeric information`,
- },{
- value: "text",
- label: i18n.str`Text`,
- description:i18n.str`Free form text input`,
- }]
- }],
+ label: i18n.str`Fields`,
+ fields: [
+ {
+ id: "name" as UIHandlerId,
+ type: "text",
+ required: true,
+ label: i18n.str`Name`,
+ help: i18n.str`Name of the field in the form`,
+ },
+ {
+ id: "type" as UIHandlerId,
+ type: "choiceStacked",
+ required: true,
+ label: i18n.str`Type`,
+ help: i18n.str`Type of information being asked`,
+ choices: [
+ {
+ value: "number",
+ label: i18n.str`Number`,
+ description: i18n.str`Numeric information`,
+ },
+ {
+ value: "text",
+ label: i18n.str`Text`,
+ description: i18n.str`Free form text input`,
+ },
+ ],
+ },
+ ],
labelFieldId: "name" as UIHandlerId,
- })
+ });
}
const officer = useOfficer();
const session = officer.state === "ready" ? officer.account : undefined;
- const decisionForm = useFormState<{ justification: string, fields: object }>(
+ const decisionForm = useFormState<{ justification: string; fields: object }>(
getShapeFromFields(formDesign),
{ justification: "" },
(d) => {
@@ -512,8 +546,8 @@ function SubmitNewDecision({ decision, onComplete }: { onComplete: () => void; d
return {
status: "ok",
errors: undefined,
- result: d as any
- }
+ result: d as any,
+ };
},
);
@@ -521,100 +555,98 @@ function SubmitNewDecision({ decision, onComplete }: { onComplete: () => void; d
decisionForm === undefined || !session
? undefined
: withErrorHandler(
- () => {
- const request: Omit<AmlDecisionRequest, "officer_sig"> = {
- ...decision.request,
- properties: {
- ...decision.request.properties,
- fields: decisionForm.status.result.fields,
- },
- justification: decisionForm.status.result.justification ?? "empty",
- }
- return lib.exchange.makeAmlDesicion(session, request);
- },
- onComplete,
- (fail) => {
- switch (fail.case) {
- case HttpStatusCode.Forbidden:
- if (session) {
- return i18n.str`Wrong credentials for "${session}"`;
- } else {
- return i18n.str`Wrong credentials.`;
- }
- 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 <div>
- <LocalNotificationBanner notification={notification} />
- <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
- <i18n.Translate>Submit decision</i18n.Translate>
- </h1>
- <form
- class="space-y-6"
- noValidate
- onSubmit={(e) => {
- e.preventDefault();
- }}
- autoCapitalize="none"
- autoCorrect="off"
- >
- <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
- <RenderAllFieldsByUiConfig
- fields={convertUiField(
- i18n,
- formDesign,
- decisionForm.handler,
- getConverterById,
- )}
- />
- </div>
-
- <div class="mt-6 flex items-center justify-end gap-x-6">
- <button
- onClick={onComplete}
- class="text-sm font-semibold leading-6 text-gray-900"
- >
- <i18n.Translate>Cancel</i18n.Translate>
- </button>
-
- <Button
- type="submit"
- handler={submitHandler}
- disabled={!submitHandler}
- class="disabled:opacity-50 disabled:cursor-default 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>Confirm</i18n.Translate>
- </Button>
- </div>
+ () => {
+ const request: Omit<AmlDecisionRequest, "officer_sig"> = {
+ ...decision.request,
+ properties: {
+ ...decision.request.properties,
+ fields: decisionForm.status.result.fields,
+ },
+ justification:
+ decisionForm.status.result.justification ?? "empty",
+ };
+ return lib.exchange.makeAmlDesicion(session, request);
+ },
+ onComplete,
+ (fail) => {
+ switch (fail.case) {
+ case HttpStatusCode.Forbidden:
+ if (session) {
+ return i18n.str`Wrong credentials for "${session}"`;
+ } else {
+ return i18n.str`Wrong credentials.`;
+ }
+ 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);
+ }
+ },
+ );
- </form>
+ return (
+ <div>
+ <LocalNotificationBanner notification={notification} />
+ <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
+ <i18n.Translate>Submit decision</i18n.Translate>
+ </h1>
+ <form
+ class="space-y-6"
+ noValidate
+ onSubmit={(e) => {
+ e.preventDefault();
+ }}
+ autoCapitalize="none"
+ autoCorrect="off"
+ >
+ <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
+ <RenderAllFieldsByUiConfig
+ fields={convertUiField(
+ i18n,
+ formDesign,
+ decisionForm.handler,
+ getConverterById,
+ )}
+ />
+ </div>
- <h1 class="my-2 text-xl font-bold tracking-tight text-gray-900 ">
- <i18n.Translate>New rules to submit</i18n.Translate>
- </h1>
+ <div class="mt-6 flex items-center justify-end gap-x-6">
+ <button
+ onClick={onComplete}
+ class="text-sm font-semibold leading-6 text-gray-900"
+ >
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
- <ShowDecisionLimitInfo
- since={AbsoluteTime.fromProtocolTimestamp(
- decision.request.decision_time,
- )}
- until={AbsoluteTime.fromProtocolTimestamp(
- decision.request.new_rules.expiration_time,
- )}
- ruleSet={decision.request.new_rules}
- startOpen
- />
+ <Button
+ type="submit"
+ handler={submitHandler}
+ disabled={!submitHandler}
+ class="disabled:opacity-50 disabled:cursor-default 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>Confirm</i18n.Translate>
+ </Button>
+ </div>
+ </form>
- </div>
+ <h1 class="my-2 text-xl font-bold tracking-tight text-gray-900 ">
+ <i18n.Translate>New rules to submit</i18n.Translate>
+ </h1>
+ <ShowDecisionLimitInfo
+ since={AbsoluteTime.fromProtocolTimestamp(
+ decision.request.decision_time,
+ )}
+ until={AbsoluteTime.fromProtocolTimestamp(
+ decision.request.new_rules.expiration_time,
+ )}
+ ruleSet={decision.request.new_rules}
+ startOpen
+ />
+ </div>
+ );
}
function ShowDecisionLimitInfo({
@@ -622,9 +654,8 @@ function ShowDecisionLimitInfo({
since,
until,
startOpen,
- justification
+ justification,
}: {
-
since: AbsoluteTime;
until: AbsoluteTime;
justification?: string;
@@ -635,52 +666,43 @@ function ShowDecisionLimitInfo({
const { config } = useExchangeApiContext();
const [opened, setOpened] = useState(startOpen ?? false);
-
function Header() {
return (
- <ul
- role="list"
- class="divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl"
+ <div
+ class="p-4 relative bg-gray-50 flex justify-between cursor-pointer"
+ onClick={() => setOpened((o) => !o)}
>
- <li class="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6">
- <div class="flex min-w-0 gap-x-4">
- <div class="flex mt-2 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 bg-gray-200 inset-y-0 flex items-center px-3">
- <i18n.Translate>Since</i18n.Translate>
- </div>
- <div class="p-2 disabled:bg-gray-200 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">
- <Time
- format="dd/MM/yyyy HH:mm:ss"
- timestamp={since}
- />
- </div>
+ <div class="flex min-w-0 gap-x-4">
+ <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 bg-gray-200 inset-y-0 flex items-center px-3">
+ <i18n.Translate>Since</i18n.Translate>
+ </div>
+ <div class="p-2 disabled:bg-gray-200 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">
+ <Time format="dd/MM/yyyy HH:mm:ss" timestamp={since} />
</div>
</div>
- <div class="flex shrink-0 items-center gap-x-4">
- <div class="flex mt-2 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 bg-gray-200 inset-y-0 flex items-center px-3">
- {AbsoluteTime.isExpired(until) ? (
- <i18n.Translate>Expired</i18n.Translate>
- ) : (
- <i18n.Translate>Expires</i18n.Translate>
- )}
- </div>
- <div class="p-2 disabled:bg-gray-200 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">
- <Time
- format="dd/MM/yyyy HH:mm:ss"
- timestamp={until}
- />
- </div>
+ </div>
+ <div class="flex shrink-0 items-center gap-x-4">
+ <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 bg-gray-200 inset-y-0 flex items-center px-3">
+ {AbsoluteTime.isExpired(until) ? (
+ <i18n.Translate>Expired</i18n.Translate>
+ ) : (
+ <i18n.Translate>Expires</i18n.Translate>
+ )}
+ </div>
+ <div class="p-2 disabled:bg-gray-200 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">
+ <Time format="dd/MM/yyyy HH:mm:ss" timestamp={until} />
</div>
</div>
- </li>
- </ul>
+ </div>
+ </div>
);
}
if (!opened) {
return (
- <div class="mt-4 cursor-pointer" onClick={() => setOpened(true)}>
+ <div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl">
<Header />
</div>
);
@@ -690,101 +712,114 @@ function ShowDecisionLimitInfo({
);
return (
- <div>
- <div class="mt-4 cursor-pointer" onClick={() => setOpened(false)}>
+ <div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl">
<Header />
- </div>
+ <div class="p-4 grid gap-y-4">
+ {!justification ? undefined : (
+ <div class="">
+ <label
+ for="comment"
+ class="block text-sm font-medium leading-6 text-gray-900"
+ >
+ <i18n.Translate>AML officer justification</i18n.Translate>
+ </label>
+ <div class="mt-2">
+ <textarea
+ rows={2}
+ readOnly
+ name="comment"
+ id="comment"
+ 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"
+ >
+ {justification}
+ </textarea>
+ </div>
+ </div>
+ )}
- {!justification ? undefined : (
- <div class="mt-4">
- <label
- for="comment"
- class="block text-sm font-medium leading-6 text-gray-900"
- >
- <i18n.Translate>AML officer justification</i18n.Translate>
- </label>
- <div class="mt-2">
- <textarea
- rows={2}
- readOnly
- name="comment"
- id="comment"
- 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"
- >
- {justification}
- </textarea>
+ <div class="">
+ <div class="flex mt-2 rounded-md w-fit shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
+ <div class="whitespace-nowrap pointer-events-none bg-gray-200 inset-y-0 items-center px-3 flex">
+ <i18n.Translate>Max balance</i18n.Translate>
+ </div>
+ <div class="p-2 disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6">
+ {!balanceLimit ? (
+ <i18n.Translate>Unlimited</i18n.Translate>
+ ) : (
+ <RenderAmount
+ value={Amounts.parseOrThrow(balanceLimit.threshold)}
+ spec={config.currency_specification}
+ />
+ )}
+ </div>
+ </div>
</div>
- </div>
- )}
- {!balanceLimit ? undefined : (
- <div class="px-4">
- <div class="flex mt-2 rounded-md w-fit shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
- <div class="whitespace-nowrap pointer-events-none bg-gray-200 inset-y-0 items-center px-3 flex">
- <i18n.Translate>Max balance</i18n.Translate>
+ {!ruleSet.rules.length ? (
+ <Attention
+ title={i18n.str`There are no rules for operations`}
+ type="warning"
+ />
+ ) : (
+ <div class="">
+ <table class="min-w-full divide-y divide-gray-300">
+ <thead class="bg-gray-50">
+ <tr>
+ <th
+ scope="col"
+ class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6"
+ >
+ <i18n.Translate>Operation</i18n.Translate>
+ </th>
+ <th
+ scope="col"
+ class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
+ >
+ <i18n.Translate>Timeframe</i18n.Translate>
+ </th>
+ <th
+ scope="col"
+ class="relative py-3.5 pl-3 pr-4 sm:pr-6 text-right"
+ >
+ <i18n.Translate>Amount</i18n.Translate>
+ </th>
+ </tr>
+ </thead>
+ <tbody class="divide-y divide-gray-200">
+ {ruleSet.rules.map((r) => {
+ if (r.operation_type === "BALANCE") return;
+ return (
+ <tr>
+ <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 text-left">
+ {r.operation_type}
+ </td>
+ <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
+ {r.timeframe.d_us === "forever" ? (
+ <i18n.Translate>Forever</i18n.Translate>
+ ) : (
+ formatDuration(
+ intervalToDuration({
+ start: 0,
+ end: r.timeframe.d_us / 1000,
+ }),
+ )
+ )}
+ </td>
+ <td class=" relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 text-right">
+ <RenderAmount
+ value={Amounts.parseOrThrow(r.threshold)}
+ spec={config.currency_specification}
+ />
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
</div>
- <div class="p-2 disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6">
- <RenderAmount
- value={Amounts.parseOrThrow(balanceLimit.threshold)}
- spec={config.currency_specification}
- />
- </div>
- </div>
+ )}
</div>
- )}
-
- <div class="w-full px-4 pt-4">
- <table class="min-w-full divide-y divide-gray-300">
- <thead class="bg-gray-50">
- <tr>
- <th
- scope="col"
- class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6"
- >
- <i18n.Translate>Operation</i18n.Translate>
- </th>
- <th
- scope="col"
- class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
- >
- <i18n.Translate>Timeframe</i18n.Translate>
- </th>
- <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
- <i18n.Translate>Amount</i18n.Translate>
- </th>
- </tr>
- </thead>
- <tbody class="divide-y divide-gray-200">
- {ruleSet.rules.map((r) => {
- if (r.operation_type === "BALANCE") return;
- return (
- <tr>
- <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 text-left">
- {r.operation_type}
- </td>
- <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
- {r.timeframe.d_us === "forever"
- ? ""
- : formatDuration(
- intervalToDuration({
- start: 0,
- end: r.timeframe.d_us / 1000,
- }),
- )}
- </td>
- <td class=" relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 text-right">
- <RenderAmount
- value={Amounts.parseOrThrow(r.threshold)}
- spec={config.currency_specification}
- />
- </td>
- </tr>
- );
- })}
- </tbody>
- </table>
</div>
- </div>
);
}
@@ -1005,13 +1040,13 @@ function InputAmount(
if (
sep_pos !== -1 &&
l - sep_pos - 1 >
- config.currency_specification.num_fractional_input_digits
+ config.currency_specification.num_fractional_input_digits
) {
e.currentTarget.value = e.currentTarget.value.substring(
0,
sep_pos +
- config.currency_specification.num_fractional_input_digits +
- 1,
+ config.currency_specification.num_fractional_input_digits +
+ 1,
);
}
onChange(e.currentTarget.value);
@@ -1049,9 +1084,9 @@ function parseJustification(
listOfAllKnownForms: FormMetadata[],
):
| OperationOk<{
- justification: Justification;
- metadata: FormMetadata;
- }>
+ justification: Justification;
+ metadata: FormMetadata;
+ }>
| OperationFail<ParseJustificationFail> {
try {
const justification = JSON.parse(s);
@@ -1099,212 +1134,212 @@ function parseJustification(
const THRESHOLD_2000_WEEK: (currency: string) => TalerExchangeApi.KycRule[] = (
currency,
) => [
- {
- operation_type: "WITHDRAW",
- threshold: `${currency}:2000`,
- timeframe: {
- d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ {
+ operation_type: "WITHDRAW",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "DEPOSIT",
- threshold: `${currency}:2000`,
- timeframe: {
- d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "DEPOSIT",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "AGGREGATE",
- threshold: `${currency}:2000`,
- timeframe: {
- d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "AGGREGATE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "MERGE",
- threshold: `${currency}:2000`,
- timeframe: {
- d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "MERGE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "BALANCE",
- threshold: `${currency}:2000`,
- timeframe: {
- d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "BALANCE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "CLOSE",
- threshold: `${currency}:2000`,
- timeframe: {
- d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "CLOSE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
},
- ];
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+];
const THRESHOLD_100_HOUR: (currency: string) => TalerExchangeApi.KycRule[] = (
currency,
) => [
- {
- operation_type: "WITHDRAW",
- threshold: `${currency}:100`,
- timeframe: {
- d_us: 1 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ {
+ operation_type: "WITHDRAW",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "DEPOSIT",
- threshold: `${currency}:100`,
- timeframe: {
- d_us: 1 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "DEPOSIT",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "AGGREGATE",
- threshold: `${currency}:100`,
- timeframe: {
- d_us: 1 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "AGGREGATE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "MERGE",
- threshold: `${currency}:100`,
- timeframe: {
- d_us: 1 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "MERGE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "BALANCE",
- threshold: `${currency}:100`,
- timeframe: {
- d_us: 1 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "BALANCE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
},
- {
- operation_type: "CLOSE",
- threshold: `${currency}:100`,
- timeframe: {
- d_us: 1 * 60 * 60 * 1000 * 1000,
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "CLOSE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
},
- ];
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+];
const FREEZE_RULES: (currency: string) => TalerExchangeApi.KycRule[] = (
currency,
) => [
- {
- operation_type: "WITHDRAW",
- threshold: `${currency}:0`,
- timeframe: {
- d_us: "forever",
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ {
+ operation_type: "WITHDRAW",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
},
- {
- operation_type: "DEPOSIT",
- threshold: `${currency}:0`,
- timeframe: {
- d_us: "forever",
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "DEPOSIT",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
},
- {
- operation_type: "AGGREGATE",
- threshold: `${currency}:0`,
- timeframe: {
- d_us: "forever",
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "AGGREGATE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
},
- {
- operation_type: "MERGE",
- threshold: `${currency}:0`,
- timeframe: {
- d_us: "forever",
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "MERGE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
},
- {
- operation_type: "BALANCE",
- threshold: `${currency}:0`,
- timeframe: {
- d_us: "forever",
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "BALANCE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
},
- {
- operation_type: "CLOSE",
- threshold: `${currency}:0`,
- timeframe: {
- d_us: "forever",
- },
- measures: ["verboten"],
- display_priority: 1,
- exposed: true,
- is_and_combinator: true,
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+ {
+ operation_type: "CLOSE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
},
- ];
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
+ },
+];