Skip to content

Commit

Permalink
feat: ads onboarding (#1678)
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelpeixe authored Jun 24, 2022
1 parent 48e1613 commit 80c0bf4
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 146 deletions.
126 changes: 126 additions & 0 deletions assets/wizards/advertising/components/onboarding/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* WordPress dependencies.
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import { useEffect, useState, useRef, Fragment } from '@wordpress/element';

/**
* Internal dependencies.
*/
import { Card, ButtonCard, Notice, TextControl } from '../../../../components/src';
import GoogleOAuth from '../../../connections/views/main/google';
import { handleJSONFile } from '../utils';

export default function AdsOnboarding( { onUpdate, onSuccess } ) {
const credentialsInputFile = useRef( null );
const [ fileError, setFileError ] = useState( '' );
const [ inFlight, setInFlight ] = useState( false );
const [ networkCode, setNetworkCode ] = useState( '' );
const [ isConnected, setIsConnected ] = useState( false );
const [ useOAuth, setUseOAuth ] = useState( null );

const updateGAMCredentials = credentials => {
setInFlight( true );
apiFetch( {
path: '/newspack/v1/wizard/advertising/credentials',
method: 'post',
data: { credentials, onboarding: true },
} )
.then( () => {
setIsConnected( true );
if ( typeof onSuccess === 'function' ) onSuccess();
} )
.finally( () => {
setInFlight( false );
} );
};

const handleCredentialsFile = event => {
if ( event.target.files.length && event.target.files[ 0 ] ) {
handleJSONFile( event.target.files[ 0 ] )
.then( credentials => updateGAMCredentials( credentials ) )
.catch( err => setFileError( err ) );
}
};

useEffect( () => {
if ( typeof onUpdate === 'function' ) {
onUpdate( { networkCode } );
}
}, [ networkCode ] );

return (
<Card noBorder>
<div className="ads-onboarding">
{ ( true === useOAuth || null === useOAuth ) && (
<Fragment>
{ useOAuth && (
<p>
{ __(
'Authenticate with Google in order to connect your Google Ad Manager account:',
'newspack'
) }
</p>
) }
<GoogleOAuth onInit={ err => setUseOAuth( ! err ) } onSuccess={ onSuccess } />
</Fragment>
) }
{ false === useOAuth && (
<Fragment>
{ isConnected ? (
<Notice isSuccess noticeText={ __( "We're all set here!", 'newspack' ) } />
) : (
<Fragment>
<p>
{ __(
'Enter your Google Ad Manager network code and service account credentials for a full integration:',
'newspack'
) }
</p>
<TextControl
disabled={ inFlight }
isWide
name="network_code"
label={ __( 'Network code', 'newspack' ) }
value={ networkCode }
onChange={ setNetworkCode }
/>
<ButtonCard
disabled={ inFlight }
onClick={ () => credentialsInputFile.current.click() }
title={ __( 'Upload credentials', 'newspack' ) }
desc={ [
__(
'Upload your Service Account credentials file to connect your GAM account.',
'newspack'
),
fileError && <Notice noticeText={ fileError } isError />,
] }
chevron
/>
<p>
<a
href="https://support.google.com/admanager/answer/6078734"
target="_blank"
rel="noopener noreferrer"
>
{ __( 'How to get a service account user for API access', 'newspack' ) }
</a>
.
</p>
<input
type="file"
accept=".json"
ref={ credentialsInputFile }
style={ { display: 'none' } }
onChange={ handleCredentialsFile }
/>
</Fragment>
) }
</Fragment>
) }
</div>
</Card>
);
}
29 changes: 29 additions & 0 deletions assets/wizards/advertising/components/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* WordPress dependencies.
*/
import { __ } from '@wordpress/i18n';

/**
* Handle JSON from a file input.
*
* @param {File} file The file to extract JSON content.
* @return {Promise} which resolves into a JSON object or reject with message.
*/
export function handleJSONFile( file ) {
return new Promise( ( resolve, reject ) => {
const reader = new FileReader();
reader.readAsText( file, 'UTF-8' );
reader.onload = function ( ev ) {
let json;
try {
json = JSON.parse( ev.target.result );
} catch ( error ) {
reject( __( 'Invalid JSON file', 'newspack' ) );
}
resolve( json );
};
reader.onerror = function () {
reject( __( 'Unable to read file', 'newspack' ) );
};
} );
}
1 change: 1 addition & 0 deletions assets/wizards/advertising/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class AdvertisingWizard extends Component {
subHeaderText={ __( 'Manage ad providers and their settings.', 'newspack' ) }
services={ services }
toggleService={ this.toggleService }
fetchAdvertisingData={ this.fetchAdvertisingData }
tabbedNavigation={ tabs }
/>
) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import { ButtonCard, Grid, Notice, SectionHeader } from '../../../../components/src';
import { handleJSONFile } from '../../components/utils';

const ServiceAccountConnection = ( { updateWithAPI, isConnected } ) => {
const credentialsInputFile = useRef( null );
Expand All @@ -29,21 +30,9 @@ const ServiceAccountConnection = ( { updateWithAPI, isConnected } ) => {

const handleCredentialsFile = event => {
if ( event.target.files.length && event.target.files[ 0 ] ) {
const reader = new FileReader();
reader.readAsText( event.target.files[ 0 ], 'UTF-8' );
reader.onload = function ( ev ) {
let credentials;
try {
credentials = JSON.parse( ev.target.result );
} catch ( error ) {
setFileError( __( 'Invalid JSON file', 'newspack' ) );
return;
}
updateGAMCredentials( credentials );
};
reader.onerror = function () {
setFileError( __( 'Unable to read file', 'newspack' ) );
};
handleJSONFile( event.target.files[ 0 ] )
.then( credentials => updateGAMCredentials( credentials ) )
.catch( err => setFileError( err ) );
}
};

Expand Down
115 changes: 95 additions & 20 deletions assets/wizards/advertising/views/providers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,71 @@
* WordPress dependencies
*/
import { ExternalLink } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';

/**
* Internal dependencies
*/
import { PluginToggle, ActionCard, withWizardScreen } from '../../../../components/src';
import {
PluginToggle,
ActionCard,
Modal,
Card,
Button,
withWizardScreen,
} from '../../../../components/src';
import GAMOnboarding from '../../components/onboarding';

/**
* Advertising management screen.
*/
const Providers = ( { services, toggleService } ) => {
const Providers = ( { services, fetchAdvertisingData, toggleService } ) => {
const { google_ad_manager } = services;
const [ inFlight, setInFlight ] = useState( false );
const [ networkCode, setNetworkCode ] = useState( '' );
const [ isOnboarding, setIsOnboarding ] = useState( false );

const updateGAMNetworkCode = () => {
setInFlight( true );
apiFetch( {
path: '/newspack/v1/wizard/advertising/network_code/',
method: 'POST',
data: { network_code: networkCode, is_gam: false },
quiet: true,
} ).finally( () => {
fetchAdvertisingData();
setInFlight( false );
setIsOnboarding( false );
} );
};

let notifications = [];

if ( google_ad_manager.enabled && google_ad_manager.status.error ) {
notifications = notifications.concat( [ google_ad_manager.status.error, '\u00A0' ] );
} else if ( google_ad_manager?.created_targeting_keys?.length > 0 ) {
notifications = notifications.concat( [
__( 'Created custom targeting keys:' ) + '\u00A0',
google_ad_manager.created_targeting_keys.join( ', ' ) + '. \u00A0',
// eslint-disable-next-line react/jsx-indent
<ExternalLink
href={ `https://admanager.google.com/${ google_ad_manager.network_code }#inventory/custom_targeting/list` }
key="google-ad-manager-custom-targeting-link"
>
{ __( 'Visit your GAM dashboard' ) }
</ExternalLink>,
] );
}

if ( google_ad_manager.enabled && ! google_ad_manager.status.connected ) {
notifications.push(
<Button key="gam-connect-account" isLink onClick={ () => setIsOnboarding( true ) }>
{ __( 'Click here to connect your account.', 'newspack' ) }
</Button>
);
}

return (
<>
Expand All @@ -29,24 +82,20 @@ const Providers = ( { services, toggleService } ) => {
actionText={ google_ad_manager && google_ad_manager.enabled && __( 'Configure' ) }
toggle
toggleChecked={ google_ad_manager && google_ad_manager.enabled }
toggleOnChange={ value => toggleService( 'google_ad_manager', value ) }
titleLink={ google_ad_manager ? '#/google_ad_manager' : null }
href={ google_ad_manager && '#/google_ad_manager' }
notification={
google_ad_manager.status.error
? [ google_ad_manager.status.error ]
: google_ad_manager.created_targeting_keys?.length > 0 && [
__( 'Created custom targeting keys:' ) + '\u00A0',
google_ad_manager.created_targeting_keys.join( ', ' ) + '. \u00A0',
// eslint-disable-next-line react/jsx-indent
<ExternalLink
href={ `https://admanager.google.com/${ google_ad_manager.network_code }#inventory/custom_targeting/list` }
key="google-ad-manager-custom-targeting-link"
>
{ __( 'Visit your GAM dashboard' ) }
</ExternalLink>,
]
}
toggleOnChange={ value => {
toggleService( 'google_ad_manager', value ).then( () => {
if (
value === true &&
! google_ad_manager.status.connected &&
! google_ad_manager.status.network_code
) {
setIsOnboarding( true );
}
} );
} }
titleLink={ google_ad_manager?.enabled ? '#/google_ad_manager' : null }
href={ google_ad_manager?.enabled && '#/google_ad_manager' }
notification={ notifications.length ? notifications : null }
notificationLevel={ google_ad_manager.created_targeting_keys?.length ? 'success' : 'error' }
/>
<PluginToggle
Expand All @@ -57,6 +106,32 @@ const Providers = ( { services, toggleService } ) => {
},
} }
/>
{ isOnboarding && (
<Modal
title={ __( 'Google Ad Manager Setup', 'newspack-ads' ) }
onRequestClose={ () => setIsOnboarding( false ) }
>
<GAMOnboarding
onUpdate={ data => setNetworkCode( data.networkCode ) }
onSuccess={ () => {
fetchAdvertisingData();
setIsOnboarding( false );
} }
/>
<Card buttonsCard noBorder className="justify-end">
<Button isSecondary disabled={ inFlight } onClick={ () => setIsOnboarding( false ) }>
{ __( 'Cancel', 'newspack' ) }
</Button>
<Button
isPrimary
disabled={ inFlight || ! networkCode }
onClick={ () => updateGAMNetworkCode() }
>
{ __( 'Save', 'newspack' ) }
</Button>
</Card>
</Modal>
) }
</>
);
};
Expand Down
Loading

0 comments on commit 80c0bf4

Please sign in to comment.