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

Settings: Allow Users to Insert Header Code #86079

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/components/inline-support-link/context-links.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ const contextLinks = {
link: 'https://wordpress.com/support/export/',
post_id: 2087,
},
'insert-header-code': {
link: 'https://wordpress.com/support/adding-code-to-headers/',
post_id: 187480,
},
'introduction-to-woocommerce': {
link: 'https://wordpress.com/support/introduction-to-woocommerce/',
post_id: 176336,
Expand Down
19 changes: 19 additions & 0 deletions client/my-sites/site-settings/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selecto
import DeleteSite from './delete-site';
import DisconnectSite from './disconnect-site';
import ConfirmDisconnection from './disconnect-site/confirm';
import HeaderCodeSettings from './header-code';
import ManageConnection from './manage-connection';
import SiteOwnerTransfer from './site-owner-transfer/site-owner-transfer';
import StartOver from './start-over';
Expand Down Expand Up @@ -61,6 +62,19 @@ export function redirectIfCantStartSiteOwnerTransfer( context, next ) {
next();
}

export function redirectIfCantAddHeaderCode( context, next ) {
const state = context.store.getState();
const siteId = getSelectedSiteId( state );

if (
( ! isSiteAutomatedTransfer( state, siteId ) && ! isJetpackSite( state, siteId ) ) ||
! canCurrentUser( state, siteId, 'manage_options' )
) {
return page.redirect( '/settings/general/' + getSelectedSiteSlug( state ) );
}
next();
}

export function general( context, next ) {
context.primary = <SiteSettingsMain />;
next();
Expand Down Expand Up @@ -111,6 +125,11 @@ export function startSiteOwnerTransfer( context, next ) {
next();
}

export function headerCode( context, next ) {
context.primary = <HeaderCodeSettings />;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will all site types support this? I see Jetpack sites will, but what about WP.com simple / Atomic / etc.?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in Automattic/jetpack#34881 explicitly excludes Simple. As things stand I don't see anything stopping it from Atomic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, we should make sure to redirect in Calypso if someone tries to access this with a WP.com simple site.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comments below!

next();
}

export function legacyRedirects( context, next ) {
const { section } = context.params;
const redirectMap = {
Expand Down
122 changes: 122 additions & 0 deletions client/my-sites/site-settings/header-code/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { Button, Card, Gridicon } from '@automattic/components';
import { useTranslate } from 'i18n-calypso';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import DocumentHead from 'calypso/components/data/document-head';
import QuerySiteSettings from 'calypso/components/data/query-site-settings';
import FormSettingExplanation from 'calypso/components/forms/form-setting-explanation';
import FormTextarea from 'calypso/components/forms/form-textarea';
import HeaderCake from 'calypso/components/header-cake';
import InlineSupportLink from 'calypso/components/inline-support-link';
import Main from 'calypso/components/main';
import NavigationHeader from 'calypso/components/navigation-header';
import { successNotice, errorNotice } from 'calypso/state/notices/actions';
import getSiteSetting from 'calypso/state/selectors/get-site-setting';
import { saveSiteSettings } from 'calypso/state/site-settings/actions';
import {
isRequestingSiteSettings,
isSavingSiteSettings,
isSiteSettingsSaveSuccessful,
getSiteSettingsSaveError,
} from 'calypso/state/site-settings/selectors';
import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors';
import './style.scss';

const HeaderCodeSettings = () => {
const translate = useTranslate();
const dispatch = useDispatch();

const [ editedCode, setEditedCode ] = useState( '' );

const siteId = useSelector( ( state ) => getSelectedSiteId( state ) );
const headCode = useSelector( ( state ) =>
getSiteSetting( state, siteId, 'jetpack_header_code' )
);
const isRequestingSettings = useSelector( ( state ) =>
isRequestingSiteSettings( state, siteId )
);
const isSaveFailure = useSelector( ( state ) => getSiteSettingsSaveError( state, siteId ) );
const isSaveSuccess = useSelector( ( state ) => isSiteSettingsSaveSuccessful( state, siteId ) );
const isSavingSettings = useSelector( ( state ) => isSavingSiteSettings( state, siteId ) );
const siteSlug = useSelector( ( state ) => getSelectedSiteSlug( state, siteId ) );

const saveSettings = () => {
dispatch(
saveSiteSettings( siteId, {
jetpack_header_code: editedCode,
} )
);
};

const noticeOptions = {
duration: 4000,
};

useEffect( () => {
if ( isSaveSuccess ) {
dispatch( successNotice( translate( 'Code updated successfully.' ), noticeOptions ) );
} else if ( isSaveFailure ) {
dispatch( errorNotice( translate( 'There was an error saving your code.' ), noticeOptions ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question about errors: will we get more details about the error from the API? If yes, can we be a bit more specific about what kind of error occurred?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at this further, and it seems that although there's error details, I'd need to manually add a switch case for each one. It doesn't seem like that's been consolidated anywhere in Calypso (unless I'm missing something!), so the rest of Settings just does a generic error.

}
}, [ isSaveSuccess, isSaveFailure ] );

useEffect( () => {
if ( headCode ) {
setEditedCode( headCode );
}
}, [ headCode ] );

return (
<Main className="header-code">
<DocumentHead title={ translate( 'Header Code' ) } />
{ siteId && <QuerySiteSettings siteId={ siteId } /> }
<NavigationHeader
title={ translate( 'Header Code' ) }
subtitle={ translate( 'Insert HTML meta tags and JavaScript code to your site.' ) }
/>
<HeaderCake backHref={ `/settings/general/${ siteSlug }` } isCompact>
<h1>{ translate( 'Header Code' ) }</h1>
</HeaderCake>
<Card>
<p className="header-code__text">
{ translate(
'The following code will be inserted within {{code}}<head>{{/code}} on your site. {{link}}Learn more{{/link}}.',
{
components: {
code: <code />,
link: <InlineSupportLink supportContext="insert-header-code" showIcon={ false } />,
},
}
) }
</p>

<FormTextarea
className="header-code__editor"
disabled={ isRequestingSettings || isSavingSettings }
value={ editedCode }
onChange={ ( input ) => setEditedCode( input.target.value ) }
/>
<FormSettingExplanation className="header-code__warning">
<Gridicon icon="info" size={ 18 } />
<span>
{ translate(
"This code will be run for all your site's viewers. Please make sure that you understand any code and trust its source before adding it here."
) }
</span>
</FormSettingExplanation>
<div className="header-code__button">
<Button
disabled={ headCode === editedCode || ! editedCode || isRequestingSettings }
primary
busy={ isSavingSettings }
onClick={ saveSettings }
>
{ translate( 'Save' ) }
</Button>
</div>
</Card>
</Main>
);
};

export default HeaderCodeSettings;
15 changes: 15 additions & 0 deletions client/my-sites/site-settings/header-code/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.header-code__editor {
font-family: $code;
font-size: $font-body-small;
min-height: 250px;
}

.header-code .header-code__warning {
display: flex;
font-style: normal;
margin-bottom: 1.5em;

.gridicon {
margin-right: 4px;
}
}
13 changes: 13 additions & 0 deletions client/my-sites/site-settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
disconnectSite,
disconnectSiteConfirm,
general,
headerCode,
legacyRedirects,
manageConnection,
redirectIfCantAddHeaderCode,
redirectIfCantDeleteSite,
redirectIfCantStartSiteOwnerTransfer,
redirectToGeneral,
Expand Down Expand Up @@ -111,6 +113,17 @@ export default function () {
clientRender
);

page(
'/settings/header-code/:site_id',
Aurorum marked this conversation as resolved.
Show resolved Hide resolved
siteSelection,
redirectIfCantAddHeaderCode,
navigation,
setScroll,
headerCode,
makeLayout,
clientRender
);

page( '/settings/traffic/:site_id', redirectToTraffic );
page( '/settings/analytics/:site_id?', redirectToTraffic );
page( '/settings/seo/:site_id?', redirectToTraffic );
Expand Down
13 changes: 13 additions & 0 deletions client/my-sites/site-settings/site-tools/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ class SiteTools extends Component {
showClone,
showDeleteContent,
showDeleteSite,
showHeaderCode,
showManageConnection,
showStartSiteTransfer,
siteId,
} = this.props;

const changeAddressLink = `/domains/manage/${ siteSlug }`;
const headerCodeLink = `/settings/header-code/${ siteSlug }`;
const startOverLink = `/settings/start-over/${ siteSlug }`;
const startSiteTransferLink = `/settings/start-site-transfer/${ siteSlug }`;
const deleteSiteLink = `/settings/delete-site/${ siteSlug }`;
Expand All @@ -74,6 +76,9 @@ class SiteTools extends Component {
"Remove all posts, pages, and media to start fresh while keeping your site's address."
);

const headerCode = translate( 'Header code' );
const headerCodeText = translate( 'Insert code within the <head> of your site.' );

const deleteSite = translate( 'Delete your site permanently' );
const deleteSiteText = translate(
"Delete all your posts, pages, media, and data, and give up your site's address."
Expand Down Expand Up @@ -117,6 +122,13 @@ class SiteTools extends Component {
description={ copyText }
/>
) }
{ showHeaderCode && (
<SiteToolsLink
href={ headerCodeLink }
title={ headerCode }
description={ headerCodeText }
/>
) }
{ showClone && (
<SiteToolsLink href={ cloneUrl } title={ cloneTitle } description={ cloneText } />
) }
Expand Down Expand Up @@ -219,6 +231,7 @@ export default compose( [
showClone: 'active' === rewindState.state && ! isAtomic,
showDeleteContent: isAtomic || ( ! isJetpack && ! isVip && ! isP2Hub ),
showDeleteSite: ( ! isJetpack || isAtomic ) && ! isVip && sitePurchasesLoaded,
showHeaderCode: isJetpack || isAtomic,
Aurorum marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we show this only to admins? (if the current user has the capability to manage_options)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary in this case! The entire page is blocked for non-admins.

showManageConnection: isJetpack && ! isAtomic,
showStartSiteTransfer,
siteId,
Expand Down
Loading