Skip to content

Commit f16362d

Browse files
authored
Merge pull request #3512 from JoinColony/feat/16115956-staged-description
feat: add staged payment description
2 parents 33e26ed + bf0411d commit f16362d

File tree

16 files changed

+284
-115
lines changed

16 files changed

+284
-115
lines changed

src/components/common/ColonyActions/helpers/getActionTitleValues.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '~utils/colonyActions.ts';
1515

1616
import { generateMessageValues } from './getEventTitleValues.ts';
17-
import { mapColonyActionToExpectedFormat } from './mapItemToMessageFormat.tsx';
17+
import { useMapColonyActionToExpectedFormat } from './mapItemToMessageFormat.tsx';
1818

1919
import type React from 'react';
2020

@@ -38,13 +38,15 @@ export enum ActionTitleMessageKeys {
3838
RecipientsNumber = 'recipientsNumber',
3939
TokensNumber = 'tokensNumber',
4040
SplitAmount = 'splitAmount',
41+
MilestonesCount = 'milestonesCount',
42+
Milestones = 'milestones',
43+
StagedAmount = 'stagedAmount',
4144
}
4245

4346
/* Maps actionTypes to message values as found in en-actions.ts */
4447
const getMessageDescriptorKeys = (actionType: AnyActionType) => {
4548
switch (true) {
46-
case actionType.includes(ColonyActionType.Payment) &&
47-
!actionType.includes(ExtendedColonyActionType.SplitPayment):
49+
case actionType === ColonyActionType.Payment:
4850
return [
4951
ActionTitleMessageKeys.Recipient,
5052
ActionTitleMessageKeys.Amount,
@@ -128,6 +130,11 @@ const getMessageDescriptorKeys = (actionType: AnyActionType) => {
128130
return [
129131
ActionTitleMessageKeys.Initiator,
130132
ActionTitleMessageKeys.Recipient,
133+
ActionTitleMessageKeys.StagedAmount,
134+
ActionTitleMessageKeys.TokenSymbol,
135+
ActionTitleMessageKeys.Milestones,
136+
ActionTitleMessageKeys.MilestonesCount,
137+
ActionTitleMessageKeys.TokensNumber,
131138
];
132139
case actionType.includes(ExtendedColonyActionType.AddSafe):
133140
return [ActionTitleMessageKeys.ChainName];
@@ -159,29 +166,33 @@ const getMessageDescriptorKeys = (actionType: AnyActionType) => {
159166
};
160167

161168
/* Returns the correct message values according to the action type. */
162-
const getActionTitleValues = ({
169+
const useGetActionTitleValues = ({
163170
actionData,
164171
colony,
165172
keyFallbackValues,
166173
expenditureData,
167174
networkInverseFee,
168175
}: {
169-
actionData: ColonyAction;
170-
colony: Pick<Colony, 'metadata' | 'nativeToken'>;
176+
actionData: ColonyAction | null | undefined;
177+
colony: Pick<Colony, 'metadata' | 'nativeToken'> | undefined;
171178
keyFallbackValues?: Partial<Record<ActionTitleMessageKeys, React.ReactNode>>;
172179
expenditureData?: Expenditure;
173180
networkInverseFee?: string;
174181
}) => {
175-
const { isMotion, pendingColonyMetadata } = actionData;
182+
const { isMotion, pendingColonyMetadata } = actionData || {};
176183

177-
const updatedItem = mapColonyActionToExpectedFormat({
184+
const updatedItem = useMapColonyActionToExpectedFormat({
178185
actionData,
179186
colony,
180187
keyFallbackValues,
181188
expenditureData,
182189
networkInverseFee,
183190
});
184191

192+
if (!actionData || !colony) {
193+
return {};
194+
}
195+
185196
const actionType = getExtendedActionType(
186197
actionData,
187198
isMotion ? pendingColonyMetadata : colony.metadata,
@@ -193,4 +204,4 @@ const getActionTitleValues = ({
193204
});
194205
};
195206

196-
export default getActionTitleValues;
207+
export default useGetActionTitleValues;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { default as getActionTitleValues } from './getActionTitleValues.ts';
1+
export { default as useGetActionTitleValues } from './getActionTitleValues.ts';
22
export * from './mapItemToMessageFormat.tsx';

src/components/common/ColonyActions/helpers/mapItemToMessageFormat.tsx

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { AddressZero } from '@ethersproject/constants';
22
import Decimal from 'decimal.js';
3+
import { BigNumber } from 'ethers';
34
import React from 'react';
45

56
import { ACTIONS_WITH_NETWORK_FEE } from '~constants/actions.ts';
6-
import { ColonyActionType, type SimpleTarget } from '~gql';
7+
import { getNetworkTokenList } from '~constants/tokens/getNetworkTokenList.ts';
8+
import { ColonyActionType, type TokenFragment, type SimpleTarget } from '~gql';
9+
import useUserByAddress from '~hooks/useUserByAddress.ts';
710
import FriendlyName from '~shared/FriendlyName/index.ts';
811
import MaskedAddress from '~shared/MaskedAddress/index.ts';
912
import Numeral from '~shared/Numeral/index.ts';
@@ -16,8 +19,10 @@ import {
1619
type ColonyExtension,
1720
type Token,
1821
type Expenditure,
22+
type ExpenditureStage,
23+
type ExpenditureSlot,
1924
} from '~types/graphql.ts';
20-
import { notMaybe } from '~utils/arrays/index.ts';
25+
import { notMaybe, notNull } from '~utils/arrays/index.ts';
2126
import { formatRolesTitle } from '~utils/colonyActions.ts';
2227
import { getRecipientsNumber, getTokensNumber } from '~utils/expenditures.ts';
2328
import { getAmountLessFee } from '~utils/getAmountLessFee.ts';
@@ -53,8 +58,9 @@ const getDomainNameFromChangelog = (
5358
return changelogItem.newName;
5459
};
5560

56-
const getRecipientData = (
57-
actionData: ColonyAction,
61+
const useGetRecipientData = (
62+
actionData: ColonyAction | null | undefined,
63+
expenditure: Expenditure | null | undefined,
5864
):
5965
| User
6066
| Colony
@@ -70,8 +76,13 @@ const getRecipientData = (
7076
recipientToken,
7177
safeTransaction,
7278
recipientAddress,
73-
} = actionData;
79+
} = actionData || {};
7480
const safeRecipient = safeTransaction?.transactions?.items[0]?.recipient;
81+
const stagedPaymentRecipientAddress =
82+
expenditure?.slots[0].recipientAddress || '';
83+
const stagedPaymentRecipient = useUserByAddress(
84+
stagedPaymentRecipientAddress,
85+
);
7586

7687
return (
7788
[
@@ -81,12 +92,16 @@ const getRecipientData = (
8192
recipientToken,
8293
safeRecipient,
8394
recipientAddress,
95+
stagedPaymentRecipient.user,
8496
].find(notMaybe) || undefined
8597
);
8698
};
8799

88-
const getRecipient = (actionData: ColonyAction) => {
89-
const recipient = getRecipientData(actionData);
100+
const useGetRecipient = (
101+
actionData: ColonyAction | null | undefined,
102+
expenditure: Expenditure | null | undefined,
103+
) => {
104+
const recipient = useGetRecipientData(actionData, expenditure);
90105

91106
return (
92107
<span>
@@ -135,22 +150,76 @@ const getInitiator = (actionData: ColonyAction) => {
135150
);
136151
};
137152

138-
export const mapColonyActionToExpectedFormat = ({
153+
interface ExpenditureStagesData {
154+
summedAmount: string;
155+
stagedPaymentToken: TokenFragment | null | undefined;
156+
}
157+
158+
const getExpenditureStagesData = (
159+
stages: ExpenditureStage[],
160+
slots: ExpenditureSlot[],
161+
colony: Pick<Colony, 'tokens'>,
162+
) => {
163+
const predefinedTokens = getNetworkTokenList();
164+
const colonyTokens = colony.tokens?.items.filter(notNull) || [];
165+
const allTokens = [...colonyTokens, ...predefinedTokens];
166+
167+
const tokensSet = new Set(
168+
slots.flatMap(
169+
(slot) => slot.payouts?.map((payout) => payout.tokenAddress) ?? [],
170+
),
171+
);
172+
173+
const hasSingleTokenType = tokensSet.size === 1;
174+
175+
if (!hasSingleTokenType) {
176+
return {
177+
summedAmount: '0',
178+
stagedPaymentToken: null,
179+
};
180+
}
181+
182+
const result = stages.reduce<ExpenditureStagesData>(
183+
(acc, stage): ExpenditureStagesData => {
184+
const currentSlot = slots.find((slot) => slot.id === stage.slotId);
185+
const tokenAddress = currentSlot?.payouts?.[0]?.tokenAddress;
186+
187+
const token = allTokens.find(
188+
({ token: currentToken }) => currentToken.tokenAddress === tokenAddress,
189+
);
190+
191+
return {
192+
summedAmount: BigNumber.from(acc.summedAmount)
193+
.add(BigNumber.from(currentSlot?.payouts?.[0]?.amount || '0'))
194+
.toString(),
195+
stagedPaymentToken: acc.stagedPaymentToken || token?.token,
196+
};
197+
},
198+
{
199+
summedAmount: BigNumber.from('0').toString(),
200+
stagedPaymentToken: null,
201+
},
202+
);
203+
204+
return {
205+
summedAmount: result.summedAmount.toString(),
206+
stagedPaymentToken: result.stagedPaymentToken,
207+
};
208+
};
209+
210+
export const useMapColonyActionToExpectedFormat = ({
139211
actionData,
140212
colony,
141213
keyFallbackValues = {},
142214
expenditureData,
143215
networkInverseFee,
144216
}: {
145-
actionData: ColonyAction;
146-
colony: Pick<Colony, 'nativeToken' | 'tokens'>;
217+
actionData: ColonyAction | null | undefined;
218+
colony: Pick<Colony, 'nativeToken' | 'tokens'> | undefined;
147219
keyFallbackValues?: Partial<Record<ActionTitleMessageKeys, React.ReactNode>>;
148220
expenditureData?: Expenditure;
149221
networkInverseFee?: string;
150222
}) => {
151-
// // @TODO: item.actionType === ColonyMotions.SetUserRolesMotion ? updatedRoles : roles,
152-
const formattedRolesTitle = formatRolesTitle(actionData.roles);
153-
154223
const getFormattedValueWithFallback = (
155224
value: React.ReactNode,
156225
fallbackKey: ActionTitleMessageKeys,
@@ -163,6 +232,17 @@ export const mapColonyActionToExpectedFormat = ({
163232
return keyFallbackValues[fallbackKey];
164233
};
165234

235+
const recipient = getFormattedValueWithFallback(
236+
useGetRecipient(actionData, expenditureData),
237+
ActionTitleMessageKeys.Recipient,
238+
notMaybe(useGetRecipientData(actionData, expenditureData)),
239+
);
240+
if (!actionData || !colony) {
241+
return {};
242+
}
243+
// // @TODO: item.actionType === ColonyMotions.SetUserRolesMotion ? updatedRoles : roles,
244+
const formattedRolesTitle = formatRolesTitle(actionData.roles);
245+
166246
const getAmount = (
167247
actionType: ColonyActionType,
168248
amount?: string | null,
@@ -209,6 +289,12 @@ export const mapColonyActionToExpectedFormat = ({
209289
actionData.fromDomain?.metadata || actionData.pendingDomainMetadata;
210290
}
211291

292+
const { stagedPaymentToken, summedAmount } = getExpenditureStagesData(
293+
expenditureData?.metadata?.stages || [],
294+
expenditureData?.slots || [],
295+
colony,
296+
);
297+
212298
return {
213299
...actionData,
214300
[ActionTitleMessageKeys.Amount]: getFormattedValueWithFallback(
@@ -237,11 +323,7 @@ export const mapColonyActionToExpectedFormat = ({
237323
ActionTitleMessageKeys.Initiator,
238324
notMaybe(getInitiatorData(actionData)),
239325
),
240-
[ActionTitleMessageKeys.Recipient]: getFormattedValueWithFallback(
241-
getRecipient(actionData),
242-
ActionTitleMessageKeys.Recipient,
243-
notMaybe(getRecipientData(actionData)),
244-
),
326+
[ActionTitleMessageKeys.Recipient]: recipient,
245327
[ActionTitleMessageKeys.ToDomain]: getFormattedValueWithFallback(
246328
actionData.toDomain?.metadata?.name ??
247329
formatMessage({ id: 'unknownDomain' }),
@@ -317,6 +399,12 @@ export const mapColonyActionToExpectedFormat = ({
317399
id: 'decisionMethod.multiSig',
318400
})} `
319401
: '',
402+
[ActionTitleMessageKeys.StagedAmount]: (
403+
<Numeral
404+
value={summedAmount}
405+
decimals={getTokenDecimalsWithFallback(stagedPaymentToken?.decimals)}
406+
/>
407+
),
320408
[ActionTitleMessageKeys.SplitAmount]: getFormattedValueWithFallback(
321409
<Numeral
322410
value={

src/components/common/ColonyActionsTable/partials/ActionDescription/ActionDescription.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import clsx from 'clsx';
22
import React, { type FC } from 'react';
33

4-
import { getActionTitleValues } from '~common/ColonyActions/helpers/index.ts';
4+
import useGetActionTitleValues from '~common/ColonyActions/helpers/getActionTitleValues.ts';
55
import { ADDRESS_ZERO } from '~constants/index.ts';
66
import { useColonyContext } from '~context/ColonyContext/ColonyContext.ts';
77
import { useMobile } from '~hooks';
@@ -63,7 +63,7 @@ const ActionDescription: FC<ActionDescriptionProps> = ({
6363

6464
const actionMetadataDescription = formatText(
6565
{ id: 'action.title' },
66-
getActionTitleValues({
66+
useGetActionTitleValues({
6767
actionData: {
6868
...action,
6969
recipientAddress: recipientUser?.walletAddress ?? recipientAddress,

src/components/common/ColonyActionsTable/partials/ActionMobileDescription/ActionMobileDescription.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import clsx from 'clsx';
22
import React, { type FC } from 'react';
33

4-
import getActionTitleValues from '~common/ColonyActions/helpers/getActionTitleValues.ts';
4+
import useGetActionTitleValues from '~common/ColonyActions/helpers/getActionTitleValues.ts';
55
import { useColonyContext } from '~context/ColonyContext/ColonyContext.ts';
66
import useNetworkInverseFee from '~hooks/useNetworkInverseFee.ts';
77
import useShouldDisplayMotionCountdownTime from '~hooks/useShouldDisplayMotionCountdownTime.ts';
@@ -56,7 +56,7 @@ const ActionMobileDescription: FC<ActionMobileDescriptionProps> = ({
5656

5757
const actionMetadataDescription = formatText(
5858
{ id: 'action.title' },
59-
getActionTitleValues({
59+
useGetActionTitleValues({
6060
actionData: action,
6161
colony,
6262
expenditureData: expenditure ?? undefined,

src/components/common/Extensions/UserHub/partials/NotificationsTab/partials/Notification/Action/ActionNotification.tsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useMemo, type FC } from 'react';
22
import { useNavigate } from 'react-router-dom';
33

4-
import { getActionTitleValues } from '~common/ColonyActions/index.ts';
4+
import useGetActionTitleValues from '~common/ColonyActions/helpers/getActionTitleValues.ts';
55
import {
66
type NotificationColonyFragment,
77
NotificationType,
@@ -67,25 +67,24 @@ const ActionNotification: FC<NotificationProps> = ({
6767
});
6868
};
6969

70+
const titleValues = useGetActionTitleValues({
71+
actionData: action,
72+
colony: colony
73+
? {
74+
nativeToken: colony?.nativeToken,
75+
metadata: colony?.metadata,
76+
}
77+
: undefined,
78+
networkInverseFee,
79+
});
80+
7081
const actionMetadataDescription = useMemo(() => {
7182
if (!action || !colony) {
7283
return null;
7384
}
7485

75-
return formatText(
76-
{ id: 'action.title' },
77-
getActionTitleValues({
78-
actionData: action,
79-
colony: {
80-
nativeToken: {
81-
...colony.nativeToken,
82-
},
83-
metadata: colony.metadata,
84-
},
85-
networkInverseFee,
86-
}),
87-
);
88-
}, [action, colony, networkInverseFee]);
86+
return formatText({ id: 'action.title' }, titleValues);
87+
}, [action, colony, titleValues]);
8988

9089
const actionTitle =
9190
action?.metadata?.customTitle || action?.decisionData?.title || '';

0 commit comments

Comments
 (0)