diff --git a/packages/data-stores/src/index.ts b/packages/data-stores/src/index.ts index 4593ced128a7d7..cd57901aa2ac41 100644 --- a/packages/data-stores/src/index.ts +++ b/packages/data-stores/src/index.ts @@ -35,6 +35,7 @@ export * from './queries/use-site-query'; export * from './mutations/use-domains-bulk-actions-mutation'; export * from './queries/use-bulk-domain-update-status-query'; export * from './site-reset'; +export { useNewsletterCategories } from './newsletter-categories'; const { SubscriptionManager } = Reader; diff --git a/packages/data-stores/src/newsletter-categories/index.ts b/packages/data-stores/src/newsletter-categories/index.ts new file mode 100644 index 00000000000000..279b90d89e794a --- /dev/null +++ b/packages/data-stores/src/newsletter-categories/index.ts @@ -0,0 +1,64 @@ +import { useQuery, UseQueryResult } from '@tanstack/react-query'; +import request from 'wpcom-proxy-request'; + +export type NewsletterCategory = { + id: number; + name: string; + slug: string; + description: string; + parent: number; + subscribed?: boolean; +}; + +export type NewsletterCategories = { + enabled: boolean; + newsletterCategories: NewsletterCategory[]; +}; + +type NewsletterCategoryQueryProps = { + siteId: number; +}; + +type NewsletterCategoryResponse = { + enabled: boolean; + newsletter_categories: NewsletterCategory[]; +}; + +export const getNewsletterCategoriesKey = ( siteId?: string | number ) => [ + 'newsletter-categories', + siteId, +]; + +const convertNewsletterCategoryResponse = ( + response: NewsletterCategoryResponse +): NewsletterCategories => ( { + enabled: response.enabled, + newsletterCategories: response.newsletter_categories, +} ); + +export const useNewsletterCategories = ( { + siteId, +}: NewsletterCategoryQueryProps ): UseQueryResult< NewsletterCategories > => { + const path = `/sites/${ siteId }/newsletter-categories`; + + return useQuery( { + queryKey: getNewsletterCategoriesKey( siteId ), + queryFn: async () => { + try { + const response = await request< NewsletterCategoryResponse >( { + path, + apiVersion: '2', + apiNamespace: 'wpcom/v2', + } ); + return convertNewsletterCategoryResponse( response ); + } catch ( e ) { + return { + enabled: false, + newsletterCategories: [], + error: e instanceof Error ? e.message : 'Unknown error', + }; + } + }, + enabled: !! siteId, + } ); +}; diff --git a/packages/subscriber/src/components/add-form/index.tsx b/packages/subscriber/src/components/add-form/index.tsx index 60b65519dd81c6..80a92c41fc0a13 100644 --- a/packages/subscriber/src/components/add-form/index.tsx +++ b/packages/subscriber/src/components/add-form/index.tsx @@ -1,9 +1,9 @@ /* 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'; +import { TextControl, FormFileUpload, Button, ComboboxControl } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { createElement, createInterpolateElement } from '@wordpress/element'; import { sprintf } from '@wordpress/i18n'; @@ -18,6 +18,7 @@ import { useEffect, useRef, useCallback, + useMemo, } from 'react'; import { useActiveJobRecognition } from '../../hooks/use-active-job-recognition'; import { useInProgressState } from '../../hooks/use-in-progress-state'; @@ -50,6 +51,7 @@ interface Props { hidden?: boolean; isWPCOMSite?: boolean; disabled?: boolean; + onCategorySelect?: ( email: string, category: string ) => void; } export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => { @@ -81,6 +83,7 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => { hidden = false, isWPCOMSite = false, disabled, + onCategorySelect, } = props; const { @@ -109,6 +112,7 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => { const [ isDirtyEmails, setIsDirtyEmails ] = useState< boolean[] >( [] ); const [ emailFormControls, setEmailFormControls ] = useState( emailControlPlaceholder ); const [ submitAttemptCount, setSubmitAttemptCount ] = useState( 0 ); + const [ emailCategories, setEmailCategories ] = useState< Record< string, string > >( {} ); const importSelector = useSelect( ( select ) => select( Subscriber.store ).getImportSubscribersSelector(), @@ -431,6 +435,21 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => { ); } + const { data: newsletterCategoriesData } = useNewsletterCategories( { + siteId, + } ); + + const categoryOptions = useMemo( + () => + newsletterCategoriesData?.newsletterCategories?.map( + ( category: { id: number; name: string } ) => ( { + label: category.name, + value: category.id.toString(), + } ) + ) ?? [], + [ newsletterCategoriesData ] + ); + if ( hidden ) { return null; } @@ -455,6 +474,7 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => {
{ emailFormControls.map( ( placeholder, i ) => { const showError = isDirtyEmails[ i ] && ! isValidEmails[ i ] && emails[ i ]; + const currentEmail = emails[ i ] || ''; return (
@@ -463,15 +483,34 @@ export const AddSubscriberForm: FunctionComponent< Props > = ( props ) => { { __( 'Emails' ) } ) } - : undefined } - onChange={ ( value: string ) => onEmailChange( value, i ) } - onBlur={ () => setIsDirtyEmail( emails[ i ], i ) } - /> +
+ : undefined } + onChange={ ( value: string ) => onEmailChange( value, i ) } + onBlur={ () => setIsDirtyEmail( emails[ i ], i ) } + /> + + { currentEmail && isValidEmails[ i ] && ( + { + setEmailCategories( ( prev ) => ( { + ...prev, + [ currentEmail ]: value || '', + } ) ); + onCategorySelect?.( currentEmail, value || '' ); + } } + options={ categoryOptions } + allowReset + placeholder={ __( 'Select or create category' ) } + /> + ) } +
{ showError && (