Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
743aa7d
Version coverage matrix + retry logic + docs
mgascam Sep 4, 2025
aa61895
[Disputes] Persist user-entered values on revisit
mgascam Sep 4, 2025
bb566a6
Stabilize flaky specs and helpers
mgascam Sep 4, 2025
9370ddc
Include disputes UI persistence needed to deflake tests
mgascam Sep 4, 2025
199a7a4
Add changelog
mgascam Sep 4, 2025
57bb481
Revert "Include disputes UI persistence needed to deflake tests"
mgascam Sep 5, 2025
6fa5652
PR feedback: amend docs
mgascam Sep 5, 2025
ccfa166
Merge branch 'develop' into dev/woopmnt-5249-e2e-version-coverage-and-ci
mgascam Sep 5, 2025
97b6efd
Use npm script for re-running failed specs
mgascam Sep 5, 2025
104037d
Merge branch 'dev/woopmnt-5249-e2e-version-coverage-and-ci' into dev/…
mgascam Sep 5, 2025
f7f60f3
Skip flaky test
mgascam Sep 5, 2025
4dd123b
Merge branch 'dev/woopmnt-5249-e2e-fixes' into dev/woopmnt-5249-dispu…
mgascam Sep 5, 2025
01790ff
Re-enable skipped test
mgascam Sep 5, 2025
29ac992
Merge branch 'develop' into dev/woopmnt-5249-e2e-fixes
mgascam Sep 5, 2025
756083b
Merge branch 'dev/woopmnt-5249-e2e-fixes' into dev/woopmnt-5249-dispu…
mgascam Sep 5, 2025
35b3693
Merge branch 'develop' into dev/woopmnt-5249-disputes-ui
mgascam Sep 8, 2025
48fdbc4
Merge branch 'develop' into dev/woopmnt-5249-disputes-ui
mgascam Sep 8, 2025
4c350a0
Enhance dispute evidence submission flow with loading state handling …
mgascam Sep 8, 2025
ca1795e
Re-enable skipped test
mgascam Sep 8, 2025
fcb40c5
Add changelog
mgascam Sep 8, 2025
3a8e92d
Merge branch 'develop' into dev/woopmnt-5249-disputes-ui
mgascam Sep 9, 2025
58411c5
Merge branch 'develop' into dev/woopmnt-5249-disputes-ui
mgascam Sep 9, 2025
8819044
Merge branch 'develop' into dev/woopmnt-5249-disputes-ui
mgascam Sep 11, 2025
2b08ea4
Merge branch 'develop' into dev/woopmnt-5249-disputes-ui
mgascam Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/dev-woopmnt-5249-disputes-ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: add

