From 854e36c413c7866c276521aaad24ee7543ed5bd0 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Thu, 6 Feb 2025 14:46:26 -0300 Subject: [PATCH] Jetpack SEO: process assistant results summary (#41585) * address primary buttons styles * lower delay to 1500 on all steps * update onStart signature and add includeResults on steps * add results collection and summary process, remove unnecessary props on hooks, add includeResults where needed along with label * changelog --- .../fix-jetpack-seo-assistant-results-summary | 4 + .../seo-assistant/assistant-wizard.tsx | 44 +++++++---- .../components/seo-assistant/style.scss | 22 +++--- .../components/seo-assistant/types.tsx | 13 +++- .../seo-assistant/use-completion-step.tsx | 78 +++++++++++++------ .../use-meta-description-step.tsx | 10 +-- .../seo-assistant/use-title-step.tsx | 10 +-- .../seo-assistant/use-welcome-step.tsx | 2 +- 8 files changed, 115 insertions(+), 68 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-results-summary diff --git a/projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-results-summary b/projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-results-summary new file mode 100644 index 0000000000000..3973a3d558e0c --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-results-summary @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Jetpack SEO: add completion summary step diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/assistant-wizard.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/assistant-wizard.tsx index d7cc994da7a87..49e6369ede00e 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/assistant-wizard.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/assistant-wizard.tsx @@ -22,7 +22,7 @@ export default function AssistantWizard( { close } ) { stepsEndRef.current?.scrollIntoView( { behavior: 'smooth' } ); }; const keywordsInputRef = useRef( null ); - const [ , setResults ] = useState( {} ); + const [ results, setResults ] = useState( {} ); const [ lastStepValue, setLastStepValue ] = useState( '' ); useEffect( () => { @@ -52,9 +52,10 @@ export default function AssistantWizard( { close } ) { await currentStepData?.onStart( { fromSkip: ! lastStepValue, stepValue: lastStepValue, + results, } ); setIsBusy( false ); - }, [ currentStepData, lastStepValue ] ); + }, [ currentStepData, lastStepValue, results ] ); const handleNext = useCallback( () => { debug( 'handleNext, stepsCount', stepsCount ); @@ -97,22 +98,25 @@ export default function AssistantWizard( { close } ) { setIsBusy( true ); const stepValue = await steps[ currentStep ]?.onSubmit?.(); debug( 'stepValue', stepValue ); - const newResults = { - [ steps[ currentStep ].id ]: { - value: steps[ currentStep ].value.trim(), - type: steps[ currentStep ].type, - label: steps[ currentStep ].label, - }, - }; - debug( 'newResults', newResults ); - setResults( prev => ( { ...prev, ...newResults } ) ); + if ( steps[ currentStep ].includeInResults ) { + const newResults = { + [ steps[ currentStep ].id ]: { + value: stepValue?.trim?.(), + type: steps[ currentStep ].type, + label: steps[ currentStep ].label, + }, + }; + debug( 'newResults', newResults ); + setResults( prev => ( { ...prev, ...newResults } ) ); + } debug( 'set last step value', stepValue ); - setLastStepValue( stepValue.trim() ); + setLastStepValue( stepValue?.trim?.() ); if ( steps[ currentStep ]?.type === 'completion' ) { + debug( 'completion step, closing wizard' ); handleDone(); } else { - // always give half a second before moving forward + debug( 'step type', steps[ currentStep ]?.type ); handleNext(); } }, [ currentStep, handleDone, handleNext, steps ] ); @@ -139,6 +143,7 @@ export default function AssistantWizard( { close } ) { const handleBack = () => { if ( currentStep > 1 ) { + setIsBusy( true ); debug( 'moving back to ' + ( currentStep - 1 ) ); setCurrentStep( currentStep - 1 ); setCurrentStepData( steps[ currentStep - 1 ] ); @@ -148,8 +153,19 @@ export default function AssistantWizard( { close } ) { const handleSkip = useCallback( async () => { setIsBusy( true ); await steps[ currentStep ]?.onSkip?.(); + const step = steps[ currentStep ]; + if ( ! results[ step.id ] && step.includeInResults ) { + setResults( prev => ( { + ...prev, + [ step.id ]: { + value: '', + type: step.type, + label: step.label, + }, + } ) ); + } handleNext(); - }, [ currentStep, steps, handleNext ] ); + }, [ currentStep, steps, handleNext, results ] ); const handleRetry = useCallback( async () => { debug( 'handleRetry' ); diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss index bd808045c8931..0acaea3025d52 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss @@ -17,6 +17,15 @@ width: unset; } + .components-button.is-primary { + background-color: #3858E9; + } + + .components-button.is-secondary { + box-shadow: inset 0 0 0 1px #3858e9, 0 0 0 currentColor; + color: #3858e9; + } + &__header { flex: 0 0 auto; display: flex; @@ -154,10 +163,6 @@ box-shadow: none; outline: 0; } - - .components-button.is-primary { - background-color: #3858E9; - } } // submit and retry buttons @@ -204,15 +209,6 @@ align-items: center; gap: 16px; animation: assistantInputAppear 0.3s ease-out; - - .components-button.is-primary { - background-color: #3858E9; - } - - .components-button.is-secondary { - box-shadow: inset 0 0 0 1px #3858e9, 0 0 0 currentColor; - color: #3858e9; - } } &__completion { diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/types.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/types.tsx index cca9bcebce3e3..4556ec26f338e 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/types.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/types.tsx @@ -11,22 +11,29 @@ export interface Message { export type OptionMessage = Pick< Message, 'id' | 'content' >; +export interface Results { + [ key: string ]: { + value: string; + type: string; + label: string; + }; +} + export interface Step { id: string; title: string; label?: string; messages: Message[]; type: StepType; - onStart?: ( options?: { fromSkip: boolean; stepValue: string } ) => void; + onStart?: ( options?: { fromSkip: boolean; stepValue: string; results: Results } ) => void; onSubmit?: () => Promise< string >; onSkip?: () => void; value?: string; setValue?: | React.Dispatch< React.SetStateAction< string > > | React.Dispatch< React.SetStateAction< Array< string > > >; - setCompleted?: React.Dispatch< React.SetStateAction< boolean > >; - completed?: boolean; autoAdvance?: number; + includeInResults?: boolean; // Input step properties placeholder?: string; diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-completion-step.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-completion-step.tsx index 221b3a01b1751..50ad239eeb007 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-completion-step.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-completion-step.tsx @@ -1,16 +1,16 @@ import { createInterpolateElement, useCallback, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useMessages } from './wizard-messages'; -import type { Step } from './types'; +import type { Step, Results } from './types'; export const useCompletionStep = (): Step => { - const [ keywords, setKeywords ] = useState( '' ); - const [ completed, setCompleted ] = useState( false ); + const [ value, setValue ] = useState( '' ); const { messages, setMessages, addMessage } = useMessages(); const startHandler = useCallback( - async ( { fromSkip } ) => { + async ( { fromSkip, results } ) => { const firstMessages = []; + if ( fromSkip ) { firstMessages.push( { content: __( 'Skipped!', 'jetpack' ), @@ -19,32 +19,62 @@ export const useCompletionStep = (): Step => { } ); } setMessages( firstMessages ); - await new Promise( resolve => setTimeout( resolve, 2000 ) ); + + await new Promise( resolve => setTimeout( resolve, 1500 ) ); + + const resultsString = Object.values( results ) + .map( ( result: Results[ string ] ) => `${ result.value ? '✅' : '❌' } ${ result.label }` ) + .join( '
' ); + addMessage( { content: createInterpolateElement( - "Here's your updated checklist:
✅ Title
✅ Meta description

", + `Here's your updated checklist:
${ resultsString }

`, { br:
, } ), id: '1', } ); - addMessage( { - content: createInterpolateElement( - __( - 'SEO optimization complete! 🎉
Your blog post is now search-engine friendly.', - 'jetpack' + + const incomplete: { total: number; completed: number } = Object.values( results ).reduce( + ( acc: { total: number; completed: number }, result: Results[ string ] ) => { + const total = acc.total + 1; + const completed = acc.completed + ( result.value ? 1 : 0 ); + return { total, completed }; + }, + { total: 0, completed: 0 } + ) as { total: number; completed: number }; + + const incompleteString = + incomplete.completed === incomplete.total + ? '' + : `${ incomplete.completed } out of ${ incomplete.total }`; + + if ( incompleteString ) { + addMessage( { + content: createInterpolateElement( + `You've optimized ${ incompleteString } items! 🎉
Your post is looking great! Come back anytime to complete the rest.`, + { + strong: , + br:
, + } ), - { br:
, strong: } - ), - showIcon: false, - id: '3', - } ); - addMessage( { - content: __( 'Happy blogging! 😊', 'jetpack' ), - showIcon: false, - id: '4', - } ); + id: '2', + } ); + } else { + addMessage( { + content: createInterpolateElement( + __( + 'SEO optimization complete! 🎉
Your blog post is now search-engine friendly.
Happy blogging! 😊', + 'jetpack' + ), + { br:
, strong: } + ), + showIcon: false, + id: '3', + } ); + } + return 'completion'; }, [ setMessages, addMessage ] @@ -58,9 +88,7 @@ export const useCompletionStep = (): Step => { type: 'completion', onStart: startHandler, submitCtaLabel: __( 'Done!', 'jetpack' ), - completed, - setCompleted, - value: keywords, - setValue: setKeywords, + value, + setValue, }; }; diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-meta-description-step.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-meta-description-step.tsx index ad37940d48b7d..4a37081440edb 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-meta-description-step.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-meta-description-step.tsx @@ -9,7 +9,6 @@ export const useMetaDescriptionStep = (): Step => { const [ metaDescriptionOptions, setMetaDescriptionOptions ] = useState< OptionMessage[] >( [] ); const { messages, setMessages, addMessage, editLastMessage, setSelectedMessage } = useMessages(); const { editPost } = useDispatch( 'core/editor' ); - const [ completed, setCompleted ] = useState( false ); const handleMetaDescriptionSelect = useCallback( ( option: OptionMessage ) => { @@ -22,7 +21,6 @@ export const useMetaDescriptionStep = (): Step => { const handleMetaDescriptionSubmit = useCallback( async () => { await editPost( { meta: { advanced_seo_description: selectedMetaDescription } } ); addMessage( { content: __( 'Meta description updated! ✅', 'jetpack' ) } ); - setCompleted( true ); return selectedMetaDescription; }, [ selectedMetaDescription, addMessage, editPost ] ); @@ -54,7 +52,7 @@ export const useMetaDescriptionStep = (): Step => { 'Explore breathtaking flower and plant photography in our Flora Guide, featuring tips and inspiration for gardening and plant enthusiasts to enhance their outdoor spaces.', }, ] ), - 3000 + 1500 ) ); } @@ -90,7 +88,7 @@ export const useMetaDescriptionStep = (): Step => { 'Explore breathtaking flower and plant photography in our Flora Guide, featuring tips and inspiration for gardening and plant enthusiasts to enhance their outdoor spaces.', }, ] ), - 3000 + 1500 ) ); @@ -101,6 +99,7 @@ export const useMetaDescriptionStep = (): Step => { return { id: 'meta', title: __( 'Add meta description', 'jetpack' ), + label: __( 'Meta description', 'jetpack' ), messages: messages, type: 'options', options: metaDescriptionOptions, @@ -112,7 +111,6 @@ export const useMetaDescriptionStep = (): Step => { onStart: handleMetaDescriptionGenerate, value: selectedMetaDescription, setValue: setSelectedMetaDescription, - completed, - setCompleted, + includeInResults: true, }; }; diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-title-step.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-title-step.tsx index 87f2764b82385..51d6b8171d3c6 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-title-step.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-title-step.tsx @@ -9,7 +9,6 @@ export const useTitleStep = (): Step => { const [ titleOptions, setTitleOptions ] = useState< OptionMessage[] >( [] ); const { editPost } = useDispatch( 'core/editor' ); const { messages, setMessages, addMessage, editLastMessage, setSelectedMessage } = useMessages(); - const [ completed, setCompleted ] = useState( false ); const [ prevStepValue, setPrevStepValue ] = useState(); const handleTitleSelect = useCallback( @@ -57,7 +56,7 @@ export const useTitleStep = (): Step => { 'Flora Guide: Beautiful Photos of Flowers and Plants for Gardening Enthusiasts', }, ] ), - 3000 + 1500 ) ); } @@ -107,7 +106,7 @@ export const useTitleStep = (): Step => { 'Flora Guide: Beautiful Photos of Flowers and Plants for Gardening Enthusiasts', }, ] ), - 2000 + 1500 ) ); setTitleOptions( [ ...titleOptions, ...newTitles ] ); @@ -117,13 +116,13 @@ export const useTitleStep = (): Step => { const handleTitleSubmit = useCallback( async () => { await editPost( { title: selectedTitle, meta: { jetpack_seo_html_title: selectedTitle } } ); addMessage( { content: __( 'Title updated! ✅', 'jetpack' ) } ); - setCompleted( true ); return selectedTitle; }, [ selectedTitle, addMessage, editPost ] ); return { id: 'title', title: __( 'Optimise Title', 'jetpack' ), + label: __( 'Title', 'jetpack' ), messages, type: 'options', options: titleOptions, @@ -135,7 +134,6 @@ export const useTitleStep = (): Step => { onStart: handleTitleGenerate, value: selectedTitle, setValue: setSelectedTitle, - completed, - setCompleted, + includeInResults: true, }; }; diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-welcome-step.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-welcome-step.tsx index 5163f890b8dc0..6b0d1eb7f24b7 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-welcome-step.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/use-welcome-step.tsx @@ -26,6 +26,6 @@ export const useWelcomeStep = (): Step => { id: '2', }, ], - autoAdvance: 2000, + autoAdvance: 1500, }; };