Skip to content

Commit

Permalink
Add category selection to subscriber modal. (#97292)
Browse files Browse the repository at this point in the history
* Add category selection to subscriber modal.
  • Loading branch information
allilevine authored Jan 2, 2025
1 parent 188be76 commit 72f4c2f
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function SubscriberUploadForm( { nextStepUrl, siteId, skipNextSte
const onSubmit = useCallback(
async ( event: FormEvent< HTMLFormElement > ) => {
event.preventDefault();
selectedFile && importCsvSubscribers( siteId, selectedFile, [], true );
selectedFile && importCsvSubscribers( siteId, selectedFile, [], [], true );
},
[ selectedFile, importCsvSubscribers, siteId ]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ const AddSubscribersModal = ( { site }: AddSubscribersModalProps ) => {
<label className="add-subscribers-modal__label">{ translate( 'Email' ) }</label>
<AddSubscriberForm
siteId={ site.ID }
siteUrl={ site.URL }
hasSubscriberLimit={ hasSubscriberLimit }
submitBtnAlwaysEnable
onImportStarted={ onImportStarted }
Expand Down Expand Up @@ -297,12 +298,14 @@ const AddSubscribersModal = ( { site }: AddSubscribersModalProps ) => {
) }
<UploadSubscribersForm
siteId={ site.ID }
siteUrl={ site.URL }
hasSubscriberLimit={ hasSubscriberLimit }
onImportStarted={ onImportStarted }
onImportFinished={ onImportFinished }
recordTracksEvent={ recordTracksEvent }
hidden={ isUploading }
disabled={ isImportInProgress }
isWPCOMSite={ ! isJetpack }
/>
</>
) }
Expand Down
28 changes: 22 additions & 6 deletions packages/data-stores/src/subscriber/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ export function createActions() {
/**
* ↓ Import subscribers by CSV
*/
const importCsvSubscribersStart = ( siteId: number, file?: File, emails: string[] = [] ) => ( {
const importCsvSubscribersStart = (
siteId: number,
file?: File,
emails: string[] = [],
categories: number[] = []
) => ( {
type: 'IMPORT_CSV_SUBSCRIBERS_START' as const,
siteId,
file,
emails,
categories,
} );

const importCsvSubscribersStartSuccess = ( siteId: number, jobId: number ) => ( {
Expand All @@ -42,21 +48,31 @@ export function createActions() {
siteId: number,
file?: File,
emails: string[] = [],
categories: number[] = [],
parseOnly: boolean = false
) {
yield importCsvSubscribersStart( siteId, file, emails );
yield importCsvSubscribersStart( siteId, file, emails, categories );

try {
const token = oauthToken.getToken();
const formDataEntries: Array<
[ string, string | number | File ] | [ string, File, string ]
> = [
...( file ? [ [ 'import', file, file.name ] as [ string, File, string ] ] : [] ),

...categories.map( ( categoryId ) => [ 'categories[]', categoryId ] as [ string, number ] ),

...emails.map( ( email ) => [ 'emails[]', email ] as [ string, string ] ),

[ 'data', JSON.stringify( { parse_only: parseOnly } ) ] as [ string, string ],
];

const data: ImportSubscribersResponse = yield wpcomRequest( {
path: `/sites/${ encodeURIComponent( siteId ) }/subscribers/import`,
method: 'POST',
apiNamespace: 'wpcom/v2',
token: typeof token === 'string' ? token : undefined,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
formData: file && [ [ 'import', file, file.name ] ],
body: { emails, parse_only: parseOnly },
formData: formDataEntries as ( string | File )[][],
} );

yield importCsvSubscribersStartSuccess( siteId, data.upload_id );
Expand Down
140 changes: 140 additions & 0 deletions packages/subscriber/src/components/add-form/categories-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { recordTracksEvent } from '@automattic/calypso-analytics';
import { localizeUrl } from '@automattic/i18n-utils';
import { Button, Popover, ToggleControl, FormTokenField } from '@wordpress/components';
import { TokenItem } from '@wordpress/components/build-types/form-token-field/types';
import { createInterpolateElement, useRef, useState } from '@wordpress/element';
import { info } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';

interface Props {
siteId: number;
siteUrl?: string;
newsletterCategories?: Array< { name: string; id: number; parent?: number } >;
selectedCategories: number[];
setSelectedCategories: ( categories: number[] ) => void;
isWPCOMSite?: boolean;
}

export const CategoriesSection: React.FC< Props > = ( {
siteId,
siteUrl = '',
newsletterCategories = [],
selectedCategories,
setSelectedCategories,
isWPCOMSite = false,
} ) => {
const { __ } = useI18n();
const [ showCategories, setShowCategories ] = useState( false );
const [ showInfoPopover, setShowInfoPopover ] = useState( false );
const infoButtonRef = useRef< HTMLButtonElement >( null );

const handleCategoryChange = ( tokens: ( string | TokenItem )[] ) => {
// Filter out any invalid category names.
const validTokens = tokens
.map( ( token ) => {
const tokenName = typeof token === 'string' ? token : token.value;
const category = newsletterCategories.find( ( cat ) => cat.name === tokenName );
return category?.id;
} )
.filter( ( id ): id is number => id !== undefined );

recordTracksEvent( 'calypso_subscriber_add_form_categories_change', {
site_id: siteId,
categories_count: validTokens.length,
action: 'added',
} );

setSelectedCategories( validTokens );
};

const handleToggle = ( value: boolean ) => {
setShowCategories( value );
if ( ! value ) {
setSelectedCategories( [] );
}
recordTracksEvent( 'calypso_subscriber_add_form_categories_toggle', {
site_id: siteId,
enabled: value,
} );
};

const getCategoriesUrl = () => {
if ( ! isWPCOMSite ) {
return siteUrl ? `${ siteUrl }/wp-admin/admin.php?page=jetpack#/newsletter` : `#`;
}
return `/settings/newsletter/${ siteId }`;
};

return (
<div className="add-subscriber__categories-container">
<h3>
{ __( 'Categories' ) } <span>({ __( 'optional' ) })</span>
</h3>
<ToggleControl
__nextHasNoMarginBottom
label={
<div className="categories-toggle-container">
<p>
{ createInterpolateElement(
__( 'Add these subscribers to specific <link>categories</link>.' ),
{
link: <a href={ getCategoriesUrl() } target="_blank" rel="noopener noreferrer" />,
}
) }
</p>
<Button
icon={ info }
onClick={ () => setShowInfoPopover( ! showInfoPopover ) }
ref={ infoButtonRef }
/>
{ showInfoPopover && infoButtonRef.current && (
<Popover
anchor={ infoButtonRef.current }
onClose={ () => setShowInfoPopover( false ) }
position="middle left"
noArrow={ false }
ignoreViewportSize
>
<div className="categories-info-popover">
<p>
{ __(
'Adding newsletter categories helps you segment your subscribers more effectively.'
) }{ ' ' }
<a
href={ localizeUrl(
isWPCOMSite
? 'https://wordpress.com/support/newsletter-settings/enable-newsletter-categories/'
: 'https://jetpack.com/newsletter/newsletter-categories/'
) }
target="_blank"
rel="noopener noreferrer"
>
{ __( 'Learn more' ) }
</a>
</p>
</div>
</Popover>
) }
</div>
}
checked={ showCategories }
onChange={ handleToggle }
/>

{ showCategories && newsletterCategories && (
<FormTokenField
__next40pxDefaultSize
__nextHasNoMarginBottom
__experimentalShowHowTo={ false }
value={ selectedCategories
.map( ( id ) => newsletterCategories.find( ( cat ) => cat.id === id )?.name ?? '' )
.filter( ( name ) => name !== '' ) }
suggestions={ newsletterCategories.map( ( cat ) => cat.name ) }
onChange={ handleCategoryChange }
label=""
placeholder={ __( 'Search…' ) }
/>
) }
</div>
);
};
25 changes: 23 additions & 2 deletions packages/subscriber/src/components/add-form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable wpcalypso/jsx-classname-namespace */
import { FormInputValidation } from '@automattic/components';
import { Subscriber } from '@automattic/data-stores';
import { Subscriber, useNewsletterCategories } from '@automattic/data-stores';
import { localizeUrl } from '@automattic/i18n-utils';
import { Title, SubTitle, NextButton } from '@automattic/onboarding';
import { TextControl, FormFileUpload, Button } from '@wordpress/components';
Expand All @@ -23,12 +23,14 @@ import { useActiveJobRecognition } from '../../hooks/use-active-job-recognition'
import { useInProgressState } from '../../hooks/use-in-progress-state';
import { RecordTrackEvents, useRecordAddFormEvents } from '../../hooks/use-record-add-form-events';
import AddSubscribersDisclaimer from '../add-subscribers-disclaimer';
import { CategoriesSection } from './categories-section';
import { tip } from './icon';

import './style.scss';

interface Props {
siteId: number;
siteUrl?: string;
hasSubscriberLimit?: boolean;
flowName?: string;
showTitle?: boolean;
Expand Down Expand Up @@ -60,6 +62,7 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => {
};
const {
siteId,
siteUrl,
hasSubscriberLimit,
flowName,
showTitle = true,
Expand All @@ -83,6 +86,12 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => {
disabled,
} = props;

const { data: newsletterCategoriesData } = useNewsletterCategories( {
siteId,
} );

const [ selectedCategories, setSelectedCategories ] = useState< number[] >( [] );

const {
addSubscribers,
importCsvSubscribers,
Expand Down Expand Up @@ -184,7 +193,7 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => {
} else {
// import subscribers proving CSV and manual list of emails
( selectedFile || validEmails.length ) &&
importCsvSubscribers( siteId, selectedFile, validEmails );
importCsvSubscribers( siteId, selectedFile, validEmails, selectedCategories );
}

! validEmails.length && ! selectedFile && allowEmptyFormSubmit && onImportFinished?.();
Expand Down Expand Up @@ -492,6 +501,18 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => {

{ renderEmptyFormValidationMsg() }

{ newsletterCategoriesData?.enabled &&
newsletterCategoriesData?.newsletterCategories.length > 0 && (
<CategoriesSection
siteId={ siteId }
siteUrl={ siteUrl }
newsletterCategories={ newsletterCategoriesData?.newsletterCategories }
selectedCategories={ selectedCategories }
setSelectedCategories={ setSelectedCategories }
isWPCOMSite={ isWPCOMSite }
/>
) }

<AddSubscribersDisclaimer buttonLabel={ submitBtnName } />

<NextButton
Expand Down
44 changes: 44 additions & 0 deletions packages/subscriber/src/components/add-form/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,48 @@
padding: 0;
}
}

.add-subscriber__categories-container {
margin-top: 28px;
margin-bottom: 20px;

h3 {
font-size: 0.875rem;
font-weight: 600;
}

span {
font-weight: normal;
}

label {
margin: 0;
}

.categories-toggle-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;

p {
font-size: 0.875rem;
margin: 0;
}
}

.components-button {
border: none;
}
}
}

.categories-info-popover {
padding: 16px;
min-width: 280px;

p {
font-size: 0.875rem;
margin: 0;
}
}
Loading

0 comments on commit 72f4c2f

Please sign in to comment.