Added outline launch wizard framework

refs https://github.com/TryGhost/Team/issues/450

- adds `launch/*` routes corresponding to launch wizard steps
- sets up navigation between each wizard step
- adds components for each `launch/*` step to contain step-related functionality and facilitate later refactoring to a generalised wizard component
- adds link to the launch wizard from the dashboard screen
This commit is contained in:
Kevin Ansfield 2021-01-18 13:48:23 +00:00
parent beac5543ce
commit d48abaa1f8
31 changed files with 343 additions and 1 deletions

View File

@ -0,0 +1 @@
<h2>Connect Stripe</h2>

View File

@ -0,0 +1,4 @@
import Component from '@glimmer/component';
export default class GhLaunchWizardConnectStripeComponent extends Component {
}

View File

@ -0,0 +1 @@
<h2>Customise design</h2>

View File

@ -0,0 +1,4 @@
import Component from '@glimmer/component';
export default class GhLaunchWizardCustomiseDesignComponent extends Component {
}

View File

@ -0,0 +1 @@
<h2>Set pricing</h2>

View File

@ -0,0 +1,4 @@
import Component from '@glimmer/component';
export default class GhLaunchWizardSetPricingComponent extends Component {
}

View File

@ -0,0 +1,12 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class LaunchController extends Controller {
@service router;
@action
close() {
this.router.transitionTo('dashboard');
}
}

View File

@ -0,0 +1,17 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class LaunchCompleteController extends Controller {
@service router;
@action
next() {
this.router.transitionTo('dashboard');
}
@action
goBack() {
this.router.transitionTo('launch.set-pricing');
}
}

View File

@ -0,0 +1,17 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class LaunchConnectStripeController extends Controller {
@service router;
@action
next() {
this.router.transitionTo('launch.set-pricing');
}
@action
goBack() {
this.router.transitionTo('launch.customise-design');
}
}

View File

@ -0,0 +1,12 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class LaunchCustomiseDesignController extends Controller {
@service router;
@action
next() {
this.router.transitionTo('launch.connect-stripe');
}
}

View File

@ -0,0 +1,17 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class LaunchSetPricingController extends Controller {
@service router;
@action
next() {
this.router.transitionTo('launch.complete');
}
@action
goBack() {
this.router.transitionTo('launch.connect-stripe');
}
}

View File

