Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect MC Account within combo card #2639

Draft
wants to merge 53 commits into
base: feature/2567-kickoff-mc-ads-account-creation
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7ccf37f
Hide Tax UI during onboarding & keep in edit campaign
dsawardekar Aug 19, 2024
f3d0847
Removes tax rate related e2e tests since no onboarding UI
dsawardekar Aug 20, 2024
87d46c2
Fixes linter warnings
dsawardekar Aug 20, 2024
98aa6f0
Fixes linter warnings
dsawardekar Aug 20, 2024
d8d0ba9
Removes unused code in e2e tests
dsawardekar Aug 20, 2024
7acafdf
Removes extra line break
dsawardekar Aug 20, 2024
57ecc5d
Merge branch 'tweak/default-tax-2491' into update/2490-hide-tax-rate-…
dsawardekar Aug 20, 2024
9cd398f
Hide default prop per CR feedback
dsawardekar Aug 23, 2024
a4abb29
Update error checking to ignore if hideTaxRates is true
dsawardekar Aug 23, 2024
c2c3242
Mock default MC settings with tax_rate set
joemcgill Aug 26, 2024
4870093
Remove unused e2e test helpers
joemcgill Aug 26, 2024
debc575
Merge branch 'update/2490-hide-tax-rate-selection-ui-onboarding' of g…
dsawardekar Sep 5, 2024
630c73d
Removes unused function
dsawardekar Sep 5, 2024
3e4910f
Updates props per CR feedback
dsawardekar Sep 5, 2024
5db0bad
Updates jsdoc
dsawardekar Sep 11, 2024
bd39f03
Merge branch 'feature/2458-streamline-onboarding' into update/2490-hi…
dsawardekar Sep 11, 2024
e7e6687
First pass at appselectcontrol changes
dsawardekar Sep 11, 2024
3b87196
Fix linter warnings
dsawardekar Sep 12, 2024
8eb1fff
Merge pull request #2542 from woocommerce/update/2490-hide-tax-rate-s…
joemcgill Sep 16, 2024
5ab1b61
Move AdsAccountSelectControl
asvinb Sep 17, 2024
2fb0353
Update Jest tests.
asvinb Sep 17, 2024
fd9f50a
Update label.
asvinb Sep 17, 2024
a17ce78
Fix e2e tests.
asvinb Sep 17, 2024
6342061
Do not use translation.
asvinb Sep 18, 2024
f0e12a7
Merge branch 'feature/2458-streamline-onboarding' into update/2593-up…
asvinb Sep 20, 2024
a37c974
Tweak WP Proxy Response to force the types of the fields
puntope Sep 25, 2024
7cb7531
Symlink WC instead of moving the WC folder
jorgemd24 Sep 25, 2024
2583950
Fix mixed spacing
jorgemd24 Sep 26, 2024
24c212e
Merge pull request #2625 from woocommerce/dev/fix-bluebird-dependency…
jorgemd24 Sep 26, 2024
502a89e
Merge branch 'develop' into fix/api-pull-type-prices
puntope Sep 27, 2024
c93a26a
Merge pull request #2624 from woocommerce/fix/api-pull-type-prices
puntope Sep 27, 2024
d575ba6
Move auto select logic to AppSelectControl.
asvinb Sep 30, 2024
a2a1e84
Merge branch 'develop' into feature/2458-streamline-onboarding
joemcgill Sep 30, 2024
345e6b1
Merge branch 'feature/2458-streamline-onboarding' into feature/2590-c…
joemcgill Sep 30, 2024
dc9b152
Merge branch 'feature/2509-consolidate-google-account-cards' into upd…
joemcgill Sep 30, 2024
0eafa45
Address CR feedback.
asvinb Oct 1, 2024
8c43ac6
Use useRef to prevent re renders.
asvinb Oct 2, 2024
874ef00
Save WIP.
asvinb Oct 3, 2024
1ead7f9
Merge branch 'feature/2567-kickoff-mc-ads-account-creation' into upda…
asvinb Oct 3, 2024
4428639
Extract DisconnectAccount button.
asvinb Oct 3, 2024
bebfaed
Add ConnectAccountCard component.
asvinb Oct 4, 2024
872787e
Add ConnectMCBody and ConnectMCFooter component.
asvinb Oct 4, 2024
004c9f6
Check for undefined values.
asvinb Oct 4, 2024
08b16e0
Make better use of useRef.
asvinb Oct 4, 2024
f290352
Updated JSDocs.
asvinb Oct 4, 2024
d8f3ed9
Fix JSDocs.
asvinb Oct 7, 2024
9475ec2
Save WIP.
asvinb Oct 7, 2024
3965234
Check for non connected state.
asvinb Oct 7, 2024
8a4ba4c
Add preConditionReady check.
asvinb Oct 7, 2024
8b850fd
Revert dev temp changes.
asvinb Oct 7, 2024
7d31e5c
Merge pull request #2608 from woocommerce/update/2593-update-app-sele…
asvinb Oct 7, 2024
176a4a8
Save E2E tests,
asvinb Oct 7, 2024
b4b4347
Merge branch 'feature/2509-consolidate-google-account-cards' into upd…
asvinb Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bin/install-wp-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ install_wc() {
if [ ! -f "$WC_VERSION_FILE" ]; then
# set up testing suite
rm -rf "$WC_DIR"
mkdir -p "$WC_DIR"
echo "Installing WooCommerce ($WC_VERSION)."
# Grab the necessary plugins.
WC_TMPDIR="${TMPDIR}/woocommerce-${WC_VERSION}"
rm -rf "${WC_TMPDIR}"
git clone --quiet --depth=1 --branch="${WC_VERSION}" https://github.com/woocommerce/woocommerce.git "${WC_TMPDIR}"
mv "${WC_TMPDIR}"/plugins/woocommerce/* "$WC_DIR"
touch "$WC_VERSION_FILE"

ln -s "${WC_TMPDIR}"/plugins/woocommerce "$WC_DIR"
touch "$WC_VERSION_FILE"

# Install composer for WooCommerce
cd "${WC_DIR}"
Expand Down
28 changes: 28 additions & 0 deletions js/src/components/ads-account-select-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Internal dependencies
*/
import AppSelectControl from '.~/components/app-select-control';
import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts';

/**
* @param {Object} props The component props
* @return {JSX.Element} An enhanced AppSelectControl component.
*/
const AdsAccountSelectControl = ( props ) => {
const { existingAccounts } = useExistingGoogleAdsAccounts();

const options = existingAccounts?.map( ( acc ) => ( {
value: acc.id,
label: `${ acc.name } (${ acc.id })`,
} ) );

return (
<AppSelectControl
options={ options }
autoSelectFirstOption
{ ...props }
/>
);
};

export default AdsAccountSelectControl;
60 changes: 52 additions & 8 deletions js/src/components/app-select-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
* External dependencies
*/
import { SelectControl } from '@wordpress/components';
import { useEffect, useRef } from '@wordpress/element';
import classNames from 'classnames';
import { noop } from 'lodash';

/**
* Internal dependencies
Expand All @@ -12,18 +14,60 @@ import './index.scss';
/**
* Renders a `@wordpress/components`'s `SelectControl` with margin-bottom removed.
*
* If you provide `className` via props,
* it will be added to the container div's `className`,
* so that you can further control its style.
*
* @param {*} props
* @param {*} props The component props.
* @param {Array} [props.options=[]] Array of options for the select dropdown. Each option should be an object containing `label` and `value` properties.
* @param {string} [props.className] Additional classname to further control the style of the component.
* @param {Function} [props.onChange=noop] Callback function triggered when the selected value changes. Receives the new value as an argument.
* @param {string} [props.value] The currently selected value. This component should be used as a controlled component. A special case is that after mounting, when `autoSelectFirstOption` is true and `value` is undefined, it tries to call back `onChange` once to select the first option so that the `value` can be consistent with the `<select>` element's own value.
* @param {boolean} [props.autoSelectFirstOption=false] If true, automatically triggers the onChange callback with the first option as value when no value is provided. If only one option is available, the select control is also changed to non-interactive.
* @param {*} [props.rest] Additional props passed to the `SelectControl` component.
*/
const AppSelectControl = ( props ) => {
const { className, ...rest } = props;
const {
options = [],
className,
onChange = noop,
value,
autoSelectFirstOption = false,
...rest
} = props;
const shouldAutoSelectOnceRef = useRef( autoSelectFirstOption === true );

useEffect( () => {
if ( ! shouldAutoSelectOnceRef.current ) {
return;
}

if ( value === undefined && options.length ) {
shouldAutoSelectOnceRef.current = false;
onChange( options[ 0 ].value );
}
}, [ value, options, onChange ] );

let selectProps = {
options,
value,
onChange,
...rest,
};

const hasSingleValueStyle = autoSelectFirstOption && options?.length === 1;
if ( hasSingleValueStyle ) {
selectProps = {
...selectProps,
suffix: ' ',
tabIndex: '-1',
readOnly: true,
};
}

return (
<div className={ classNames( 'app-select-control', className ) }>
<SelectControl { ...rest } />
<div
className={ classNames( 'app-select-control', className, {
'app-select-control--has-single-value': hasSingleValueStyle,
} ) }
>
<SelectControl { ...selectProps } />
</div>
);
};
Expand Down
4 changes: 4 additions & 0 deletions js/src/components/app-select-control/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
margin-bottom: 0;
}
}

