diff options
author | Sebastian <sebasjm@gmail.com> | 2024-09-11 12:18:02 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-09-11 12:18:16 -0300 |
commit | e2ede9ff4f5df733fc89e10d5b61f3867b0479ae (patch) | |
tree | b34c906799bab26b3a56d414ef096953b3e50c34 | |
parent | f401ab076bb037e46c4b12e371dfe13a777bf582 (diff) |
fix #9156
-rw-r--r-- | packages/aml-backoffice-ui/src/Routing.tsx | 17 | ||||
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/CaseDetails.tsx | 438 | ||||
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/Cases.tsx | 2 | ||||
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/Search.tsx | 147 | ||||
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx | 2 | ||||
-rw-r--r-- | packages/web-util/src/utils/route.ts | 8 |
6 files changed, 382 insertions, 232 deletions
diff --git a/packages/aml-backoffice-ui/src/Routing.tsx b/packages/aml-backoffice-ui/src/Routing.tsx index c7f9a40fe..d69b47184 100644 --- a/packages/aml-backoffice-ui/src/Routing.tsx +++ b/packages/aml-backoffice-ui/src/Routing.tsx @@ -15,6 +15,7 @@ */ import { + decodeCrockFromURI, urlPattern, useCurrentLocation, useNavigationContext, @@ -22,7 +23,7 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { assertUnreachable } from "@gnu-taler/taler-util"; +import { assertUnreachable, parsePaytoUri, PaytoString } from "@gnu-taler/taler-util"; import { useEffect } from "preact/hooks"; import { ExchangeAmlFrame } from "./ExchangeAmlFrame.js"; import { useOfficer } from "./hooks/officer.js"; @@ -107,6 +108,10 @@ export const privatePages = { /\/case\/(?<cid>[a-zA-Z0-9]+)\/new/, ({ cid }) => `#/case/${cid}/new`, ), + caseDetailsNewAccount: urlPattern<{ cid: string, payto: string }>( + /\/case\/(?<cid>[a-zA-Z0-9]+)\/(?<payto>[a-zA-Z0-9]+)/, + ({ cid, payto }) => `#/case/${cid}/${payto}`, + ), caseDetails: urlPattern<{ cid: string }>( /\/case\/(?<cid>[a-zA-Z0-9]+)/, ({ cid }) => `#/case/${cid}`, @@ -129,12 +134,18 @@ function PrivateRouting(): VNode { case "profile": { return <Officer />; } + case "caseUpdate": { + return ( + <CaseUpdate account={location.values.cid} type={location.values.type} /> + ); + } case "caseDetails": { return <CaseDetails account={location.values.cid} />; } - case "caseUpdate": { + case "caseDetailsNewAccount": { + console.log(location.values) return ( - <CaseUpdate account={location.values.cid} type={location.values.type} /> + <CaseDetails account={location.values.cid} paytoString={decodeCrockFromURI(location.values.payto)} /> ); } case "caseNew": { diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx index 54f979846..88366c1d0 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx @@ -22,6 +22,7 @@ import { HttpStatusCode, OperationFail, OperationOk, + PaytoString, TalerError, TalerErrorDetail, TalerExchangeApi, @@ -171,7 +172,7 @@ export function getEventsFromAmlHistory( return ke.sort(selectSooner); } -export function CaseDetails({ account }: { account: string }) { +export function CaseDetails({ account, paytoString }: { account: string, paytoString?: PaytoString }) { const [selected, setSelected] = useState<AbsoluteTime>(AbsoluteTime.now()); const [showForm, setShowForm] = useState<{ justification: Justification; @@ -273,6 +274,7 @@ export function CaseDetails({ account }: { account: string }) { onClick={async () => { if (!session) return; lib.exchange.makeAmlDesicion(session, { + payto_uri: paytoString, decision_time: AbsoluteTime.toProtocolTimestamp( AbsoluteTime.now(), ), @@ -298,6 +300,7 @@ export function CaseDetails({ account }: { account: string }) { onClick={async () => { if (!session) return; lib.exchange.makeAmlDesicion(session, { + payto_uri: paytoString, decision_time: AbsoluteTime.toProtocolTimestamp( AbsoluteTime.now(), ), @@ -323,6 +326,7 @@ export function CaseDetails({ account }: { account: string }) { onClick={async () => { if (!session) return; lib.exchange.makeAmlDesicion(session, { + payto_uri: paytoString, decision_time: AbsoluteTime.toProtocolTimestamp( AbsoluteTime.now(), ), @@ -348,6 +352,7 @@ export function CaseDetails({ account }: { account: string }) { onClick={async () => { if (!session) return; lib.exchange.makeAmlDesicion(session, { + payto_uri: paytoString, decision_time: AbsoluteTime.toProtocolTimestamp( AbsoluteTime.now(), ), @@ -385,24 +390,28 @@ export function CaseDetails({ account }: { account: string }) { <h1 class="my-4 text-base font-semibold leading-6 text-black"> <i18n.Translate>KYC collection events</i18n.Translate> </h1> - <ShowTimeline - history={events} - onSelect={(e) => { - switch (e.type) { - case "aml-form": { - // const { justification, metadata } = e; - // setShowForm({ justification, metadata }); - break; + {events.length === 0 ? + <Attention title={i18n.str`No events found`} type="warning" /> + : + <ShowTimeline + history={events} + onSelect={(e) => { + switch (e.type) { + case "aml-form": { + // const { justification, metadata } = e; + // setShowForm({ justification, metadata }); + break; + } + case "kyc-collection": + case "kyc-expiration": { + setSelected(e.when); + break; + } + case "aml-form-error": } - case "kyc-collection": - case "kyc-expiration": { - setSelected(e.when); - break; - } - case "aml-form-error": - } - }} - /> + }} + /> + } {/* {selected && <ShowEventDetails event={selected} />} */} {selected && <ShowConsolidated history={events} until={selected} />} {restDecisions.length > 0 ? ( @@ -415,7 +424,10 @@ export function CaseDetails({ account }: { account: string }) { })} </Fragment> ) : ( - <div /> + !activeDecision ? + <div class="ty-4"> + <Attention title={i18n.str`No aml history found`} type="warning" /> + </div> : undefined )} </div> ); @@ -569,11 +581,11 @@ function ShowDecisionInfo({ {r.timeframe.d_us === "forever" ? "" : formatDuration( - intervalToDuration({ - start: 0, - end: r.timeframe.d_us / 1000, - }), - )} + 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 @@ -808,13 +820,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); @@ -852,9 +864,9 @@ function parseJustification( listOfAllKnownForms: FormMetadata[], ): | OperationOk<{ - justification: Justification; - metadata: FormMetadata; - }> + justification: Justification; + metadata: FormMetadata; + }> | OperationFail<ParseJustificationFail> { try { const justification = JSON.parse(s); @@ -902,212 +914,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, + { + 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, }, - 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: "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: "AGGREGATE", - 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: "MERGE", - 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: "BALANCE", - 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: "CLOSE", - 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, - }, -]; + ]; const THRESHOLD_100_HOUR: (currency: string) => TalerExchangeApi.KycRule[] = ( currency, ) => [ - { - operation_type: "WITHDRAW", - threshold: `${currency}:100`, - timeframe: { - d_us: 1 * 60 * 60 * 1000 * 1000, + { + 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, }, - 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: "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: "AGGREGATE", - 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: "MERGE", - 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: "BALANCE", - 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: "CLOSE", - 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, - }, -]; + ]; const FREEZE_RULES: (currency: string) => TalerExchangeApi.KycRule[] = ( currency, ) => [ - { - operation_type: "WITHDRAW", - threshold: `${currency}:0`, - timeframe: { - d_us: "forever", + { + operation_type: "WITHDRAW", + 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: "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: "AGGREGATE", - 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: "MERGE", - 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: "BALANCE", - 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: "CLOSE", - 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, - }, -]; + ]; diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index c274cbcb0..278d4bac2 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -387,7 +387,7 @@ export const SearchIcon = () => ( </svg> ); -function Pagination({ +export function Pagination({ onFirstPage, onNext, }: { diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx b/packages/aml-backoffice-ui/src/pages/Search.tsx index f9612c025..2bca86310 100644 --- a/packages/aml-backoffice-ui/src/pages/Search.tsx +++ b/packages/aml-backoffice-ui/src/pages/Search.tsx @@ -14,6 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { + AbsoluteTime, assertUnreachable, buildPayto, encodeCrock, @@ -28,10 +29,12 @@ import { import { Attention, convertUiField, + encodeCrockForURI, getConverterById, InternationalizationAPI, Loading, RenderAllFieldsByUiConfig, + Time, UIFormElementConfig, UIHandlerId, useTranslationContext, @@ -51,6 +54,9 @@ import { undefinedIfEmpty } from "./CreateAccount.js"; import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; import { useAccountInformation } from "../hooks/account.js"; import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; +import { useAccountDecisions } from "../hooks/decisions.js"; +import { privatePages } from "../Routing.js"; +import { Pagination, ToInvestigateIcon } from "./Cases.js"; export function Search() { const officer = useOfficer(); @@ -108,18 +114,19 @@ export function Search() { } function ShowResult({ payto }: { payto: PaytoUri }): VNode { - const account = encodeCrock(hashPaytoUri(payto)); + const paytoStr = stringifyPaytoUri(payto) + const account = encodeCrock(hashPaytoUri(paytoStr)); const { i18n } = useTranslationContext(); - const details = useAccountInformation(account); - if (!details) { + const history = useAccountDecisions(account); + if (!history) { return <Loading /> } - if (details instanceof TalerError) { - return <ErrorLoadingWithDebug error={details} />; + if (history instanceof TalerError) { + return <ErrorLoadingWithDebug error={history} />; } - if (details.type === "fail") { - switch (details.case) { + if (history.type === "fail") { + switch (history.case) { case HttpStatusCode.Forbidden: { return ( <Fragment> @@ -155,13 +162,123 @@ function ShowResult({ payto }: { payto: PaytoUri }): VNode { ); } default: { - assertUnreachable(details) + assertUnreachable(history) } } } - - return <div > - found {JSON.stringify(details.body.details, undefined, 2)} + + if (history.body.length) { + return <div class="mt-8"> + <div class="mb-2"> + <a + href={privatePages.caseDetails.url({ + cid: account, + })} + class="text-indigo-600 hover:text-indigo-900" + > + <i18n.Translate> + Check account details + </i18n.Translate> + </a> + </div> + <div class="sm:flex sm:items-center"> + <div class="sm:flex-auto"> + <div> + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate>Account most recent decisions</i18n.Translate> + </h1> + </div> + </div> + </div> + + <div class="flow-root"> + <div class="overflow-x-auto"> + {!history.body.length ? ( + <div>empty result </div> + ) : ( + <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> + <table class="min-w-full divide-y divide-gray-300"> + <thead> + <tr> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-80" + > + <i18n.Translate>When</i18n.Translate> + </th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-80" + > + <i18n.Translate>Justification</i18n.Translate> + </th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-40" + > + <i18n.Translate>Status</i18n.Translate> + </th> + </tr> + </thead> + <tbody class="divide-y divide-gray-200 bg-white"> + {history.body.map((r, idx) => { + return ( + <tr key={r.h_payto} class="hover:bg-gray-100 "> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> + <Time format="dd/MM/yyyy HH:mm" timestamp={AbsoluteTime.fromProtocolTimestamp(r.decision_time)} /> + </td> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> + {r.justification} + </td> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900"> + + + {idx === 0 ? <span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"> + <i18n.Translate>LATEST</i18n.Translate> + </span> : undefined} + {r.is_active ? <span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"> + <i18n.Translate>ACTIVE</i18n.Translate> + </span> : undefined} + {r.decision_time ? ( + <span title="require investigation"> + <ToInvestigateIcon /> + </span> + ) : undefined} + </td> + </tr> + ); + })} + </tbody> + </table> + <Pagination + onFirstPage={history.isFirstPage ? undefined : history.loadFirst} + onNext={history.isLastPage ? undefined : history.loadNext} /> + </div> + )} + </div> + </div> + </div> + } + // const detailsUrl = new URL(, window.location.href) + // detailsUrl.searchParams.set("payto", encodeCrockForURI(paytoStr)) + return <div class="mt-4"> + <Attention title={i18n.str`Account not found`} type="warning"> + <i18n.Translate> + There is no history known for this account yet. + </i18n.Translate> + + <a + href={privatePages.caseDetailsNewAccount.url({ + cid: account, + payto: encodeCrockForURI(paytoStr), + })} + class="text-indigo-600 hover:text-indigo-900" + > + <i18n.Translate> + You can make a decision for this account anyway. + </i18n.Translate> + </a> + </Attention> </div> } @@ -210,7 +327,7 @@ function XTalerBankForm({ class="disabled:bg-gray-100 disabled:text-gray-500 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" onClick={() => onSearch(paytoUri)} > - Search + <i18n.Translate>Search</i18n.Translate> </button> </form> ); @@ -224,7 +341,11 @@ function IbanForm({ const fields = ibanFields(i18n); const form = useFormState( getShapeFromFields(fields), - {}, + { + account: "DE6985463381715", + name: "alice", + bic: "SANDBOX" + }, createIbanPaytoValidator(i18n), ); const paytoUri = diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx index 2866755c2..fcec8609a 100644 --- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx +++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx @@ -98,7 +98,7 @@ export function ShowConsolidated({ return ( <Fragment> - <div class="space-y-10 divide-y -mt-5 divide-gray-900/10"> + <div class="space-y-10 divide-y divide-gray-900/10"> {formConfig.design.map((section, i) => { if (!section) return <Fragment />; return ( diff --git a/packages/web-util/src/utils/route.ts b/packages/web-util/src/utils/route.ts index cbac8851a..fbbbfebd1 100644 --- a/packages/web-util/src/utils/route.ts +++ b/packages/web-util/src/utils/route.ts @@ -25,7 +25,13 @@ export type AppLocation = string & { }; export type EmptyObject = Record<string, never>; - +/** + * FIXME: receive parameters + * maybe return URL for reverse function instead of string + * @param pattern + * @param reverse + * @returns + */ export function urlPattern< T extends Record<string, string | undefined> = EmptyObject, >(pattern: RegExp, reverse: (p: T) => string): RouteDefinition<T> { |