@ -21,6 +21,13 @@ Router.map(function () {
this.route('signup', {path: '/signup/:token'});
this.route('reset', {path: '/reset/:token'});
this.route('launch', function () {
this.route('customise-design');
this.route('connect-stripe');
this.route('set-pricing');
this.route('complete');
});
this.route('about');
this.route('site');
this.route('dashboard');

View File

@ -0,0 +1,12 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import {inject as service} from '@ember/service';
export default class LaunchRoute extends AuthenticatedRoute {
@service config;
beforeModel() {
if (!this.config.get('enableDeveloperExperiments')) {
this.transitionTo('site');
}
}
}

View File

@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class LaunchCompleteRoute extends Route {
}

View File

@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class LaunchConnectStripeRoute extends Route {
}

View File

@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class LaunchCustomiseDesignRoute extends Route {
}

View File

@ -0,0 +1,7 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
export default class LaunchIndexRoute extends AuthenticatedRoute {
beforeModel() {
this.transitionTo('launch.customise-design');
}
}

View File

@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class LaunchSetPricingRoute extends Route {
}

View File

@ -62,6 +62,7 @@
@import "layouts/preview-email.css";
@import "layouts/portal-settings.css";
@import "layouts/billing.css";
@import "layouts/fullscreen-wizard.css";
:root {

View File

@ -62,6 +62,7 @@
@import "layouts/preview-email.css";
@import "layouts/portal-settings.css";
@import "layouts/billing.css";
@import "layouts/fullscreen-wizard.css";
/* ---------------------------✈️----------------------------- */

View File

@ -0,0 +1,11 @@
.fullscreen-wizard-container {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
height: 100vh;
background: white;
overflow: auto;
}

View File

@ -6,6 +6,6 @@
</GhCanvasHeader>
<section class="view-container">
<LinkTo @route="launch">Launch site - Finish setup</LinkTo>
</section>
</section>

View File

@ -0,0 +1,8 @@
<div class="fullscreen-wizard-container">
<button type="button" class="close absolute top-6 right-6 w6" title="Close" {{on "click" this.close}} data-test-button="close-wizard">
{{svg-jar "close"}}
<span class="hidden">Close</span>
</button>
{{outlet}}
</div>

View File

@ -0,0 +1,9 @@
<button type="button" class="absolute top-6 left-6 w6" title="Previous step" {{on "click" this.goBack}} data-test-button="wizard-back">
{{svg-jar "arrow-left"}}
<span class="hidden">Go to previous step</span>
</button>
<section class="ma19">
<h2>Congrats</h2>
<button type="button" {{on "click" this.next}} data-test-button="wizard-next">Start publishing</button>
</section>

View File

@ -0,0 +1,9 @@
<button type="button" class="absolute top-6 left-6 w6" title="Previous step" {{on "click" this.goBack}} data-test-button="wizard-back">
{{svg-jar "arrow-left"}}
<span class="hidden">Go to previous step</span>
</button>
<section class="ma19">
<GhLaunchWizard::ConnectStripe />
<button type="button" {{on "click" this.next}} data-test-button="wizard-next">Next</button>
</section>

View File

@ -0,0 +1,4 @@
<section class="ma19">
<GhLaunchWizard::CustomiseDesign />
<button type="button" {{on "click" this.next}} data-test-button="wizard-next">Next</button>
</section>

View File

@ -0,0 +1,9 @@
<button type="button" class="absolute top-6 left-6 w6" title="Previous step" {{on "click" this.goBack}} data-test-button="wizard-back">
{{svg-jar "arrow-left"}}
<span class="hidden">Go to previous step</span>
</button>
<section class="ma19">
<GhLaunchWizard::SetPricing />
<button type="button" {{on "click" this.next}} data-test-button="wizard-next">Finish & launch my site</button>
</section>

View File

@ -0,0 +1,104 @@
import {authenticateSession} from 'ember-simple-auth/test-support';
import {currentURL, visit} from '@ember/test-helpers';
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support';
describe('Acceptance: Launch flow', function () {
const hooks = setupApplicationTest();
setupMirage(hooks);
it('is not accessible when logged out', async function () {
await visit('/launch');
expect(currentURL()).to.equal('/signin');
await visit('/launch/customise-design');
expect(currentURL()).to.equal('/signin');
await visit('/launch/connect-stripe');
expect(currentURL()).to.equal('/signin');
await visit('/launch/set-pricing');
expect(currentURL()).to.equal('/signin');
await visit('/launch/complete');
expect(currentURL()).to.equal('/signin');
});
describe('when logged in', function () {
beforeEach(async function () {
// TODO: remove this setup when out of dev experiments
this.server.loadFixtures('configs');
const config = this.server.schema.configs.first();
config.update({
enableDeveloperExperiments: true
});
let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]});
return await authenticateSession();
});
it('can visit /launch', async function () {
await visit('/launch');
expect(currentURL()).to.equal('/launch/customise-design');
});
it('can visit /launch/customise-design', async function () {
await visit('/launch/customise-design');
expect(currentURL()).to.equal('/launch/customise-design');
});
it('can visit /launch/connect-stripe', async function () {
await visit('/launch/connect-stripe');
expect(currentURL()).to.equal('/launch/connect-stripe');
});
it('can visit /launch/set-pricing', async function () {
await visit('/launch/set-pricing');
expect(currentURL()).to.equal('/launch/set-pricing');
});
it('can visit /launch/complete', async function () {
await visit('/launch/complete');
expect(currentURL()).to.equal('/launch/complete');
});
});
// TODO: remove this whole section when out of dev experiments
describe('developer experiments', function () {
describe('when disabled', function () {
beforeEach(async function () {
this.server.loadFixtures('configs');
const config = this.server.schema.configs.first();
config.update({
enableDeveloperExperiments: false
});
let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]});
return await authenticateSession();
});
it('redirects all routes to /site', async function () {
await visit('/launch');
expect(currentURL()).to.equal('/site');
await visit('/launch/customise-design');
expect(currentURL()).to.equal('/site');
await visit('/launch/connect-stripe');
expect(currentURL()).to.equal('/site');
await visit('/launch/set-pricing');
expect(currentURL()).to.equal('/site');
await visit('/launch/complete');
expect(currentURL()).to.equal('/site');
});
});
});
});

View File

@ -0,0 +1,18 @@
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {hbs} from 'ember-cli-htmlbars';
import {render} from '@ember/test-helpers';
import {setupRenderingTest} from 'ember-mocha';
describe('Integration: Component: gh-launch-wizard/connect-stripe', function () {
setupRenderingTest();
it('renders', async function () {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<GhLaunchWizard::ConnectStripe />`);
expect(this.element.textContent.trim()).to.equal('Connect Stripe');
});
});

View File

@ -0,0 +1,18 @@
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {hbs} from 'ember-cli-htmlbars';
import {render} from '@ember/test-helpers';
import {setupRenderingTest} from 'ember-mocha';
describe('Integration: Component: gh-launch-wizard/customise-design', function () {
setupRenderingTest();
it('renders', async function () {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<GhLaunchWizard::CustomiseDesign />`);
expect(this.element.textContent.trim()).to.equal('Customise design');
});
});

View File

@ -0,0 +1,18 @@
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {hbs} from 'ember-cli-htmlbars';
import {render} from '@ember/test-helpers';
import {setupRenderingTest} from 'ember-mocha';
describe('Integration: Component: gh-launch-wizard/set-pricing', function () {
setupRenderingTest();
it('renders', async function () {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<GhLaunchWizard::SetPricing />`);
expect(this.element.textContent.trim()).to.equal('Set pricing');
});
});