Disputes - Add a loading state to the “Challenge dispute” flow
227 changes: 136 additions & 91 deletions client/disputes/new-evidence/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ import RecommendedDocuments from './recommended-documents';
import InlineNotice from 'components/inline-notice';
import ShippingDetails from './shipping-details';
import CoverLetter from './cover-letter';
import { Button, HorizontalRule } from '@wordpress/components';
import {
Button,
HorizontalRule,
Spinner,
Flex,
FlexItem,
} from '@wordpress/components';
import { getAdminUrl } from 'wcpay/utils';
import { StepperPanel } from 'wcpay/components/stepper';
import {
Expand Down Expand Up @@ -98,6 +104,7 @@ export default ( { query }: { query: { id: string } } ) => {
const [ dispute, setDispute ] = useState< any >();
const [ evidence, setEvidence ] = useState< any >( {} );
const [ productType, setProductType ] = useState< string >( '' );
const [ isInitialLoading, setIsInitialLoading ] = useState( true );
const [ currentStep, setCurrentStep ] = useState( 0 );
const [ isAccordionOpen, setIsAccordionOpen ] = useState( true );
const [ productDescription, setProductDescription ] = useState( '' );
Expand Down Expand Up @@ -145,6 +152,7 @@ export default ( { query }: { query: { id: string } } ) => {
useEffect( () => {
const fetchDispute = async () => {
try {
setIsInitialLoading( true );
const d: any = await apiFetch( { path } );
setDispute( d );
// fallback to multiple if no product type is set
Expand Down Expand Up @@ -249,6 +257,8 @@ export default ( { query }: { query: { id: string } } ) => {
}
} catch ( error ) {
createErrorNotice( String( error ) );
} finally {
setIsInitialLoading( false );
}
};
fetchDispute();
Expand Down Expand Up @@ -475,7 +485,7 @@ export default ( { query }: { query: { id: string } } ) => {
shipping_tracking_number: shippingTrackingNumber,
shipping_address: shippingAddress,
customer_purchase_ip: dispute.order?.ip_address,
} ).filter( ( [ value ] ) => value && value !== '' )
} ).filter( ( [ , value ] ) => value && value !== '' )
);

// Update metadata with the current productType
Expand Down Expand Up @@ -514,6 +524,126 @@ export default ( { query }: { query: { id: string } } ) => {
dispute.status !== 'needs_response' &&
dispute.status !== 'warning_needs_response';

// --- Accordion summary content (must be before any early returns) ---
const summaryItems = useMemo( () => {
if ( ! dispute ) return [];
const disputeReasonSummary = reasons[ disputeReason ]?.summary || [];
return [
{
title: __( 'Dispute Amount', 'woocommerce-payments' ),
content: formatExplicitCurrency(
dispute.amount,
dispute.currency
),
},
{
title: __( 'Disputed On', 'woocommerce-payments' ),
content: dispute.created
? formatDateTimeFromTimestamp( dispute.created, {
separator: ', ',
includeTime: false,
} )
: '–',
},
{
title: __( 'Reason', 'woocommerce-payments' ),
content: (
<>
{ reasons[ disputeReason ]?.display || disputeReason }
{ disputeReasonSummary.length > 0 && (
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Learn more',
'woocommerce-payments'
) }
content={
<div className="dispute-reason-tooltip">
<p>
{ reasons[ disputeReason ]
?.display || disputeReason }
</p>
<Paragraphs>
{ disputeReasonSummary }
</Paragraphs>
<p>
<a
href="https://woocommerce.com/document/woopayments/fraud-and-disputes/managing-disputes/"
target="_blank"
rel="noopener noreferrer"
>
{ __(
'Learn more',
'woocommerce-payments'
) }
</a>
</p>
</div>
}
/>
) }
</>
),
},
{
title: __( 'Respond By', 'woocommerce-payments' ),
content: (
<DisputeDueByDate
dueBy={ dispute.evidence_details?.due_by }
/>
),
},
{
title: __( 'Order', 'woocommerce-payments' ),
content: <OrderLink order={ dispute.order } />,
},
];
}, [ dispute, disputeReason ] );

// Focus on heading when step changes (must be before any early returns)
useEffect( () => {
// Use setTimeout to ensure the DOM has updated with the new step content
const timeoutId = setTimeout( () => {
const headingRef = stepHeadingRefs.current[ currentStep ];
if ( headingRef ) {
headingRef.focus();
}
}, 100 );

return () => clearTimeout( timeoutId );
}, [ currentStep ] );

// --- Initial loading state ---
if ( isInitialLoading ) {
return (
<Page>
<ErrorBoundary>
<Flex
direction="column"
align="center"
justify="center"
className="wcpay-dispute-evidence-new__loading"
aria-busy="true"
aria-live="polite"
data-testid="new-evidence-loading"
>
<FlexItem>
<Spinner />
</FlexItem>
<FlexItem>
<div>
{ __(
'Loading dispute…',
'woocommerce-payments'
) }
</div>
</FlexItem>
</Flex>
</ErrorBoundary>
</Page>
);
}

