Skip to content

Commit f5d7600

Browse files
taipeicoderclaude
andauthored
Plugins "Get started": fix blank page + duplicate site when backing out of checkout (#112462)
* Plugins "Get started": clean back-out of checkout without a blank page or duplicate site Backing out of checkout in the with-plugin flow had two problems: - The back URL dropped the required billing_period query dependency for free plugins, so the flow controller threw and the plans step rendered blank. Always include billing_period (empty for free plugins). - Re-entering the flow restarted at the domains step and created a duplicate site. Extend the existing onboarding "skip domains on browser-back" path (persist domains data on completion, re-submit it on re-entry) to the with-plugin flow, so it reuses the created site and lands on the plans grid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Plugins "Get started": make the domains-reuse opt-in a declarative flow flag Replace the duplicated [ 'onboarding', 'with-plugin' ] flow-name list in controller.js and main.jsx with a persistsDomainsOnReEntry flag on the flow config, mirroring the existing excludeFromManageSiteFlows pattern. A flow now opts into the domains-persist/skip-on-browser-back behavior in one place instead of two lists that must stay in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Plugins "Get started": type the persistsDomainsOnReEntry flow config field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3373d9e commit f5d7600

6 files changed

Lines changed: 40 additions & 13 deletions

File tree

client/signup/config/flows-pure.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ export function generateFlows( {
130130
showRecaptcha: true,
131131
providesDependenciesInQuery: [ 'plugin', 'billing_period', 'intervalType' ],
132132
optionalDependenciesInQuery: [ 'intervalType' ],
133+
// Persist domains data so re-entering via browser back from checkout skips the domains
134+
// step and reuses the created site instead of making a duplicate.
135+
persistsDomainsOnReEntry: true,
133136
hideProgressIndicator: true,
134137
},
135138
{
@@ -141,6 +144,7 @@ export function generateFlows( {
141144
showRecaptcha: true,
142145
providesDependenciesInQuery: [ 'coupon' ],
143146
optionalDependenciesInQuery: [ 'coupon' ],
147+
persistsDomainsOnReEntry: true,
144148
hideProgressIndicator: true,
145149
},
146150
{

client/signup/config/flows.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,19 @@ function getCheckoutUrl( dependencies, localeSlug, flowName, destination ) {
2727
const queryArgs = getQueryArgs() ?? {};
2828

2929
// with-plugin: point "back" at the plans grid (params from the dependency store — the URL query
30-
// is empty by now), not the post-purchase destination which can't render pre-purchase. Known
31-
// tradeoff: this re-enters signup and recreates the site; kept over a blank page for now.
30+
// is empty by now), not the post-purchase destination which can't render pre-purchase. The flow
31+
// skips the domains step on this re-entry (see controller.js) so it reuses the created site.
3232
let backDestination = destination;
3333
if ( flowName === 'with-plugin' ) {
3434
const { pluginParameter, pluginBillingPeriod } = dependencies;
3535
backDestination = addQueryArgs(
3636
{
3737
...( pluginParameter && { plugin: pluginParameter } ),
38+
// billing_period is a required query dependency of this flow, so always include it
39+
// (empty for free plugins) — otherwise the flow controller rejects the URL.
40+
billing_period: pluginBillingPeriod ?? '',
41+
// Default the grid to the plugin's billing interval.
3842
...( pluginBillingPeriod && {
39-
billing_period: pluginBillingPeriod,
40-
// Default the grid to the plugin's billing interval.
4143
intervalType: pluginBillingPeriod === 'MONTHLY' ? 'monthly' : 'yearly',
4244
} ),
4345
},

client/signup/controller.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,8 @@ export default {
209209
const flowName = getFlowName( context.params, userLoggedIn );
210210
const stepName = getStepName( context.params );
211211
const stepSectionName = getStepSectionName( context.params );
212-
const { providesDependenciesInQuery, excludeFromManageSiteFlows } = flows.getFlow(
213-
flowName,
214-
userLoggedIn
215-
);
212+
const { providesDependenciesInQuery, excludeFromManageSiteFlows, persistsDomainsOnReEntry } =
213+
flows.getFlow( flowName, userLoggedIn );
216214

217215
// Update initialContext to help woocommerce-install support site switching.
218216
if ( 'woocommerce-install' === flowName ) {
@@ -249,13 +247,13 @@ export default {
249247
const isManageSiteFlow =
250248
! excludeFromManageSiteFlows && ! isAddNewSiteFlow && isReEnteringSignupViaBrowserBack;
251249

252-
// Hydrate the store with domains dependencies from session storage,
253-
// only in the onboarding flow.
250+
// Hydrate the store with domains dependencies from session storage so re-entering via
251+
// browser back from checkout skips the domains step instead of recreating the site.
254252
const domainsDependencies = getDomainsDependencies();
255253
if (
256254
domainsDependencies &&
257255
isManageSiteFlow &&
258-
flowName === 'onboarding' &&
256+
persistsDomainsOnReEntry &&
259257
stepName !== 'domains'
260258
) {
261259
const { step, dependencies } = JSON.parse( domainsDependencies );

client/signup/main.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,9 @@ class Signup extends Component {
451451
setSignupCompleteFlowName( this.props.flowName );
452452
}
453453

454-
// Persist current domains data in the onboarding flow.
455-
if ( this.props.flowName === 'onboarding' ) {
454+
// Persist current domains data so re-entering via browser back from checkout can skip the
455+
// domains step instead of recreating the site.
456+
if ( flows.getFlow( this.props.flowName, this.props.isLoggedIn ).persistsDomainsOnReEntry ) {
456457
const { domainItem, siteUrl, domainCart } = dependencies;
457458
const { stepSectionName } = this.props;
458459

client/signup/test/flows.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,26 @@ describe( 'Signup Flows Configuration', () => {
131131
expect( result ).toContain( 'billing_period%3DANNUALLY' );
132132
expect( result ).toContain( 'intervalType%3Dyearly' );
133133
} );
134+
135+
test( 'should still include an empty billing_period for a free plugin', () => {
136+
// A free plugin has no billing period, but billing_period is a required query dependency,
137+
// so the back URL must still carry it (empty) or the flow controller rejects the URL.
138+
const flowsModule = require( 'calypso/signup/config/flows' );
139+
const { filterDestination } = flowsModule.default;
140+
141+
const dependencies = {
142+
siteSlug: 'test-site',
143+
cartItem: 'personal_plan',
144+
pluginParameter: 'mailpoet',
145+
};
146+
const destination = '/marketplace/plugin/mailpoet/install/test-site';
147+
148+
const result = filterDestination( destination, dependencies, 'with-plugin', 'en' );
149+
150+
expect( result ).toContain( 'plans-with-plugin' );
151+
expect( result ).toContain( 'plugin%3Dmailpoet' );
152+
expect( result ).toContain( 'billing_period%3D' );
153+
expect( result ).not.toContain( 'intervalType' );
154+
} );
134155
} );
135156
} );

client/signup/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface Flow {
2020
disallowResume?: boolean;
2121
showRecaptcha?: boolean;
2222
enableBranchSteps?: boolean;
23+
persistsDomainsOnReEntry?: boolean;
2324
hideProgressIndicator?: boolean;
2425
helpCenterButtonText?: string;
2526
enableHotjar?: boolean;

0 commit comments

Comments
 (0)