diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/style.scss b/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/style.scss index b25deabfed3266..a41d831c906f7b 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/style.scss +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/style.scss @@ -50,9 +50,7 @@ } } - svg { - fill: var(--studio-blue-50); - } + } .wpcom__loading-ellipsis { diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/components/form/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/components/form/index.tsx new file mode 100644 index 00000000000000..72c7ae6feae068 --- /dev/null +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/components/form/index.tsx @@ -0,0 +1,187 @@ +import { FormLabel } from '@automattic/components'; +import { NextButton } from '@automattic/onboarding'; +import { CheckboxControl } from '@wordpress/components'; +import clsx from 'clsx'; +import { useTranslate } from 'i18n-calypso'; +import { FC, useEffect } from 'react'; +import { Control, Controller, FieldError, useForm } from 'react-hook-form'; +import FormTextArea from 'calypso/components/forms/form-textarea'; +import Notice from 'calypso/components/notice'; +import { useSiteSlugParam } from 'calypso/landing/stepper/hooks/use-site-slug-param'; +import { + type TicketMigrationData, + useMigrationTicketMutation, +} from '../../hooks/use-migration-ticket-mutation'; + +interface CheckboxProps { + label: string; + control: Control< TicketMigrationData >; + value: string; +} + +const CheckboxIntents = ( { label, control, value }: CheckboxProps ) => ( + { + return ( + { + if ( isChecked ) { + field.onChange( [ ...field.value, value ] ); + } else { + field.onChange( field.value.filter( ( v ) => v !== value ) ); + } + } } + checked={ field.value.includes( value ) } + label={ label } + name={ field.name } + /> + ); + } } + /> +); + +interface OtherDetailsProps { + label: string; + control: Control< TicketMigrationData >; + error?: FieldError; +} + +const OtherDetails = ( { label, control, error }: OtherDetailsProps ) => { + const translate = useTranslate(); + return ( + { + return ( +
+ { label } + + { error && error.message && ( +

{ error.message }

+ ) } +
+ ); + } } + /> + ); +}; + +interface FormProps { + onComplete: () => void; +} + +const Form: FC< FormProps > = ( { onComplete } ) => { + const translate = useTranslate(); + const siteSlug = useSiteSlugParam() ?? ''; + + const { + control, + handleSubmit, + watch, + setError, + formState: { errors }, + } = useForm< TicketMigrationData >( { + defaultValues: { + intents: [], + otherDetails: '', + }, + } ); + + const { mutate: createTicket, isSuccess } = useMigrationTicketMutation( siteSlug ); + + useEffect( () => { + if ( isSuccess ) { + if ( process.env.NODE_ENV !== 'development' ) { + alert( 'Success!' ); + } + onComplete(); + } + }, [ isSuccess, onComplete ] ); + + const onSubmit = handleSubmit( ( data: TicketMigrationData ) => { + if ( data.intents.length === 0 ) { + setError( 'intents', { + type: 'manual', + message: translate( 'Please select an option.' ), + } ); + } + createTicket( { + intents: data.intents, + otherDetails: data.otherDetails, + } ); + } ); + + const intents = watch( 'intents' ); + const isOtherChecked = intents.includes( 'other' ); + + return ( +
+
+ { errors.intents && ( + + ) } +
+
+

+ { translate( 'What brought you here today?' ) } +

+
+ + + + + + + + { isOtherChecked && ( + + ) } +
+ { translate( 'Continue' ) } + +
+ ); +}; + +export default Form; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/hooks/use-migration-ticket-mutation.ts b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/hooks/use-migration-ticket-mutation.ts new file mode 100644 index 00000000000000..8822f517be8f1e --- /dev/null +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/hooks/use-migration-ticket-mutation.ts @@ -0,0 +1,27 @@ +import { DefaultError, useMutation } from '@tanstack/react-query'; + +export interface TicketMigrationData { + intents: string[]; + otherDetails: string; +} + +interface ApiResponse { + success: boolean; +} + +const setMigration = ( + siteSlug: string, + { intents, otherDetails }: TicketMigrationData +): Promise< ApiResponse > => { + // eslint-disable-next-line no-console + console.log( 'setMigration', siteSlug, intents, otherDetails ); + return Promise.resolve( { success: true } ); +}; + +export const useMigrationTicketMutation = ( siteSlug: string ) => { + return useMutation< ApiResponse, DefaultError, TicketMigrationData >( { + mutationKey: [ 'create-migration-ticket', siteSlug ], + mutationFn: ( { intents, otherDetails } ) => + setMigration( siteSlug, { intents, otherDetails } ), + } ); +}; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/index.tsx new file mode 100644 index 00000000000000..f8a0a0cc8fc7be --- /dev/null +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/index.tsx @@ -0,0 +1,80 @@ +import { StepContainer } from '@automattic/onboarding'; +import { useTranslate } from 'i18n-calypso'; +import { type FC } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import DocumentHead from 'calypso/components/data/document-head'; +import FormattedHeader from 'calypso/components/formatted-header'; +import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; +import Form from './components/form'; +import type { StepProps } from '../../types'; + +import './style.scss'; + +const extractDomainFromUrl = ( url: string ) => { + try { + const parsedUrl = new URL( url ); + return parsedUrl.hostname; + } catch ( error ) { + return url; + } +}; + +const SiteMigrationAlreadyWPCOM: FC< StepProps > = ( { stepName, flow, navigation } ) => { + const translate = useTranslate(); + const [ query ] = useSearchParams(); + const from = query.get( 'from' )!; + + const title = translate( 'Your site is already on {{br /}}WordPress.com', { + components: { + br:
, + }, + } ); + const subtitle = translate( + "Let's figure out your next steps for {{strong}}%(from)s{{/strong}} together.", + { + args: { + from: extractDomainFromUrl( from ), + }, + components: { + strong: , + }, + } + ); + const subHeaderText = ( + <> +

{ subtitle }

+

{ translate( 'Please complete the form below.' ) }

+ + ); + + const onSubmit = () => { + navigation?.submit?.(); + }; + + return ( + <> + + + } + isFullLayout + stepContent={
} + recordTracksEvent={ recordTracksEvent } + /> + + ); +}; + +export default SiteMigrationAlreadyWPCOM; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/style.scss b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/style.scss new file mode 100644 index 00000000000000..7d92fe8b733331 --- /dev/null +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/style.scss @@ -0,0 +1,66 @@ +@import "@wordpress/base-styles/breakpoints"; + +.site-migration-already-wpcom__form-title--error, +.site-migration-already-wpcom__form-error { + color: var(--color-error); + font-size: 0.875rem; + font-weight: 500; +} + +.site-migration-already-wpcom__form-container { + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; +} + +.site-migration-already-wpcom__form { + display: flex; + flex-direction: column; + gap: 16px; +} + +.site-migration-already-wpcom__form .components-checkbox-control__label { + font-size: 0.875rem; + font-weight: 400; +} + +.site-migration-already-wpcom__form-title-container { + display: flex; + flex-direction: row; + gap: 0.25rem; + align-items: center; + margin-bottom: 0.5rem; +} + +.site-migration-already-wpcom__form-title { + font-size: 0.875rem; + font-weight: 600; + margin-bottom: 0; +} + +.site-migration-already-wpcom__form-textarea-container { + margin-top: 1.5rem; +} + +.site-migration-already-wpcom__form-textarea { + width: 100%; +} + +.site-migration-already-wpcom__form-textarea--error { + border-color: var(--color-error); +} + +.site-migration-already-wpcom__form-error-notice { + margin-bottom: 0; +} + +.site-migration-already-wpcom__form-content--error { + * { + color: var(--color-error); + + } + input[type="checkbox"] { + border-color: var(--color-error); + } +} diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/test/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/test/index.tsx new file mode 100644 index 00000000000000..8fb03fbc74eb28 --- /dev/null +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/test/index.tsx @@ -0,0 +1,65 @@ +/** + * @jest-environment jsdom + */ +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import SiteMigrationAlreadyWPCOM from '../'; +import { StepProps } from '../../../types'; +import { mockStepProps, renderStep, RenderStepOptions } from '../../test/helpers/index'; + +const continueButton = () => screen.getByRole( 'button', { name: 'Continue' } ); +const intentByName = ( intent: string ) => screen.getByRole( 'checkbox', { name: intent } ); +const otherDetails = () => screen.getByRole( 'textbox', { name: 'Other details' } ); + +describe( 'SiteMigrationAlreadyWPCOM', () => { + const render = ( props?: Partial< StepProps >, renderOptions?: RenderStepOptions ) => { + const combinedProps = { ...mockStepProps( props ) }; + return renderStep( , renderOptions ); + }; + + it( 'renders the site domain from the query params', () => { + render( {}, { initialEntry: '/some-path?from=https://example.com' } ); + expect( screen.getByText( 'example.com' ) ).toBeVisible(); + } ); + + it( 'shows an error when the user does not select an option', async () => { + const submit = jest.fn(); + render( { navigation: { submit } }, { initialEntry: '/some-path?from=https://example.com' } ); + + await userEvent.click( continueButton() ); + expect( await screen.findByText( /Please select an option/ ) ).toBeVisible(); + } ); + + it( 'shows an error when the user selects other but does not provide details', async () => { + render( {}, { initialEntry: '/some-path?from=https://example.com' } ); + userEvent.click( intentByName( 'Other' ) ); + await userEvent.click( continueButton() ); + + expect( await screen.findByText( /Please, provide more details/ ) ).toBeVisible(); + } ); + + it( 'navigate to next step when the submits an intent', async () => { + const navigation = { submit: jest.fn() }; + render( { navigation }, { initialEntry: '/some-path?from=https://example.com' } ); + + userEvent.click( intentByName( 'Transfer my domain to WordPress.com' ) ); + await userEvent.click( continueButton() ); + + expect( navigation.submit ).toHaveBeenCalled(); + } ); + + it( 'navigate to next step when the user describes their needs manually', async () => { + const navigation = { submit: jest.fn() }; + render( { navigation }, { initialEntry: '/some-path?from=https://example.com' } ); + + await userEvent.click( intentByName( 'Other' ) ); + await userEvent.type( + otherDetails(), + 'I need to migrate my site to WordPress.com but I already have a WordPress.com account' + ); + await userEvent.click( continueButton() ); + + expect( navigation.submit ).toHaveBeenCalled(); + } ); +} ); diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/types.ts b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-already-wpcom/types.ts new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/client/landing/stepper/declarative-flow/internals/steps.tsx b/client/landing/stepper/declarative-flow/internals/steps.tsx index 4474256b510716..4c9449d0d77679 100644 --- a/client/landing/stepper/declarative-flow/internals/steps.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps.tsx @@ -291,6 +291,11 @@ export const STEPS = { asyncComponent: () => import( './steps-repository/site-migration-plugin-install' ), }, + SITE_MIGRATION_ALREADY_WPCOM: { + slug: 'already-wpcom', + asyncComponent: () => import( './steps-repository/site-migration-already-wpcom' ), + }, + PICK_SITE: { slug: 'sitePicker', asyncComponent: () => import( './steps-repository/site-picker' ), diff --git a/client/landing/stepper/declarative-flow/migration/index.tsx b/client/landing/stepper/declarative-flow/migration/index.tsx index 7da67a74e510f0..57bc9021a9f768 100644 --- a/client/landing/stepper/declarative-flow/migration/index.tsx +++ b/client/landing/stepper/declarative-flow/migration/index.tsx @@ -35,6 +35,7 @@ const { SITE_MIGRATION_STARTED, SITE_MIGRATION_ASSISTED_MIGRATION, SITE_MIGRATION_CREDENTIALS, + SITE_MIGRATION_ALREADY_WPCOM, } = STEPS; const steps = [ @@ -48,6 +49,7 @@ const steps = [ SITE_MIGRATION_STARTED, SITE_MIGRATION_ASSISTED_MIGRATION, SITE_MIGRATION_CREDENTIALS, + SITE_MIGRATION_ALREADY_WPCOM, ]; const plans: { [ key: string ]: string } = { @@ -281,7 +283,6 @@ const useCreateStepHandlers = ( navigate: Navigate< StepperStep[] >, flowObject: return navigateWithQueryParams( MIGRATION_HOW_TO_MIGRATE, [], props ); }, }, - [ SITE_MIGRATION_ASSISTED_MIGRATION.slug ]: { submit: ( props?: ProvidedDependencies ) => { const hasError = getFromPropsOrUrl( 'hasError', props );