// --- Handle step changes ---
const handleStepChange = async ( newStep: number ) => {
// Only save if not in readOnly mode
Expand All @@ -532,19 +662,6 @@ export default ( { query }: { query: { id: string } } ) => {
window.scrollTo( { top: 0, behavior: 'smooth' } );
};

// Focus on heading when step changes
useEffect( () => {
// Use setTimeout to ensure the DOM has updated with the new step content
const timeoutId = setTimeout( () => {
const headingRef = stepHeadingRefs.current[ currentStep ];
if ( headingRef ) {
headingRef.focus();
}
}, 100 );

return () => clearTimeout( timeoutId );
}, [ currentStep ] );

const updateProductType = ( newType: string ) => {
recordEvent( 'wcpay_dispute_product_selected', { selection: newType } );
setProductType( newType );
Expand Down Expand Up @@ -682,82 +799,6 @@ export default ( { query }: { query: { id: string } } ) => {
setUploadedFiles( ( prev ) => ( { ...prev, [ key ]: '' } ) );
};

// --- Accordion summary content ---
const summaryItems = useMemo( () => {
if ( ! dispute ) return [];
const disputeReasonSummary = reasons[ disputeReason ]?.summary || [];
return [
{
title: __( 'Dispute Amount', 'woocommerce-payments' ),
content: formatExplicitCurrency(
dispute.amount,
dispute.currency
),
},
{
title: __( 'Disputed On', 'woocommerce-payments' ),
content: dispute.created
? formatDateTimeFromTimestamp( dispute.created, {
separator: ', ',
includeTime: false,
} )
: '–',
},
{
title: __( 'Reason', 'woocommerce-payments' ),
content: (
<>
{ reasons[ disputeReason ]?.display || disputeReason }
{ disputeReasonSummary.length > 0 && (
<ClickTooltip
buttonIcon={ <HelpOutlineIcon /> }
buttonLabel={ __(
'Learn more',
'woocommerce-payments'
) }
content={
<div className="dispute-reason-tooltip">
<p>
{ reasons[ disputeReason ]
?.display || disputeReason }
</p>
<Paragraphs>
{ disputeReasonSummary }
</Paragraphs>
<p>
<a
href="https://woocommerce.com/document/woopayments/fraud-and-disputes/managing-disputes/"
target="_blank"
rel="noopener noreferrer"
>
{ __(
'Learn more',
'woocommerce-payments'
) }
</a>
</p>
</div>
}
/>
) }
</>
),
},
{
title: __( 'Respond By', 'woocommerce-payments' ),
content: (
<DisputeDueByDate
dueBy={ dispute.evidence_details?.due_by }
/>
),
},
{
title: __( 'Order', 'woocommerce-payments' ),
content: <OrderLink order={ dispute.order } />,
},
];
}, [ dispute, disputeReason ] );

// --- Recommended documents ---
const recommendedDocumentFields = getRecommendedDocumentFields(
disputeReason,
Expand Down Expand Up @@ -1053,6 +1094,7 @@ export default ( { query }: { query: { id: string } } ) => {
<Button
variant="tertiary"
onClick={ () => doSave( false ) }
data-testid="save-for-later-button"
__next40pxDefaultSize
>
{ __(
Expand Down Expand Up @@ -1093,6 +1135,7 @@ export default ( { query }: { query: { id: string } } ) => {
<Button
variant="tertiary"
onClick={ () => doSave( false ) }
data-testid="save-for-later-button"
__next40pxDefaultSize
>
{ __(
Expand Down Expand Up @@ -1132,6 +1175,7 @@ export default ( { query }: { query: { id: string } } ) => {
<Button
variant="tertiary"
onClick={ () => doSave( false ) }
data-testid="save-for-later-button"
__next40pxDefaultSize
>
{ __( 'Save for later', 'woocommerce-payments' ) }
Expand All @@ -1151,6 +1195,7 @@ export default ( { query }: { query: { id: string } } ) => {
doSave( true );
}
} }
data-testid="submit-evidence-button"
__next40pxDefaultSize
>
{ __( 'Submit', 'woocommerce-payments' ) }
Expand Down
Loading
Loading