From bbcaf60887ae1ee8aebf2912afe66256e1615cbc Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 9 Apr 2026 09:20:00 +1000 Subject: [PATCH 1/2] HOtfix for PM-4815 and handles not showing on copilots application page --- src/apps/admin/src/AdminHomeRedirect.tsx | 2 +- .../Tab/config/system-admin-tabs-config.ts | 2 +- src/apps/admin/src/lib/utils/index.ts | 6 +++++ .../src/services/copilot-opportunities.ts | 26 +++++++++++++++++-- .../assignment-card/AssignmentCard.tsx | 8 +++--- .../AssignmentOfferModal.tsx | 4 +-- src/apps/engagements/src/lib/utils/index.ts | 8 ++++++ 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/apps/admin/src/AdminHomeRedirect.tsx b/src/apps/admin/src/AdminHomeRedirect.tsx index a96869288..9bf239061 100644 --- a/src/apps/admin/src/AdminHomeRedirect.tsx +++ b/src/apps/admin/src/AdminHomeRedirect.tsx @@ -5,7 +5,7 @@ import { reportsRootRoute } from '~/apps/reports' import { ProfileContextData, useProfileContext } from '~/libs/core' import { manageChallengeRouteId } from './config/routes.config' -import { isAdministrator } from './lib/utils' +import { isAdministrator } from './lib/utils/access' /** * Redirects authenticated admin-app users to the first route they can access. diff --git a/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts b/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts index 1154c6cbf..1aa72a376 100644 --- a/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts +++ b/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts @@ -1,7 +1,7 @@ import _ from 'lodash' import { TabsNavItem } from '~/libs/ui' -import { isAdministrator } from '~/apps/admin/src/lib/utils' +import { isAdministrator } from '~/apps/admin/src/lib/utils/access' import { billingAccountRouteId, defaultReviewersRouteId, diff --git a/src/apps/admin/src/lib/utils/index.ts b/src/apps/admin/src/lib/utils/index.ts index 5e827a00b..869914c86 100644 --- a/src/apps/admin/src/lib/utils/index.ts +++ b/src/apps/admin/src/lib/utils/index.ts @@ -6,3 +6,9 @@ export * from './number' export * from './string' export * from './others' export * from './access' +export { + administratorOnlyRoles, + adminReportsAccessRoles, + canAccessAdminReports, + isAdministrator, +} from './access' diff --git a/src/apps/copilots/src/services/copilot-opportunities.ts b/src/apps/copilots/src/services/copilot-opportunities.ts index 3446a9ef6..4437b2aaf 100644 --- a/src/apps/copilots/src/services/copilot-opportunities.ts +++ b/src/apps/copilots/src/services/copilot-opportunities.ts @@ -12,6 +12,11 @@ export const copilotBaseUrl = `${EnvironmentConfig.API.V6}/projects` const PAGE_SIZE = 20 +type CopilotApplicationApiResponse = Omit & { + id: number | string + userId: number | string +} + /** * Creates a CopilotOpportunity object by merging the provided data and its nested data, * setting specific properties, and formatting the createdAt date. @@ -27,6 +32,23 @@ function copilotOpportunityFactory(data: any): CopilotOpportunity { } } +/** + * Normalizes copilot application identifiers that the API may serialize as strings. + * + * The copilot opportunity UI joins applications to member records and the signed-in profile + * using numeric ids, so we coerce the API payload into the shape expected by the UI. + * + * @param data - Raw copilot application returned by the API. + * @returns A copilot application with numeric identifiers. + */ +function copilotApplicationFactory(data: CopilotApplicationApiResponse): CopilotApplication { + return { + ...data, + id: Number(data.id), + userId: Number(data.userId), + } +} + export interface CopilotOpportunitiesResponse { isValidating: boolean; data: CopilotOpportunity[]; @@ -144,8 +166,8 @@ export const useCopilotApplications = (opportunityId?: string): CopilotApplicati ? buildUrl(`${copilotBaseUrl}/copilots/opportunity/${opportunityId}/applications`) : undefined - const fetcher = (urlp: string): Promise => xhrGetAsync(urlp) - .then(data => data) + const fetcher = (urlp: string): Promise => xhrGetAsync(urlp) + .then(data => data.map(copilotApplicationFactory)) .catch(() => []) return useSWR(url, fetcher) diff --git a/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx b/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx index 46be7f5a7..20f58c71e 100644 --- a/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx +++ b/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx @@ -10,11 +10,11 @@ import { EnvironmentConfig } from '~/config' import type { Engagement, EngagementAssignment } from '../../lib/models' import { formatCurrencyAmount, - formatDate, - formatLocation, formatStandardHoursPerWeek, - truncateText, -} from '../../lib/utils' +} from '../../lib/utils/currency.utils' +import { formatDate } from '../../lib/utils/date.utils' +import { formatLocation } from '../../lib/utils/api.utils' +import { truncateText } from '../../lib/utils/application.utils' import { StatusBadge } from '../status-badge' import styles from './AssignmentCard.module.scss' diff --git a/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx b/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx index 8f5fbdc6a..b1c943639 100644 --- a/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx +++ b/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx @@ -5,9 +5,9 @@ import { BaseModal, Button } from '~/libs/ui' import type { Engagement, EngagementAssignment } from '../../lib/models' import { formatCurrencyAmount, - formatDate, formatStandardHoursPerWeek, -} from '../../lib/utils' +} from '../../lib/utils/currency.utils' +import { formatDate } from '../../lib/utils/date.utils' import styles from './AssignmentOfferModal.module.scss' diff --git a/src/apps/engagements/src/lib/utils/index.ts b/src/apps/engagements/src/lib/utils/index.ts index 8f5f0a9b8..2fc1ffd2b 100644 --- a/src/apps/engagements/src/lib/utils/index.ts +++ b/src/apps/engagements/src/lib/utils/index.ts @@ -3,3 +3,11 @@ export * from './application.utils' export * from './currency.utils' export * from './date.utils' export * from './terms.utils' +export { formatLocation } from './api.utils' +export { truncateText } from './application.utils' +export { + formatCurrencyAmount, + formatStandardHoursPerWeek, + normalizePositiveNumericValue, +} from './currency.utils' +export { formatDate } from './date.utils' From d85a59d8ce3fdc3d1b37404e6c7c705a43158cc1 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Tue, 28 Apr 2026 07:42:36 +1000 Subject: [PATCH 2/2] Hide BA details deploy testing --- .circleci/config.yml | 1 + .../PaymentFormModal.spec.tsx | 4 + .../PaymentFormModal/PaymentFormModal.tsx | 19 +- .../PaymentHistoryModal.spec.tsx | 12 +- .../PaymentHistoryModal.tsx | 7 +- ...rojectBillingAccountExpiredNotice.spec.tsx | 31 +- .../ProjectBillingAccountExpiredNotice.tsx | 311 ++++++++++++------ .../ProjectsTable/ProjectsTable.spec.tsx | 21 +- .../ProjectsTable/ProjectsTable.tsx | 26 +- src/apps/work/src/lib/constants.ts | 6 + 10 files changed, 302 insertions(+), 136 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 717a8915d..36aa6b62a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -226,6 +226,7 @@ workflows: branches: only: - dev + - hide_ba_details - deployQa: context: org-global diff --git a/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.spec.tsx b/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.spec.tsx index c58fb5c39..47ac68dc7 100644 --- a/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.spec.tsx +++ b/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.spec.tsx @@ -51,6 +51,10 @@ jest.mock('../../utils', () => ({ getAssignmentStandardHoursPerWeek: jest.fn(() => 40), })) +jest.mock('../../constants', () => ({ + BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED: false, +})) + describe('PaymentFormModal', () => { const member: Assignment = { agreementRate: '821.20', diff --git a/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx b/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx index b50141c4d..2cec47e65 100644 --- a/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx +++ b/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx @@ -17,6 +17,9 @@ import { Button, } from '~/libs/ui' +import { + BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED, +} from '../../constants' import { Assignment, } from '../../models' @@ -156,7 +159,9 @@ const PaymentFormModal: FC = ( [hoursWorked, ratePerHour], ) const challengeFee = useMemo( - () => calculatePaymentChallengeFee(amount, props.billingAccountMarkup), + () => (BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED + ? calculatePaymentChallengeFee(amount, props.billingAccountMarkup) + : undefined), [amount, props.billingAccountMarkup], ) const paymentTitle = useMemo( @@ -289,10 +294,14 @@ const PaymentFormModal: FC = ( Rate Per Week {formatCurrency(props.member?.agreementRate)} -
- Billing Account - {props.billingAccountId || 'Unavailable'} -
+ {BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED + ? ( +
+ Billing Account + {props.billingAccountId || 'Unavailable'} +
+ ) + : undefined}
diff --git a/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.spec.tsx b/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.spec.tsx index 622822f14..e4688e855 100644 --- a/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.spec.tsx +++ b/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.spec.tsx @@ -12,6 +12,10 @@ jest.mock('../../hooks', () => ({ useFetchAssignmentPayments: (...args: unknown[]): unknown => mockUseFetchAssignmentPayments(...args), })) +jest.mock('../../constants', () => ({ + BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED: false, +})) + jest.mock('~/libs/ui', () => ({ BaseModal: (props: { buttons?: JSX.Element @@ -92,9 +96,9 @@ describe('PaymentHistoryModal', () => { .toBeTruthy() expect(screen.getByText('payment.manager')) .toBeTruthy() - expect(screen.getByText('Fee:')) - .toBeTruthy() - expect(screen.getByText('$18.60')) - .toBeTruthy() + expect(screen.queryByText('Fee:')) + .toBeNull() + expect(screen.queryByText('$18.60')) + .toBeNull() }) }) diff --git a/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.tsx b/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.tsx index 406db4486..bb7b33318 100644 --- a/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.tsx +++ b/src/apps/work/src/lib/components/PaymentHistoryModal/PaymentHistoryModal.tsx @@ -5,6 +5,9 @@ import { Button, } from '~/libs/ui' +import { + BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED, +} from '../../constants' import { useFetchAssignmentPayments, } from '../../hooks' @@ -88,7 +91,9 @@ const PaymentHistoryModal: FC = (
    {paymentsResult.payments.map((payment, index) => { const paymentAmount = getPaymentAmount(payment) - const paymentChallengeFee = getPaymentChallengeFee(payment) + const paymentChallengeFee = BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED + ? getPaymentChallengeFee(payment) + : undefined const paymentStatus = getPaymentStatus(payment) const paymentHoursWorked = getPaymentHoursWorked(payment) const paymentRemarks = getPaymentRemarks(payment) diff --git a/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.spec.tsx b/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.spec.tsx index bda54cc16..d7e1ebbe0 100644 --- a/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.spec.tsx +++ b/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.spec.tsx @@ -1,6 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ import { - fireEvent, render, screen, } from '@testing-library/react' @@ -33,6 +32,11 @@ jest.mock('../BillingAccountLineItemsModal', () => ({ ), })) +jest.mock('../../constants', () => ({ + BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED: false, + BILLING_ACCOUNT_DETAILS_MODAL_ENABLED: false, +})) + jest.mock('~/libs/ui', () => ({ IconOutline: { InformationCircleIcon: (): JSX.Element => info, @@ -88,7 +92,7 @@ describe('ProjectBillingAccountExpiredNotice', () => { }) }) - it('keeps billing account details and line items available when remaining funds are insufficient', () => { + it('hides billing account budget and line-item details while billing details are disabled', () => { render( { .toBeTruthy() expect(screen.getByText(/80001063/)) .toBeTruthy() - expect(screen.getByText('$1,025 / $1,000 spent')) - .toBeTruthy() - expect(screen.getByText(/The billing account for this project has insufficient remaining funds,/)) - .toBeTruthy() - expect(screen.getByRole('link', { name: 'click here to update' }) - .getAttribute('href')) - .toBe('/projects/project-1/edit') - - fireEvent.click(screen.getByRole('button', { + expect(screen.queryByText('$1,025 / $1,000 spent')) + .toBeNull() + expect(screen.queryByText(/The billing account for this project has insufficient remaining funds,/)) + .toBeNull() + expect(screen.queryByRole('link', { name: 'click here to update' })) + .toBeNull() + expect(screen.queryByRole('button', { name: 'View billing account details', })) - - expect(screen.getByRole('dialog') - .textContent) - .toContain('Billing account details for 80001063') + .toBeNull() + expect(screen.queryByRole('dialog')) + .toBeNull() }) }) diff --git a/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.tsx b/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.tsx index 124cde600..74d1b500b 100644 --- a/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.tsx +++ b/src/apps/work/src/lib/components/ProjectBillingAccountExpiredNotice/ProjectBillingAccountExpiredNotice.tsx @@ -8,6 +8,10 @@ import { Link } from 'react-router-dom' import { IconOutline } from '~/libs/ui' +import { + BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED, + BILLING_ACCOUNT_DETAILS_MODAL_ENABLED, +} from '../../constants' import { useFetchBillingAccountDetails, useFetchBillingAccounts, @@ -18,6 +22,9 @@ import type { UseFetchBillingAccountsResult, UseFetchProjectBillingAccountResult, } from '../../hooks' +import type { + BillingAccountDetails, +} from '../../services' import { getProjectBillingAccountChallengeIssue, getProjectBillingAccountNoticeMessage, @@ -34,6 +41,28 @@ interface ProjectBillingAccountExpiredNoticeProps { } type BudgetStatus = 'healthy' | 'warning' | 'critical' +type BillingAccountIssue = ReturnType + +interface BillingBudgetInfo { + spent: number + status: BudgetStatus + totalBudget: number +} + +interface BillingAccountDetailsContentProps { + billingAccountId: string + billingAccountName: string | undefined + budgetInfo: BillingBudgetInfo | undefined + onOpenModal: () => void +} + +interface RenderBillingAccountContentParams { + billingAccountDetailsContent: JSX.Element | undefined + billingAccountModal: JSX.Element | undefined + canManageProject: boolean + projectId: string + visibleBillingAccountIssue: BillingAccountIssue +} function normalizeOptionalString(value: unknown): string | undefined { if (value === undefined || value === null) { @@ -74,6 +103,168 @@ function getBudgetStatus(remaining: number, total: number): BudgetStatus { return 'healthy' } +/** + * Hides budget-derived billing account notices while budget display is disabled. + * + * @param billingAccountIssue The billing account issue resolved for the project. + * @returns The issue to display, or `undefined` when the temporary hide applies. + */ +function getVisibleBillingAccountIssue( + billingAccountIssue: BillingAccountIssue, +): BillingAccountIssue { + const isInsufficientFundsNoticeHidden = !BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED + && billingAccountIssue === 'insufficient-funds' + + return isInsufficientFundsNoticeHidden + ? undefined + : billingAccountIssue +} + +/** + * Builds the optional spent/total budget display model from fetched billing details. + * + * @param billingAccountDetails Billing account details returned by the work app hook. + * @returns Spent, total, and status information, or `undefined` while hidden or unavailable. + */ +function getBillingAccountBudgetInfo( + billingAccountDetails: BillingAccountDetails | undefined, +): BillingBudgetInfo | undefined { + if (!BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED || !billingAccountDetails) { + return undefined + } + + const totalBudget = Number(billingAccountDetails.budget) || 0 + const remaining = Number(billingAccountDetails.totalBudgetRemaining) || 0 + const status = getBudgetStatus(remaining, totalBudget) + + return { + spent: Math.max(totalBudget - remaining, 0), + status, + totalBudget, + } +} + +/** + * Renders the visible billing account label plus optional budget and details controls. + * + * @param props Billing account label, optional budget data, and modal open handler. + * @returns The billing account details row for project pages. + */ +const BillingAccountDetailsContent: FC = ( + props: BillingAccountDetailsContentProps, +) => { + const budgetStatusClass = props.budgetInfo + ? styles[`budget${props.budgetInfo.status.charAt(0) + .toUpperCase()}${props.budgetInfo.status.slice(1)}`] + : '' + + return ( +
    + + Billing account: + {' '} + {props.billingAccountName || 'Unknown'} + {' '} + / + {' '} + {props.billingAccountId} + + {props.budgetInfo + ? ( + + {formatCurrency(props.budgetInfo.spent)} + {' / '} + {formatCurrency(props.budgetInfo.totalBudget)} + {' spent'} + + ) + : undefined} + {BILLING_ACCOUNT_DETAILS_MODAL_ENABLED + ? ( + + ) + : undefined} +
    + ) +} + +/** + * Renders the temporarily enabled/disabled line-item modal. + * + * @param billingAccountDetails Billing account detail payload, if loaded. + * @param isModalOpen Whether the details modal has been requested. + * @param onClose Close handler passed to the modal. + * @returns The line-item modal, or `undefined` when the feature is hidden. + */ +function renderBillingAccountModal( + billingAccountDetails: BillingAccountDetails | undefined, + isModalOpen: boolean, + onClose: () => void, +): JSX.Element | undefined { + if (!BILLING_ACCOUNT_DETAILS_MODAL_ENABLED || !isModalOpen || !billingAccountDetails) { + return undefined + } + + return ( + + ) +} + +/** + * Renders the project billing-account issue notice when one should remain visible. + * + * @param params Project billing display state and rendered child content. + * @returns The notice stack, normal details content, or an empty fragment. + */ +function renderBillingAccountContent(params: RenderBillingAccountContentParams): JSX.Element { + if (params.visibleBillingAccountIssue) { + const noticeMessage = getProjectBillingAccountNoticeMessage(params.visibleBillingAccountIssue) + const managedNoticeMessage = `${noticeMessage.slice(0, -1)}, ` + + return ( +
    + {params.billingAccountDetailsContent} +
    + {params.canManageProject + ? ( + <> + {managedNoticeMessage} + + click here to update + + + ) + : ( + {noticeMessage} + )} +
    + {params.billingAccountModal} +
    + ) + } + + if (!params.billingAccountDetailsContent) { + return <> + } + + return ( + <> + {params.billingAccountDetailsContent} + {params.billingAccountModal} + + ) +} + export const ProjectBillingAccountExpiredNotice: FC = ( props: ProjectBillingAccountExpiredNoticeProps, ) => { @@ -87,7 +278,9 @@ export const ProjectBillingAccountExpiredNotice: FC { - if (!billingAccountDetailsData) { - return undefined - } - - const totalBudget = Number(billingAccountDetailsData.budget) || 0 - const remaining = Number(billingAccountDetailsData.totalBudgetRemaining) || 0 - const status = getBudgetStatus(remaining, totalBudget) + const visibleBillingAccountIssue = getVisibleBillingAccountIssue( + getProjectBillingAccountChallengeIssue(billingAccount), + ) - return { - spent: Math.max(totalBudget - remaining, 0), - status, - totalBudget, - } - }, [billingAccountDetailsData]) + const budgetInfo = useMemo( + () => getBillingAccountBudgetInfo(billingAccountDetailsData), + [billingAccountDetailsData], + ) const handleOpenModal = useCallback((): void => { setIsModalOpen(true) @@ -140,88 +324,29 @@ export const ProjectBillingAccountExpiredNotice: FC - - Billing account: - {' '} - {billingAccountName || 'Unknown'} - {' '} - / - {' '} - {normalizedBillingAccountId} - - {budgetInfo && ( - <> - - {formatCurrency(budgetInfo.spent)} - {' / '} - {formatCurrency(budgetInfo.totalBudget)} - {' spent'} - - - - )} -
- ) - : undefined - const billingAccountModal = isModalOpen && billingAccountDetailsData - ? ( - ) : undefined - - if (billingAccountIssue) { - const noticeMessage = getProjectBillingAccountNoticeMessage(billingAccountIssue) - const managedNoticeMessage = `${noticeMessage.slice(0, -1)}, ` - - return ( -
- {billingAccountDetailsContent} -
- {props.canManageProject - ? ( - <> - {managedNoticeMessage} - - click here to update - - - ) - : ( - {noticeMessage} - )} -
- {billingAccountModal} -
- ) - } - - if (!normalizedBillingAccountId) { - return <> - } - - return ( - <> - {billingAccountDetailsContent} - {billingAccountModal} - + const billingAccountModal = renderBillingAccountModal( + billingAccountDetailsData, + isModalOpen, + handleCloseModal, ) + + return renderBillingAccountContent({ + billingAccountDetailsContent, + billingAccountModal, + canManageProject: props.canManageProject, + projectId: props.projectId, + visibleBillingAccountIssue, + }) } export default ProjectBillingAccountExpiredNotice diff --git a/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.spec.tsx b/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.spec.tsx index f247f35e6..c91068d4a 100644 --- a/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.spec.tsx +++ b/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.spec.tsx @@ -1,7 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ import type { ReactNode } from 'react' import { - fireEvent, render, screen, } from '@testing-library/react' @@ -34,6 +33,8 @@ jest.mock('../BillingAccountLineItemsModal', () => ({ })) jest.mock('../../constants', () => ({ + BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED: false, + BILLING_ACCOUNT_DETAILS_MODAL_ENABLED: false, PROJECT_STATUS: { DRAFT: 'draft', }, @@ -145,7 +146,7 @@ describe('ProjectsTable', () => { .toBe('/projects/100440/challenges') }) - it('shows project billing account spent totals and opens the line-item modal', () => { + it('hides billing account spent totals and line-item details while billing details are disabled', () => { mockedUseFetchBillingAccounts.mockReturnValue({ billingAccounts: [ { @@ -184,15 +185,13 @@ describe('ProjectsTable', () => { expect(screen.getAllByText('Access BA / 80001063').length) .toBeGreaterThan(0) - expect(screen.getAllByText('$350 / $1,000 spent').length) - .toBeGreaterThan(0) - - fireEvent.click(screen.getAllByRole('button', { + expect(screen.queryByText('$350 / $1,000 spent')) + .toBeNull() + expect(screen.queryByRole('button', { name: 'View billing account details', - })[0]) - - expect(screen.getByRole('dialog') - .textContent) - .toContain('Billing account details for 80001063') + })) + .toBeNull() + expect(screen.queryByRole('dialog')) + .toBeNull() }) }) diff --git a/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.tsx b/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.tsx index ebcd6dc9c..d99fca69e 100644 --- a/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.tsx +++ b/src/apps/work/src/lib/components/ProjectsTable/ProjectsTable.tsx @@ -14,7 +14,11 @@ import { TableColumn, } from '~/libs/ui' -import { PROJECT_STATUS } from '../../constants' +import { + BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED, + BILLING_ACCOUNT_DETAILS_MODAL_ENABLED, + PROJECT_STATUS, +} from '../../constants' import { useFetchBillingAccountDetails, useFetchBillingAccounts, @@ -237,7 +241,7 @@ interface ProjectBillingAccountCellProps { * modal only after the details button is opened. * * @param props Project row and matching billing-account summary from the list API. - * @returns Billing-account label, spent/total badge, and optional line-item modal. + * @returns Billing-account label, with budget and line-item details shown only when enabled. */ const ProjectBillingAccountCell: FC = ( props: ProjectBillingAccountCellProps, @@ -246,9 +250,14 @@ const ProjectBillingAccountCell: FC = ( const normalizedBillingAccountId = normalizeOptionalString(props.project.billingAccountId) || normalizeOptionalString(props.billingAccount?.id) const billingAccountDetailsResult: UseFetchBillingAccountDetailsResult = useFetchBillingAccountDetails( - isModalOpen ? normalizedBillingAccountId : undefined, + BILLING_ACCOUNT_DETAILS_MODAL_ENABLED && isModalOpen + ? normalizedBillingAccountId + : undefined, ) - const budgetInfo = getBillingAccountBudgetInfo(props.billingAccount) + const billingAccountDetails = billingAccountDetailsResult.billingAccountDetails + const budgetInfo = BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED + ? getBillingAccountBudgetInfo(props.billingAccount) + : undefined const handleOpenModal = useCallback((): void => { setIsModalOpen(true) @@ -257,6 +266,9 @@ const ProjectBillingAccountCell: FC = ( const handleCloseModal = useCallback((): void => { setIsModalOpen(false) }, []) + const shouldShowBillingAccountModal = BILLING_ACCOUNT_DETAILS_MODAL_ENABLED + && isModalOpen + && !!billingAccountDetails return (
@@ -273,7 +285,7 @@ const ProjectBillingAccountCell: FC = ( ) : undefined} - {normalizedBillingAccountId + {BILLING_ACCOUNT_DETAILS_MODAL_ENABLED && normalizedBillingAccountId ? ( ) : undefined} - {isModalOpen && billingAccountDetailsResult.billingAccountDetails + {shouldShowBillingAccountModal && billingAccountDetails ? ( ) diff --git a/src/apps/work/src/lib/constants.ts b/src/apps/work/src/lib/constants.ts index 42837dddb..c7fde3d60 100644 --- a/src/apps/work/src/lib/constants.ts +++ b/src/apps/work/src/lib/constants.ts @@ -2,6 +2,12 @@ import { EnvironmentConfig } from '~/config' export const WORK_APP_BODY_CLASS = 'work-app' +export const BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED = false + +export const BILLING_ACCOUNT_DETAILS_MODAL_ENABLED = false + +export const BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED = false + const DEFAULT_CREATE_FORUM_TYPE_IDS = [ '927abff4-7af9-4145-8ba1-577c16e64e2e', 'dc876fa4-ef2d-4eee-b701-b555fcc6544c',