diff --git a/.github/workflows/e2e-pull-request.yml b/.github/workflows/e2e-pull-request.yml index 4ed61a3882b..e1226644daf 100644 --- a/.github/workflows/e2e-pull-request.yml +++ b/.github/workflows/e2e-pull-request.yml @@ -3,8 +3,9 @@ name: E2E Tests - Pull Request on: pull_request: branches: - - develop - - trunk + # - develop + # - trunk + - dev/10240-update-npm-scripts # [TODO] Remove this line after the branch is merged. workflow_dispatch: workflow_call: inputs: diff --git a/assets/css/admin.rtl.css b/assets/css/admin.rtl.css index d43de4d4b7f..193b24527f8 100644 --- a/assets/css/admin.rtl.css +++ b/assets/css/admin.rtl.css @@ -155,11 +155,93 @@ padding-top: 2px; } +/* WCPay Fraud Risk Level meta box */ +.wcpay-fraud-risk-level { + border-bottom: 1px solid #ddd; + padding: 8px 12px; +} + +.wcpay-fraud-risk-level > p { + margin: 0; +} + +.wcpay-fraud-risk-level__title { + font-weight: 600; +} + +.wcpay-fraud-risk-level__bar { + display: grid; + gap: 4px; + grid-template-columns: 50% auto; + margin: 6px 0 8px; +} + +.wcpay-fraud-risk-level__bar::after, +.wcpay-fraud-risk-level__bar::before { + background-color: #bbb; + content: ''; + border-radius: 4px; + height: 4px; +} + +.wcpay-fraud-risk-level--normal .wcpay-fraud-risk-level__title { + color: #008a20; +} + +.wcpay-fraud-risk-level--normal .wcpay-fraud-risk-level__bar { + grid-template-columns: 15% auto; +} + +.wcpay-fraud-risk-level--normal .wcpay-fraud-risk-level__bar::before { + background-color: #008a20; +} + +.wcpay-fraud-risk-level--elevated .wcpay-fraud-risk-level__title { + color: #b16202; +} + +.wcpay-fraud-risk-level--elevated .wcpay-fraud-risk-level__bar { + grid-template-columns: 60% auto; +} + +.wcpay-fraud-risk-level--elevated .wcpay-fraud-risk-level__bar::before { + background-color: #b16202; +} + +.wcpay-fraud-risk-level--highest .wcpay-fraud-risk-level__bar::before { + background-color: #b32d2e; +} + +.wcpay-fraud-risk-level--highest .wcpay-fraud-risk-level__bar { + grid-template-columns: 100% auto; +} + +.wcpay-fraud-risk-level--highest .wcpay-fraud-risk-level__bar::before { + background-color: #b32d2e; +} + +/* WCPay Fraud Risk Action meta box */ +#wcpay-order-fraud-and-risk-meta-box div.inside { + margin-top: 0; + padding: 0; +} + +.wcpay-fraud-risk-action { + padding: 8px 12px 12px; +} + +.wcpay-fraud-risk-action > p { + margin: 0 0 6px; +} + +.wcpay-fraud-risk-action > p:last-child { + margin-bottom: 0; +} + .wcpay-fraud-risk-meta-allow, .wcpay-fraud-risk-meta-review, .wcpay-fraud-risk-meta-blocked { font-weight: 600; - font-size: 14px; } .wcpay-fraud-risk-meta-allow img { diff --git a/assets/css/success.css b/assets/css/success.css index b07595fda5d..b014a6d0e44 100644 --- a/assets/css/success.css +++ b/assets/css/success.css @@ -8,6 +8,7 @@ .wc-payment-gateway-method-logo-wrapper img { margin-right: 0.5rem; + max-height: 20px; } .wc-payment-gateway-method-logo-wrapper.wc-payment-lpm-logo img { diff --git a/assets/css/success.rtl.css b/assets/css/success.rtl.css index c0778375319..811cc09ba1f 100644 --- a/assets/css/success.rtl.css +++ b/assets/css/success.rtl.css @@ -8,8 +8,13 @@ .wc-payment-gateway-method-logo-wrapper img { margin-left: 0.5rem; + max-height: 20px; } .wc-payment-gateway-method-logo-wrapper.wc-payment-lpm-logo img { max-height: 26px; } + +.wc-payment-gateway-method-logo-wrapper.wc-payment-card-logo img { + max-height: 1em; +} diff --git a/assets/images/banner.png b/assets/images/banner.png deleted file mode 100644 index ae8772ff316..00000000000 Binary files a/assets/images/banner.png and /dev/null differ diff --git a/assets/images/cards/cartes_bancaires.svg b/assets/images/cards/cartes_bancaires.svg index 94f51339e65..9bb5b3ec6c0 100644 --- a/assets/images/cards/cartes_bancaires.svg +++ b/assets/images/cards/cartes_bancaires.svg @@ -1,4 +1,30 @@ - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/cards/diners.svg b/assets/images/cards/diners.svg index 6e2242f0824..58731692b19 100644 --- a/assets/images/cards/diners.svg +++ b/assets/images/cards/diners.svg @@ -1,19 +1,12 @@ - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/assets/images/cards/eftpos.svg b/assets/images/cards/eftpos.svg new file mode 100644 index 00000000000..68f579f18b6 --- /dev/null +++ b/assets/images/cards/eftpos.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/cards/unionpay.svg b/assets/images/cards/unionpay.svg index 69308a567a1..6b53f36d765 100644 --- a/assets/images/cards/unionpay.svg +++ b/assets/images/cards/unionpay.svg @@ -1,14 +1,98 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/cards/woo-card.svg b/assets/images/cards/woo-card.svg index 9ca1d3dedb2..73d345f196f 100644 --- a/assets/images/cards/woo-card.svg +++ b/assets/images/cards/woo-card.svg @@ -1 +1,63 @@ - + + + + + + + + + + + + + + + + diff --git a/assets/images/icons/close.svg b/assets/images/icons/close.svg new file mode 100644 index 00000000000..99970341e34 --- /dev/null +++ b/assets/images/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/illustrations/setup.svg b/assets/images/illustrations/setup.svg index b5c8abde96d..0acbe18100f 100644 --- a/assets/images/illustrations/setup.svg +++ b/assets/images/illustrations/setup.svg @@ -1 +1,33 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/logo.svg b/assets/images/logo.svg index 2e406e07269..6e488dcbae0 100644 --- a/assets/images/logo.svg +++ b/assets/images/logo.svg @@ -1,14 +1,4 @@ - - - - - - - - - - - - - + + + diff --git a/assets/images/payment-method-icons/affirm.svg b/assets/images/payment-method-icons/affirm.svg index 142ddff5361..0d5176932bc 100644 --- a/assets/images/payment-method-icons/affirm.svg +++ b/assets/images/payment-method-icons/affirm.svg @@ -1 +1,14 @@ - + + + + + + + + + + + + + + diff --git a/assets/images/payment-method-icons/afterpay.svg b/assets/images/payment-method-icons/afterpay.svg index d61125e3b66..7823737ad64 100644 --- a/assets/images/payment-method-icons/afterpay.svg +++ b/assets/images/payment-method-icons/afterpay.svg @@ -1,21 +1,12 @@ - - - - - - - - - - + + + + + + + + + + + diff --git a/assets/images/payment-method-icons/amex.svg b/assets/images/payment-method-icons/amex.svg index f476392e52a..7b5bf66f9b8 100644 --- a/assets/images/payment-method-icons/amex.svg +++ b/assets/images/payment-method-icons/amex.svg @@ -1,20 +1,12 @@ - - - - - - - - - - + + + + + + + + + + + diff --git a/assets/images/payment-method-icons/applepay.svg b/assets/images/payment-method-icons/applepay.svg index c0d1dc61af3..d8695298672 100644 --- a/assets/images/payment-method-icons/applepay.svg +++ b/assets/images/payment-method-icons/applepay.svg @@ -1 +1,5 @@ - + + + + + diff --git a/assets/images/payment-method-icons/discover.svg b/assets/images/payment-method-icons/discover.svg index 7f7a66af36f..a41879771d0 100644 --- a/assets/images/payment-method-icons/discover.svg +++ b/assets/images/payment-method-icons/discover.svg @@ -1 +1,39 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/payment-method-icons/gpay.svg b/assets/images/payment-method-icons/gpay.svg index e70aae5d46c..48f91b5c40d 100644 --- a/assets/images/payment-method-icons/gpay.svg +++ b/assets/images/payment-method-icons/gpay.svg @@ -1 +1,9 @@ - + + + + + + + + + diff --git a/assets/images/payment-method-icons/jcb.svg b/assets/images/payment-method-icons/jcb.svg index 697c616c071..f29d4b4726e 100644 --- a/assets/images/payment-method-icons/jcb.svg +++ b/assets/images/payment-method-icons/jcb.svg @@ -1 +1,68 @@ - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/payment-method-icons/klarna.svg b/assets/images/payment-method-icons/klarna.svg index 27151a76668..aaf21506737 100644 --- a/assets/images/payment-method-icons/klarna.svg +++ b/assets/images/payment-method-icons/klarna.svg @@ -1,15 +1,4 @@ - - - + + + diff --git a/assets/images/payment-method-icons/mastercard.svg b/assets/images/payment-method-icons/mastercard.svg index c8c61efb07f..f4a3d10b601 100644 --- a/assets/images/payment-method-icons/mastercard.svg +++ b/assets/images/payment-method-icons/mastercard.svg @@ -1 +1,7 @@ - + + + + + + + diff --git a/assets/images/payment-method-icons/visa.svg b/assets/images/payment-method-icons/visa.svg index ffcf787ed7c..264ad64dcd6 100644 --- a/assets/images/payment-method-icons/visa.svg +++ b/assets/images/payment-method-icons/visa.svg @@ -1 +1,6 @@ - + + + + + + diff --git a/assets/images/payment-method-icons/woopay.svg b/assets/images/payment-method-icons/woopay.svg index 3617507266d..f7bbd77a80e 100644 --- a/assets/images/payment-method-icons/woopay.svg +++ b/assets/images/payment-method-icons/woopay.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + + diff --git a/assets/images/payment-methods/bancontact.svg b/assets/images/payment-methods/bancontact.svg index 02697acc46e..4c2cf84e5ae 100644 --- a/assets/images/payment-methods/bancontact.svg +++ b/assets/images/payment-methods/bancontact.svg @@ -13,4 +13,4 @@ - + \ No newline at end of file diff --git a/assets/images/payment-methods/becs.svg b/assets/images/payment-methods/becs.svg new file mode 100644 index 00000000000..736525b21c1 --- /dev/null +++ b/assets/images/payment-methods/becs.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/payment-methods/ideal.svg b/assets/images/payment-methods/ideal.svg index 8ddcf64e013..3b8e35ed046 100644 --- a/assets/images/payment-methods/ideal.svg +++ b/assets/images/payment-methods/ideal.svg @@ -1,10 +1,32 @@ - - - - - - + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/assets/images/payment-methods/przelewy24.svg b/assets/images/payment-methods/przelewy24.svg new file mode 100644 index 00000000000..9b0d5a7a648 --- /dev/null +++ b/assets/images/payment-methods/przelewy24.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/payment-methods/woo-short.svg b/assets/images/payment-methods/woo-short.svg new file mode 100644 index 00000000000..86f9239c8b6 --- /dev/null +++ b/assets/images/payment-methods/woo-short.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/payment-methods/woo.svg b/assets/images/payment-methods/woo.svg index 02a570b4bff..4ba29ecc057 100644 --- a/assets/images/payment-methods/woo.svg +++ b/assets/images/payment-methods/woo.svg @@ -1 +1,4 @@ - + + + + diff --git a/assets/images/woo-logo.svg b/assets/images/woo-logo.svg index 21cd27114b3..f03bec541a5 100644 --- a/assets/images/woo-logo.svg +++ b/assets/images/woo-logo.svg @@ -1,13 +1,5 @@ - - - - - - - - - - - - + + + + diff --git a/assets/images/woopay.svg b/assets/images/woopay.svg index c89b1a041f5..08b052dc924 100644 --- a/assets/images/woopay.svg +++ b/assets/images/woopay.svg @@ -1,23 +1,4 @@ - - - - - - - - + + + diff --git a/assets/images/woopayments.svg b/assets/images/woopayments.svg index a11a88e7d2c..b79c26ba367 100644 --- a/assets/images/woopayments.svg +++ b/assets/images/woopayments.svg @@ -1 +1,11 @@ - \ No newline at end of file + + + + + + + + + + + diff --git a/changelog.txt b/changelog.txt index 91a25628b19..62ee75bda53 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,75 @@ *** WooPayments Changelog *** += 8.9.0 - 2025-02-04 = +* Add - Add a popover to WooPayments to present all possible payment methods +* Add - Added persistent column visibility preferences for reporting tables, allowing merchants to customize and save their preferred table view layouts across sessions. +* Add - Add support for `WP_DEVELOPMENT_MODE`. Any non-empty setting will force the plugin into development mode. +* Add - Add wcpay_capture_error_amount_too_small error type and minimum amount details when capturing payments below the supported threshold. +* Add - Admin analytics page E2E tests for Playwright +* Add - Enable "Reader fee" type filtering for transactions list. +* Add - Show Bank reference key on top of the payout details page, whenever available. +* Fix - Add currency code to fee breakdown when multi-currency is enabled, and currencies share the same symbol. +* Fix - Added timestamp to the order note when terminal payment fails. +* Fix - fix: add payment method page query initialization - ensuring that the WP_Query is initialized when checking for payment methods availability +* Fix - fix: avoid ECE error when no address is provided on initialization +* Fix - Fix cart subtotal amount when manually renewing a subscription. +* Fix - Fixed fraud prevention token not available on blocks checkout +* Fix - Fix flaky Multi-Currency test. +* Fix - Fix global styling for popovers +* Fix - Fix guest users being able to buy subscriptions with WooPay via Direct Checkout. +* Fix - Fix incorrect payment method logos used on WooPayment Settings +* Fix - Fix the amounts in the Filter by Price block to reflect the shopper's selected currency. +* Fix - Pass theme editor footer and header styles to WooPay. +* Fix - Prevent potential fatal when initializing the WooPay express checkout button. +* Fix - Set a default minimum height to the WooPay button. +* Fix - Update gateway form fields references to prevent errors. +* Fix - Update set up live payments task list item copy +* Fix - WooPay blocks checkout terms and condition default text +* Update - Design improvements related to the overview page +* Update - feat: GooglePay/ApplePay refactor to leverage Store API is enabled by default. Please contact us if you encounter new issues with these buttons. +* Update - Improve loading experience in embedded KYC +* Update - Remove date format notice across all admin pages. +* Update - Removes Sofort payment method from settings and checkout, permanently deprecates Sofort from settings. +* Update - Replace payments overview page connection success notice with a modal for live accounts. +* Update - Simplify localization of CSV exports to use user language settings from WP Admin, allowing the CSV export to match the localization of the data presented in the admin UI. +* Update - Update the loader image for test account creation +* Update - Woo Colors and logos +* Dev - Add E2E environment variables to Playwright config. +* Dev - Add e2e tests for the multi-currency widget setup. +* Dev - Converted E2E merchant-progressive-onboarding spec from Puppeteer to Playwright. +* Dev - Convert merchant-orders-partial-refund spec from Puppeteer to Playwright. +* Dev - Convert merchant admin transactions E2E test to Playwright +* Dev - Convert order refund failure E2E tests to Playwright +* Dev - Convert shopper-checkout-purchase spec from Puppeteer to Playwright +* Dev - Convert shopper-multi-currency-widget spec from Puppeteer to Playwright +* Dev - Convert shopper checkout save card and purchase test to Playwright +* Dev - Convert shopper checkout with site editor theme spec to Playwright +* Dev - Convert shopper checkout with UPE methods E2E test to Playwright, split saving card tests to separate file +* Dev - Convert shopper free coupon checkout E2E test to Playwright +* Dev - Convert shopper purchase multiple subscriptions E2E tests to Playwright +* Dev - Convert Shopper WC Blocks saved card checkout and usage test to Playwright +* Dev - Convert the merchant orders status change spec from Puppeteer to Playwright. +* Dev - Convert the shopper-myaccount-payment-methods-add-fail spec from Puppeteer to Playwright. +* Dev - E2E Playwright Migration: convert Admin Disputes spec +* Dev - E2E Playwright Migration: convert Order Manual Capture spec +* Dev - E2E Playwright Migration: convert shopper-checkout-failures spec to Playwright and remove Puppeteer spec. +* Dev - E2E Playwright Migration: convert shopper-wc-blocks-checkout-purchase spec +* Dev - Enhance log file format to provide more information about the request flow. +* Dev - Fix flakiness in saved card tests caused by selling the same cart multiple times, triggering duplicate order protection +* Dev - Migrate merchant subscription settings spec to Playwright +* Dev - Migrate shopper subscription free-trial purchase E2E test to Playwright +* Dev - Migrate shopper subscriptions - No signup fee E2E test to Playwright +* Dev - Migrate the merchant subscriptions renew action scheduler spec from Puppeteer to Playwright. +* Dev - Migrate the Shopper Renew Subscription spec to Playwright and remove the corresponding Puppeteer test. +* Dev - Migrate the Shopper Subscriptions Manage Payments spec to Playwright and remove the corresponding Puppeteer test. +* Dev - Optimise memory consumption when formatting log entries. +* Dev - Port merchant subscription renewal E2E test to Playwright +* Dev - Refactoring of snackbar checks in Playwright e2e tests +* Dev - Refresh customer instance with REST API, replace customer creation by new order with anonymous customer +* Dev - Removed unused get single transaction endpoint +* Dev - Remove the subscriptions spec for Puppeteer +* Dev - Restore activated currencies after all tests that deactivate current ones. + = 8.8.0 - 2025-01-15 = * Add - Allow transactions filtered by Payment Method. * Add - Falback terms and conditions for WooPay. diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js index 3a12347327f..9dee1ec0a99 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/add-payment-methods-task.test.js @@ -66,11 +66,9 @@ describe( 'AddPaymentMethodsTask', () => { 'card', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', 'sepa_debit', - 'sofort', ] ); useGetPaymentMethodStatuses.mockReturnValue( { card_payments: { @@ -85,10 +83,6 @@ describe( 'AddPaymentMethodsTask', () => { status: upeCapabilityStatuses.ACTIVE, requirements: [], }, - giropay_payments: { - status: upeCapabilityStatuses.ACTIVE, - requirements: [], - }, ideal_payments: { status: upeCapabilityStatuses.ACTIVE, requirements: [], @@ -101,10 +95,6 @@ describe( 'AddPaymentMethodsTask', () => { status: upeCapabilityStatuses.ACTIVE, requirements: [], }, - sofort_payments: { - status: upeCapabilityStatuses.ACTIVE, - requirements: [], - }, } ); useSettings.mockReturnValue( { saveSettings: () => Promise.resolve( true ), @@ -186,11 +176,9 @@ describe( 'AddPaymentMethodsTask', () => { const expectedToBeUnchecked = [ 'Bancontact', 'EPS', - 'giropay', 'iDEAL', 'Przelewy24 (P24)', 'SEPA Direct Debit', - 'Sofort', ]; expectedToBeUnchecked.forEach( function ( checkboxName ) { @@ -243,11 +231,9 @@ describe( 'AddPaymentMethodsTask', () => { const expectedToBeUnchecked = [ 'Bancontact', 'EPS', - 'giropay', 'iDEAL', 'Przelewy24 (P24)', 'SEPA Direct Debit', - 'Sofort', ]; expectedToBeUnchecked.forEach( function ( checkboxName ) { @@ -294,15 +280,7 @@ describe( 'AddPaymentMethodsTask', () => { it( 'should remove the un-checked payment methods, if they were present before', async () => { const setCompletedMock = jest.fn(); const updateEnabledPaymentMethodsMock = jest.fn(); - const initialMethods = [ - 'card', - 'bancontact', - 'eps', - 'giropay', - 'p24', - 'ideal', - 'sofort', - ]; + const initialMethods = [ 'card', 'bancontact', 'eps', 'p24', 'ideal' ]; useEnabledPaymentMethodIds.mockReturnValue( [ initialMethods, updateEnabledPaymentMethodsMock, @@ -321,11 +299,9 @@ describe( 'AddPaymentMethodsTask', () => { const expectedToBeUnchecked = [ 'Bancontact', 'EPS', - 'giropay', 'iDEAL', 'Przelewy24 (P24)', 'SEPA Direct Debit', - 'Sofort', ]; expectedToBeUnchecked.forEach( function ( checkboxName ) { @@ -335,7 +311,7 @@ describe( 'AddPaymentMethodsTask', () => { jest.useFakeTimers(); // Uncheck methods. act( () => { - const methodsToCheck = [ 'Bancontact', 'giropay' ]; + const methodsToCheck = [ 'Bancontact', 'iDEAL' ]; methodsToCheck.forEach( function ( checkboxName ) { userEvent.click( screen.getByLabelText( checkboxName ) ); jest.runOnlyPendingTimers(); @@ -350,7 +326,7 @@ describe( 'AddPaymentMethodsTask', () => { expect( updateEnabledPaymentMethodsMock ).toHaveBeenCalledWith( [ 'card', 'bancontact', - 'giropay', + 'ideal', ] ); await waitFor( () => expect( setCompletedMock ).toHaveBeenCalledWith( @@ -376,10 +352,6 @@ describe( 'AddPaymentMethodsTask', () => { status: upeCapabilityStatuses.ACTIVE, requirements: [], }, - giropay_payments: { - status: upeCapabilityStatuses.PENDING_APPROVAL, - requirements: [], - }, ideal_payments: { status: upeCapabilityStatuses.ACTIVE, requirements: [], @@ -392,10 +364,6 @@ describe( 'AddPaymentMethodsTask', () => { status: upeCapabilityStatuses.PENDING_VERIFICATION, requirements: [], }, - sofort_payments: { - status: upeCapabilityStatuses.ACTIVE, - requirements: [], - }, } ); render( @@ -422,9 +390,9 @@ describe( 'AddPaymentMethodsTask', () => { it( 'should render the activation modal when requirements exist for the payment method', () => { const setCompletedMock = jest.fn(); useEnabledPaymentMethodIds.mockReturnValue( [ [ 'card' ], jest.fn() ] ); - useGetAvailablePaymentMethodIds.mockReturnValue( [ 'sofort' ] ); + useGetAvailablePaymentMethodIds.mockReturnValue( [ 'ideal' ] ); useGetPaymentMethodStatuses.mockReturnValue( { - sofort_payments: { + ideal_payments: { status: upeCapabilityStatuses.UNREQUESTED, requirements: [ 'company.tax_id' ], }, @@ -440,9 +408,9 @@ describe( 'AddPaymentMethodsTask', () => { ); - expect( screen.queryByLabelText( 'Sofort' ) ).toBeInTheDocument(); + expect( screen.queryByLabelText( 'iDEAL' ) ).toBeInTheDocument(); - const cardCheckbox = screen.getByLabelText( 'Sofort' ); + const cardCheckbox = screen.getByLabelText( 'iDEAL' ); expect( cardCheckbox ).not.toBeChecked(); @@ -456,7 +424,7 @@ describe( 'AddPaymentMethodsTask', () => { expect( screen.queryByText( - /You need to provide more information to enable Sofort on your checkout/ + /You need to provide more information to enable iDEAL on your checkout/ ) ).toBeInTheDocument(); diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js index cc2abefe96d..8b75261455d 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/currency-information-for-methods.test.js @@ -65,10 +65,8 @@ describe( 'CurrencyInformationForMethods', () => { 'card', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', - 'sofort', 'sepa_debit', ] } /> @@ -89,10 +87,8 @@ describe( 'CurrencyInformationForMethods', () => { 'card', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', - 'sofort', 'sepa_debit', ] } /> @@ -120,10 +116,8 @@ describe( 'CurrencyInformationForMethods', () => { 'card', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', - 'sofort', 'sepa_debit', ] } /> diff --git a/client/additional-methods-setup/upe-preview-methods-selector/test/setup-complete-task.test.js b/client/additional-methods-setup/upe-preview-methods-selector/test/setup-complete-task.test.js index fe312f4abcc..81ae65f67af 100644 --- a/client/additional-methods-setup/upe-preview-methods-selector/test/setup-complete-task.test.js +++ b/client/additional-methods-setup/upe-preview-methods-selector/test/setup-complete-task.test.js @@ -21,16 +21,7 @@ jest.mock( '../../../data', () => ( { describe( 'SetupComplete', () => { beforeEach( () => { useEnabledPaymentMethodIds.mockReturnValue( [ - [ - 'card', - 'bancontact', - 'eps', - 'giropay', - 'sofort', - 'ideal', - 'p24', - 'sepa_debit', - ], + [ 'card', 'bancontact', 'eps', 'ideal', 'p24', 'sepa_debit' ], () => null, ] ); } ); @@ -75,10 +66,8 @@ describe( 'SetupComplete', () => { 'card', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', - 'sofort', 'sepa_debit', ], }, @@ -98,7 +87,7 @@ describe( 'SetupComplete', () => { it( 'renders setup complete messaging when context value says that one payment method has been removed', () => { useEnabledPaymentMethodIds.mockReturnValue( [ - [ 'card', 'sofort' ], + [ 'card', 'ideal' ], () => null, ] ); render( @@ -110,10 +99,8 @@ describe( 'SetupComplete', () => { 'card', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', - 'sofort', 'sepa_debit', ], }, @@ -133,7 +120,7 @@ describe( 'SetupComplete', () => { it( 'renders setup complete messaging when context value says that one payment method has been added', () => { useEnabledPaymentMethodIds.mockReturnValue( [ - [ 'card', 'giropay' ], + [ 'card', 'ideal' ], () => null, ] ); render( @@ -161,10 +148,8 @@ describe( 'SetupComplete', () => { const additionalMethods = [ 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', - 'sofort', 'sepa_debit', ]; useEnabledPaymentMethodIds.mockReturnValue( [ diff --git a/client/banner/banner.scss b/client/banner/banner.scss deleted file mode 100644 index 6ca7b6beb78..00000000000 --- a/client/banner/banner.scss +++ /dev/null @@ -1,40 +0,0 @@ -.woocommerce-payments-banner { - margin-bottom: 32px; - - svg { - max-width: 100%; - } - - &.account-page { - border: none; - border-bottom: 1px solid $studio-gray-5; - border-radius: 0; - .components-card__body { - padding: 24px 24px; - } - } - - @include breakpoint( '>480px' ) { - .components-card__body { - background: url( 'assets/images/banner.png?asset' ) no-repeat right; - background-size: contain; - } - } - - .woocommerce-payments-banner-logo { - margin-left: -$gap-large; - } - - .woocommerce-payments-banner-pill { - /* adapted from https://github.com/woocommerce/woocommerce-admin/blob/main/packages/components/src/pill/style.scss */ - font-size: 12px; - border: 1px solid $studio-gray-70; - border-radius: 28px; - color: $studio-gray-70; - display: inline-block; - padding: 2px 8px; - position: relative; - top: -27px; - left: 25px; - } -} diff --git a/client/banner/index.js b/client/banner/index.js deleted file mode 100644 index 7018ffae9df..00000000000 --- a/client/banner/index.js +++ /dev/null @@ -1,50 +0,0 @@ -/** @format */ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import React from 'react'; - -/** - * Internal dependencies - */ -import { Card, CardBody } from '@wordpress/components'; -import WCPayLogo from './wcpay-logo'; -import './banner.scss'; - -const Banner = ( { style } ) => { - let logoWidth, - logoHeight, - showPill, - className = 'woocommerce-payments-banner'; - if ( style === 'account-page' ) { - logoWidth = 196; - logoHeight = 65; - showPill = true; - className += ' account-page'; - } else { - logoWidth = 257; - logoHeight = 70; - showPill = false; - } - return ( - - - - { showPill && ( -
-
- { __( 'Recommended', 'woocommerce-payments' ) } -
-
- ) } - - - ); -}; - -export default Banner; diff --git a/client/banner/wcpay-logo.js b/client/banner/wcpay-logo.js deleted file mode 100644 index 2d4a577b9de..00000000000 --- a/client/banner/wcpay-logo.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Internal dependencies - */ -import logoImg from 'assets/images/logo.svg?asset'; -import { __ } from '@wordpress/i18n'; - -export default ( props ) => ( - { -); diff --git a/client/capital/index.tsx b/client/capital/index.tsx index 469b2d283a8..2a4afa04ff7 100644 --- a/client/capital/index.tsx +++ b/client/capital/index.tsx @@ -25,7 +25,6 @@ import { useLoans } from 'wcpay/data'; import { getAdminUrl } from 'wcpay/utils'; import './style.scss'; import { formatDateTimeFromString } from 'wcpay/utils/date-time'; -import DateFormatNotice from 'wcpay/components/date-format-notice'; const columns = [ { @@ -210,7 +209,6 @@ const CapitalPage = (): JSX.Element => { return ( - { wcpaySettings.accountLoans.has_active_loan && ( @@ -227,6 +225,7 @@ const CapitalPage = (): JSX.Element => { rows={ getRowsData( loans ) } rowsPerPage={ loans.length } summary={ getSummary( loans ) } + // The Capital Loan table does not have column configuration enabled, see issue #10106. showMenu={ false } /> diff --git a/client/capital/test/__snapshots__/index.test.tsx.snap b/client/capital/test/__snapshots__/index.test.tsx.snap index 9f0e93091cf..718c48b0fa8 100644 --- a/client/capital/test/__snapshots__/index.test.tsx.snap +++ b/client/capital/test/__snapshots__/index.test.tsx.snap @@ -4,55 +4,8 @@ exports[`CapitalPage renders the TableCard component with loan data 1`] = `
-
- -
- The date and time formats now match your preferences. You can update them anytime in the - - settings - - . -
- -
diff --git a/client/card-readers/list/index.tsx b/client/card-readers/list/index.tsx index 2cd75d1a639..2b0c4b52ee0 100644 --- a/client/card-readers/list/index.tsx +++ b/client/card-readers/list/index.tsx @@ -36,7 +36,7 @@ const ReadersList = (): JSX.Element => { const { readers, isLoading } = useReaders( 10 ); return ( - + diff --git a/client/card-readers/settings/index.tsx b/client/card-readers/settings/index.tsx index 94d12194348..017db6bbf7b 100644 --- a/client/card-readers/settings/index.tsx +++ b/client/card-readers/settings/index.tsx @@ -38,7 +38,7 @@ const ReceiptSettings = (): JSX.Element => { const areInputsValid = isBusinessInputsValid && isContactsInputsValid; return ( - + diff --git a/client/checkout/express-checkout-buttons.scss b/client/checkout/express-checkout-buttons.scss index be3e02845d8..c153906b5a5 100644 --- a/client/checkout/express-checkout-buttons.scss +++ b/client/checkout/express-checkout-buttons.scss @@ -12,6 +12,7 @@ } > div { + margin: 4px; margin-bottom: 12px; &:last-of-type { diff --git a/client/checkout/upe-styles/index.js b/client/checkout/upe-styles/index.js index bc54434aec1..502cf370382 100644 --- a/client/checkout/upe-styles/index.js +++ b/client/checkout/upe-styles/index.js @@ -167,9 +167,9 @@ export const appearanceSelectors = { buttonSelectors: [ '#place_order' ], linkSelectors: [ 'a' ], containerSelectors: [ '.woocommerce-checkout-review-order-table' ], - headerSelectors: [ '.site-header' ], - footerSelectors: [ '.site-footer' ], - footerLink: [ '.site-footer a' ], + headerSelectors: [ '.site-header', 'header > div' ], + footerSelectors: [ '.site-footer', 'footer > div' ], + footerLink: [ '.site-footer a', 'footer a' ], }, /** diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index de5a870acc2..eeb4a62f119 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -859,7 +859,7 @@ describe( 'isUsingSavedPaymentMethod', () => { } ); test( 'non-tokenized payment gateway is selected', () => { - const paymentMethodType = 'sofort'; + const paymentMethodType = 'ideal'; expect( isUsingSavedPaymentMethod( paymentMethodType ) ).toBe( false ); } ); diff --git a/client/checkout/woopay/express-button/style.scss b/client/checkout/woopay/express-button/style.scss new file mode 100644 index 00000000000..cc33e471acd --- /dev/null +++ b/client/checkout/woopay/express-button/style.scss @@ -0,0 +1,121 @@ +#wcpay-woopay-button { + .woopay-express-button { + font-size: 18px; + font-weight: 500; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', + 'Helvetica Neue', 'Helvetica', 'Roboto', 'Arial', sans-serif; + letter-spacing: 0.8px; + height: 40px; + width: 100%; + background: $white; + border-radius: 4px; + border: none; + color: $studio-black; + display: flex; + align-items: center; + justify-content: center; + white-space: nowrap; + text-transform: none; + list-style-type: none; + min-height: auto; + + .button-content { + display: flex; + align-content: center; + justify-content: center; + transform: scale( 0.9 ); + } + + &:not( :disabled ) { + &:focus, + &:focus-within { + outline: 4px solid $studio-woocommerce-purple-10; + } + + &:hover { + cursor: pointer; + } + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &.is-loading, + &.is-loading:hover, + &.is-loading:disabled { + opacity: 1 !important; + } + + svg { + fill: $studio-woocommerce-purple-60; + width: 99px; + } + + &[data-type='buy'], + &[data-type='book'], + &[data-type='donate'] { + min-width: 200px; + + svg { + margin-left: 5px; + } + } + + &[data-theme='dark'] { + background: $studio-woocommerce-purple-40; + color: $white; + border-color: $studio-woocommerce-purple-40; + + svg { + fill: $white; + } + &:not( :disabled ):hover { + border-color: $studio-woocommerce-purple-30; + background: $studio-woocommerce-purple-30; + } + } + + &[data-theme='light-outline'] { + border: 1px solid $studio-black; + &:not( :disabled ):hover { + background: #e0e0e0; + } + } + + &[data-size='medium'] { + height: 48px; + + &[data-type='buy'], + &[data-type='book'], + &[data-type='donate'] { + min-width: 229px; + } + + .button-content { + transform: scale( 1 ); + } + } + + &[data-size='large'] { + height: 55px; + + &[data-type='buy'], + &[data-type='book'], + &[data-type='donate'] { + min-width: 229px; + } + + .button-content { + transform: scale( 1.1 ); + } + + // For this width range, the button's text is too large to fit + // in the button. The text is reduced to 22px to fit. + @media screen and ( min-width: 785px ) and ( max-width: 850px ) { + font-size: 22px; + } + } + } +} diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index 5362542eed3..483a5a38c66 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -364,40 +364,42 @@ export const WoopayExpressCheckoutButton = ( { }, [] ); return ( - +
+ +
); }; diff --git a/client/checkout/woopay/express-button/woopay-icon-light.js b/client/checkout/woopay/express-button/woopay-icon-light.js index 8d6de3b2d2c..3d60eb9f9fb 100644 --- a/client/checkout/woopay/express-button/woopay-icon-light.js +++ b/client/checkout/woopay/express-button/woopay-icon-light.js @@ -5,43 +5,19 @@ import React from 'react'; export default ( props ) => ( - - - - + - - ); diff --git a/client/checkout/woopay/express-button/woopay-icon.js b/client/checkout/woopay/express-button/woopay-icon.js index 200e42b1aa0..51355e249a5 100644 --- a/client/checkout/woopay/express-button/woopay-icon.js +++ b/client/checkout/woopay/express-button/woopay-icon.js @@ -5,31 +5,13 @@ import React from 'react'; export default ( props ) => ( - - - - + ); diff --git a/client/checkout/woopay/style.scss b/client/checkout/woopay/style.scss index 349ebb18f7f..51ae95e9b5d 100644 --- a/client/checkout/woopay/style.scss +++ b/client/checkout/woopay/style.scss @@ -1,3 +1,4 @@ +@import './express-button/style'; /* stylelint-disable selector-id-pattern */ #contact_details { float: none; @@ -122,161 +123,6 @@ } } -.woopay-express-button { - font-size: 18px; - font-weight: 500; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', - 'Helvetica', 'Roboto', 'Arial', sans-serif; - letter-spacing: 0.8px; - height: 40px; - background: $white !important; - border: 1px solid $white !important; - color: $studio-black !important; - width: 100%; - border-radius: 4px; - padding-top: 1px; - padding-bottom: 1px; - display: flex !important; - align-items: center; - justify-content: center; - white-space: nowrap; - text-transform: none; - list-style-type: none; - - &:not( :disabled ):hover { - background: #e0e0e0 !important; - cursor: pointer; - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - &.is-loading, - &.is-loading:hover, - &.is-loading:disabled { - opacity: 1 !important; - } - - svg { - fill: $studio-woocommerce-purple-60; - position: relative; - top: 1px; - } - - &[data-type='buy'], - &[data-type='book'], - &[data-type='donate'] { - min-width: 200px; - - svg { - flex-shrink: 0.4; - margin-left: 6px; - } - } - - &[data-width-type='wide'] { - svg { - flex-shrink: 1; - } - } - - &[data-theme='dark'] { - background: $studio-woocommerce-purple-60 !important; - color: $white !important; - border-color: $studio-woocommerce-purple-60 !important; - - svg { - fill: $white; - } - &:not( :disabled ):hover { - background: #533582 !important; - } - } - - &[data-theme='light-outline'] { - border-color: $studio-black !important; - &:not( :disabled ):hover { - background: #e0e0e0 !important; - } - } - - &[data-size='medium'] { - font-size: 22px; - height: 48px; - - svg { - top: 2px; - width: auto; - height: 27px; - min-width: 60px; - } - - &[data-type='buy'], - &[data-type='book'], - &[data-type='donate'] { - min-width: 229px; - - svg { - flex-shrink: 0.6; - margin-left: 8px; - } - } - - &[data-width-type='wide'] { - &[data-type='buy'], - &[data-type='book'], - &[data-type='donate'] { - svg { - flex-shrink: 1; - } - } - } - } - - &[data-size='large'] { - font-size: 26px; - height: 55px; - - svg { - top: 3px; - width: auto; - height: 32px; - min-width: 70px; - } - - &[data-type='buy'], - &[data-type='book'], - &[data-type='donate'] { - min-width: 229px; - - svg { - flex-shrink: 0.6; - margin-left: 10px; - margin-bottom: 0.25rem; - } - } - - &[data-width-type='wide'] { - &[data-type='buy'], - &[data-type='book'], - &[data-type='donate'] { - svg { - flex-shrink: 1; - margin-bottom: 0; - } - } - } - - // For this width range, the button's text is too large to fit - // in the button. The text is reduced to 22px to fit. - @media screen and ( min-width: 785px ) and ( max-width: 850px ) { - font-size: 22px; - } - } -} - @keyframes spinner__animation { 0% { animation-timing-function: cubic-bezier( diff --git a/client/components/account-status/account-tools/strings.tsx b/client/components/account-status/account-tools/strings.tsx index 83ca0f9c1bc..143f863ed15 100644 --- a/client/components/account-status/account-tools/strings.tsx +++ b/client/components/account-status/account-tools/strings.tsx @@ -13,7 +13,7 @@ export default { title: __( 'Account Tools', 'woocommerce-payments' ), description: isInTestModeOnboarding() ? __( - 'Your account is in sandbox mode. If you are experiencing problems completing account setup, or wish to test with a different email/country associated with your account, you can reset your account and start from the beginning.', + 'You are using a test account. If you are experiencing problems completing account setup, or wish to test with a different email/country associated with your account, you can reset your account and start from the beginning.', 'woocommerce-payments' ) : __( diff --git a/client/components/confirmation-modal/styles.scss b/client/components/confirmation-modal/styles.scss index 5021df9e9ff..eb25744ba56 100644 --- a/client/components/confirmation-modal/styles.scss +++ b/client/components/confirmation-modal/styles.scss @@ -10,8 +10,11 @@ flex-direction: column; padding: 0 $grid-unit-30 $grid-unit-30; p { - padding: 1em 0; + padding: 0 0 1em 0; margin: 0; + &.no-padding { + padding: 0; + } } } diff --git a/client/components/copy-button/index.tsx b/client/components/copy-button/index.tsx new file mode 100644 index 00000000000..67d14eb4725 --- /dev/null +++ b/client/components/copy-button/index.tsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import React, { useState } from 'react'; +import { __ } from '@wordpress/i18n'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import './style.scss'; + +interface CopyButtonProps { + /** + * The text to copy to the clipboard. + */ + textToCopy: string; + + /** + * The label for the button. Also used as the aria-label. + */ + label: string; +} + +export const CopyButton: React.FC< CopyButtonProps > = ( { + textToCopy, + label, +} ) => { + const [ copied, setCopied ] = useState( false ); + + const copyToClipboard = () => { + navigator.clipboard.writeText( textToCopy ); + setCopied( true ); + }; + + return ( + + + ); +}; diff --git a/client/components/copy-button/style.scss b/client/components/copy-button/style.scss new file mode 100644 index 00000000000..58b4555540c --- /dev/null +++ b/client/components/copy-button/style.scss @@ -0,0 +1,51 @@ +.woopayments-copy-button { + line-height: 1.2em; + display: inline-flex; + background: transparent; + border: none; + border-radius: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; + color: inherit; + margin-left: 2px; + align-items: center; + + i { + display: block; + width: 1.2em; + height: 1.2em; + mask-image: url( 'assets/images/icons/copy.svg?asset' ); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + background-color: currentColor; + + &:hover { + opacity: 0.7; + } + + &:active { + transform: scale( 0.9 ); + } + } + + &.state--copied i { + mask-image: url( 'assets/images/icons/check-green.svg?asset' ); + background-color: $studio-green-50; + animation: copy-indicator 2s forwards; + } + + @keyframes copy-indicator { + 0% { + opacity: 1; + } + 95% { + opacity: 1; + } + // a quick fade-out from 1%→0% at the end + 100% { + opacity: 0; + } + } +} diff --git a/client/components/copy-button/test/__snapshots__/index.test.tsx.snap b/client/components/copy-button/test/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..44cd5831dc5 --- /dev/null +++ b/client/components/copy-button/test/__snapshots__/index.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CopyButton renders the button correctly 1`] = ` +
+ +
+`; diff --git a/client/components/copy-button/test/index.test.tsx b/client/components/copy-button/test/index.test.tsx new file mode 100644 index 00000000000..7a771a7fe39 --- /dev/null +++ b/client/components/copy-button/test/index.test.tsx @@ -0,0 +1,67 @@ +/** @format **/ + +/** + * External dependencies + */ +import React from 'react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; + +/** + * Internal dependencies + */ +import { CopyButton } from '..'; + +describe( 'CopyButton', () => { + it( 'renders the button correctly', () => { + const { container: copyButtonContainer } = render( + + ); + + expect( copyButtonContainer ).toMatchSnapshot(); + } ); + + describe( 'when the button is clicked', () => { + it( 'copies the text to the clipboard and shows copied state', async () => { + render( + + ); + + const button = screen.queryByRole( 'button', { + name: /Copy bank reference ID to clipboard/i, + } ); + + if ( ! button ) { + throw new Error( 'Button not found' ); + } + + //Mock the clipboard API + Object.assign( navigator, { + clipboard: { + writeText: jest.fn().mockResolvedValueOnce( undefined ), + }, + } ); + + await act( async () => { + fireEvent.click( button ); + } ); + + expect( navigator.clipboard.writeText ).toHaveBeenCalledWith( + 'test_bank_reference_id' + ); + expect( button ).toHaveClass( 'state--copied' ); + + act( () => { + fireEvent.animationEnd( button ); + } ); + + expect( button ).not.toHaveClass( 'state--copied' ); + } ); + } ); +} ); diff --git a/client/components/csv-export-modal/index.tsx b/client/components/csv-export-modal/index.tsx deleted file mode 100644 index 2cddf0908ab..00000000000 --- a/client/components/csv-export-modal/index.tsx +++ /dev/null @@ -1,227 +0,0 @@ -/** @format */ -/** - * External dependencies - */ -import React, { useState } from 'react'; -import { __ } from '@wordpress/i18n'; -import { - Button, - SelectControl, - CheckboxControl, - ExternalLink, -} from '@wordpress/components'; -import interpolateComponents from '@automattic/interpolate-components'; -import { useDispatch } from '@wordpress/data'; -import DomainsIcon from 'gridicons/dist/domains'; - -/** - * Internal dependencies - */ -import { ReportingExportLanguageHook } from 'wcpay/settings/reporting-settings/interfaces'; -import { useReportingExportLanguage, useSettings } from 'wcpay/data'; -import ConfirmationModal from 'wcpay/components/confirmation-modal'; -import { getAdminUrl, getExportLanguageOptions } from 'wcpay/utils'; -import './styles.scss'; - -interface CSVExportModalProps { - totalItems: number; - exportType: 'transactions' | 'deposits' | 'disputes'; - onClose: () => void; - onSubmit: ( language: string ) => void; -} - -interface SettingsHook { - isSaving: boolean; - isLoading: boolean; - saveSettings: () => void; -} - -const CVSExportModal: React.FunctionComponent< CSVExportModalProps > = ( { - totalItems, - exportType, - onClose, - onSubmit, -} ) => { - const { updateOptions } = useDispatch( 'wc/admin/options' ); - const { saveSettings } = useSettings() as SettingsHook; - - const [ - exportLanguage, - updateExportLanguage, - ] = useReportingExportLanguage() as ReportingExportLanguageHook; - - const [ modalLanguage, setModalLanguage ] = useState( exportLanguage ); - const [ modalRemember, setModalRemember ] = useState( true ); - - const onDownload = async () => { - onSubmit( modalLanguage ); - - // If the Remember checkbox is checked, dismiss the modal. - if ( modalRemember ) { - await updateOptions( { - wcpay_reporting_export_modal_dismissed: modalRemember, - } ); - - updateExportLanguage( modalLanguage ); - saveSettings(); - - wcpaySettings.reporting.exportModalDismissed = true; - } - }; - - const buttonContent = ( - <> - - - - ); - - const getModalTitle = ( type: string ): string => { - switch ( type ) { - case 'transactions': - return __( - 'Export transactions report', - 'woocommerce-payments' - ); - case 'deposits': - return __( 'Export deposits report', 'woocommerce-payments' ); - case 'disputes': - return __( 'Export disputes report', 'woocommerce-payments' ); - default: - return __( 'Export report', 'woocommerce-payments' ); - } - }; - - const getExportNumberText = ( type: string ): string => { - switch ( type ) { - case 'transactions': - return __( - 'Exporting {{total/}} transactions…', - 'woocommerce-payments' - ); - case 'deposits': - return __( - 'Exporting {{total/}} deposits…', - 'woocommerce-payments' - ); - case 'disputes': - return __( - 'Exporting {{total/}} disputes…', - 'woocommerce-payments' - ); - default: - return __( - 'Exporting {{total/}} rows…', - 'woocommerce-payments' - ); - } - }; - - const getExportLabel = ( type: string ): string => { - switch ( type ) { - case 'transactions': - return __( - 'Export transactions report in', - 'woocommerce-payments' - ); - case 'deposits': - return __( - 'Export deposits report in', - 'woocommerce-payments' - ); - case 'disputes': - return __( - 'Export disputes report in', - 'woocommerce-payments' - ); - default: - return __( 'Export report in', 'woocommerce-payments' ); - } - }; - - const handleExportLanguageChange = ( language: string ) => { - setModalLanguage( language ); - }; - - const handleExportLanguageRememberChange = ( value: boolean ) => { - setModalRemember( value ); - }; - - return ( - { - return false; - } } - > -
- { interpolateComponents( { - mixedString: getExportNumberText( exportType ), - components: { - total: { totalItems }, - }, - } ) } -
- -
-

Settings

- -
-
- - - { getExportLabel( exportType ) } - -
-
- -
-
- -
- - ), - }, - } ) } - checked={ modalRemember } - onChange={ handleExportLanguageRememberChange } - data-testid="export-modal-remember" - /> -
-
-
- ); -}; - -export default CVSExportModal; diff --git a/client/components/csv-export-modal/styles.scss b/client/components/csv-export-modal/styles.scss deleted file mode 100644 index 600a6d2a18a..00000000000 --- a/client/components/csv-export-modal/styles.scss +++ /dev/null @@ -1,51 +0,0 @@ -.reporting-export-modal { - .components-modal__header { - border-bottom: 1px solid #dcdcde !important; - } - - .wcpay-confirmation-modal__footer { - .is-secondary { - box-shadow: none; - } - } - - &__items-number { - border-bottom: 1px solid #dcdcde; - padding: 15px 0; - } - - &__settings { - @include breakpoint( '>660px' ) { - min-width: 500px; - } - - &--language { - display: flex; - flex-wrap: wrap; - } - - &--language-label { - flex: 1 1 200px; - display: flex; - - .domains-icon { - width: 16px; - margin: 7px 0; - } - - .export-label { - padding: 10px 0 0 8px; - } - } - - &--language-select { - flex: 1 1 200px; - } - - &--remember { - p { - padding-top: 0 !important; - } - } - } -} diff --git a/client/components/csv-export-modal/test/__snapshots__/index.test.tsx.snap b/client/components/csv-export-modal/test/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 251b5f8438f..00000000000 --- a/client/components/csv-export-modal/test/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RefundModal it renders correctly 1`] = ` - `; @@ -49,15 +69,35 @@ exports[`DepositsStatus renders blocked status 2`] = ` /> - Temporarily suspended ( - - learn more - - ) +
+
+ + + + + +
+
+
`; @@ -172,15 +212,35 @@ exports[`DepositsStatus renders pending verification status 1`] = ` /> - Temporarily suspended ( - - learn more - - ) +
+
+ + + + + +
+
+
`; diff --git a/client/components/page/index.tsx b/client/components/page/index.tsx index 82f5c81b757..0a269b2dcfd 100644 --- a/client/components/page/index.tsx +++ b/client/components/page/index.tsx @@ -12,6 +12,7 @@ import ErrorBoundary from '../error-boundary'; import './style.scss'; interface PageProps { + id?: string; isNarrow?: boolean; maxWidth?: string | number; className?: string; @@ -21,6 +22,7 @@ interface PageProps { // more concise; we get the `children` prop for free. const Page: React.FC< PageProps > = ( { children, + id = '', maxWidth, isNarrow, className = '', @@ -40,7 +42,11 @@ const Page: React.FC< PageProps > = ( { }, [] ); return ( -
+
{ children }
); diff --git a/client/components/payment-delete-illustration/styles.scss b/client/components/payment-delete-illustration/styles.scss index e667cf0a373..524a44b8b1e 100644 --- a/client/components/payment-delete-illustration/styles.scss +++ b/client/components/payment-delete-illustration/styles.scss @@ -1,13 +1,13 @@ .payment-delete-illustration { &__wrapper { - margin: 80px auto 24px auto; + margin: 59px auto 24px auto; width: 35%; } &__illustrations { position: relative; margin: 0 auto; - width: 122px; + width: 100px; &::before { content: ' '; @@ -18,8 +18,9 @@ right: 0; background: no-repeat url( 'assets/images/cards/unknown.svg?asset' ); transform: translate( -22%, -50% ); - width: 135px; - height: 105px; + height: 78px; + width: 110px; + background-size: 110px 78px; } } @@ -38,11 +39,11 @@ &__payment-cross-icon { position: absolute; top: -24px; - right: -24px; - width: 45px; + right: -16px; + width: 35px; height: auto; fill: #cc1818; - background: $white; + background: #fff; border-radius: 999px; } } diff --git a/client/components/payment-method-logos/index.tsx b/client/components/payment-method-logos/index.tsx index 06c5b35d9f4..beb73857366 100644 --- a/client/components/payment-method-logos/index.tsx +++ b/client/components/payment-method-logos/index.tsx @@ -1,7 +1,8 @@ /** * External dependencies */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; +import { Popover } from '@wordpress/components'; /** * Internal dependencies @@ -17,7 +18,15 @@ import Affirm from 'assets/images/payment-method-icons/affirm.svg?asset'; import Klarna from 'assets/images/payment-method-icons/klarna.svg?asset'; import Jcb from 'assets/images/payment-method-icons/jcb.svg?asset'; import GooglePay from 'assets/images/payment-method-icons/gpay.svg?asset'; - +import Cartebancaire from 'assets/images/cards/cartes_bancaires.svg?asset'; +import UnionPay from 'assets/images/cards/unionpay.svg?asset'; +import Diners from 'assets/images/cards/diners.svg?asset'; +import Eftpos from 'assets/images/cards/eftpos.svg?asset'; +import Ideal from 'assets/images/payment-methods/ideal.svg?asset'; +import Bancontact from 'assets/images/payment-methods/bancontact.svg?asset'; +import Eps from 'assets/images/payment-methods/eps.svg?asset'; +import Becs from 'assets/images/payment-methods/becs.svg?asset'; +import Przelewy24 from 'assets/images/payment-methods/przelewy24.svg?asset'; import './style.scss'; const PaymentMethods = [ @@ -61,17 +70,76 @@ const PaymentMethods = [ name: 'klarna', component: Klarna, }, + { + name: 'cartebancaire', + component: Cartebancaire, + }, + { + name: 'unionpay', + component: UnionPay, + }, + { + name: 'diners', + component: Diners, + }, + { + name: 'eftpos', + component: Eftpos, + }, { name: 'jcb', component: Jcb, }, + { + name: 'bancontact', + component: Bancontact, + }, + { + name: 'becs', + component: Becs, + }, + { + name: 'eps', + component: Eps, + }, + { + name: 'ideal', + component: Ideal, + }, + { + name: 'przelewy24', + component: Przelewy24, + }, ]; -export const WooPaymentMethodsLogos: React.VFC< { +export const WooPaymentsMethodsLogos: React.VFC< { maxElements: number; -} > = ( { maxElements = 10 } ) => { + isWooPayEligible: boolean; +} > = ( { + maxElements = 10, + /** + * Whether the store (location) is eligible for WooPay. + * Based on this we will include or not the WooPay logo in the list. + */ + isWooPayEligible = false, +} ) => { const totalPaymentMethods = 20; const [ maxShownElements, setMaxShownElements ] = useState( maxElements ); + const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); + const popoverTimeoutRef = useRef< NodeJS.Timeout >(); + + // Reduce the total number of payment methods by one if the store is not eligible for WooPay. + const maxSupportedPaymentMethods = isWooPayEligible + ? totalPaymentMethods + : totalPaymentMethods - 1; + + const getMaxShownElements = ( maxElementsNumber: number ) => { + if ( ! isWooPayEligible ) { + return maxElementsNumber + 1; + } + + return maxElementsNumber; + }; useEffect( () => { const updateMaxElements = () => { @@ -88,10 +156,43 @@ export const WooPaymentMethodsLogos: React.VFC< { window.addEventListener( 'resize', updateMaxElements ); }, [ maxElements ] ); + const visiblePaymentMethods = PaymentMethods.slice( + 0, + getMaxShownElements( maxShownElements ) + ).filter( ( pm ) => isWooPayEligible || pm.name !== 'woopay' ); + + const hiddenPaymentMethods = PaymentMethods.slice( + getMaxShownElements( maxShownElements ) + ).filter( ( pm ) => isWooPayEligible || pm.name !== 'woopay' ); + + const showPopover = () => { + if ( popoverTimeoutRef.current ) { + clearTimeout( popoverTimeoutRef.current ); + } + setIsPopoverVisible( true ); + }; + + const hidePopover = () => { + // Add a delay before hiding the popover + popoverTimeoutRef.current = setTimeout( () => { + setIsPopoverVisible( false ); + }, 300 ); // 300ms delay + }; + + // Cleanup timeout on unmount + useEffect( () => { + return () => { + if ( popoverTimeoutRef.current ) { + clearTimeout( popoverTimeoutRef.current ); + } + }; + }, [] ); + return ( - <> -
- { PaymentMethods.slice( 0, maxShownElements ).map( ( pm ) => { +
+ { visiblePaymentMethods + .slice( 0, maxShownElements ) + .map( ( pm ) => { return ( ); } ) } - { maxShownElements < totalPaymentMethods && ( -
- + { totalPaymentMethods - maxShownElements } -
- ) } -
- + { maxShownElements < maxSupportedPaymentMethods && ( +
setIsPopoverVisible( ! isPopoverVisible ) } + onMouseEnter={ showPopover } + onMouseLeave={ hidePopover } + role="button" + tabIndex={ 0 } + onKeyDown={ ( event ) => { + if ( event.key === 'Enter' || event.key === ' ' ) { + setIsPopoverVisible( ! isPopoverVisible ); + } + } } + > + + { maxSupportedPaymentMethods - maxShownElements } + { isPopoverVisible && ( + setIsPopoverVisible( false ) } + onMouseEnter={ showPopover } + onMouseLeave={ hidePopover } + > +
+ { hiddenPaymentMethods.map( ( pm ) => { + return ( + { + ); + } ) } +
+
+ ) } +
+ ) } +
); }; diff --git a/client/components/payment-method-logos/style.scss b/client/components/payment-method-logos/style.scss index ea36db24ecc..70081fb6a08 100644 --- a/client/components/payment-method-logos/style.scss +++ b/client/components/payment-method-logos/style.scss @@ -1,14 +1,13 @@ .connect-account-page__payment-methods--logos { display: flex; align-items: center; - border: 1px solid $gray-300; - border-bottom: none; - padding: 12px; + padding: $gap-small 0; + gap: $gap-smaller; + img { - width: 37px; + width: 38px; height: 24px; - margin-right: 8px; - border: 1px solid $gray-300; + border: 1px solid $gray-100; border-radius: 3px; } @@ -23,4 +22,31 @@ font-size: 11px; font-weight: 600; } + + &-count:hover { + cursor: pointer; + color: rgba( 56, 88, 233, 1 ); + background-color: rgba( 56, 88, 233, 0.15 ); + border-color: rgba( 56, 88, 233, 1 ); + } + + &-inside-popover { + display: grid; + grid-template-columns: repeat( 4, 1fr ); + grid-template-rows: repeat( 3, 1fr ); + gap: $gap-smaller; + padding: 0; + } + + &-popover { + .components-popover__content { + background: $white; + border: 1px solid $gray-100; + border-radius: 3px; + max-width: 246px; + padding: $gap-smaller; + box-shadow: none; + margin-top: $gap-smaller; + } + } } diff --git a/client/components/payment-methods-checkboxes/test/index.test.tsx b/client/components/payment-methods-checkboxes/test/index.test.tsx index 59b231190e0..afbf32084b5 100644 --- a/client/components/payment-methods-checkboxes/test/index.test.tsx +++ b/client/components/payment-methods-checkboxes/test/index.test.tsx @@ -35,11 +35,9 @@ describe( 'PaymentMethodsCheckboxes', () => { const upeMethods = [ [ 'bancontact', true ], [ 'eps', false ], - [ 'giropay', false ], [ 'ideal', false ], [ 'p24', false ], [ 'sepa_debit', false ], - [ 'sofort', false ], ]; render( @@ -62,29 +60,23 @@ describe( 'PaymentMethodsCheckboxes', () => { const paymentMethods = screen.getAllByRole( 'listitem' ); const bancontact = within( paymentMethods[ 0 ] ); const eps = within( paymentMethods[ 1 ] ); - const giropay = within( paymentMethods[ 2 ] ); - const ideal = within( paymentMethods[ 3 ] ); - const p24 = within( paymentMethods[ 4 ] ); - const sepa = within( paymentMethods[ 5 ] ); - const sofort = within( paymentMethods[ 6 ] ); + const ideal = within( paymentMethods[ 2 ] ); + const p24 = within( paymentMethods[ 3 ] ); + const sepa = within( paymentMethods[ 4 ] ); expect( bancontact.getByRole( 'checkbox' ) ).toBeChecked(); expect( eps.getByRole( 'checkbox' ) ).not.toBeChecked(); - expect( giropay.getByRole( 'checkbox' ) ).not.toBeChecked(); expect( ideal.getByRole( 'checkbox' ) ).not.toBeChecked(); expect( p24.getByRole( 'checkbox' ) ).not.toBeChecked(); expect( sepa.getByRole( 'checkbox' ) ).not.toBeChecked(); - expect( sofort.getByRole( 'checkbox' ) ).not.toBeChecked(); jest.useFakeTimers(); act( () => { userEvent.click( bancontact.getByRole( 'checkbox' ) ); userEvent.click( eps.getByRole( 'checkbox' ) ); - userEvent.click( giropay.getByRole( 'checkbox' ) ); userEvent.click( ideal.getByRole( 'checkbox' ) ); userEvent.click( p24.getByRole( 'checkbox' ) ); userEvent.click( sepa.getByRole( 'checkbox' ) ); - userEvent.click( sofort.getByRole( 'checkbox' ) ); jest.runOnlyPendingTimers(); } ); @@ -95,11 +87,9 @@ describe( 'PaymentMethodsCheckboxes', () => { 'bancontact', false ); - expect( handleChange ).toHaveBeenNthCalledWith( 3, 'giropay', true ); - expect( handleChange ).toHaveBeenNthCalledWith( 4, 'ideal', true ); - expect( handleChange ).toHaveBeenNthCalledWith( 5, 'p24', true ); - expect( handleChange ).toHaveBeenNthCalledWith( 6, 'sepa_debit', true ); - expect( handleChange ).toHaveBeenNthCalledWith( 7, 'sofort', true ); + expect( handleChange ).toHaveBeenNthCalledWith( 3, 'ideal', true ); + expect( handleChange ).toHaveBeenNthCalledWith( 4, 'p24', true ); + expect( handleChange ).toHaveBeenNthCalledWith( 5, 'sepa_debit', true ); jest.useRealTimers(); } ); @@ -108,10 +98,10 @@ describe( 'PaymentMethodsCheckboxes', () => { render( { ); - const sofortCheckbox = screen.getByRole( 'checkbox', { - name: 'Sofort', + const idealCheckbox = screen.getByRole( 'checkbox', { + name: 'iDEAL', } ); - expect( sofortCheckbox ).not.toBeChecked(); + expect( idealCheckbox ).not.toBeChecked(); jest.useFakeTimers(); act( () => { - userEvent.click( sofortCheckbox ); + userEvent.click( idealCheckbox ); jest.runOnlyPendingTimers(); } ); expect( handleChange ).toHaveBeenCalledTimes( 1 ); - expect( handleChange ).toHaveBeenNthCalledWith( 1, 'sofort', true ); + expect( handleChange ).toHaveBeenNthCalledWith( 1, 'ideal', true ); jest.useRealTimers(); } ); @@ -161,10 +151,10 @@ describe( 'PaymentMethodsCheckboxes', () => { const page = render( { render( { /> ); - const sofortCheckbox = screen.getByRole( 'checkbox', { - name: 'Sofort', + const idealCheckbox = screen.getByRole( 'checkbox', { + name: 'iDEAL', } ); - expect( sofortCheckbox ).not.toBeChecked(); - userEvent.click( sofortCheckbox ); + expect( idealCheckbox ).not.toBeChecked(); + userEvent.click( idealCheckbox ); expect( handleChange ).toHaveBeenCalledTimes( 0 ); // Because the input is disabled. - expect( sofortCheckbox ).not.toBeChecked(); + expect( idealCheckbox ).not.toBeChecked(); } ); it( "doesn't show the disabled notice pill on payment methods with active and unrequested statuses", () => { @@ -233,20 +223,20 @@ describe( 'PaymentMethodsCheckboxes', () => { render( = ( { <> { interpolateComponents( { mixedString: sprintf( - /* translators: %1$s: WooPayments */ + /* translators: 1: WooPayments */ __( // eslint-disable-next-line max-len - 'Sandbox mode gives you access to all %1$s features while checkout transactions are simulated. {{learnMoreLink}}Learn more{{/learnMoreLink}}', + 'A test account gives you access to all %1$s features while checkout transactions are simulated. {{learnMoreLink}}Learn more{{/learnMoreLink}}', 'woocommerce-payments' ), 'WooPayments' diff --git a/client/components/stripe-spinner/index.tsx b/client/components/stripe-spinner/index.tsx new file mode 100644 index 00000000000..eec0c9cae92 --- /dev/null +++ b/client/components/stripe-spinner/index.tsx @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import React from 'react'; + +/** + * Internal dependencies + */ +import './style.scss'; + +/** + * Copy of the Stripe spinner in order to avoid pulling another Stripe dependency. + */ +const StripeSpinner = () => { + return ( + + + + ); +}; + +export default StripeSpinner; diff --git a/client/components/stripe-spinner/style.scss b/client/components/stripe-spinner/style.scss new file mode 100644 index 00000000000..f8e9fc68816 --- /dev/null +++ b/client/components/stripe-spinner/style.scss @@ -0,0 +1,32 @@ +/* These keyframes and spinner style are copied from the Stripe Spinner component https://docs.stripe.com/stripe-apps/components/spinner?app-sdk-version=8 */ + +@keyframes SpinnerAnimationShow { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes SpinnerAnimationRotation { + 0% { + transform: scaleX( -1 ) rotateZ( 0deg ); + } + 50% { + transform: scaleX( -1 ) rotateZ( -180deg ); + } + 100% { + transform: scaleX( -1 ) rotateZ( -360deg ); + } +} + +.stripe-spinner { + animation: SpinnerAnimationShow 250ms ease, + SpinnerAnimationRotation 0.7s linear infinite; // The timings are exactly like Stripe Spinner component. + transition-timing-function: ease; + transition-property: opacity, transform; + transform-origin: 50% 50%; + display: inline-block; + vertical-align: middle; // Support stripe loader inside the buttons. +} diff --git a/client/connect-account-page/index.tsx b/client/connect-account-page/index.tsx index 42cb5f25f88..bc49d16cdfd 100644 --- a/client/connect-account-page/index.tsx +++ b/client/connect-account-page/index.tsx @@ -31,7 +31,7 @@ import SetupImg from 'assets/images/illustrations/setup.svg?asset'; import strings from './strings'; import './style.scss'; import InlineNotice from 'components/inline-notice'; -import { WooPaymentMethodsLogos } from 'components/payment-method-logos'; +import { WooPaymentsMethodsLogos } from 'components/payment-method-logos'; import WooLogo from 'assets/images/woo-logo.svg?asset'; import { sanitizeHTML } from 'wcpay/utils/sanitize'; import { isInTestModeOnboarding } from 'wcpay/utils'; @@ -56,7 +56,11 @@ const TestDriveLoader: React.FunctionComponent< { - setup + setup @@ -464,7 +468,11 @@ const ConnectAccountPage: React.FC = () => { } return ( - + { errorMessage && ( {
- +

diff --git a/client/connect-account-page/style.scss b/client/connect-account-page/style.scss index 96efc3493c2..b84255609b3 100644 --- a/client/connect-account-page/style.scss +++ b/client/connect-account-page/style.scss @@ -1,154 +1,275 @@ -.connect-account-page { - h2 { - @include wp-title-small; - color: $gray-90; - margin: 0; - } +#connect-account-page { + .connect-account-page { + h2 { + @include wp-title-small; + color: $gray-90; + margin: 0; + } - &__heading { - padding: $gap-large $gap-large 0; + &__heading { + padding: $gap-large $gap-large 0; - img { - height: 22px; - margin-bottom: $gap-large; - } + img { + height: 22px; + margin-bottom: $gap-small; + } - @media screen and ( min-width: $break-small ) { h2 { - margin-right: 7%; - @include wp-title-large; + margin: $gap-large 0; + } + + @media screen and ( min-width: $break-small ) { + h2 { + @include wp-title-large; + line-height: normal; + } } } - } - &__content { - padding: $gap-large; - } + &__content { + padding: $gap-large; + padding-top: 0; + } - &__payment-methods { - padding: 0 $gap-large; + &__payment-methods { + padding: 0 $gap-large; - p { - @include wp-small-button; - color: $gray-700; - margin-top: 0; - } - &__icons { - border: 1px solid $gray-300; - border-bottom: none; - padding: $gap-small; - display: grid; - column-gap: $gap-smaller; - row-gap: $gap; - grid-template-columns: repeat( auto-fill, minmax( 39px, 1fr ) ); + p { + @include wp-small-button; + color: $gray-700; + margin-top: 0; + } - img { - height: 24px; + &__icons { + border: 1px solid $gray-300; + border-bottom: none; + padding: $gap-small; + display: grid; + column-gap: $gap-smaller; + row-gap: $gap; + grid-template-columns: repeat( auto-fill, minmax( 39px, 1fr ) ); + + img { + height: 24px; + } + + span { + @include wc-others-caption; + color: $gray-700; + display: none; + + @media screen and ( min-width: $break-small ) { + display: initial; + white-space: nowrap; + align-self: end; + } + } } - span { - @include wc-others-caption; - color: $gray-700; - display: none; + &__description { + padding: $gap-small; + display: flex; + flex-direction: column; + gap: 10px; + border: 1px solid $gray-300; + + p { + font-size: 11px; + line-height: 16px; + color: $gray-900; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 0; + } + + span { + @include wc-others-caption; + color: $gray-700; + } + + &__divider { + border-top: 1px solid $gray-200; + @media screen and ( min-width: $break-small ) { + border-right: 1px solid $gray-200; + } + } @media screen and ( min-width: $break-small ) { - display: initial; - white-space: nowrap; - align-self: end; + flex-direction: row; } } } - &__description { - padding: $gap-small; - display: flex; - flex-direction: column; - gap: 10px; - border: 1px solid $gray-300; - p { - font-size: 11px; - line-height: 16px; + + &__buttons { + padding: $gap-large; + + button { + width: 100%; + justify-content: center; + margin-right: $gap-small; + @media screen and ( min-width: $break-small ) { + width: auto; + justify-content: left; + } + + &.is-primary { + background: $studio-wordpress-blue-50; + + &:hover { + background: $studio-wordpress-blue-40; + } + } + } + } + + &__details { + padding: $gap-larger $gap-large; + + .wcpay-component-tip-box { + margin-top: $gap-large; + } + } + + &__steps { + @media screen and ( min-width: $break-small ) { + display: grid; + grid-template-columns: repeat( 3, 1fr ); + column-gap: 20px; + } + } + + &__step { + padding-top: $gap-smaller; + display: grid; + grid-template-rows: auto 1fr auto; + + span { + background-color: $studio-woocommerce-purple-0; + color: $studio-woocommerce-purple-60; + font-size: 20px; + font-weight: 800; + border-radius: 12px; + height: 40px; + width: 40px; + display: inline-block; + text-align: center; + line-height: 40px; + margin-bottom: $gap-smaller; + } + + h3 { + @include wp-subtitle-small; color: $gray-900; - font-weight: 600; - text-transform: uppercase; margin-bottom: 0; } - span { + + p { @include wc-others-caption; - color: $gray-700; + color: $gray-800; + margin-top: $gap-smallest; } - &__divider { - border-top: 1px solid $gray-200; - @media screen and ( min-width: $break-small ) { - border-right: 1px solid $gray-200; + } + + &__incentive { + background: #faf8ff; + border: 1px solid #ae7dd0; + border-radius: 2px; + padding: $gap-large; + margin-bottom: $gap-large; + + &-pill { + display: inline-block; + padding: 0 $gap-small; + border: 1px solid #1d2327; // Gray 90 + border-radius: 16px; + text-transform: uppercase; + font-size: 12px; + line-height: 22px; + font-weight: 600; + color: $gray-900; + margin-bottom: $gap-small; + } + + p { + @include wc-others-caption; + color: #50575e; + + &:last-child { + margin-bottom: 0; } } + @media screen and ( min-width: $break-small ) { - flex-direction: row; + h2 { + margin-right: 10%; + } + + p { + margin-right: 15%; + } } } - } - &__buttons { - padding: $gap-large; - button { - width: 100%; - justify-content: center; - margin-right: $gap-small; - @media screen and ( min-width: $break-small ) { - width: auto; - justify-content: left; + &__sandbox-mode-panel { + .components-panel__body.is-opened { + padding: $gap-large; + + h2 { + margin: -24px -24px 5px; + } } - } - } - &__details { - padding: $gap-larger $gap-large; + h2 { + button { + @include wp-subtitle; + padding: $gap $gap-largest $gap $gap-large !important; - .wcpay-component-tip-box { - margin-top: $gap-large; - } - } + &:focus { + box-shadow: none !important; + } - &__steps { - @media screen and ( min-width: $break-small ) { - display: grid; - grid-template-columns: repeat( 3, 1fr ); - column-gap: 20px; - } - } + &:hover { + background: #fff !important; + } + } + } - &__step { - padding-top: $gap-smaller; - display: grid; - grid-template-rows: auto 1fr auto; - - span { - background-color: $studio-woocommerce-purple-0; - color: $studio-woocommerce-purple-60; - font-size: 20px; - font-weight: 800; - border-radius: 12px; - height: 40px; - width: 40px; - display: inline-block; - text-align: center; - line-height: 40px; - margin-bottom: $gap-smaller; - } + .components-notice { + margin-top: 8px; + } - h3 { - @include wp-subtitle-small; - color: $gray-900; - margin-bottom: 0; + button.is-secondary { + width: 100%; + justify-content: center; + @media screen and ( min-width: $break-small ) { + width: auto; + justify-content: left; + } + } } - p { - @include wc-others-caption; - color: $gray-800; - margin-top: $gap-smallest; + &__preloader { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: #fff; + z-index: 999999; + text-align: center; + + img.logo { + position: absolute; + height: 40px; + width: 40px; + top: 18px; + left: 36px; + } } } +} +.connect-account-page { &__info-modal { &.components-modal__frame { max-width: 700px; @@ -167,94 +288,4 @@ @include modal-footer-buttons; } } - - &__incentive { - background: #faf8ff; - border: 1px solid #ae7dd0; - border-radius: 2px; - padding: $gap-large; - margin-bottom: $gap-large; - - &-pill { - display: inline-block; - padding: 0 $gap-small; - border: 1px solid #1d2327; // Gray 90 - border-radius: 16px; - text-transform: uppercase; - font-size: 12px; - line-height: 22px; - font-weight: 600; - color: $gray-900; - margin-bottom: $gap-small; - } - - p { - @include wc-others-caption; - color: #50575e; - - &:last-child { - margin-bottom: 0; - } - } - - @media screen and ( min-width: $break-small ) { - h2 { - margin-right: 10%; - } - - p { - margin-right: 15%; - } - } - } - &__sandbox-mode-panel { - .components-panel__body.is-opened { - padding: $gap-large; - h2 { - margin: -24px -24px 5px; - } - } - h2 { - button { - @include wp-subtitle; - padding: $gap $gap-largest $gap $gap-large !important; - &:focus { - box-shadow: none !important; - } - &:hover { - background: #fff !important; - } - } - } - .components-notice { - margin-top: 8px; - } - button.is-secondary { - width: 100%; - justify-content: center; - @media screen and ( min-width: $break-small ) { - width: auto; - justify-content: left; - } - } - } - - &__preloader { - position: fixed; - width: 100%; - height: 100%; - top: 0; - left: 0; - background: #fff; - z-index: 999999; - text-align: center; - - img.logo { - position: absolute; - height: 40px; - width: 40px; - top: 18px; - left: 36px; - } - } } diff --git a/client/connect-account-page/test/__snapshots__/index.test.tsx.snap b/client/connect-account-page/test/__snapshots__/index.test.tsx.snap index 3cfc2216716..fc2b9048af3 100644 --- a/client/connect-account-page/test/__snapshots__/index.test.tsx.snap +++ b/client/connect-account-page/test/__snapshots__/index.test.tsx.snap @@ -4,6 +4,7 @@ exports[`ConnectAccountPage should render correctly 1`] = `

+
+
+
+ + Withdrawal details + +
+
+
+
+

+ Bank account +

+
+ MOCK BANK •••• 1234 (USD) +
+
+
+

+ Bank reference ID +

+
+
+ Not available +
+
+
+
+
+
+
`; @@ -191,11 +327,6 @@ exports[`Deposit overview renders instant deposit correctly 1`] = `
-
- MOCK BANK •••• 1234 (USD) -
  • +
    +
    +
    + + Payout details + +
    +
    +
    +
    +

    + Bank account +

    +
    + MOCK BANK •••• 1234 (USD) +
    +
    +
    +

    + Bank reference ID +

    +
    +
    + Not available +
    +
    +
    +
    +
    +
    +
    `; diff --git a/client/deposits/index.tsx b/client/deposits/index.tsx index d500a05a6a5..60e18ec86b8 100644 --- a/client/deposits/index.tsx +++ b/client/deposits/index.tsx @@ -22,7 +22,6 @@ import { useSettings } from 'wcpay/data'; import DepositsList from './list'; import { hasAutomaticScheduledDeposits } from 'wcpay/deposits/utils'; import { recordEvent } from 'wcpay/tracks'; -import DateFormatNotice from 'wcpay/components/date-format-notice'; const useNextDepositNoticeState = () => { const { updateOptions } = useDispatch( 'wc/admin/options' ); @@ -149,7 +148,6 @@ const DepositsPage: React.FC = () => { return ( - diff --git a/client/deposits/list/index.tsx b/client/deposits/list/index.tsx index 52194cb184f..22a1532dcc4 100644 --- a/client/deposits/list/index.tsx +++ b/client/deposits/list/index.tsx @@ -5,7 +5,6 @@ */ import React, { useState } from 'react'; import { recordEvent } from 'tracks'; -import { useMemo } from '@wordpress/element'; import { __, _n, sprintf } from '@wordpress/i18n'; import { TableCard, Link } from '@woocommerce/components'; import { onQueryChange, getQuery } from '@woocommerce/navigation'; @@ -23,7 +22,6 @@ import { parseInt } from 'lodash'; */ import type { DepositsTableHeader } from 'wcpay/types/deposits'; import { useDeposits, useDepositsSummary } from 'wcpay/data'; -import { useReportingExportLanguage } from 'data/index'; import { displayType, depositStatusLabels } from '../strings'; import { formatExplicitCurrency, @@ -37,16 +35,10 @@ import DownloadButton from 'components/download-button'; import { getDepositsCSV } from 'wcpay/data/deposits/resolvers'; import { applyThousandSeparator } from '../../utils/index.js'; import DepositStatusChip from 'components/deposit-status-chip'; -import { - isExportModalDismissed, - getExportLanguage, - isDefaultSiteLanguage, -} from 'utils'; -import CSVExportModal from 'components/csv-export-modal'; -import { ReportingExportLanguageHook } from 'wcpay/settings/reporting-settings/interfaces'; import './style.scss'; import { formatDateTimeFromString } from 'wcpay/utils/date-time'; +import { usePersistedColumnVisibility } from 'wcpay/hooks/use-persisted-table-column-visibility'; const getColumns = ( sortByDate?: boolean ): DepositsTableHeader[] => [ { @@ -104,10 +96,6 @@ const getColumns = ( sortByDate?: boolean ): DepositsTableHeader[] => [ ]; export const DepositsList = (): JSX.Element => { - const [ - exportLanguage, - ] = useReportingExportLanguage() as ReportingExportLanguageHook; - const [ isDownloading, setIsDownloading ] = useState( false ); const { createNotice } = useDispatch( 'core/notices' ); const { deposits, isLoading } = useDeposits( getQuery() ); @@ -115,10 +103,12 @@ export const DepositsList = (): JSX.Element => { getQuery() ); - const [ isCSVExportModalOpen, setCSVExportModalOpen ] = useState( false ); - const sortByDate = ! getQuery().orderby || 'date' === getQuery().orderby; - const columns = useMemo( () => getColumns( sortByDate ), [ sortByDate ] ); + const columns = getColumns( sortByDate ); + const { columnsToDisplay, onColumnsChange } = usePersistedColumnVisibility< + DepositsTableHeader + >( 'wc_payments_payouts_hidden_columns', columns ); + const totalRows = depositsSummary.count || 0; const rows = deposits.map( ( deposit ) => { @@ -171,7 +161,9 @@ export const DepositsList = (): JSX.Element => { }, }; - return columns.map( ( { key } ) => data[ key ] || { display: null } ); + return columnsToDisplay.map( + ( { key } ) => data[ key ] || { display: null } + ); } ); const isCurrencyFiltered = 'string' === typeof getQuery().store_currency_is; @@ -220,12 +212,9 @@ export const DepositsList = (): JSX.Element => { const downloadable = !! rows.length; - const endpointExport = async ( language: string ) => { - // We destructure page and path to get the right params. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { page, path, ...params } = getQuery(); + const endpointExport = async () => { const userEmail = wcpaySettings.currentUserEmail; - const locale = getExportLanguage( language, exportLanguage ); + const userLocale = wcpaySettings.userLocale.code; const { date_before: dateBefore, @@ -268,7 +257,7 @@ export const DepositsList = (): JSX.Element => { } >( { path: getDepositsCSV( { userEmail, - locale, + userLocale, dateAfter, dateBefore, dateBetween, @@ -313,11 +302,7 @@ export const DepositsList = (): JSX.Element => { const downloadType = totalRows > rows.length ? 'endpoint' : 'browser'; if ( 'endpoint' === downloadType ) { - if ( ! isDefaultSiteLanguage() && ! isExportModalDismissed() ) { - setCSVExportModalOpen( true ); - } else { - endpointExport( '' ); - } + endpointExport(); } else { const params = getQuery(); @@ -353,16 +338,6 @@ export const DepositsList = (): JSX.Element => { setIsDownloading( false ); }; - const closeModal = () => { - setCSVExportModalOpen( false ); - }; - - const exportDeposits = ( language: string ) => { - endpointExport( language ); - - closeModal(); - }; - return ( @@ -372,11 +347,12 @@ export const DepositsList = (): JSX.Element => { isLoading={ isLoading } rowsPerPage={ parseInt( getQuery().per_page ?? '' ) || 25 } totalRows={ totalRows } - headers={ columns } + headers={ columnsToDisplay } rows={ rows } summary={ summary } query={ getQuery() } onQueryChange={ onQueryChange } + onColumnsChange={ onColumnsChange } actions={ [ downloadable && ( { ), ] } /> - { ! isDefaultSiteLanguage() && - ! isExportModalDismissed() && - isCSVExportModalOpen && ( - - ) } ); }; diff --git a/client/deposits/list/test/__snapshots__/index.tsx.snap b/client/deposits/list/test/__snapshots__/index.tsx.snap index c26a364fd04..9bf906e0af4 100644 --- a/client/deposits/list/test/__snapshots__/index.tsx.snap +++ b/client/deposits/list/test/__snapshots__/index.tsx.snap @@ -4,6 +4,7 @@ exports[`Deposits list renders correctly a single deposit 1`] = `
    ( { useDeposits: jest.fn(), useDepositsSummary: jest.fn(), - useReportingExportLanguage: jest.fn( () => [ 'en', jest.fn() ] ), } ) ); jest.mock( '@woocommerce/csv-export', () => { @@ -44,6 +39,15 @@ jest.mock( '@woocommerce/csv-export', () => { jest.mock( '@wordpress/api-fetch', () => jest.fn() ); +jest.mock( '@woocommerce/data', () => { + const actualModule = jest.requireActual( '@woocommerce/data' ); + + return { + ...actualModule, + useUserPreferences: jest.fn(), + }; +} ); + const mockDeposits = [ { id: 'po_mock1', @@ -85,10 +89,10 @@ declare const global: { connect: { country: string; }; - reporting?: { - exportModalDismissed: boolean; - }; dateFormat: string; + userLocale: { + code: string; + }; }; }; @@ -122,8 +126,8 @@ const mockDownloadCSVFile = downloadCSVFile as jest.MockedFunction< typeof downloadCSVFile >; -const mockUseReportingExportLanguage = useReportingExportLanguage as jest.MockedFunction< - typeof useReportingExportLanguage +const mockUseUserPreferences = useUserPreferences as jest.MockedFunction< + typeof useUserPreferences >; describe( 'Deposits list', () => { @@ -133,7 +137,11 @@ describe( 'Deposits list', () => { // the query string is preserved across tests, so we need to reset it updateQueryString( {}, '/', {} ); - mockUseReportingExportLanguage.mockReturnValue( [ 'en', jest.fn() ] ); + mockUseUserPreferences.mockReturnValue( { + updateUserPreferences: jest.fn(), + wc_payments_payouts_hidden_columns: '', + isRequesting: false, + } as any ); global.wcpaySettings = { zeroDecimalCurrencies: [], @@ -151,10 +159,10 @@ describe( 'Deposits list', () => { precision: 2, }, }, - reporting: { - exportModalDismissed: true, - }, dateFormat: 'M j Y', + userLocale: { + code: 'en', + }, }; } ); diff --git a/client/disable-confirmation-modal/index.js b/client/disable-confirmation-modal/index.js index d8f96db2e28..954c37af2f6 100644 --- a/client/disable-confirmation-modal/index.js +++ b/client/disable-confirmation-modal/index.js @@ -24,7 +24,7 @@ import { ApplePayIcon, GooglePayIcon, LinkIcon, - WooIcon, + WooIconShort, } from 'wcpay/payment-methods-icons'; const DisableConfirmationModal = ( { onClose, onConfirm } ) => { @@ -64,9 +64,8 @@ const DisableConfirmationModal = ( { onClose, onConfirm } ) => { mixedString: sprintf( /* translators: %s: WooPayments */ __( - '%s is currently powering multiple popular payment methods on your store. ' + - 'Without it, they will no longer be available to your customers ' + - 'which may {{strong}}influence conversions and sales on your store.{{/strong}}', + '%s is currently powering multiple popular payment methods on your store.' + + ' Without it, they will no longer be available to your customers, which may influence sales.', 'woocommerce-payments' ), 'WooPayments' @@ -77,34 +76,14 @@ const DisableConfirmationModal = ( { onClose, onConfirm } ) => { } ) }

    - { interpolateComponents( { - mixedString: sprintf( - /* translators: %s: WooPayments */ - __( - 'You can enable %s again at any time in {{settingsLink}}settings{{/settingsLink}}.', - 'woocommerce-payments' - ), - 'WooPayments' + { sprintf( + /* translators: %s: WooPayments */ + __( + 'Payment methods that need %s:', + 'woocommerce-payments' ), - components: { - settingsLink: ( - // eslint-disable-next-line jsx-a11y/anchor-has-content - - ), - }, - } ) } -

    -

    - - { sprintf( - /* translators: %s: WooPayments */ - __( - 'Payment methods that need %s:', - 'woocommerce-payments' - ), - 'WooPayments' - ) } - + 'WooPayments' + ) }

      { enabledMethodIds @@ -153,13 +132,13 @@ const DisableConfirmationModal = ( { onClose, onConfirm } ) => { { isWooPayEnabled && (
    • ) }
    -

    +

    { interpolateComponents( { mixedString: sprintf( /* translators: %s: WooPayments */ diff --git a/client/disputes/evidence/test/__snapshots__/index.js.snap b/client/disputes/evidence/test/__snapshots__/index.js.snap index 52ef5ecbc55..d48607ce79d 100644 --- a/client/disputes/evidence/test/__snapshots__/index.js.snap +++ b/client/disputes/evidence/test/__snapshots__/index.js.snap @@ -320,6 +320,7 @@ exports[`Dispute evidence page renders correctly 1`] = `

    [ { @@ -218,13 +207,11 @@ export const DisputesList = (): JSX.Element => { getQuery() ); - const [ isCSVExportModalOpen, setCSVExportModalOpen ] = useState( false ); - - const [ - exportLanguage, - ] = useReportingExportLanguage() as ReportingExportLanguageHook; - const headers = getHeaders( getQuery().orderby ); + const { columnsToDisplay, onColumnsChange } = usePersistedColumnVisibility< + DisputesTableHeader + >( 'wc_payments_disputes_hidden_columns', headers ); + const totalRows = disputesSummary.count || 0; const rows = disputes.map( ( dispute ) => { @@ -358,13 +345,13 @@ export const DisputesList = (): JSX.Element => { const downloadable = !! rows.length; - const endpointExport = async ( language: string ) => { + const endpointExport = async () => { // We destructure page and path to get the right params. // eslint-disable-next-line @typescript-eslint/no-unused-vars const { page, path, ...params } = getQuery(); const userEmail = wcpaySettings.currentUserEmail; - const locale = getExportLanguage( language, exportLanguage ); + const userLocale = wcpaySettings.userLocale.code; const { date_before: dateBefore, date_after: dateAfter, @@ -405,7 +392,7 @@ export const DisputesList = (): JSX.Element => { } >( { path: getDisputesCSV( { userEmail, - locale, + userLocale, dateAfter, dateBefore, dateBetween, @@ -451,11 +438,7 @@ export const DisputesList = (): JSX.Element => { const downloadType = totalRows > rows.length ? 'endpoint' : 'browser'; if ( 'endpoint' === downloadType ) { - if ( ! isDefaultSiteLanguage() && ! isExportModalDismissed() ) { - setCSVExportModalOpen( true ); - } else { - endpointExport( '' ); - } + endpointExport(); } else { const csvColumns = [ { @@ -543,19 +526,8 @@ export const DisputesList = (): JSX.Element => { disputesSummary.currencies || ( isCurrencyFiltered ? [ getQuery().store_currency_is ?? '' ] : [] ); - const closeModal = () => { - setCSVExportModalOpen( false ); - }; - - const exportDisputes = ( language: string ) => { - endpointExport( language ); - - closeModal(); - }; - return ( - { isLoading={ isLoading } rowsPerPage={ parseInt( getQuery().per_page ?? '', 10 ) || 25 } totalRows={ totalRows } - headers={ headers } + headers={ columnsToDisplay } rows={ rows } summary={ summary } query={ getQuery() } onQueryChange={ onQueryChange } + onColumnsChange={ onColumnsChange } actions={ [ downloadable && ( { ), ] } /> - { ! isDefaultSiteLanguage() && - ! isExportModalDismissed() && - isCSVExportModalOpen && ( - - ) } ); }; diff --git a/client/disputes/test/__snapshots__/index.tsx.snap b/client/disputes/test/__snapshots__/index.tsx.snap index b3fae6b5d47..33c4b3e6204 100644 --- a/client/disputes/test/__snapshots__/index.tsx.snap +++ b/client/disputes/test/__snapshots__/index.tsx.snap @@ -4,55 +4,8 @@ exports[`Disputes list renders correctly 1`] = `
    -
    diff --git a/client/disputes/test/index.tsx b/client/disputes/test/index.tsx index 37bbd2e93af..2d20360426c 100644 --- a/client/disputes/test/index.tsx +++ b/client/disputes/test/index.tsx @@ -6,17 +6,13 @@ import { render, waitFor } from '@testing-library/react'; import { downloadCSVFile } from '@woocommerce/csv-export'; import apiFetch from '@wordpress/api-fetch'; import os from 'os'; +import { useUserPreferences } from '@woocommerce/data'; /** * Internal dependencies */ import DisputesList from '..'; -import { - useDisputes, - useDisputesSummary, - useReportingExportLanguage, - useSettings, -} from 'data/index'; +import { useDisputes, useDisputesSummary, useSettings } from 'data/index'; import { getUnformattedAmount } from 'wcpay/utils/test-utils'; import React from 'react'; import { @@ -55,10 +51,18 @@ jest.mock( '@wordpress/data', () => ( { jest.mock( 'data/index', () => ( { useDisputes: jest.fn(), useDisputesSummary: jest.fn(), - useReportingExportLanguage: jest.fn( () => [ 'en', jest.fn() ] ), useSettings: jest.fn(), } ) ); +jest.mock( '@woocommerce/data', () => { + const actualModule = jest.requireActual( '@woocommerce/data' ); + + return { + ...actualModule, + useUserPreferences: jest.fn(), + }; +} ); + const mockDownloadCSVFile = downloadCSVFile as jest.MockedFunction< typeof downloadCSVFile >; @@ -77,8 +81,8 @@ const mockUseSettings = useSettings as jest.MockedFunction< typeof useSettings >; -const mockUseReportingExportLanguage = useReportingExportLanguage as jest.MockedFunction< - typeof useReportingExportLanguage +const mockUseUserPreferences = useUserPreferences as jest.MockedFunction< + typeof useUserPreferences >; declare const global: { @@ -98,11 +102,11 @@ declare const global: { precision: number; }; }; - reporting?: { - exportModalDismissed: boolean; - }; dateFormat?: string; timeFormat?: string; + userLocale: { + code: string; + }; }; }; @@ -173,8 +177,6 @@ describe( 'Disputes list', () => { new Date( '2019-11-07T12:33:37.000Z' ).getTime() ); - mockUseReportingExportLanguage.mockReturnValue( [ 'en', jest.fn() ] ); - mockUseSettings.mockReturnValue( { isLoading: false, isSaving: false, @@ -182,6 +184,12 @@ describe( 'Disputes list', () => { isDirty: false, } ); + mockUseUserPreferences.mockReturnValue( { + updateUserPreferences: jest.fn(), + wc_payments_disputes_hidden_columns: '', + isRequesting: false, + } as any ); + global.wcpaySettings = { zeroDecimalCurrencies: [], connect: { @@ -198,11 +206,11 @@ describe( 'Disputes list', () => { precision: 2, }, }, - reporting: { - exportModalDismissed: true, - }, dateFormat: 'Y-m-d', timeFormat: 'g:iA', + userLocale: { + code: 'en', + }, }; } ); @@ -228,6 +236,40 @@ describe( 'Disputes list', () => { expect( list ).toMatchSnapshot(); } ); + test( 'renders columns hidden as per user preferences', () => { + mockUseDisputes.mockReturnValue( { + isLoading: false, + disputes: mockDisputes, + } ); + + mockUseDisputesSummary.mockReturnValue( { + isLoading: false, + disputesSummary: { + count: 25, + }, + } ); + + mockUseUserPreferences.mockReturnValue( { + wc_payments_disputes_hidden_columns: [ 'customerEmail' ], + } as any ); + + const { getByRole, queryByRole } = render( ); + + // Email column should not be visible, as it is hidden in user preferences. + expect( + queryByRole( 'columnheader', { + name: /Email/i, + } ) + ).not.toBeInTheDocument(); + + // Country column should be visible, as it is not hidden in user preferences. + expect( + getByRole( 'columnheader', { + name: /Country/i, + } ) + ).toBeInTheDocument(); + } ); + describe( 'Download button', () => { test( 'renders when there are one or more disputes', () => { mockUseDisputes.mockReturnValue( { diff --git a/client/documents/index.tsx b/client/documents/index.tsx index c95c9d3a6ba..07f75e99ddf 100644 --- a/client/documents/index.tsx +++ b/client/documents/index.tsx @@ -9,11 +9,10 @@ import React from 'react'; import Page from 'components/page'; import DocumentsList from './list'; import { TestModeNotice } from 'components/test-mode-notice'; -import DateFormatNotice from 'wcpay/components/date-format-notice'; + export const DocumentsPage = (): JSX.Element => { return ( - diff --git a/client/documents/list/index.tsx b/client/documents/list/index.tsx index ff4a947f130..6b74534838c 100644 --- a/client/documents/list/index.tsx +++ b/client/documents/list/index.tsx @@ -20,6 +20,7 @@ import Page from '../../components/page'; import { getDocumentUrl } from 'wcpay/utils'; import VatFormModal from 'wcpay/vat/form-modal'; import { formatDateTimeFromString } from 'wcpay/utils/date-time'; +import { usePersistedColumnVisibility } from 'wcpay/hooks/use-persisted-table-column-visibility'; interface Column extends TableCardColumn { key: 'date' | 'type' | 'description' | 'download'; @@ -160,7 +161,11 @@ export const DocumentsList = (): JSX.Element => { } }, [ requestedDocumentID, requestedDocumentType, downloadDocument ] ); - const columnsToDisplay = getColumns(); + const columns = getColumns(); + + const { columnsToDisplay, onColumnsChange } = usePersistedColumnVisibility< + Column + >( 'wc_payments_documents_hidden_columns', columns ); const totalRows = documentsSummary.count || 0; const rows = documents.map( ( document: Document ) => { @@ -243,6 +248,7 @@ export const DocumentsList = (): JSX.Element => { summary={ summary } query={ getQuery() } onQueryChange={ onQueryChange } + onColumnsChange={ onColumnsChange } actions={ [] } />
    ( { jest.mock( 'wcpay/vat/form', () => jest.fn() ); +jest.mock( '@woocommerce/data', () => { + const actualModule = jest.requireActual( '@woocommerce/data' ); + + return { + ...actualModule, + useUserPreferences: jest.fn(), + }; +} ); + const mockUseDocuments = useDocuments as jest.MockedFunction< typeof useDocuments >; @@ -31,6 +41,10 @@ const mockUseDocumentsSummary = useDocumentsSummary as jest.MockedFunction< typeof useDocumentsSummary >; +const mockUseUserPreferences = useUserPreferences as jest.MockedFunction< + typeof useUserPreferences +>; + declare const global: { wcpaySettings: { accountStatus: { @@ -79,6 +93,12 @@ describe( 'Documents list', () => { isLoading: false, } ); + mockUseUserPreferences.mockReturnValue( { + updateUserPreferences: jest.fn(), + wc_payments_documents_hidden_columns: '', + isRequesting: false, + } as any ); + ( { container, rerender } = render( ) ); } ); diff --git a/client/express-checkout/blocks/components/express-checkout-preview.js b/client/express-checkout/blocks/components/express-checkout-preview.js index 58e5ea2aaae..92b328d6126 100644 --- a/client/express-checkout/blocks/components/express-checkout-preview.js +++ b/client/express-checkout/blocks/components/express-checkout-preview.js @@ -81,7 +81,7 @@ export const ExpressCheckoutPreviewComponent = ( { return (
    + 0; + + if ( hasValidRates ) { + shippingRates = shippingData.shippingRates[ 0 ].shipping_rates.map( + ( rate ) => { + return { + id: rate.rate_id, + amount: parseInt( rate.price, 10 ), + displayName: rate.name, + }; + } + ); + } else { + shippingRates = [ + { + id: 'pending', + displayName: __( + 'Pending', + 'woocommerce-payments' + ), + amount: 0, + }, + ]; + } + } + const options = { lineItems: normalizeLineItems( billing?.cartTotalItems ), emailRequired: true, - shippingAddressRequired: shippingData?.needsShipping, + shippingAddressRequired, phoneNumberRequired: getExpressCheckoutData( 'checkout' )?.needs_payer_phone ?? false, - shippingRates: shippingData?.shippingRates[ 0 ]?.shipping_rates?.map( - ( r ) => { - return { - id: r.rate_id, - amount: parseInt( r.price, 10 ), - displayName: r.name, - }; - } - ), + shippingRates, allowedShippingCountries: getExpressCheckoutData( 'checkout' ) .allowed_shipping_countries, }; diff --git a/client/express-checkout/event-handlers.js b/client/express-checkout/event-handlers.js index f32c62b8ec8..da224669771 100644 --- a/client/express-checkout/event-handlers.js +++ b/client/express-checkout/event-handlers.js @@ -23,6 +23,8 @@ import { let lastSelectedAddress = null; export const shippingAddressChangeHandler = async ( api, event, elements ) => { + lastSelectedAddress = event.address; + try { const response = await api.expressCheckoutECECalculateShippingOptions( normalizeShippingAddress( event.address ) @@ -33,8 +35,6 @@ export const shippingAddressChangeHandler = async ( api, event, elements ) => { amount: response.total.amount, } ); - lastSelectedAddress = event.address; - event.resolve( { shippingRates: response.shipping_options, lineItems: normalizeLineItems( response.displayItems ), diff --git a/client/globals.d.ts b/client/globals.d.ts index 482b6a436d7..0b61d866700 100644 --- a/client/globals.d.ts +++ b/client/globals.d.ts @@ -122,13 +122,27 @@ declare global { storeName: string; isNextDepositNoticeDismissed: boolean; isInstantDepositNoticeDismissed: boolean; - isDateFormatNoticeDismissed: boolean; - reporting: { - exportModalDismissed?: boolean; - }; - locale: { + isConnectionSuccessModalDismissed: boolean; + userLocale: { + /** + * The locale of the current user profile, represented as a locale code supported by transact-platform-server. + * + * @example 'es' // Spanish + * + * @see WC_Payments_Utils::convert_locale_to_language_code + */ code: string; + /** + * The English name of the locale. + * + * @example 'Spanish' + */ english_name: string; + /** + * The native name of the locale. + * + * @example 'Español' + */ native_name: string; }; trackingInfo?: { @@ -188,6 +202,20 @@ declare global { adminUrl: string; countries: Record< string, string >; homeUrl: string; + locale: { + /** + * The locale of the current site, as set in WP Admin → Settings → General. + * + * @example 'en_AU' // English (Australia) + */ + siteLocale: string; + /** + * The locale of the current user profile, as set in WP Admin → Users → Profile → Language. + * + * @example 'en_UK' // English (United Kingdom) + */ + userLocale: string; + }; siteTitle: string; }; diff --git a/client/hooks/use-persisted-table-column-visibility.ts b/client/hooks/use-persisted-table-column-visibility.ts new file mode 100644 index 00000000000..8906e4e578f --- /dev/null +++ b/client/hooks/use-persisted-table-column-visibility.ts @@ -0,0 +1,104 @@ +/** + * External dependencies + */ +import { useMemo } from 'react'; +import { useUserPreferences } from '@woocommerce/data'; +import type { TableCardColumn } from '@woocommerce/components'; + +/** + * Type for user preferences returned from useUserPreferences hook. + * + * These preference keys are defined and managed in: + * + * @see WC_Payments::add_user_data_fields() in includes/class-wc-payments.php + * + * Note: This interface must stay in sync with the PHP implementation + */ +interface UserPreferences { + wc_payments_transactions_hidden_columns: string[] | ''; + wc_payments_transactions_blocked_hidden_columns: string[] | ''; + wc_payments_transactions_risk_review_hidden_columns: string[] | ''; + wc_payments_transactions_uncaptured_hidden_columns: string[] | ''; + wc_payments_payouts_hidden_columns: string[] | ''; + wc_payments_disputes_hidden_columns: string[] | ''; + wc_payments_documents_hidden_columns: string[] | ''; +} + +/** + * Hook to manage column visibility for a TableCard component. + * + * This hook is used to manage the visibility of columns in a TableCard component. + * It uses the `@woocommerce/data` `useUserPreferences` hook to get the user's preferences and store them in the `wp_usermeta` table. + */ +export const usePersistedColumnVisibility = < + ColumnType extends TableCardColumn +>( + /** + * The key used to store the user's preference for hidden columns in the `wp_usermeta` table. + * + * This value will be prepended with `woocommerce_admin_` and used as the `meta_key` in the DB. + */ + columnPrefsKey: keyof UserPreferences, + /** + * The array of all columns to be passed to the `TableCard` component. + * + * Visibility of each column will adhere to stored user preferences using the column's `visible` prop. + * + * If the user's preference is not found, the default visibility value provided in the column's `visible` prop is used. + */ + allColumns: ColumnType[] +): { + /** + * A function to be passed to the `TableCard` component's `onColumnsChange` prop. + * + * This function is used to update the user's preference for hidden columns on each column visibility change event. + */ + onColumnsChange: ( shownColumns: string[] ) => void; + /** + * An array of columns to be passed to the `TableCard` component's `columns` prop, with visibility of columns adhering to stored user preferences. + * + * If the user's preference is not found, the default visibility value of each column is used. + */ + columnsToDisplay: ColumnType[]; +} => { + const { updateUserPreferences, ...userPrefs } = useUserPreferences(); + + // If returned value is undefined or empty string, use default visibility value. + const userPrefHiddenColumns = + ( ( userPrefs as unknown ) as UserPreferences )[ columnPrefsKey ] ?? ''; + + // When the user changes the column visibility, update the user's preference for hidden columns. + const onColumnsChange = ( shownColumns: string[] ) => { + const columns = allColumns.map( ( column ) => column.key ); + const hiddenColumns = columns.filter( + ( column ) => ! shownColumns.includes( column ) + ); + if ( columnPrefsKey ) { + const userDataFields = { + [ columnPrefsKey ]: hiddenColumns, + }; + updateUserPreferences( userDataFields ); + } + }; + + // When the user's preference for hidden columns is updated, update the columns to display. + const columnsToDisplay = useMemo( () => { + // If the user preference is not set (is empty string), return all columns with default visibility. + if ( ! Array.isArray( userPrefHiddenColumns ) ) { + return allColumns; + } + + // If the user preference is set, hide the column. + return allColumns.map( ( column ) => { + return { + ...column, + visible: ! userPrefHiddenColumns.includes( column.key ), + }; + } ); + }, [ allColumns, userPrefHiddenColumns ] ); + + return { + onColumnsChange, + columnsToDisplay, + }; +}; diff --git a/client/onboarding/index.tsx b/client/onboarding/index.tsx index b301a0c980e..619aae801ee 100644 --- a/client/onboarding/index.tsx +++ b/client/onboarding/index.tsx @@ -14,7 +14,6 @@ import { OnboardingForm } from './form'; import Step from './step'; import BusinessDetails from './steps/business-details'; import EmbeddedKyc from './steps/embedded-kyc'; -import StoreDetails from './steps/store-details'; import { trackStarted } from './tracking'; import { getAdminUrl } from 'wcpay/utils'; import './style.scss'; @@ -42,11 +41,6 @@ const OnboardingStepper = () => { - - - - - diff --git a/client/onboarding/kyc/index.tsx b/client/onboarding/kyc/index.tsx index 1a617bc4f37..4b31ea224a3 100644 --- a/client/onboarding/kyc/index.tsx +++ b/client/onboarding/kyc/index.tsx @@ -10,9 +10,9 @@ import WooLogo from 'assets/images/woo-logo.svg'; import Page from 'components/page'; import { OnboardingContextProvider } from 'onboarding/context'; import EmbeddedKyc from 'onboarding/steps/embedded-kyc'; -import strings from 'onboarding/strings'; import { getConnectUrl } from 'utils'; import { trackKycExit } from 'wcpay/onboarding/tracking'; +import CloseIcon from 'assets/images/icons/close.svg?asset'; const OnboardingKycPage: React.FC = () => { const urlParams = new URLSearchParams( window.location.search ); @@ -52,12 +52,6 @@ const OnboardingKycPage: React.FC = () => {
    - Woo { className="stepper__nav-button" onClick={ handleExit } > - { strings.cancel } + Close
    diff --git a/client/onboarding/step.tsx b/client/onboarding/step.tsx index ad353fc0996..2e529495027 100644 --- a/client/onboarding/step.tsx +++ b/client/onboarding/step.tsx @@ -2,7 +2,6 @@ * External dependencies */ import React from 'react'; -import ChevronLeft from 'gridicons/dist/chevron-left'; /** * Internal dependencies @@ -12,6 +11,7 @@ import { OnboardingSteps } from './types'; import { useTrackAbandoned } from './tracking'; import strings from './strings'; import WooLogo from 'assets/images/woo-logo.svg'; +import CloseIcon from 'assets/images/icons/close.svg?asset'; import './style.scss'; interface Props { @@ -30,23 +30,13 @@ const Step: React.FC< Props > = ( { name, children, showHeading = true } ) => { return ( <>
    - Woo
    diff --git a/client/onboarding/steps/business-details.tsx b/client/onboarding/steps/business-details.tsx index cc6aa01b5c8..2d21ec27a1d 100644 --- a/client/onboarding/steps/business-details.tsx +++ b/client/onboarding/steps/business-details.tsx @@ -19,6 +19,9 @@ import { BusinessType } from 'onboarding/types'; import InlineNotice from 'components/inline-notice'; import strings from 'onboarding/strings'; +/** + * Contains business and store details KYC logic. + */ const BusinessDetails: React.FC = () => { const { data, setData } = useOnboardingContext(); const countries = getAvailableCountries(); @@ -51,6 +54,19 @@ const BusinessDetails: React.FC = () => { ); const selectedMcc = mccsFlatList.find( ( mcc ) => mcc.key === data.mcc ); + const annualRevenues = Object.entries( strings.annualRevenues ).map( + ( [ key, name ] ) => ( { + key, + name, + } ) + ); + const goLiveTimeframes = Object.entries( strings.goLiveTimeframes ).map( + ( [ key, name ] ) => ( { + key, + name, + } ) + ); + const handleTiedChange = ( name: keyof OnboardingFields, selectedItem?: Item | null @@ -139,9 +155,19 @@ const BusinessDetails: React.FC = () => { selectedBusinessType && selectedBusinessStructure && selectedMcc && ( - - { strings.tos } - + <> + + + + { strings.tos } + + ) } ); diff --git a/client/onboarding/steps/embedded-kyc.tsx b/client/onboarding/steps/embedded-kyc.tsx index c14005f2d06..07b50dc4e51 100644 --- a/client/onboarding/steps/embedded-kyc.tsx +++ b/client/onboarding/steps/embedded-kyc.tsx @@ -17,7 +17,7 @@ import { __ } from '@wordpress/i18n'; */ import appearance from '../kyc/appearance'; import BannerNotice from 'wcpay/components/banner-notice'; -import LoadBar from 'wcpay/components/load-bar'; +import StripeSpinner from 'wcpay/components/stripe-spinner'; import { useOnboardingContext } from 'wcpay/onboarding/context'; import { createAccountSession, @@ -51,6 +51,7 @@ const EmbeddedKyc: React.FC< Props > = ( { setStripeConnectInstance, ] = useState< StripeConnectInstance | null >( null ); const [ loading, setLoading ] = useState( true ); + const [ finalizingAccount, setFinalizingAccount ] = useState( false ); const [ loadErrorMessage, setLoadErrorMessage ] = useState( '' ); const fetchAccountSession = useCallback( async () => { @@ -65,7 +66,6 @@ const EmbeddedKyc: React.FC< Props > = ( { return accountSession; // Return the full account session object } - setLoading( false ); setLoadErrorMessage( __( "Failed to create account session. Please check that you're using the latest version of WooPayments.", @@ -73,7 +73,6 @@ const EmbeddedKyc: React.FC< Props > = ( { ) ); } catch ( error ) { - setLoading( false ); setLoadErrorMessage( __( 'Failed to retrieve account session. Please try again later.', @@ -106,15 +105,12 @@ const EmbeddedKyc: React.FC< Props > = ( { setClientSecret( () => fetchClientSecret ); } } catch ( error ) { - setLoading( false ); setLoadErrorMessage( __( 'Failed to create account session. Please check that you are using the latest version of WooPayments.', 'woocommerce-payments' ) ); - } finally { - setLoading( false ); } }; @@ -153,6 +149,8 @@ const EmbeddedKyc: React.FC< Props > = ( { const urlSource = urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown'; + setFinalizingAccount( true ); + try { const response = await finalizeOnboarding( urlSource ); if ( response.success ) { @@ -185,10 +183,19 @@ const EmbeddedKyc: React.FC< Props > = ( { return ( <> - { loading && } + { loading && ( +
    + +
    + ) } { loadErrorMessage && ( { loadErrorMessage } ) } + { finalizingAccount && ( +
    + +
    + ) } { stripeConnectInstance && ( = () => { }, [] ); return ( -
    +

    { strings.steps.loading.heading }

    diff --git a/client/onboarding/steps/store-details.tsx b/client/onboarding/steps/store-details.tsx deleted file mode 100644 index 8f2606e965f..00000000000 --- a/client/onboarding/steps/store-details.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; - -/** - * Internal dependencies - */ -import strings from '../strings'; -import { OnboardingSelectField } from '../form'; - -const annualRevenues = Object.entries( strings.annualRevenues ).map( - ( [ key, name ] ) => ( { - key, - name, - } ) -); -const goLiveTimeframes = Object.entries( strings.goLiveTimeframes ).map( - ( [ key, name ] ) => ( { - key, - name, - } ) -); - -const StoreDetails: React.FC = () => { - return ( - <> - - - - ); -}; - -export default StoreDetails; diff --git a/client/onboarding/steps/test/business-details.tsx b/client/onboarding/steps/test/business-details.tsx index c39d7aef358..615fc82811b 100644 --- a/client/onboarding/steps/test/business-details.tsx +++ b/client/onboarding/steps/test/business-details.tsx @@ -15,6 +15,7 @@ import { getBusinessTypes, getMccsFlatList, } from 'onboarding/utils'; +import strings from '../../strings'; jest.mock( 'onboarding/utils', () => ( { getAvailableCountries: jest.fn(), @@ -230,5 +231,29 @@ describe( 'BusinessDetails', () => { 'Single member LLC' ); expect( mccField ).toHaveTextContent( 'Popular Software' ); + + const annualRevenueField = screen.getByText( + strings.placeholders.annual_revenue + ); + const goLiveTimeframeField = screen.getByText( + strings.placeholders.go_live_timeframe + ); + + user.click( annualRevenueField ); + user.click( + screen.getByText( strings.annualRevenues.from_250k_to_1m ) + ); + + user.click( goLiveTimeframeField ); + user.click( + screen.getByText( strings.goLiveTimeframes.within_1month ) + ); + + expect( annualRevenueField ).toHaveTextContent( + strings.annualRevenues.from_250k_to_1m + ); + expect( goLiveTimeframeField ).toHaveTextContent( + strings.goLiveTimeframes.within_1month + ); } ); } ); diff --git a/client/onboarding/steps/test/store-details.tsx b/client/onboarding/steps/test/store-details.tsx deleted file mode 100644 index 976638d49f6..00000000000 --- a/client/onboarding/steps/test/store-details.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import user from '@testing-library/user-event'; - -/** - * Internal dependencies - */ -import StoreDetails from '../store-details'; -import { OnboardingContextProvider } from '../../context'; -import strings from '../../strings'; - -describe( 'StoreDetails', () => { - it( 'renders and updates fields data when they are changed', () => { - render( - - - - ); - const annualRevenueField = screen.getByText( - strings.placeholders.annual_revenue - ); - const goLiveTimeframeField = screen.getByText( - strings.placeholders.go_live_timeframe - ); - - user.click( annualRevenueField ); - user.click( - screen.getByText( strings.annualRevenues.from_250k_to_1m ) - ); - - user.click( goLiveTimeframeField ); - user.click( - screen.getByText( strings.goLiveTimeframes.within_1month ) - ); - - expect( annualRevenueField ).toHaveTextContent( - strings.annualRevenues.from_250k_to_1m - ); - expect( goLiveTimeframeField ).toHaveTextContent( - strings.goLiveTimeframes.within_1month - ); - } ); -} ); diff --git a/client/onboarding/strings.tsx b/client/onboarding/strings.tsx index 4792d923042..bc67948ee39 100644 --- a/client/onboarding/strings.tsx +++ b/client/onboarding/strings.tsx @@ -43,6 +43,10 @@ export default { 'This will take place in a secure environment through our partner. Once your business details are verified, you’ll be redirected back to your store dashboard.', 'woocommerce-payments' ), + cta: __( + 'Finish your verification process', + 'woocommerce-payments' + ), }, embedded: { heading: __( diff --git a/client/onboarding/style.scss b/client/onboarding/style.scss index 415849c1d06..fdc8eec4852 100644 --- a/client/onboarding/style.scss +++ b/client/onboarding/style.scss @@ -17,7 +17,7 @@ body.wcpay-onboarding__body { padding-left: 8px; padding-right: 8px; display: grid; - grid-template-columns: 102px 1fr 102px; + grid-template-columns: 94px 1fr; // Sizing counted for the right padding inside 1st column (logo) align-items: stretch; background-color: #fff; border-bottom: 1px solid $gray-300; @@ -56,7 +56,7 @@ body.wcpay-onboarding__body { &-logo { justify-self: center; align-self: center; - height: 28px; + height: 17px; } } @@ -109,6 +109,42 @@ body.wcpay-onboarding__body { $gutenberg-blueberry-focus ); } + &.inline { + width: auto; // Adjust the button width not to take 100%. + margin-top: 0; // No need to have margin in this case. + } + // Support busy/disabled state for gutenberg blueberry MOX button. + &.components-button.is-primary.is-busy, + &.components-button.is-primary.is-busy:disabled, + &.components-button.is-primary.is-busy[aria-disabled='true'] { + background: linear-gradient( + -45deg, + var( + --wp-components-color-accent, + $gutenberg-blueberry + ) + 33%, + var( + --wp-components-color-accent-darker-20, + $gutenberg-blueberry-focus + ) + 33%, + var( + --wp-components-color-accent-darker-20, + $gutenberg-blueberry-focus + ) + 70%, + var( + --wp-components-color-accent, + var( + --wp-components-color-accent, + $gutenberg-blueberry + ) + ) + 70% + ); + background-size: 100px 100%; + } } } @@ -142,14 +178,6 @@ body.wcpay-onboarding__body { margin-top: 4px; } - .loading-step { - max-width: 520px; - position: absolute; - top: 50%; - left: 50%; - transform: translate( -50%, -50% ); - } - .onboarding-mode__note { background-color: $wp-blue-0; padding: $gap-small $gap; @@ -218,4 +246,13 @@ body.wcpay-onboarding__body { .woocommerce-layout__jitm { display: none; } + + // Wrap loader so it's centered and does not get cut. + .embedded-kyc-loader-wrapper { + text-align: center; + height: 35px; + &.padded { + padding-top: 61px; // Takes the same padding as Stripe embedded component. + } + } } diff --git a/client/overview/connection-sucess-notice.tsx b/client/overview/connection-sucess-notice.tsx deleted file mode 100644 index 0afe9949e75..00000000000 --- a/client/overview/connection-sucess-notice.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { __ } from '@wordpress/i18n'; -import { Card, DropdownMenu } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import ConfettiImage from '../../assets/images/confetti.svg'; - -const ConnectionSuccessNotice: React.FC = () => { - const [ isDismissed, setIsDismissed ] = React.useState( false ); - - const { - accountStatus: { - progressiveOnboarding: { - isEnabled: isPoEnabled, - isComplete: isPoComplete, - }, - status: accountStatus, - }, - testModeOnboarding, - } = wcpaySettings; - - const DismissMenu = () => { - return ( - setIsDismissed( true ), - }, - ] } - /> - ); - }; - const isPoDisabledOrCompleted = ! isPoEnabled || isPoComplete; - return ! isDismissed && ! testModeOnboarding && isPoDisabledOrCompleted ? ( - - - confetti - { accountStatus !== 'complete' ? ( -

    - { __( - 'Congratulations! Your store is being verified.', - 'woocommerce-payments' - ) } -

    - ) : ( -

    - { __( - 'Congratulations! Your store has been verified.', - 'woocommerce-payments' - ) } -

    - ) } -
    - ) : null; -}; - -export default ConnectionSuccessNotice; diff --git a/client/overview/index.js b/client/overview/index.js index 4bce8041b07..301d39772c0 100644 --- a/client/overview/index.js +++ b/client/overview/index.js @@ -15,7 +15,7 @@ import { dispatch } from '@wordpress/data'; import AccountBalances from 'components/account-balances'; import AccountStatus from 'components/account-status'; import ActiveLoanSummary from 'components/active-loan-summary'; -import ConnectionSuccessNotice from './connection-sucess-notice'; +import ConnectionSuccessModal from './modal/connection-success'; import DepositsOverview from 'components/deposits-overview'; import ErrorBoundary from 'components/error-boundary'; import FRTDiscoverabilityBanner from 'components/fraud-risk-tools-banner'; @@ -32,7 +32,6 @@ import { useDisputes, useGetSettings, useSettings } from 'data'; import SandboxModeSwitchToLiveNotice from 'wcpay/components/sandbox-mode-switch-to-live-notice'; import './style.scss'; import BannerNotice from 'wcpay/components/banner-notice'; -import DateFormatNotice from 'wcpay/components/date-format-notice'; const OverviewPageError = () => { const queryParams = getQuery(); @@ -94,6 +93,8 @@ const OverviewPage = () => { const accountRejected = accountStatus.status && accountStatus.status.startsWith( 'rejected' ); const accountUnderReview = accountStatus.status === 'under_review'; + const paymentsEnabled = accountStatus.paymentsEnabled; + const depositsEnabled = accountStatus.deposits?.status === 'enabled'; const showConnectionSuccess = queryParams[ 'wcpay-connection-success' ] === '1'; @@ -115,6 +116,14 @@ const OverviewPage = () => { ! progressiveOnboarding.isComplete; const showTaskList = ! accountRejected && ! accountUnderReview && tasks.length > 0; + const isPoDisabledOrCompleted = + ! progressiveOnboarding.isEnabled || progressiveOnboarding.isComplete; + const showConnectionSuccessModal = + showConnectionSuccess && + ! isTestModeOnboarding && + paymentsEnabled && + depositsEnabled && + isPoDisabledOrCompleted; const activeAccountFees = Object.entries( wcpaySettings.accountFees ) .map( ( [ key, value ] ) => { @@ -152,7 +161,6 @@ const OverviewPage = () => { - { showLoanOfferError && ( { __( @@ -193,7 +201,6 @@ const OverviewPage = () => { - { showConnectionSuccess && } { ! accountRejected && ! accountUnderReview && ( @@ -250,6 +257,11 @@ const OverviewPage = () => { ) } + { showConnectionSuccessModal && ( + + + + ) } ); }; diff --git a/client/overview/modal/connection-success/index.tsx b/client/overview/modal/connection-success/index.tsx new file mode 100644 index 00000000000..752a68fb356 --- /dev/null +++ b/client/overview/modal/connection-success/index.tsx @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import React from 'react'; +import { Modal, Button } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import './style.scss'; +import strings from './strings'; + +/** + * A modal component displayed when a live account is successfully connected. + */ +export const ConnectionSuccessModal = () => { + const [ isDismissed, setIsDismissed ] = React.useState( + wcpaySettings.isConnectionSuccessModalDismissed + ); + const { updateOptions } = useDispatch( 'wc/admin/options' ); + + const onDismiss = async () => { + setIsDismissed( true ); + + // Update the option to mark the modal as dismissed. + await updateOptions( { + wcpay_connection_success_modal_dismissed: true, + } ); + }; + + return ( + <> + { ! isDismissed && ( + +
    + { strings.description } +
    +
    + +
    +
    + ) } + + ); +}; + +export default ConnectionSuccessModal; diff --git a/client/overview/modal/connection-success/strings.tsx b/client/overview/modal/connection-success/strings.tsx new file mode 100644 index 00000000000..262e1d7d888 --- /dev/null +++ b/client/overview/modal/connection-success/strings.tsx @@ -0,0 +1,19 @@ +/* eslint-disable max-len */ +/** + * External dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +export default { + button: __( 'Dismiss', 'woocommerce-payments' ), + + heading: __( "You're ready to accept payments!", 'woocommerce-payments' ), + + description: sprintf( + __( + 'Great news — your %s account has been activated. You can now start accepting payments on your store.', + 'woocommerce-payments' + ), + 'WooPayments' + ), +}; diff --git a/client/overview/modal/connection-success/style.scss b/client/overview/modal/connection-success/style.scss new file mode 100644 index 00000000000..1616311f95d --- /dev/null +++ b/client/overview/modal/connection-success/style.scss @@ -0,0 +1,42 @@ +.components-modal__frame.woopayments-connection-success-modal { + max-width: 512px; + @media screen and ( max-width: 782px ) { + max-width: 343px; + margin: auto; + } + + .components-modal__header { + .components-button.has-icon { + padding: 0; + svg { + width: 30px; + height: 30px; + } + } + + &-heading { + font-size: 20px; + line-height: 28px; + font-weight: 300; + text-wrap: auto; + color: $gray-900; + } + } + + .woopayments-connection-success-modal__content { + display: flex; + gap: $gap-large; + flex-direction: column; + padding: $gap-small 0 $gap 0; + line-height: 20px; + font-weight: 400; + color: $gray-900; + } + + .woopayments-connection-success-modal__actions { + flex-direction: row-reverse; // Use this to force items to start from end. That would require actions to be introduced in reverse order. + padding: $gap-large 0 0 0; + display: flex; + gap: $gap-small; + } +} diff --git a/client/overview/modal/connection-success/test/index.test.tsx b/client/overview/modal/connection-success/test/index.test.tsx new file mode 100644 index 00000000000..e4a7d40da1b --- /dev/null +++ b/client/overview/modal/connection-success/test/index.test.tsx @@ -0,0 +1,58 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import user from '@testing-library/user-event'; + +/** + * Internal dependencies + */ +import ConnectionSuccessModal from '../index'; + +jest.mock( '@wordpress/data', () => ( { + useDispatch: jest.fn().mockReturnValue( { updateOptions: jest.fn() } ), +} ) ); + +declare const global: { + wcpaySettings: { + isConnectionSuccessModalDismissed: boolean; + }; +}; + +describe( 'Connection Success Modal', () => { + global.wcpaySettings = { + isConnectionSuccessModalDismissed: false, + }; + + it( 'modal is open by default', () => { + render( ); + + const queryHeading = () => + screen.queryByRole( 'heading', { + name: "You're ready to accept payments!", + } ); + + expect( queryHeading() ).toBeInTheDocument(); + } ); + + it( 'closes modal when dismiss button is clicked', () => { + global.wcpaySettings = { + isConnectionSuccessModalDismissed: false, + }; + + render( ); + + user.click( + screen.getByRole( 'button', { + name: 'Dismiss', + } ) + ); + + expect( + screen.queryByRole( 'heading', { + name: "You're ready to accept payments!", + } ) + ).not.toBeInTheDocument(); + } ); +} ); diff --git a/client/overview/modal/reset-account/index.tsx b/client/overview/modal/reset-account/index.tsx index f35be999d34..933e7d5e550 100644 --- a/client/overview/modal/reset-account/index.tsx +++ b/client/overview/modal/reset-account/index.tsx @@ -9,6 +9,7 @@ import { Button, CardDivider, Modal } from '@wordpress/components'; */ import './style.scss'; import strings from './strings'; +import { isInTestModeOnboarding } from 'utils'; interface Props { isVisible: boolean; @@ -34,13 +35,20 @@ const ResetAccountModal: React.FC< Props > = ( props: Props ) => { { strings.description }

    - { strings.beforeContinue } -
      -
    1. { strings.step1 }
    2. -
    3. { strings.step2 }
    4. -
    5. { strings.step3 }
    6. -
    - + { + // Only show the steps involved info if the account has been onboarded in live mode. + ! isInTestModeOnboarding && ( + <> + { strings.beforeContinue } +
      +
    1. { strings.step1 }
    2. +
    3. { strings.step2 }
    4. +
    5. { strings.step3 }
    6. +
    + + + ) + } { strings.confirmation }
    diff --git a/client/overview/modal/reset-account/strings.tsx b/client/overview/modal/reset-account/strings.tsx index b5c2adc2bdd..4d9bef6b2ee 100644 --- a/client/overview/modal/reset-account/strings.tsx +++ b/client/overview/modal/reset-account/strings.tsx @@ -10,11 +10,17 @@ import { __, sprintf } from '@wordpress/i18n'; import { isInTestModeOnboarding } from 'utils'; export default { - title: __( 'Reset account', 'woocommerce-payments' ), + title: isInTestModeOnboarding() + ? __( 'Reset your test account', 'woocommerce-payments' ) + : __( 'Reset account', 'woocommerce-payments' ), description: isInTestModeOnboarding() - ? __( - 'In sandbox mode, you can reset your account and onboard again at any time. Please note that all current WooPayments account details, test transactions, and payouts history will be lost.', - 'woocommerce-payments' + ? sprintf( + /* translators: 1: WooPayments. */ + __( + 'When you reset your test account, all data — including your %1$s account details, test transactions, and payouts history — will be lost. This action cannot be undone, but you can create a new test account at any time.', + 'woocommerce-payments' + ), + 'WooPayments' ) : __( 'If you are experiencing problems completing account setup, or need to change the email/country associated with your account, you can reset your account and start from the beginning.', diff --git a/client/overview/style.scss b/client/overview/style.scss index 4dbe66d79e7..8363f93a0fd 100644 --- a/client/overview/style.scss +++ b/client/overview/style.scss @@ -39,48 +39,6 @@ margin: 24px 0; } - .wcpay-connection-success { - position: relative; - text-align: center; - padding: $gap-large; - - &__dropdown { - position: absolute; - top: $gap-large; - right: $gap-large; - - .dashicons-ellipsis { - transform: rotate( 90deg ); - } - } - - .dashicons-button { - margin-right: 0; - display: none !important; - } - - img { - max-height: 73px; - max-width: 100%; - } - - h2 { - font-size: 20px; - font-weight: 400; - letter-spacing: 0.7px; - line-height: 28px; - margin-bottom: $gap-small; - - @media ( min-width: 783px ) { - padding: 0 10%; - } - } - - p { - color: $gray-700; - } - } - // Gutenberg compatibility adjustments. The component changed its classes and // styling in @wordpress/components 19.11.0. We're currently using 11.1.5. // To be removed when we upgrade this package. diff --git a/client/overview/task-list/strings.tsx b/client/overview/task-list/strings.tsx index 552d4529c7a..0508aafe5d8 100644 --- a/client/overview/task-list/strings.tsx +++ b/client/overview/task-list/strings.tsx @@ -423,7 +423,7 @@ export default { ), }, go_live: { - title: __( 'Set up live payments', 'woocommerce-payments' ), + title: __( 'Activate payments', 'woocommerce-payments' ), time: __( '10 minutes', 'woocommerce-payments' ), }, }, diff --git a/client/overview/test/index.js b/client/overview/test/index.js index f9fe1181b2f..663450ecac5 100644 --- a/client/overview/test/index.js +++ b/client/overview/test/index.js @@ -149,16 +149,6 @@ describe( 'Overview page', () => { ).toBeVisible(); } ); - it( 'Displays the success message for query param wcpay-connection-success=1', () => { - getQuery.mockReturnValue( { 'wcpay-connection-success': '1' } ); - - const { container } = render( ); - - expect( - container.querySelector( '.wcpay-connection-success' ) - ).toBeVisible(); - } ); - it( 'Displays the notice for Jetpack Identity Crisis', () => { global.wcpaySettings = { ...global.wcpaySettings, @@ -352,4 +342,56 @@ describe( 'Overview page', () => { expect( query() ).not.toBeInTheDocument(); } ); + + it( 'displays ConnectionSuccessModal if progressiveOnboarding is not enabled', () => { + getQuery.mockReturnValue( { 'wcpay-connection-success': '1' } ); + + global.wcpaySettings.accountStatus.progressiveOnboarding.isEnabled = false; + global.wcpaySettings.testModeOnboarding = false; + + render( ); + + expect( + screen.getByText( "You're ready to accept payments!" ) + ).toBeInTheDocument(); + } ); + + it( 'displays ConnectionSuccessModal if progressiveOnboarding is enabled and complete', () => { + getQuery.mockReturnValue( { 'wcpay-connection-success': '1' } ); + + global.wcpaySettings.accountStatus.progressiveOnboarding.isEnabled = true; + global.wcpaySettings.accountStatus.progressiveOnboarding.isComplete = true; + global.wcpaySettings.testModeOnboarding = false; + + render( ); + + expect( + screen.getByText( "You're ready to accept payments!" ) + ).toBeInTheDocument(); + } ); + + it( 'does not displays ConnectionSuccessModal connection success false', () => { + const query = () => + screen.queryByText( "You're ready to accept payments!" ); + + global.wcpaySettings.accountStatus.progressiveOnboarding.isEnabled = false; + global.wcpaySettings.testModeOnboarding = false; + + render( ); + + expect( query() ).not.toBeInTheDocument(); + } ); + + it( 'does not displays ConnectionSuccessModal if testModeOnboarding is false', () => { + const query = () => + screen.queryByText( "You're ready to accept payments!" ); + getQuery.mockReturnValue( { 'wcpay-connection-success': '1' } ); + + global.wcpaySettings.accountStatus.progressiveOnboarding.isEnabled = false; + global.wcpaySettings.testModeOnboarding = true; + + render( ); + + expect( query() ).not.toBeInTheDocument(); + } ); } ); diff --git a/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap index ed6c9656178..f99e5775c65 100644 --- a/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/order-details/test/__snapshots__/index.test.tsx.snap @@ -4,56 +4,9 @@ exports[`Order details page should match the snapshot - Charge without payment i
    -
    - -
    - The date and time formats now match your preferences. You can update them anytime in the - - settings - - . -
    - -
    = ( { return ( - { const { readers, chargeError, isLoading } = useCardReaderStats( props.chargeId, @@ -34,7 +34,6 @@ const PaymentCardReaderChargeDetails = ( props ) => { if ( ! isLoading && chargeError instanceof Error ) { return ( - diff --git a/client/payment-details/readers/test/__snapshots__/index.js.snap b/client/payment-details/readers/test/__snapshots__/index.js.snap index 67ce8e06e14..9e299155451 100644 --- a/client/payment-details/readers/test/__snapshots__/index.js.snap +++ b/client/payment-details/readers/test/__snapshots__/index.js.snap @@ -4,6 +4,7 @@ exports[`RenderPaymentCardReaderChargeDetails renders reader charges 1`] = `
    -
    - -
    - The date and time formats now match your preferences. You can update them anytime in the - - settings - - . -
    - -
    -
    - -
    - The date and time formats now match your preferences. You can update them anytime in the - - settings - - . -
    - -
    { ); } + const hasIdenticalSymbol = hasSameSymbol( + event.transaction_details.store_currency, + event.transaction_details.customer_currency + ); + return sprintf( - '%1$s (%2$f%% + %3$s): %4$s', + '%1$s (%2$f%% + %3$s%4$s): %5$s%6$s', baseFeeLabel, formatFee( percentage ), formatCurrency( fixed, fixedCurrency ), - formatCurrency( -feeAmount, feeCurrency ) + hasIdenticalSymbol ? ` ${ fixedCurrency }` : '', + formatCurrency( -feeAmount, feeCurrency ), + hasIdenticalSymbol ? ` ${ feeCurrency }` : '' ); }; @@ -453,7 +461,14 @@ export const feeBreakdown = ( event ) => { } = fee; const percentageRateFormatted = formatFee( percentageRate ); - const fixedRateFormatted = formatCurrency( fixedRate, currency ); + const fixedRateFormatted = `${ formatCurrency( fixedRate, currency ) }${ + hasSameSymbol( + event.transaction_details.store_currency, + event.transaction_details.customer_currency + ) + ? ` ${ currency.toUpperCase() }` + : '' + }`; const label = sprintf( feeLabelMapping( fixedRate, isCapped )[ labelType ], diff --git a/client/payment-details/timeline/test/captured-event-strings.js b/client/payment-details/timeline/test/captured-event-strings.js index 8e666904312..845d8e6664a 100644 --- a/client/payment-details/timeline/test/captured-event-strings.js +++ b/client/payment-details/timeline/test/captured-event-strings.js @@ -32,6 +32,14 @@ describe( 'Strings in captured events', () => { decimalSeparator: '.', precision: 2, }, + CA: { + code: 'CAD', + symbol: '$', + symbolPosition: 'left', + thousandSeparator: ',', + decimalSeparator: '.', + precision: 2, + }, JP: { code: 'JPY', symbol: '¥', diff --git a/client/payment-methods-icons.tsx b/client/payment-methods-icons.tsx index ecf241549d0..b0a76e3f2fd 100644 --- a/client/payment-methods-icons.tsx +++ b/client/payment-methods-icons.tsx @@ -25,6 +25,7 @@ import VisaAsset from 'assets/images/cards/visa.svg?asset'; import MasterCardAsset from 'assets/images/cards/mastercard.svg?asset'; import AmexAsset from 'assets/images/cards/amex.svg?asset'; import WooAsset from 'assets/images/payment-methods/woo.svg?asset'; +import WooAssetShort from 'assets/images/payment-methods/woo-short.svg?asset'; import ApplePayAsset from 'assets/images/cards/apple-pay.svg?asset'; import GooglePayAsset from 'assets/images/cards/google-pay.svg?asset'; import DinersClubAsset from 'assets/images/cards/diners.svg?asset'; @@ -38,12 +39,12 @@ import './style.scss'; const iconComponent = ( src: string, alt: string, - outline = true + border = true ): ReactImgFuncComponent => ( { className, ...props } ) => ( { au_becs_debit: upeCapabilityStatuses.ACTIVE, bancontact_payments: upeCapabilityStatuses.ACTIVE, eps_payments: upeCapabilityStatuses.ACTIVE, - giropay_payments: upeCapabilityStatuses.ACTIVE, ideal_payments: upeCapabilityStatuses.ACTIVE, p24_payments: upeCapabilityStatuses.ACTIVE, sepa_debit_payments: upeCapabilityStatuses.ACTIVE, - sofort_payments: upeCapabilityStatuses.ACTIVE, } ); useManualCapture.mockReturnValue( [ false, jest.fn() ] ); global.wcpaySettings = { diff --git a/client/settings/express-checkout-settings/index.scss b/client/settings/express-checkout-settings/index.scss index 7b6bd1ca1fe..0c87af7f178 100644 --- a/client/settings/express-checkout-settings/index.scss +++ b/client/settings/express-checkout-settings/index.scss @@ -45,7 +45,6 @@ width: 64px; height: 40px; margin: 1px 17px 1px 1px; // 1px to accommodate for box-shadow - background: $studio-white; svg { display: inline-block; @@ -277,7 +276,7 @@ } &__checkout-button { - background-color: $studio-woocommerce-purple-60; + background-color: $studio-woocommerce-purple-40; border-radius: 0.25rem; align-items: center; display: flex; @@ -341,6 +340,10 @@ &[data-theme='light-outline'] { background: #f0f0f0; } + + & > div { + margin: 4px; + } } &__border-radius { diff --git a/client/settings/express-checkout-settings/test/index.js b/client/settings/express-checkout-settings/test/index.js index 36b33975c64..899d5d782d5 100644 --- a/client/settings/express-checkout-settings/test/index.js +++ b/client/settings/express-checkout-settings/test/index.js @@ -76,13 +76,6 @@ describe( 'ExpressCheckoutSettings', () => { }; } ); - test( 'renders banner at the top', () => { - render( ); - - const banner = screen.queryByAltText( 'WooPayments logo' ); - expect( banner ).toBeInTheDocument(); - } ); - test( 'renders error message for invalid method IDs', () => { render( ); diff --git a/client/settings/fraud-protection/advanced-settings/index.tsx b/client/settings/fraud-protection/advanced-settings/index.tsx index 74a37e9734a..b39a659705a 100644 --- a/client/settings/fraud-protection/advanced-settings/index.tsx +++ b/client/settings/fraud-protection/advanced-settings/index.tsx @@ -341,7 +341,7 @@ const FraudProtectionAdvancedSettingsPage: React.FC = () => { setIsDirty, } } > - +
    diff --git a/client/settings/payment-method-icon/style.scss b/client/settings/payment-method-icon/style.scss index 90f90c8bc7c..f03312a753c 100644 --- a/client/settings/payment-method-icon/style.scss +++ b/client/settings/payment-method-icon/style.scss @@ -20,5 +20,6 @@ .payment-method__icon { order: initial; margin-right: 3px; + max-width: 45px; } } diff --git a/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap b/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap index 6382c0dbaa0..5d6e4f591b2 100644 --- a/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap +++ b/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap @@ -85,7 +85,7 @@ exports[`Activation Modal matches the snapshot 1`] = ` > Credit card / Debit card Credit card / Debit card { 'au_becs_debit', 'bancontact', 'eps', - 'giropay', 'ideal', 'p24', 'sepa_debit', - 'sofort', ] ); useGetPaymentMethodStatuses.mockReturnValue( { card_payments: upeCapabilityStatuses.ACTIVE, au_becs_debit: upeCapabilityStatuses.ACTIVE, bancontact_payments: upeCapabilityStatuses.ACTIVE, eps_payments: upeCapabilityStatuses.ACTIVE, - giropay_payments: upeCapabilityStatuses.ACTIVE, ideal_payments: upeCapabilityStatuses.ACTIVE, p24_payments: upeCapabilityStatuses.ACTIVE, sepa_debit_payments: upeCapabilityStatuses.ACTIVE, - sofort_payments: upeCapabilityStatuses.ACTIVE, } ); useManualCapture.mockReturnValue( [ false, jest.fn() ] ); global.wcpaySettings = { @@ -135,11 +131,9 @@ describe( 'PaymentMethodsSection', () => { 'BECS Direct Debit', 'Bancontact', 'EPS', - 'giropay', 'iDEAL', 'Przelewy24 (P24)', 'SEPA Direct Debit', - 'Sofort', ], updateEnabledMethodsMock, ] ); @@ -157,10 +151,6 @@ describe( 'PaymentMethodsSection', () => { requirements: [], }, eps_payments: { - status: upeCapabilityStatuses.INACTIVE, - requirements: [], - }, - giropay_payments: { status: upeCapabilityStatuses.PENDING_APPROVAL, requirements: [], }, @@ -173,10 +163,6 @@ describe( 'PaymentMethodsSection', () => { requirements: [], }, sepa_debit_payments: { - status: upeCapabilityStatuses.ACTIVE, - requirements: [], - }, - sofort_payments: { status: upeCapabilityStatuses.PENDING_VERIFICATION, requirements: [ 'individual.identification_number' ], }, diff --git a/client/settings/reporting-settings/components/export-language/index.tsx b/client/settings/reporting-settings/components/export-language/index.tsx deleted file mode 100644 index 950d43f5584..00000000000 --- a/client/settings/reporting-settings/components/export-language/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { __ } from '@wordpress/i18n'; -import { addQueryArgs } from '@wordpress/url'; -import { SelectControl, ExternalLink } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import interpolateComponents from '@automattic/interpolate-components'; -import { ReportingExportLanguageHook } from '../../interfaces'; -import { useReportingExportLanguage } from 'wcpay/data'; -import { getExportLanguageOptions } from 'wcpay/utils'; - -const ExportLanguage: React.FC = () => { - const [ - exportLanguage, - updateExportLanguage, - ] = useReportingExportLanguage() as ReportingExportLanguageHook; - - const handleExportLanguageChange = ( language: string ) => { - updateExportLanguage( language ); - }; - - return ( -
    - -

    - { interpolateComponents( { - mixedString: __( - 'You can change your global site language preferences in {{learnMoreLink}}General Settings{{/learnMoreLink}}.', - 'woocommerce-payments' - ), - components: { - learnMoreLink: ( - // eslint-disable-next-line max-len - - ), - }, - } ) } -

    -
    - ); -}; - -export default ExportLanguage; diff --git a/client/settings/reporting-settings/components/index.ts b/client/settings/reporting-settings/components/index.ts deleted file mode 100644 index 3c3360bd22a..00000000000 --- a/client/settings/reporting-settings/components/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Internal dependencies - */ -import ExportLanguage from './export-language'; - -export { ExportLanguage }; diff --git a/client/settings/reporting-settings/index.tsx b/client/settings/reporting-settings/index.tsx deleted file mode 100644 index 9e2ee4a0178..00000000000 --- a/client/settings/reporting-settings/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { __ } from '@wordpress/i18n'; -import { Card } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import CardBody from '../card-body'; -import { ExportLanguage } from './components'; -import './style.scss'; - -const Reporting: React.FC = () => { - return ( - <> - - -

    - - { __( - 'Report exporting default language', - 'woocommerce-payments' - ) } - -

    - -
    -
    - - ); -}; - -export default Reporting; diff --git a/client/settings/reporting-settings/interfaces.ts b/client/settings/reporting-settings/interfaces.ts deleted file mode 100644 index 22f37cb1e7b..00000000000 --- a/client/settings/reporting-settings/interfaces.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ReportingExportLanguageHook = [ - string, - ( language: string ) => void -]; diff --git a/client/settings/reporting-settings/style.scss b/client/settings/reporting-settings/style.scss deleted file mode 100644 index 354c2530e61..00000000000 --- a/client/settings/reporting-settings/style.scss +++ /dev/null @@ -1,17 +0,0 @@ -.reporting-settings { - &__text--help-text { - font-size: 12px; - color: #757575; - margin: 0; - display: inline-block; - } - - .reporting-export-language { - flex-wrap: wrap; - margin-bottom: 0; - .components-select-control { - padding-right: 16px; - margin-bottom: 0; - } - } -} diff --git a/client/settings/reporting-settings/test/__snapshots__/index.test.js.snap b/client/settings/reporting-settings/test/__snapshots__/index.test.js.snap deleted file mode 100644 index aef73119cb9..00000000000 --- a/client/settings/reporting-settings/test/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Reporting Settings should render correctly 1`] = ` -
    -
    -
    -
    -

    - - Report exporting default language - -

    -
    -
    -
    -
    -
    - -
    -
    - - -
    - -
    -
    - -
    -
    -
    -

    - You can change your global site language preferences in - - General Settings - - (opens in a new tab) - - - - . -

    -
    -
    -
    - -`; diff --git a/client/settings/reporting-settings/test/index.test.js b/client/settings/reporting-settings/test/index.test.js deleted file mode 100644 index ef0a2437145..00000000000 --- a/client/settings/reporting-settings/test/index.test.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import { render, screen, within } from '@testing-library/react'; - -/** - * Internal dependencies - */ -import Reporting from '..'; -import { useReportingExportLanguage } from 'wcpay/data'; - -jest.mock( '@wordpress/data' ); - -jest.mock( 'wcpay/data', () => ( { - useReportingExportLanguage: jest.fn(), -} ) ); - -describe( 'Reporting Settings', () => { - beforeEach( () => { - useReportingExportLanguage.mockReturnValue( [ 'en_US', jest.fn() ] ); - - global.wcpaySettings = { - locale: { - code: 'es_ES', - native_name: 'Spanish', - }, - }; - } ); - - it( 'should render correctly', () => { - const { container } = render( ); - expect( container ).toMatchSnapshot(); - - expect( - screen.getByText( /Report exporting default language/ ) - ).toBeInTheDocument(); - expect( - screen.getByText( - /You can change your global site language preferences/ - ) - ).toBeInTheDocument(); - } ); - - it( 'renders the language select', () => { - render( ); - - const languageSelect = screen.getByLabelText( /Language/ ); - expect( languageSelect ).toHaveValue( 'en_US' ); - - within( languageSelect ).getByRole( 'option', { name: /English/ } ); - within( languageSelect ).getByRole( 'option', { - name: /Site Language - Spanish/, - } ); - } ); -} ); diff --git a/client/settings/settings-layout/index.js b/client/settings/settings-layout/index.js index 391453f9fb9..0ee519802f3 100644 --- a/client/settings/settings-layout/index.js +++ b/client/settings/settings-layout/index.js @@ -7,15 +7,10 @@ import React from 'react'; /** * Internal dependencies */ -import Banner from '../../banner'; import './style.scss'; -const SettingsLayout = ( { children, displayBanner = true } ) => ( -
    - { displayBanner && } - - { children } -
    +const SettingsLayout = ( { children } ) => ( +
    { children }
    ); export default SettingsLayout; diff --git a/client/settings/settings-manager/index.js b/client/settings/settings-manager/index.js index fc238c706b2..3da257c9074 100644 --- a/client/settings/settings-manager/index.js +++ b/client/settings/settings-manager/index.js @@ -14,7 +14,6 @@ import AdvancedSettings from '../advanced-settings'; import ExpressCheckout from '../express-checkout'; import SettingsSection from '../settings-section'; import GeneralSettings from '../general-settings'; -import ReportingSettings from '../reporting-settings'; import SettingsLayout from '../settings-layout'; import SaveSettingsSection from '../save-settings-section'; import Transactions from '../transactions'; @@ -29,7 +28,6 @@ import { useSettings, } from '../../data'; import FraudProtection from '../fraud-protection'; -import { isDefaultSiteLanguage } from 'wcpay/utils'; import DuplicatedPaymentMethodsContext from './duplicated-payment-methods-context'; const ExpressCheckoutDescription = () => ( @@ -124,20 +122,6 @@ const FraudProtectionDescription = () => { ); }; -const ReportingDescription = () => { - return ( - <> -

    { __( 'Reporting', 'woocommerce-payments' ) }

    -

    - { __( - 'Adjust your report exporting language preferences.', - 'woocommerce-payments' - ) } -

    - - ); -}; - const AdvancedDescription = () => { return ( <> @@ -260,18 +244,6 @@ const SettingsManager = () => { - { ! isDefaultSiteLanguage() && ( - - - - - - - - ) } ( { + useElements: jest.fn(), + useStripe: jest.fn(), +} ) ); +jest.mock( 'tracks', () => ( { + recordUserEvent: jest.fn(), +} ) ); + +const jQueryMock = ( selector ) => { + if ( typeof selector === 'function' ) { + return selector( jQueryMock ); + } + + return { + on: () => null, + val: () => null, + is: () => null, + remove: () => null, + }; +}; +jQueryMock.blockUI = () => null; + +window.wcpayExpressCheckoutParams = {}; +window.wcpayExpressCheckoutParams.checkout = {}; + +describe( 'useExpressCheckout', () => { + beforeEach( () => { + global.$ = jQueryMock; + global.jQuery = jQueryMock; + } ); + + it( 'should provide no shipping rates when not required on click', () => { + const onClickMock = jest.fn(); + const event = { resolve: jest.fn() }; + const { result } = renderHook( () => + useExpressCheckout( { + billing: { + cartTotalItems: [], + }, + shippingData: { + needsShipping: false, + shippingRates: [], + }, + onClick: onClickMock, + onClose: {}, + setExpressPaymentError: {}, + } ) + ); + + expect( onClickMock ).not.toHaveBeenCalled(); + + result.current.onButtonClick( event ); + + expect( event.resolve ).toHaveBeenCalledWith( + expect.objectContaining( { + shippingRates: undefined, + shippingAddressRequired: false, + } ) + ); + expect( onClickMock ).toHaveBeenCalled(); + } ); + + it( 'should provide the shipping rates on click', () => { + const event = { resolve: jest.fn() }; + const { result } = renderHook( () => + useExpressCheckout( { + billing: { + cartTotalItems: [], + }, + shippingData: { + needsShipping: true, + shippingRates: [ + { + shipping_rates: [ + { + rate_id: '1', + price: '10', + name: 'Fake shipping rate', + }, + ], + }, + ], + }, + onClick: jest.fn(), + onClose: {}, + setExpressPaymentError: {}, + } ) + ); + + result.current.onButtonClick( event ); + + expect( event.resolve ).toHaveBeenCalledWith( + expect.objectContaining( { + shippingRates: expect.arrayContaining( [ + { + id: '1', + displayName: 'Fake shipping rate', + amount: 10, + }, + ] ), + shippingAddressRequired: true, + } ) + ); + } ); + + it( 'should provide the shipping rates with fallback on click', () => { + const event = { resolve: jest.fn() }; + const { result } = renderHook( () => + useExpressCheckout( { + billing: { + cartTotalItems: [], + }, + shippingData: { + needsShipping: true, + shippingRates: [], + }, + onClick: jest.fn(), + onClose: {}, + setExpressPaymentError: {}, + } ) + ); + + result.current.onButtonClick( event ); + + expect( event.resolve ).toHaveBeenCalledWith( + expect.objectContaining( { + shippingRates: expect.arrayContaining( [ + { + id: 'pending', + displayName: 'Pending', + amount: 0, + }, + ] ), + shippingAddressRequired: true, + } ) + ); + } ); +} ); diff --git a/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js b/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js index 45d7ec50d22..f1e28fd66f9 100644 --- a/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js +++ b/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js @@ -3,6 +3,7 @@ */ import { useCallback } from '@wordpress/element'; import { useStripe, useElements } from '@stripe/react-stripe-js'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -58,22 +59,46 @@ export const useExpressCheckout = ( { return; } + const shippingAddressRequired = shippingData?.needsShipping; + + let shippingRates; + if ( shippingAddressRequired ) { + const hasValidRates = + shippingData?.shippingRates[ 0 ]?.shipping_rates?.length > + 0; + + if ( hasValidRates ) { + shippingRates = shippingData.shippingRates[ 0 ].shipping_rates.map( + ( rate ) => { + return { + id: rate.rate_id, + amount: parseInt( rate.price, 10 ), + displayName: rate.name, + }; + } + ); + } else { + shippingRates = [ + { + id: 'pending', + displayName: __( + 'Pending', + 'woocommerce-payments' + ), + amount: 0, + }, + ]; + } + } + const options = { lineItems: normalizeLineItems( billing?.cartTotalItems ), emailRequired: true, - shippingAddressRequired: shippingData?.needsShipping, + shippingAddressRequired, phoneNumberRequired: getExpressCheckoutData( 'checkout' )?.needs_payer_phone ?? false, - shippingRates: shippingData?.shippingRates[ 0 ]?.shipping_rates?.map( - ( r ) => { - return { - id: r.rate_id, - amount: parseInt( r.price, 10 ), - displayName: r.name, - }; - } - ), + shippingRates, allowedShippingCountries: getExpressCheckoutData( 'checkout' ) .allowed_shipping_countries, }; diff --git a/client/tokenized-express-checkout/event-handlers.js b/client/tokenized-express-checkout/event-handlers.js index 77ee001468d..1ef908bfc79 100644 --- a/client/tokenized-express-checkout/event-handlers.js +++ b/client/tokenized-express-checkout/event-handlers.js @@ -34,6 +34,8 @@ export const setCartApiHandler = ( handler ) => ( cartApi = handler ); export const getCartApiHandler = () => cartApi; export const shippingAddressChangeHandler = async ( event, elements ) => { + lastSelectedAddress = event.address; + try { // Please note that the `event.address` might not contain all the fields. // Some fields might not be present (like `line_1` or `line_2`) due to semi-anonymized data. @@ -62,8 +64,6 @@ export const shippingAddressChangeHandler = async ( event, elements ) => { ), } ); - lastSelectedAddress = event.address; - event.resolve( { shippingRates: transformCartDataForShippingRates( cartData ), lineItems: transformCartDataForDisplayItems( cartData ), diff --git a/client/tokenized-express-checkout/index.js b/client/tokenized-express-checkout/index.js index 924fdc79482..6143c540a59 100644 --- a/client/tokenized-express-checkout/index.js +++ b/client/tokenized-express-checkout/index.js @@ -65,29 +65,18 @@ const fetchNewCartData = async () => { }; const getServerSideExpressCheckoutProductData = () => { - const requestShipping = - getExpressCheckoutData( 'product' )?.needs_shipping ?? false; const displayItems = ( getExpressCheckoutData( 'product' )?.displayItems ?? [] ).map( ( { label, amount } ) => ( { name: label, amount, } ) ); - const shippingRates = requestShipping - ? [ - { - id: 'pending', - displayName: __( 'Pending', 'woocommerce-payments' ), - amount: 0, - }, - ] - : undefined; return { total: getExpressCheckoutData( 'product' )?.total.amount, currency: getExpressCheckoutData( 'product' )?.currency, - requestShipping, - shippingRates, + requestShipping: + getExpressCheckoutData( 'product' )?.needs_shipping ?? false, requestPhone: getExpressCheckoutData( 'checkout' )?.needs_payer_phone ?? false, displayItems, @@ -258,6 +247,22 @@ jQuery( ( $ ) => { } ); } + const shippingOptionsWithFallback = + ! options.shippingRates || // server-side data on the product page initialization doesn't provide any shipping rates. + options.shippingRates.length === 0 // but it can also happen that there are no rates in the array. + ? [ + // fallback for initialization (and initialization _only_), before an address is provided by the ECE. + { + id: 'pending', + displayName: __( + 'Pending', + 'woocommerce-payments' + ), + amount: 0, + }, + ] + : options.shippingRates; + const clickOptions = { // `options.displayItems`, `options.requestShipping`, `options.requestPhone`, `options.shippingRates`, // are all coming from prior of the initialization. @@ -268,7 +273,9 @@ jQuery( ( $ ) => { emailRequired: true, shippingAddressRequired: options.requestShipping, phoneNumberRequired: options.requestPhone, - shippingRates: options.shippingRates, + shippingRates: options.requestShipping + ? shippingOptionsWithFallback + : undefined, allowedShippingCountries: getExpressCheckoutData( 'checkout' ).allowed_shipping_countries, diff --git a/client/transactions/blocked/columns.tsx b/client/transactions/blocked/columns.tsx index 8e4f410ff32..7c18b0b2b73 100644 --- a/client/transactions/blocked/columns.tsx +++ b/client/transactions/blocked/columns.tsx @@ -15,7 +15,7 @@ import { getDetailsURL } from '../../components/details-link'; import ClickableCell from '../../components/clickable-cell'; import { formatDateTimeFromString } from 'wcpay/utils/date-time'; -interface Column extends TableCardColumn { +export interface Column extends TableCardColumn { key: 'created' | 'amount' | 'customer' | 'status'; visible?: boolean; cellClassName?: string; diff --git a/client/transactions/blocked/index.tsx b/client/transactions/blocked/index.tsx index f6153231f69..a7fca85cc7c 100644 --- a/client/transactions/blocked/index.tsx +++ b/client/transactions/blocked/index.tsx @@ -31,6 +31,7 @@ import { import Page from '../../components/page'; import { recordEvent } from 'tracks'; import { + Column, getBlockedListColumns, getBlockedListColumnsStructure, } from './columns'; @@ -38,13 +39,17 @@ import { formatExplicitCurrency } from 'multi-currency/interface/functions'; import autocompleter from '../fraud-protection/autocompleter'; import DownloadButton from '../../components/download-button'; import { getFraudOutcomeTransactionsExport } from '../../data/transactions/resolvers'; +import { usePersistedColumnVisibility } from 'wcpay/hooks/use-persisted-table-column-visibility'; export const BlockedList = (): JSX.Element => { const [ isDownloading, setIsDownloading ] = useState( false ); const { createNotice } = useDispatch( 'core/notices' ); const query = getQuery(); - const columnsToDisplay = getBlockedListColumns(); + const columns = getBlockedListColumns(); + const { columnsToDisplay, onColumnsChange } = usePersistedColumnVisibility< + Column + >( 'wc_payments_transactions_blocked_hidden_columns', columns ); const { isLoading, transactions } = useFraudOutcomeTransactions( 'block', query @@ -174,6 +179,7 @@ export const BlockedList = (): JSX.Element => { summary={ summary } query={ query } onQueryChange={ onQueryChange } + onColumnsChange={ onColumnsChange } actions={ [ , key?: string ) => void; actions?: React.ReactNode[]; showMenu?: boolean; } diff --git a/client/transactions/filters/config.ts b/client/transactions/filters/config.ts index 61be5f4660d..8485dbe771d 100644 --- a/client/transactions/filters/config.ts +++ b/client/transactions/filters/config.ts @@ -30,10 +30,6 @@ export interface TransactionsFilterType { const transactionTypesOptions = Object.entries( displayType ) .map( ( [ type, label ] ) => { - //@TODO - implement filter transactions by card reader fee - if ( type === 'card_reader_fee' ) { - return null; - } return { label, value: type }; } ) .filter( function ( el ) { diff --git a/client/transactions/filters/test/__snapshots__/index.tsx.snap b/client/transactions/filters/test/__snapshots__/index.tsx.snap index a2a2a7bd3da..7feb3c5bb55 100644 --- a/client/transactions/filters/test/__snapshots__/index.tsx.snap +++ b/client/transactions/filters/test/__snapshots__/index.tsx.snap @@ -152,6 +152,11 @@ HTMLOptionsCollection [ > Dispute reversal , + ,