.app-select-control--has-single-value {
pointer-events: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const checkErrors = (
values,
shippingTimes,
finalCountryCodes,
storeCountryCode
storeCountryCode,
hideTaxRates = false
) => {
const errors = {};

Expand Down Expand Up @@ -110,6 +111,7 @@ const checkErrors = (
* Check tax rate (required for U.S. only).
*/
if (
! hideTaxRates &&
( storeCountryCode === 'US' || finalCountryCodes.includes( 'US' ) ) &&
! validTaxRateSet.has( values.tax_rate )
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import ConditionalSection from '.~/components/conditional-section';
*
* @param {Object} props React props.
* @param {string} [props.submitLabel="Complete setup"] Submit button label.
* @param {boolean} [props.hideTaxRates] Whether to hide tax rate section.
*/
const FormContent = ( {
submitLabel = __( 'Complete setup', 'google-listings-and-ads' ),
hideTaxRates,
} ) => {
const { values, isValidForm, handleSubmit, adapter } =
useAdaptiveFormContext();
const shouldDisplayTaxRate = useDisplayTaxRate( adapter.audienceCountries );
const displayTaxRate = useDisplayTaxRate( adapter.audienceCountries );
const shouldDisplayTaxRate = ! hideTaxRates && displayTaxRate;
const shouldDisplayShippingTime = values.shipping_time === 'flat';

const handleSubmitClick = ( event ) => {
Expand Down
10 changes: 8 additions & 2 deletions js/src/components/free-listings/setup-free-listings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const getSettings = ( values ) => {
* @param {() => void} [props.onContinue] Callback called once continue button is clicked. Could be async. While it's being resolved the form would turn into a saving state.
* @param {string} [props.submitLabel] Submit button label, to be forwarded to `FormContent`.
* @param {JSX.Element} props.headerTitle Title in the header block of this setup.
* @param {boolean} [props.hideTaxRates=false] Whether to hide tax rate section, to be forwarded to `FormContent`.
*/
const SetupFreeListings = ( {
targetAudience,
Expand All @@ -83,6 +84,7 @@ const SetupFreeListings = ( {
onContinue = noop,
submitLabel,
headerTitle,
hideTaxRates = false,
} ) => {
const formRef = useRef();
const { code: storeCountryCode } = useStoreCountry();
Expand All @@ -99,7 +101,8 @@ const SetupFreeListings = ( {
values,
shippingTimesData,
countries,
storeCountryCode
storeCountryCode,
hideTaxRates
);
};

Expand Down Expand Up @@ -218,7 +221,10 @@ const SetupFreeListings = ( {
validate={ handleValidate }
onSubmit={ onContinue }
>
<FormContent submitLabel={ submitLabel } />
<FormContent
submitLabel={ submitLabel }
hideTaxRates={ hideTaxRates }
/>
</AdaptiveForm>
</div>
);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import useApiFetchCallback from '.~/hooks/useApiFetchCallback';
import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices';
import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import useEventPropertiesFilter from '.~/hooks/useEventPropertiesFilter';
import AdsAccountSelectControl from './ads-account-select-control';
import AdsAccountSelectControl from '.~/components/ads-account-select-control';
import { useAppDispatch } from '.~/data';
import { FILTER_ONBOARDING } from '.~/utils/tracks';
import './index.scss';
Expand Down Expand Up @@ -95,7 +95,7 @@ const ConnectAds = ( props ) => {
<Section.Card.Body>
<Subsection.Title>
{ __(
'Select an existing account',
'Connect to an existing account',
'google-listings-and-ads'
) }
</Subsection.Title>
Expand All @@ -120,7 +120,6 @@ const ConnectAds = ( props ) => {
) }
<ContentButtonLayout>
<AdsAccountSelectControl
accounts={ accounts }
value={ value }
onChange={ setValue }
/>
Expand Down
30 changes: 14 additions & 16 deletions js/src/components/google-ads-account-card/connect-ads/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import { useAppDispatch } from '.~/data';
import { FILTER_ONBOARDING } from '.~/utils/tracks';
import expectComponentToRecordEventWithFilteredProperties from '.~/tests/expectComponentToRecordEventWithFilteredProperties';
import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts';

jest.mock( '.~/hooks/useApiFetchCallback', () =>
jest.fn().mockName( 'useApiFetchCallback' )
Expand All @@ -24,6 +25,10 @@ jest.mock( '.~/hooks/useGoogleAdsAccount', () =>
jest.fn().mockName( 'useGoogleAdsAccount' )
);

jest.mock( '.~/hooks/useExistingGoogleAdsAccounts', () =>
jest.fn().mockName( 'useExistingGoogleAdsAccounts' )
);

jest.mock( '.~/data', () => ( {
...jest.requireActual( '.~/data' ),
useAppDispatch: jest.fn(),
Expand Down Expand Up @@ -64,6 +69,10 @@ describe( 'ConnectAds', () => {
.mockName( 'refetchGoogleAdsAccount' ),
} );

useExistingGoogleAdsAccounts.mockReturnValue( {
existingAccounts: accounts,
} );

fetchGoogleAdsAccountStatus = jest
.fn()
.mockName( 'fetchGoogleAdsAccountStatus' );
Expand All @@ -76,11 +85,7 @@ describe( 'ConnectAds', () => {

it( 'should render the given accounts in a selection', () => {
render( <ConnectAds accounts={ accounts } /> );

expect( screen.getByRole( 'combobox' ) ).toBeInTheDocument();
expect(
screen.getByRole( 'option', { name: 'Select one' } )
).toBeInTheDocument();
expect(
screen.getByRole( 'option', { name: 'Account A (1)' } )
).toBeInTheDocument();
Expand Down Expand Up @@ -115,22 +120,15 @@ describe( 'ConnectAds', () => {
expect( onCreateNew ).toHaveBeenCalledTimes( 1 );
} );

it( 'should disable the "Connect" button when no account is selected', async () => {
const user = userEvent.setup();
it( 'should disable the "Connect" button when there are no accounts', async () => {
useExistingGoogleAdsAccounts.mockReturnValue( {
existingAccounts: [],
} );

render( <ConnectAds accounts={ accounts } /> );
const combobox = screen.getByRole( 'combobox' );
render( <ConnectAds accounts={ [] } /> );
const button = getConnectButton();

expect( button ).toBeDisabled();

await user.selectOptions( combobox, '1' );

expect( button ).toBeEnabled();

await user.selectOptions( combobox, '' );

expect( button ).toBeDisabled();
} );

it( 'should render a connecting state and disable the button to switch to account creation after clicking the "Connect" button', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* External dependencies
*/
import classNames from 'classnames';

/**
* Internal dependencies
*/
import Section from '.~/wcdl/section';
import AccountCard from '.~/components/account-card';
import './connect-account-card.scss';

/**
* ConnectAccountCard component renders an account card with a title, helper text, body, and footer sections.
*
* @param {Object} props The component properties.
* @param {string} props.title The title of the account card.
* @param {string} props.helperText The helper text for the account card.
* @param {JSX.Element} props.body The content for the body of the account card.
* @param {JSX.Element} props.footer The content for the footer of the account card.
* @param {boolean} [props.disabled=false] Whether display the Card in disabled style.
* @param {string} [props.className] Additional class names to apply to the card.
*/
const ConnectAccountCard = ( {
title,
helperText,
body,
footer,
disabled = false,
className,
} ) => {
return (
<AccountCard
className={ classNames(
'gla-google-combo-connect-account-card',
className
) }
title={ title }
helper={ helperText }
disabled={ disabled }
>
<Section.Card.Body className="gla-google-combo-connect-account-card__body">
{ body }
</Section.Card.Body>
<Section.Card.Footer className="gla-google-combo-connect-account-card__footer">
{ footer }
</Section.Card.Footer>
</AccountCard>
);
};

export default ConnectAccountCard;
Loading
Loading