From 8cfa081f573b1d95339aaf2ddb2aab8a17f18b38 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 24 Apr 2026 15:18:21 +0300 Subject: [PATCH] PM-4830 - payment details modal for tasks --- .../payment-view/PaymentView.spec.tsx | 41 +++++++++++ .../components/payment-view/PaymentView.tsx | 73 ++++++++++++++++++- .../payment-view/payment-view.utils.ts | 13 ++++ .../src/lib/models/WinningDetail.ts | 7 ++ 4 files changed, 130 insertions(+), 4 deletions(-) diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx index bf3806abf..20053762a 100644 --- a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx @@ -182,4 +182,45 @@ describe('PaymentView', () => { expect(workLogRemarksLink.getAttribute('target')) .toBe('_blank') }) + + it('renders task details section for task payments', async () => { + const taskPayment: Winning = { + ...payment, + description: 'Build a cool widget for the dashboard', + externalId: 'challenge-uuid-1', + type: 'task payment', + } + + mockedFetchWinningPaymentDetails.mockResolvedValue({ + paymentCreatorHandle: 'task-creator', + taskDetails: { + paymentApproverHandle: 'approver-handle', + projectId: '42', + projectName: 'My Awesome Project', + }, + }) + + render() + + await waitFor(() => { + expect(mockedFetchWinningPaymentDetails) + .toHaveBeenCalledWith(taskPayment) + }) + + expect(await screen.findByRole('heading', { name: 'Task Details' })) + .toBeTruthy() + + expect(await screen.findByRole('heading', { name: 'Task Details' })) + .toBeTruthy() + expect(await screen.findByText('task-creator')) + .toBeTruthy() + expect(await screen.findByText('approver-handle')) + .toBeTruthy() + + const projectLink = await screen.findByRole('link', { name: 'My Awesome Project' }) + expect(projectLink.getAttribute('href')) + .toBe('https://challenges.example.com/projects/42/challenges/challenge-uuid-1/view') + expect(projectLink.getAttribute('target')) + .toBe('_blank') + }) }) diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx index 02db5e10c..6556425cb 100644 --- a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx @@ -19,6 +19,7 @@ import { getMemberHandle, } from '../../services/wallet' import { + buildWorkAppChallengeUrl, buildWorkManagerAssignmentUrl, buildWorkManagerProjectUrl, formatOptionalDate, @@ -42,6 +43,7 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { const [paymentDetailsError, setPaymentDetailsError] = React.useState() const isEngagementPayment = props.payment.type.toLowerCase() === 'engagement payment' + const isTaskPayment = props.payment.type.toLowerCase() === 'task payment' const hasEngagementDetails = Boolean(paymentDetails?.engagementDetails) const handleToggleView = (newView: 'audit' | 'details' | 'external_transaction'): void => { @@ -49,7 +51,7 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { } React.useEffect(() => { - if (!isEngagementPayment) { + if (!isEngagementPayment && !isTaskPayment) { setPaymentDetails(undefined) setIsPaymentDetailsLoading(false) setPaymentDetailsError(undefined) @@ -70,7 +72,7 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { .catch(() => { if (!ignore) { setPaymentDetails(undefined) - setPaymentDetailsError('Unable to load engagement details.') + setPaymentDetailsError(isTaskPayment ? 'Unable to load task details.' : 'Unable to load engagement details.') } }) .finally(() => { @@ -82,7 +84,7 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { return () => { ignore = true } - }, [isEngagementPayment, props.payment]) + }, [isEngagementPayment, isTaskPayment, props.payment]) React.useEffect(() => { if (view === 'audit') { @@ -138,7 +140,9 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { const descriptionLink = isEngagementPayment ? buildWorkManagerAssignmentUrl(paymentDetails?.engagementDetails) - : `${TOPCODER_URL}/challenges/${props.payment.externalId}` + : isTaskPayment + ? buildWorkAppChallengeUrl(paymentDetails?.taskDetails?.projectId, props.payment.externalId) + : `${TOPCODER_URL}/challenges/${props.payment.externalId}` const projectLink = buildWorkManagerProjectUrl(paymentDetails?.engagementDetails) return ( @@ -306,6 +310,67 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { )} + {isTaskPayment && ( +
+

Task Details

+ {isPaymentDetailsLoading + ?

Loading task details...

+ : undefined} + {!isPaymentDetailsLoading && paymentDetailsError + ?

{paymentDetailsError}

+ : undefined} + {!isPaymentDetailsLoading && !paymentDetailsError && ( +
+
+ Task Creator +

+ {formatOptionalText(paymentDetails?.paymentCreatorHandle)} +

+
+
+ Task Description +

+ {props.payment.description + ? props.payment.description.substring(0, 500) + : '-'} +

+
+
+ Project Name + {buildWorkAppChallengeUrl( + paymentDetails?.taskDetails?.projectId, + props.payment.externalId, + ) && paymentDetails?.taskDetails?.projectName + ? ( + + {paymentDetails.taskDetails.projectName} + + ) + : ( +

+ {formatOptionalText(paymentDetails?.taskDetails?.projectName)} +

+ )} +
+
+ Payment Approver +

+ {formatOptionalText(paymentDetails?.taskDetails?.paymentApproverHandle)} +

+
+
+ )} +
+ )} +