From 11ac762e842c18fc7cbaed0c90d3a4ecf599b210 Mon Sep 17 00:00:00 2001 From: Alex Graham Date: Mon, 12 Jul 2021 13:06:26 -0400 Subject: [PATCH] switch account selection to be inline on review page (#403) --- ...electionModal.tsx => AccountSelection.tsx} | 160 ++++------- .../pages/QuickSetup/RequestServicesPage.tsx | 267 +++++++++--------- 2 files changed, 192 insertions(+), 235 deletions(-) rename ui/src/pages/QuickSetup/{AccountSelectionModal.tsx => AccountSelection.tsx} (65%) diff --git a/ui/src/pages/QuickSetup/AccountSelectionModal.tsx b/ui/src/pages/QuickSetup/AccountSelection.tsx similarity index 65% rename from ui/src/pages/QuickSetup/AccountSelectionModal.tsx rename to ui/src/pages/QuickSetup/AccountSelection.tsx index eb47e468..606c1bb2 100644 --- a/ui/src/pages/QuickSetup/AccountSelectionModal.tsx +++ b/ui/src/pages/QuickSetup/AccountSelection.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { v4 as uuidv4 } from 'uuid'; import { damlSetValues, makeDamlSet, createDropdownProp } from '../common'; import { useStreamQueries } from '../../Main'; @@ -7,7 +7,7 @@ import { RequestOpenAccount, RequestOpenAllocationAccount, } from '@daml.js/da-marketplace/lib/Marketplace/Custody/Service'; -import { DropdownItemProps, Form, Modal, Button, DropdownProps } from 'semantic-ui-react'; +import { DropdownItemProps, Form, DropdownProps } from 'semantic-ui-react'; import { useLedger } from '@daml/react'; import { Service as CustodyService } from '@daml.js/da-marketplace/lib/Marketplace/Custody/Service/'; import { usePartyName } from '../../config'; @@ -15,7 +15,7 @@ import { OpenAccountRequest, OpenAllocationAccountRequest, } from '@daml.js/da-marketplace/lib/Marketplace/Custody/Model'; -import { IPartyAccounts } from './RequestServicesPage'; +import { IPartyAccounts, AccountsForServices } from './RequestServicesPage'; import { Party } from '@daml/types'; import _ from 'lodash'; import { Account } from '@daml.js/da-marketplace/lib/DA/Finance/Types'; @@ -31,33 +31,24 @@ type AccountInfo = { accountLabel: string; }; export type AccountInfos = { [k: string]: AccountInfo }; -export type SetFunction = (accts: { [k: string]: Account | undefined }) => void; - -type NameMap = { [k: string]: string | undefined }; type Props = { party: Party; serviceProvider?: Party; - open: boolean; - setOpen: (bool: boolean) => void; accountsForParty?: IPartyAccounts; accountInfos: AccountInfos; - onCancel: () => void; - onFinish: SetFunction; + accountsForServices: AccountsForServices; + onChangeAccount: (k: string, acct: Account | undefined) => void; }; -const AccountSelectionModal: React.FC = ({ +const AccountSelection: React.FC = ({ party, serviceProvider, - open, - setOpen, + accountsForServices, accountsForParty, accountInfos, - onFinish, - onCancel, + onChangeAccount, }) => { - const { getName } = usePartyName(party); - const hasRegularAccount = _.values(accountInfos).reduce( (acc, info) => acc || info.accountType === AccountType.REGULAR, false @@ -67,21 +58,6 @@ const AccountSelectionModal: React.FC = ({ false ); - const emptyNamesState = _.mapValues(accountInfos, () => { - return undefined; - }); - const [accountNamesState, setAccountNamesState] = useState(emptyNamesState); - - useEffect(() => { - setAccountNamesState(prev => - _.mapValues(accountInfos, (_, key) => { - return prev[key] || undefined; - }) - ); - }, [accountInfos]); - - const disabled = _.values(accountNamesState).reduce((acc, name) => acc || !name, false); - const allocationAccountRules = accountsForParty?.allocAccounts || []; const allocationAccounts = allocationAccountRules .filter(c => c.payload.nominee === serviceProvider) @@ -111,64 +87,31 @@ const AccountSelectionModal: React.FC = ({ const accountNeeded = hasRegularAccount && !accountNames.length && !openAccountRequests.length; return ( - { - const accts = _.mapValues(accountInfos, (accountInfo, k) => { - const account = - accountInfo.accountType === AccountType.REGULAR - ? accounts.find(a => a.id.label === accountNamesState[k]) - : allocationAccounts.find(a => a.id.label === accountNamesState[k]); - return account; - }); - onFinish(accts); - setAccountNamesState(emptyNamesState); - setOpen(false); - }} - > - {`Select Accounts for ${getName(party)} requesting from ${getName( - serviceProvider || '' - )}`} - - {!custodyServices.length && (allocationAccountNeeded || accountNeeded) ? ( - <>This party must have at least one Custody service - ) : ( - !!serviceProvider && ( -
- {_.toPairs(accountInfos).map(([k, accountInfo]) => ( - - ))} -
- ) - )} -
- - - -
+ <> + {!custodyServices.length && (allocationAccountNeeded || accountNeeded) ? ( + <>This party must have at least one Custody service + ) : ( + !!serviceProvider && ( +
+ {_.toPairs(accountInfos).map(([k, accountInfo]) => ( + + ))} +
+ ) + )} + ); }; @@ -180,10 +123,10 @@ const ProviderOption = (props: { openAccountRequests: CreateEvent[]; openAllocationAccountRequests: CreateEvent[]; custodyServices: CreateEvent[]; - accountNamesState: NameMap; - setAccountNamesState: (setter: (prevState: NameMap) => NameMap) => void; + accountsForServices: { [k: string]: Account | undefined }; party: string; serviceProvider: string; + onChangeAccount: (k: string, acct: Account | undefined) => void; }) => { const { accountInfo, @@ -194,24 +137,24 @@ const ProviderOption = (props: { openAccountRequests, openAllocationAccountRequests, custodyServices, - accountNamesState, - setAccountNamesState, + accountsForServices, + onChangeAccount, accountKey, } = props; const ledger = useLedger(); const { getName } = usePartyName(party); + const makeAccountName = (accountInfo: AccountInfo) => + `${getName(party)}-${getName(serviceProvider)}-${accountInfo.accountLabel.replace(/\s+/g, '')}`; + const accountNames: DropdownItemProps[] = accounts.map(a => createDropdownProp(a.id.label)); - const allocationAccountNames: DropdownItemProps[] = allocationAccounts.map(a => - createDropdownProp(a.id.label) - ); + const allocationAccountNames: DropdownItemProps[] = allocationAccounts + .filter(acc => acc.id.label === makeAccountName(accountInfo)) + .map(a => createDropdownProp(a.id.label)); const accountOptions = accountInfo.accountType === AccountType.REGULAR ? accountNames : allocationAccountNames; - const makeAccountName = (accountInfo: AccountInfo) => - `${getName(party)}-${getName(serviceProvider)}-${accountInfo.accountLabel.replace(/\s+/g, '')}`; - const requestAccount = async (provider: string, accountInfo: AccountInfo) => { if (!serviceProvider) return; const service = custodyServices.find(s => s.payload.provider === provider); @@ -276,21 +219,22 @@ const ProviderOption = (props: { if (prepend === accountRequestPrepend) { requestAccount(rest[0], accountInfo); } else { - setAccountNamesState(prev => { - let copy = { ...prev }; - copy[accountKey] = change.value as string; - return copy; - }); + const account = + accountInfo.accountType === AccountType.REGULAR + ? accounts.find(a => a.id.label === (change.value as string)) + : allocationAccounts.find(a => a.id.label === (change.value as string)); + onChangeAccount(accountKey, account); } }; return ( <> {accountInfo.accountLabel}

} placeholder="Select..." required options={[...accountOptions, ...providerOptions]} - value={accountNamesState[accountKey]} + value={accountsForServices[accountKey]?.id.label} onChange={(_, change) => selectOrCreate(change)} /> {accountRequestExists(accountInfo) &&

Account request pending...

} @@ -298,4 +242,4 @@ const ProviderOption = (props: { ); }; -export default AccountSelectionModal; +export default AccountSelection; diff --git a/ui/src/pages/QuickSetup/RequestServicesPage.tsx b/ui/src/pages/QuickSetup/RequestServicesPage.tsx index 812ef849..e169305d 100644 --- a/ui/src/pages/QuickSetup/RequestServicesPage.tsx +++ b/ui/src/pages/QuickSetup/RequestServicesPage.tsx @@ -38,15 +38,16 @@ import { Account } from '@daml.js/da-marketplace/lib/DA/Finance/Types'; import { CreateEvent } from '@daml/ledger'; import { AssetSettlementRule } from '@daml.js/da-marketplace/lib/DA/Finance/Asset/Settlement'; import { AllocationAccountRule } from '@daml.js/da-marketplace/lib/Marketplace/Rule/AllocationAccount'; -import AccountSelectionModal, { AccountType, AccountInfos } from './AccountSelectionModal'; +import AccountSelection, { AccountType, AccountInfos } from './AccountSelection'; -type AccountsForServices = { +export type AccountsForServices = { clearingAccount?: Account; marginAccount?: Account; tradingAccount?: Account; tradingAllocAccount?: Account; biddingAccount?: Account; biddingAllocAccount?: Account; + issuanceAccount?: Account; auctionAccount?: Account; auctionAllocAccount?: Account; receivableAccount?: Account; @@ -170,77 +171,82 @@ const RequestForm = (props: { const partyOptions = identities.map(p => { return { text: p.payload.legalName, value: p.payload.customer }; }); - const [showAccountModal, setShowAccountModal] = useState(false); - const [modalAccountInfos, setModalAccountInfos] = useState({}); - const [modalOnCancelFunction, setModalOnCancelFunction] = useState<() => void>(() => { - return; - }); - const selectAccounts = useCallback( - (serviceType: ServiceKind) => { - switch (serviceType) { - case ServiceKind.CLEARING: - setModalAccountInfos({ - clearingAccount: { - accountType: AccountType.REGULAR, - accountLabel: 'Clearing Account', - }, - marginAccount: { - accountType: AccountType.ALLOCATION, - accountLabel: 'Margin Account', - }, - }); - break; - case ServiceKind.TRADING: - setModalAccountInfos({ - tradingAccount: { - accountType: AccountType.REGULAR, - accountLabel: 'Exchange Trading Account', - }, - tradingAllocAccount: { - accountType: AccountType.ALLOCATION, - accountLabel: 'Locked Account', - }, - }); - break; - case ServiceKind.BIDDING: - setModalAccountInfos({ - biddingAccount: { - accountType: AccountType.REGULAR, - accountLabel: 'Bidding Account', - }, - biddingAllocAccount: { - accountType: AccountType.ALLOCATION, - accountLabel: 'Bidding Locked Account', - }, - }); - break; - case ServiceKind.AUCTION: - setModalAccountInfos({ - auctionAccount: { - accountType: AccountType.REGULAR, - accountLabel: 'Main Auction Account', - }, - auctionAllocAccount: { - accountType: AccountType.ALLOCATION, - accountLabel: 'Locked Auction Account', - }, - receivableAccount: { - accountType: AccountType.REGULAR, - accountLabel: 'Receivables Account', - }, - }); - break; - } - setModalOnCancelFunction( - () => () => - setRequestInfo({ - ...requestInfo, - services: requestInfo?.services?.filter(s => s !== serviceType), - }) - ); - setShowAccountModal(true); + const [accountSelectInfos, setAccountSelectInfos] = useState({}); + + const handleSetAccountInfos = useCallback( + (existing: ServiceKind[]) => { + const newAccountInfos = + requestInfo?.services?.reduce((acc, svc) => { + if (existing.includes(svc)) return acc; + + switch (svc) { + case ServiceKind.CLEARING: + return { + ...acc, + clearingAccount: { + accountType: AccountType.REGULAR, + accountLabel: 'Clearing Account', + }, + marginAccount: { + accountType: AccountType.ALLOCATION, + accountLabel: 'Margin Account', + }, + }; + case ServiceKind.ISSUANCE: + return { + ...acc, + issuanceAccount: { + accountType: AccountType.REGULAR, + accountLabel: 'Issuance Account', + }, + }; + case ServiceKind.TRADING: + return { + ...acc, + tradingAccount: { + accountType: AccountType.REGULAR, + accountLabel: 'Exchange Trading Account', + }, + tradingAllocAccount: { + accountType: AccountType.ALLOCATION, + accountLabel: 'Exchange Locked Account', + }, + }; + case ServiceKind.BIDDING: + return { + ...acc, + biddingAccount: { + accountType: AccountType.REGULAR, + accountLabel: 'Bidding Account', + }, + biddingAllocAccount: { + accountType: AccountType.ALLOCATION, + accountLabel: 'Bidding Locked Account', + }, + }; + case ServiceKind.AUCTION: + return { + ...acc, + auctionAccount: { + accountType: AccountType.REGULAR, + accountLabel: 'Main Auction Account', + }, + auctionAllocAccount: { + accountType: AccountType.ALLOCATION, + accountLabel: 'Locked Auction Account', + }, + receivableAccount: { + accountType: AccountType.REGULAR, + accountLabel: 'Receivables Account', + }, + }; + default: + return acc; + } + }, {}) || {}; + setAccountSelectInfos(newAccountInfos); }, - [requestInfo, setRequestInfo] + [requestInfo] ); useEffect(() => { @@ -254,31 +260,6 @@ const RequestForm = (props: { return; } - const accounts = requestInfo?.accounts; - if (requestServices.includes(ServiceKind.CLEARING)) { - if (!accounts?.clearingAccount || !accounts?.marginAccount) - selectAccounts(ServiceKind.CLEARING); - } - - if (requestServices.includes(ServiceKind.TRADING)) { - if (!accounts?.tradingAccount || !accounts?.tradingAllocAccount) - selectAccounts(ServiceKind.TRADING); - } - - if (requestServices.includes(ServiceKind.BIDDING)) { - if (!accounts?.biddingAccount || !accounts?.biddingAllocAccount) - selectAccounts(ServiceKind.BIDDING); - } - - if (requestServices.includes(ServiceKind.AUCTION)) { - if ( - !accounts?.auctionAccount || - !accounts?.auctionAllocAccount || - !accounts?.receivableAccount - ) - selectAccounts(ServiceKind.AUCTION); - } - const matchingContracts = services.filter( s => s.contract.payload.provider === provider && s.contract.payload.customer === customer ); @@ -292,12 +273,38 @@ const RequestForm = (props: { } else { setExistingServices([]); } - }, [requestInfo, services, setModalAccountInfos, setShowAccountModal, selectAccounts]); + + handleSetAccountInfos(existingServices); + }, [requestInfo, services, setAccountSelectInfos, handleSetAccountInfos]); if (identitiesLoading) { return null; } + const hasAccountsForServices = requestInfo?.services?.reduce((acc, svc) => { + if (existingServices.includes(svc)) return acc; + const accounts = requestInfo?.accounts; + switch (svc) { + case ServiceKind.CLEARING: + return acc && !!accounts?.clearingAccount && !!accounts?.marginAccount; + case ServiceKind.ISSUANCE: + return acc && !!accounts?.issuanceAccount; + case ServiceKind.TRADING: + return acc && !!accounts?.tradingAccount && !!accounts?.tradingAllocAccount; + case ServiceKind.BIDDING: + return acc && !!accounts?.biddingAccount && !!accounts?.biddingAllocAccount; + case ServiceKind.AUCTION: + return ( + acc && + !!accounts?.auctionAccount && + !!accounts?.auctionAllocAccount && + !!accounts?.receivableAccount + ); + default: + return acc; + } + }, true); + return ( <>
@@ -311,6 +318,7 @@ const RequestForm = (props: { setRequestInfo({ ...requestInfo, customer: identities.find(p => p.payload.customer === data.value)?.payload.customer, + accounts: {}, }) } options={partyOptions} @@ -337,33 +345,19 @@ const RequestForm = (props: { setRequestInfo({ ...requestInfo, provider: identities.find(p => p.payload.customer === data.value)?.payload.customer, + accounts: {}, }) } options={partyOptions} /> -
- {existingServices.length > 0 && requestInfo?.provider && requestInfo?.customer && ( -

- {getName(requestInfo?.provider)} already provides{' '} - {itemListAsText(existingServices || [])} services to {getName(requestInfo?.customer)} -

- )} -
- + {existingServices.length > 0 && requestInfo?.provider && requestInfo?.customer && ( +

+ {getName(requestInfo?.provider)} already provides{' '} + {itemListAsText(existingServices || [])} services to {getName(requestInfo?.customer)} +

+ )} + {requestInfo && requestInfo.customer && token && ( - Accounts:} + { + onChangeAccount={(k: string, acct: Account | undefined) => { + let acctsCopy: { [k: string]: Account | undefined } = + { ...requestInfo?.accounts } || {}; + acctsCopy[k] = acct; setRequestInfo({ ...requestInfo, - accounts: { ...requestInfo?.accounts, ...accts }, + accounts: acctsCopy, }); }} /> )} + +
+
);