Removed all launch-wizard related code

No issue

- With the onboarding flow redesign, the launch wizard can no longer be accessed and is therefore deleted.
This commit is contained in:
Sanne de Vries 2022-09-02 13:20:01 +01:00
parent 0b8b530efb
commit 594e2ccb08
12 changed files with 0 additions and 1054 deletions

View File

@ -1,123 +0,0 @@
<div class="gh-launch-wizard-settings-container">
<div class="gh-stack overflow-y-auto flex-grow-1">
<div class="gh-stack-item gh-setting-first">
<div class="gh-members-stripe-info gh-launch-wizard-stripe-info">
<div class="gh-members-stripe-info-header">
<h4>Getting paid</h4>
{{svg-jar "stripe-verified-partner-badge" class="gh-members-stripe-badge"}}
</div>
<p class="f8 mt2 mb0">
Stripe is our exclusive direct payments partner. Ghost collects <strong>no fees</strong> on any payments! If you dont have a Stripe account yet, you can <a href="https://stripe.com" target="_blank" rel="noopener noreferrer" class="gh-members-stripe-link">sign up here</a>.
</p>
</div>
</div>
{{#if this.config.stripeDirect}}
<div class="gh-stack-item gh-setting flex-column">
<div class="mb4">
<label for="stripe-publishable-key" class="gh-setting-title">Stripe Publishable key</label>
<GhTextInput
@id="stripe-publishable-key"
@type="password"
@value={{readonly this.settings.stripePublishableKey}}
class="mt1 password"
{{on "input" this.setStripeDirectPublicKey}}
/>
{{#if this.stripePublishableKeyError}}<p class="mb0 mt2 f8 red">{{this.stripePublishableKeyError}}</p>{{/if}}
</div>
<div>
<label for="stripe-secret-key" class="gh-setting-title">Stripe Secret key</label>
<GhTextInput
@id="stripe-secret-key"
@type="password"
@value={{readonly this.settings.stripeSecretKey}}
class="mt1 password"
{{on "input" this.setStripeDirectSecretKey}}
/>
{{#if this.stripeSecretKeyError}}<p class="mb0 mt2 f8 red">{{this.stripeSecretKeyError}}</p>{{/if}}
<a href="https://dashboard.stripe.com/account/apikeys" target="_blank" rel="noopener noreferrer" class="mt1 fw4 f8">
Find your Stripe API keys here &raquo;
</a>
</div>
</div>
<div class="gh-setting-desc"><a href="javascript:void(0)" {{on "click" @skipStep}}>Skip</a> if you don't want to offer paid subscriptions.</div>
{{else}}
<div class="gh-stack-item gh-setting flex-wrap">
{{!-- Stripe already configured --}}
{{#if this.settings.stripeConnectAccountId}}
<div>
<h4 class="gh-setting-title">Already connected to Stripe</h4>
<p class="gh-setting-desc mt2">
Connected to <a href="https://dashboard.stripe.com/{{this.settings.stripeConnectAccountId}}" target="_blank" rel="noopener noreferrer">{{this.settings.stripeConnectDisplayName}}</a>
{{#unless this.settings.stripeConnectLivemode}}
<span class="gh-members-connect-testmodelabel">Test mode</span>
{{/unless}}
</p>
</div>
{{#if this.hasActiveStripeSubscriptions}}
<p class="red ma0 pa0 f8 nudge-bottom--2">
Cannot disconnect while there are members with active Stripe subscriptions.
</p>
{{else}}
<div class="gh-setting-action">
<button type="button" class="gh-btn" {{on "click" (perform this.openDisconnectStripeConnectModalTask)}}><span>Disconnect</span></button>
</div>
{{/if}}
{{!-- Stripe not yet configured --}}
{{else}}
<div class="w-100">
<div class="gh-setting-title">Generate secure key</div>
<div class="flex items-center mb4 gh-members-connectbutton-container justify-between mt2">
<a href="{{this.stripeConnectAuthUrl}}" class="stripe-connect" target="_blank" rel="noopener noreferrer"><span>Connect with Stripe</span></a>
<div class="ml2 flex items-center flex-nowrap">
<span class="mr2 f8 midgrey nowrap {{if this.stripeConnectTestMode "gh-members-connect-testmodeon"}}">Test mode</span>
<div class="for-switch small">
<label class="switch" for="stripe-connect-test-mode" {{on "click" this.toggleStripeConnectTestMode}}>
<input type="checkbox" class="gh-input" checked={{this.stripeConnectTestMode}} {{on "click" this.toggleStripeConnectTestMode}} data-test-checkbox="stripe-connect-test-mode">
<span class="input-toggle-component mt1"></span>
</label>
</div>
</div>
</div>
<div class="gh-setting-action">
<GhTextarea
class="gh-launch-wizard-stripe-connect-token"
placeholder="Paste your secure key here"
{{on "input" this.setStripeConnectIntegrationToken}}
/>
{{#if this.stripeConnectError}}<p class="mb0 mt2 f8 red">{{this.stripeConnectError}}</p>{{/if}}
</div>
<div class="gh-setting-desc skip-step"><a href="javascript:void(0)" {{on "click" @skipStep}}>Skip</a> if you don't want to offer paid subscriptions.</div>
</div>
{{/if}}
</div>
{{/if}}
</div>
<div class="gh-launch-wizard-nav-buttons">
<button type="button" class="gh-btn gh-btn-outline gh-btn-icon-dark gh-btn-large w-30" {{on "click" @backStep}}><span>{{svg-jar "arrow-left-tail"}}</span></button>
<GhTaskButton
@task={{this.saveAndContinueTask}}
@runningText="Saving"
@class="w-70 ml4 right gh-btn gh-btn-black gh-btn-large gh-btn-icon-right"
data-test-button="wizard-next"
>
{{#if this.saveAndContinueTask.isRunning}}
<span>Saving...</span>
{{else}}
<span>{{if this.settings.stripeConnectAccountId "Continue" "Save and continue"}}{{svg-jar "arrow-right-tail"}}</span>
{{/if}}
</GhTaskButton>
</div>
</div>
{{#if this.showDisconnectStripeConnectModal}}
<GhFullscreenModal
@modal="disconnect-stripe"
@model={{hash
stripeConnectAccountName=this.settings.stripeConnectDisplayName
}}
@confirm={{perform this.disconnectStripeConnectIntegrationTask}}
@close={{this.closeDisconnectStripeModal}}
@modifier="action wide" />
{{/if}}

View File

@ -1,203 +0,0 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
const RETRY_PRODUCT_SAVE_POLL_LENGTH = 1000;
const RETRY_PRODUCT_SAVE_MAX_POLL = 15 * RETRY_PRODUCT_SAVE_POLL_LENGTH;
export default class GhLaunchWizardConnectStripeComponent extends Component {
@service ajax;
@service config;
@service ghostPaths;
@service settings;
@service store;
@tracked hasActiveStripeSubscriptions = false;
@tracked showDisconnectStripeConnectModal = false;
@tracked stripeConnectTestMode = false;
@tracked stripeConnectError = null;
@tracked stripePublishableKeyError = null;
@tracked stripeSecretKeyError = null;
get stripeConnectAuthUrl() {
const mode = this.stripeConnectTestMode ? 'test' : 'live';
return `${this.ghostPaths.url.api('members/stripe_connect')}?mode=${mode}`;
}
constructor() {
super(...arguments);
this.args.updatePreview('');
}
willDestroy() {
super.willDestroy?.(...arguments);
// clear any unsaved settings changes when going back/forward/closing
this.settings.rollbackAttributes();
}
@action
setStripeDirectPublicKey(event) {
this.settings.set('stripeProductName', this.settings.get('title'));
this.settings.set('stripePublishableKey', event.target.value);
this.stripePublishableKeyError = null;
}
@action
setStripeDirectSecretKey(event) {
this.settings.set('stripeProductName', this.settings.get('title'));
this.settings.set('stripeSecretKey', event.target.value);
this.stripeSecretKeyError = null;
}
@action
toggleStripeConnectTestMode() {
this.stripeConnectTestMode = !this.stripeConnectTestMode;
}
@action
setStripeConnectIntegrationToken(event) {
this.settings.set('stripeProductName', this.settings.get('title'));
this.settings.set('stripeConnectIntegrationToken', event.target.value);
this.stripeConnectError = null;
}
calculateDiscount(monthly, yearly) {
if (isNaN(monthly) || isNaN(yearly)) {
return 0;
}
return monthly ? 100 - Math.floor((yearly / 12 * 100) / monthly) : 0;
}
getActivePrice(prices, interval, amount, currency) {
return prices.find((price) => {
return (
price.active && price.amount === amount && price.type === 'recurring' &&
price.interval === interval && price.currency.toLowerCase() === currency.toLowerCase()
);
});
}
@task({drop: true})
*saveTier() {
let pollTimeout = 0;
while (pollTimeout < RETRY_PRODUCT_SAVE_MAX_POLL) {
yield timeout(RETRY_PRODUCT_SAVE_POLL_LENGTH);
try {
const updatedTier = yield this.tier.save();
yield this.settings.save();
return updatedTier;
} catch (error) {
if (error.payload?.errors && error.payload.errors[0].code === 'STRIPE_NOT_CONFIGURED') {
pollTimeout += RETRY_PRODUCT_SAVE_POLL_LENGTH;
// no-op: will try saving again as stripe is not ready
continue;
} else {
throw error;
}
}
}
return this.tier;
}
@task({drop: true})
*openDisconnectStripeConnectModalTask() {
this.hasActiveStripeSubscriptions = false;
const url = this.ghostPaths.url.api('/members/') + '?filter=status:paid&limit=0';
const response = yield this.ajax.request(url);
if (response?.meta?.pagination?.total !== 0) {
this.hasActiveStripeSubscriptions = true;
return;
}
this.showDisconnectStripeConnectModal = true;
}
@action
closeDisconnectStripeModal() {
this.showDisconnectStripeConnectModal = false;
}
@task
*disconnectStripeConnectIntegrationTask() {
this.disconnectStripeError = false;
const url = this.ghostPaths.url.api('/settings/stripe/connect');
yield this.ajax.delete(url);
yield this.settings.reload();
}
@task
*saveAndContinueTask() {
if (this.config.get('stripeDirect')) {
if (!this.settings.get('stripePublishableKey')) {
this.stripePublishableKeyError = 'Enter your publishable key to continue';
}
if (!this.settings.get('stripeSecretKey')) {
this.stripeSecretKeyError = 'Enter your secret key to continue';
}
if (this.stripePublishableKeyError || this.stripeSecretKeyError) {
return false;
}
} else if (!this.settings.get('stripeConnectAccountId') && !this.settings.get('stripeConnectIntegrationToken')) {
this.stripeConnectError = 'Paste your secure key to continue';
return false;
}
if (!this.config.get('stripeDirect') && this.settings.get('stripeConnectAccountId')) {
this.args.nextStep();
return true;
}
try {
yield this.settings.save();
const tiers = yield this.store.query('tier', {filter: 'type:paid', include: 'monthly_price,yearly_price'});
this.tier = tiers.firstObject;
if (this.tier) {
this.tier.set('currency', 'usd');
this.tier.set('monthlyPrice', 500);
this.tier.set('yearlyPrice', 5000);
yield this.saveTier.perform();
this.settings.set('portalPlans', ['free', 'monthly', 'yearly']);
yield this.settings.save();
}
this.pauseAndContinueTask.perform();
return true;
} catch (error) {
if (error.payload?.errors && error.payload.errors[0].type === 'ValidationError') {
const [validationError] = error.payload.errors;
if (this.config.get('stripeDirect')) {
if (validationError.context.match(/stripe_publishable_key/)) {
this.stripePublishableKeyError = 'Invalid publishable key';
} else {
this.stripeSecretKeyError = 'Invalid secret key';
}
} else {
this.stripeConnectError = 'Invalid secure key';
}
}
throw error;
}
}
@task
*pauseAndContinueTask() {
this.args.refreshPreview();
yield timeout(500);
this.args.nextStep();
}
}

View File

@ -1,18 +0,0 @@
<div class="gh-branding-settings">
<section class="gh-launch-wizard-settings-container">
<GhBrandSettingsForm
class="overflow-y-auto flex-grow-1"
@replacePreviewContents={{@replacePreviewContents}}
/>
<div class="gh-launch-wizard-nav-buttons">
<GhTaskButton
@task={{this.saveAndContinueTask}}
@buttonText={{html-safe (concat "Save and continue " (svg-jar "arrow-right-tail"))}}
type="button"
class="gh-btn gh-btn-black gh-btn-icon-right gh-btn-large gh-launch-wizard-btn w-100"
data-test-button="wizard-next"
/>
</div>
</section>
</div>

View File

@ -1,30 +0,0 @@
import Component from '@glimmer/component';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default class GhLaunchWizardCustomiseDesignComponent extends Component {
@service notifications;
@service settings;
willDestroy() {
super.willDestroy?.(...arguments);
this.settings.rollbackAttributes();
this.settings.errors.remove('accentColor');
}
@task
*saveAndContinueTask() {
try {
if (this.settings.errors && this.settings.errors.length !== 0) {
return;
}
yield this.settings.save();
this.args.nextStep();
} catch (error) {
if (error) {
this.notifications.showAPIError(error);
throw error;
}
}
}
}

View File

@ -1,17 +0,0 @@
<div class="gh-launch-wizard-settings-container">
<div class="overflow-auto flex-grow-1">
<h4>All looks good?</h4>
<p>You are all set up to start creating content, grow an audience and make your first sale!</p>
<p>You can further customize your site in Settings.</p>
</div>
<div class="gh-launch-wizard-nav-buttons">
<button type="button" class="gh-btn gh-btn-outline gh-btn-icon-dark gh-btn-large w-30" {{on "click" @backStep}}><span>{{svg-jar "arrow-left-tail"}}</span></button>
<GhTaskButton
@task={{this.finaliseTask}}
@buttonText="Launch your site!"
@runningText="Launching..."
@class="w-70 ml4 gh-btn gh-btn-black gh-btn-large gh-btn-icon gh-launch-wizard-btn"
data-test-button="wizard-finish"
/>
</div>
</div>

View File

@ -1,47 +0,0 @@
import Component from '@glimmer/component';
import {htmlSafe} from '@ember/template';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default class GhLaunchWizardFinaliseComponent extends Component {
@service feature;
@service notifications;
@service router;
@service settings;
willDestroy() {
super.willDestroy?.(...arguments);
// clear any unsaved settings changes when going back/forward/closing
this.settings.rollbackAttributes();
}
async saveTier() {
const data = this.args.getData();
this.tier = data?.tier;
if (this.tier) {
const monthlyAmount = Math.round(data.monthlyAmount * 100);
const yearlyAmount = Math.round(data.yearlyAmount * 100);
const currency = data.currency;
this.tier.set('monthlyPrice', monthlyAmount);
this.tier.set('yearlyPrice', yearlyAmount);
this.tier.set('currency', currency);
const savedTier = await this.tier.save();
return savedTier;
}
}
@task
*finaliseTask() {
const data = this.args.getData();
if (data?.tier) {
yield this.saveTier();
this.settings.set('editorIsLaunchComplete', true);
yield this.settings.save();
}
this.router.transitionTo('dashboard');
this.notifications.showNotification(
'Launch complete!',
{type: 'success', actions: htmlSafe('<a href="#/posts">Start creating content</a>')}
);
}
}

View File

@ -1,169 +0,0 @@
<div class="gh-launch-wizard-settings-container" {{did-insert this.setup}}>
{{#if this.isConnectDisallowed}}
<div class="gh-stack overflow-y-auto flex-grow-1">
<div class="gh-setting-nossl-container">
<span class="red">{{svg-jar "shield-lock"}}</span>
<h4>Your site is not secured</h4>
<p>Paid memberships through Ghost can only be run on sites secured by SSL (HTTPS vs. HTTP). More information on adding a free SSL Certificate to your Ghost site can be <a href="https://ghost.org/integrations/lets-encrypt/" target="_blank" rel="noopener noreferrer">found here</a>.</p>
</div>
<div class="w-100 mt6">
<div class="gh-setting-title">Generate secure key</div>
<div class="flex items-center mb4 gh-members-connectbutton-container justify-between mt2">
<div class="stripe-connect disabled"><span>Connect with Stripe</span></div>
<div class="ml2 flex items-center flex-nowrap">
<span class="mr2 f8 midgrey nowrap">Test mode</span>
<div class="for-switch small disabled">
<label class="switch" for="stripe-connect-test-mode">
<input type="checkbox" class="gh-input" disabled="disabled">
<span class="input-toggle-component mt1"></span>
</label>
</div>
</div>
</div>
<div class="gh-setting-action">
<GhTextarea
class="gh-launch-wizard-stripe-connect-token"
placeholder="Paste your secure key here"
disabled="disabled"
/>
</div>
</div>
</div>
{{else}}
<div class="gh-stack overflow-y-auto flex-grow-1">
<div class="gh-stack-item flex-column">
<div class="w-100">
<GhFormGroup @class="for-select">
<div class="gh-setting-title" for="currency">Plan currency</div>
<span class="gh-select mt2">
<OneWaySelect
@disabled={{this.disabled}}
@value={{this.selectedCurrency}}
id="currency"
name="currency"
@options={{readonly this.allCurrencies}}
@optionValuePath="value"
@optionLabelPath="label"
@update={{this.setStripePlansCurrency}}
/>
{{svg-jar "arrow-down-small"}}
</span>
</GhFormGroup>
</div>
<div class="w-100 flex flex-column flex-row-ns">
<div class="w-100 w-50-ns mr3-ns">
<GhFormGroup>
<div class="gh-setting-title">Monthly price</div>
<div class="flex items-center justify-center mt2 gh-input-group gh-labs-price-label">
<GhTextInput
@disabled={{this.disabled}}
@value={{readonly this.stripeMonthlyAmount}}
@type="number"
@input={{action (mut this.stripeMonthlyAmount) value="target.value"}}
{{on "blur" this.validateStripePlans}}
/>
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/month</span>
</div>
</GhFormGroup>
</div>
<div class="w-100 w-50-ns ml2-ns">
<GhFormGroup>
<div class="gh-setting-title">Yearly price</div>
<div class="flex items-center justify-center mt2 gh-input-group gh-labs-price-label">
<GhTextInput
@disabled={{this.disabled}}
@value={{readonly this.stripeYearlyAmount}}
@type="number"
@input={{action (mut this.stripeYearlyAmount) value="target.value"}}
{{on "blur" this.validateStripePlans}}
/>
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/year</span>
</div>
</GhFormGroup>
</div>
</div>
</div>
<div class="w-100 w-50-l flex flex-column flex-row-ns">
{{#if this.stripePlanError}}
<p class="response w-100 red"> {{this.stripePlanError}} </p>
{{/if}}
</div>
<div class="gh-stack-item gh-setting flex-column">
<div class="gh-setting-title">Plans available at signup</div>
<div class="form-group mt2 mb0 for-checkbox">
<label
class="checkbox"
for="free-plan"
>
<input
type="checkbox"
checked={{this.isFreeChecked}}
id="free-plan"
name="free-plan"
disabled={{this.isFreeDisabled}}
class="gh-input post-settings-featured"
{{on "click" this.toggleFreePlan}}
data-test-checkbox="featured"
>
<span class="input-toggle-component"></span>
<p>Free</p>
</label>
</div>
<div class="form-group mb0 for-checkbox">
<label
class="checkbox"
for="monthly-plan"
>
<input
type="checkbox"
id="monthly-plan"
name="monthly-plan"
checked={{this.isMonthlyChecked}}
disabled={{not this.membersUtils.isStripeEnabled}}
class="gh-input post-settings-featured"
{{on "click" this.toggleMonthlyPlan}}
data-test-checkbox="featured"
>
<span class="input-toggle-component"></span>
<p>Monthly</p>
</label>
</div>
<div class="form-group mb0 for-checkbox">
<label
class="checkbox"
for="yearly-plan"
>
<input
type="checkbox"
id="yearly-plan"
name="yearly-plan"
checked={{this.isYearlyChecked}}
disabled={{not this.membersUtils.isStripeEnabled}}
class="gh-input post-settings-featured"
{{on "click" this.toggleYearlyPlan}}
data-test-checkbox="featured"
>
<span class="input-toggle-component"></span>
<p>Yearly</p>
</label>
</div>
</div>
</div>
{{/if}}
<div class="gh-launch-wizard-nav-buttons">
<button type="button" class="gh-btn gh-btn-outline gh-btn-icon-dark gh-btn-large w-30" {{on "click" this.backStep}}><span>{{svg-jar "arrow-left-tail"}}</span></button>
{{!-- TODO: reset "failed" state automatically --}}
<GhTaskButton
@task={{this.saveAndContinue}}
@runningText="Saving"
@class="w-70 ml4 right gh-btn gh-btn-black gh-btn-large gh-btn-icon-right"
data-test-button="wizard-next"
>
<span>{{if this.isHidden "Continue" "Save and continue"}}{{svg-jar "arrow-right-tail"}}</span>
</GhTaskButton>
</div>
</div>

View File

@ -1,209 +0,0 @@
import Component from '@glimmer/component';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
const CURRENCIES = currencies.map((currency) => {
return {
value: currency.isoCode.toLowerCase(),
label: `${currency.isoCode} - ${currency.name}`,
isoCode: currency.isoCode
};
});
export default class GhLaunchWizardSetPricingComponent extends Component {
@service config;
@service membersUtils;
@service settings;
@service store;
currencies = CURRENCIES;
@tracked stripeMonthlyAmount = 5;
@tracked stripeYearlyAmount = 50;
@tracked currency = 'usd';
@tracked isFreeChecked = true;
@tracked isMonthlyChecked = true;
@tracked isYearlyChecked = true;
@tracked stripePlanError = '';
@tracked tier;
@tracked loadingTier = false;
get selectedCurrency() {
return this.currencies.findBy('value', this.currency);
}
get allCurrencies() {
return getCurrencyOptions();
}
get isConnectDisallowed() {
const siteUrl = this.config.get('blogUrl');
return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
}
get isFreeDisabled() {
return this.settings.get('membersSignupAccess') !== 'all';
}
willDestroy() {
super.willDestroy?.(...arguments);
// clear any unsaved settings changes when going back/forward/closing
this.args.updatePreview('');
}
@action
setup() {
this.fetchDefaultTier.perform();
this.updatePreviewUrl();
}
@action
backStep() {
const tier = this.tier;
const data = this.args.getData() || {};
this.args.storeData({
...data,
tier,
isFreeChecked: this.isFreeChecked,
isMonthlyChecked: this.isMonthlyChecked,
isYearlyChecked: this.isYearlyChecked,
monthlyAmount: this.stripeMonthlyAmount,
yearlyAmount: this.stripeYearlyAmount,
currency: this.currency
});
this.args.backStep();
}
@action
setStripePlansCurrency(event) {
const newCurrency = event.value;
this.currency = newCurrency;
this.updatePreviewUrl();
}
@action
toggleFreePlan(event) {
this.isFreeChecked = event.target.checked;
this.updatePreviewUrl();
}
@action
toggleMonthlyPlan(event) {
this.isMonthlyChecked = event.target.checked;
this.updatePreviewUrl();
}
@action
toggleYearlyPlan(event) {
this.isYearlyChecked = event.target.checked;
this.updatePreviewUrl();
}
@action
validateStripePlans() {
this.stripePlanError = undefined;
try {
const yearlyAmount = this.stripeYearlyAmount;
const monthlyAmount = this.stripeMonthlyAmount;
const symbol = getSymbol(this.currency);
if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
}
this.updatePreviewUrl();
} catch (err) {
this.stripePlanError = err.message;
}
}
@task
*saveAndContinue() {
if (this.isConnectDisallowed) {
this.args.nextStep();
} else {
yield this.validateStripePlans();
if (this.stripePlanError) {
return false;
}
const tier = this.tier;
const data = this.args.getData() || {};
this.args.storeData({
...data,
tier,
isFreeChecked: this.isFreeChecked,
isMonthlyChecked: this.isMonthlyChecked,
isYearlyChecked: this.isYearlyChecked,
monthlyAmount: this.stripeMonthlyAmount,
yearlyAmount: this.stripeYearlyAmount,
currency: this.currency
});
this.args.nextStep();
}
}
@task({drop: true})
*fetchDefaultTier() {
const storedData = this.args.getData();
if (storedData?.tier) {
this.tier = storedData.tier;
if (storedData.isMonthlyChecked !== undefined) {
this.isMonthlyChecked = storedData.isMonthlyChecked;
}
if (storedData.isYearlyChecked !== undefined) {
this.isYearlyChecked = storedData.isYearlyChecked;
}
if (storedData.isFreeChecked !== undefined) {
this.isFreeChecked = storedData.isFreeChecked;
}
if (storedData.currency !== undefined) {
this.currency = storedData.currency;
}
this.stripeMonthlyAmount = storedData.monthlyAmount;
this.stripeYearlyAmount = storedData.yearlyAmount;
} else {
const tiers = yield this.store.query('tier', {filter: 'type:paid', include: 'monthly_price,yearly_price'});
this.tier = tiers.firstObject;
let portalPlans = this.settings.get('portalPlans') || [];
this.isMonthlyChecked = portalPlans.includes('monthly');
this.isYearlyChecked = portalPlans.includes('yearly');
this.isFreeChecked = portalPlans.includes('free');
const monthlyPrice = this.tier.get('monthlyPrice');
const yearlyPrice = this.tier.get('yearlyPrice');
if (monthlyPrice && monthlyPrice.amount) {
this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
this.currency = monthlyPrice.currency;
}
if (yearlyPrice && yearlyPrice.amount) {
this.stripeYearlyAmount = (yearlyPrice.amount / 100);
}
}
this.updatePreviewUrl();
}
updatePreviewUrl() {
const options = {
disableBackground: true,
currency: this.selectedCurrency.value,
monthlyPrice: Math.round(this.stripeMonthlyAmount * 100),
yearlyPrice: Math.round(this.stripeYearlyAmount * 100),
isMonthlyChecked: this.isMonthlyChecked,
isYearlyChecked: this.isYearlyChecked,
isFreeChecked: this.isFreeChecked,
portalPlans: null
};
const url = this.membersUtils.getPortalPreviewUrl(options);
this.args.updatePreview(url);
}
}

View File

@ -1,163 +0,0 @@
import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {tracked} from '@glimmer/tracking';
const DEFAULT_STEPS = {
'customise-design': {
title: 'Customise your site',
position: 'Step 1',
next: 'connect-stripe'
},
'connect-stripe': {
title: 'Connect to Stripe',
position: 'Step 2',
next: 'set-pricing',
back: 'customise-design',
skip: 'finalise'
},
'set-pricing': {
title: 'Set up subscriptions',
position: 'Step 3',
next: 'finalise',
back: 'connect-stripe'
},
finalise: {
title: 'Launch your site',
position: 'Final step',
back: 'set-pricing'
}
};
export default class LaunchController extends Controller {
@service config;
@service router;
@service settings;
queryParams = ['step'];
@tracked previewGuid = (new Date()).valueOf();
@tracked previewSrc = '';
@tracked step = 'customise-design';
@tracked data = null;
steps = DEFAULT_STEPS;
skippedSteps = [];
constructor(...args) {
super(...args);
const siteUrl = this.config.get('blogUrl');
if (envConfig.environment !== 'development' && !/^https:/.test(siteUrl)) {
this.steps = {
'customise-design': {
title: 'Customise your site',
position: 'Step 1',
next: 'set-pricing'
},
'set-pricing': {
title: 'Set up subscriptions',
position: 'Step 2',
next: 'finalise',
back: 'customise-design'
},
finalise: {
title: 'Launch your site',
position: 'Final step',
back: 'set-pricing'
}
};
} else {
this.steps = DEFAULT_STEPS;
}
}
get currentStep() {
return this.steps[this.step];
}
@action
storeData(data) {
this.data = data;
}
@action
getData() {
return this.data;
}
@action
goToStep(step) {
if (step) {
this.step = step;
}
}
@action
goNextStep() {
this.step = this.currentStep.next;
}
@action
goBackStep() {
let step = this.currentStep.back;
while (this.skippedSteps.includes(step)) {
this.skippedSteps = this.skippedSteps.filter(s => s !== step);
step = this.steps[step].back;
}
this.step = step;
}
// TODO: remember when a step is skipped so "back" works as expected
@action
skipStep() {
let step = this.currentStep.next;
let skipToStep = this.currentStep.skip;
while (step !== skipToStep) {
this.skippedSteps.push(step);
step = this.steps[step].next;
}
this.step = step;
}
@action
registerPreviewIframe(element) {
this.previewIframe = element;
}
@action
refreshPreview() {
this.previewGuid = (new Date()).valueOf();
}
@action
updatePreview(url) {
this.previewSrc = url;
}
@action
replacePreviewContents(html) {
if (this.previewIframe) {
this.previewIframe.contentWindow.document.open();
this.previewIframe.contentWindow.document.write(html);
this.previewIframe.contentWindow.document.close();
}
}
@action
close() {
this.router.transitionTo('dashboard');
}
@action
reset() {
this.data = null;
this.step = 'customise-design';
this.skippedSteps = [];
}
}

View File

@ -1,13 +0,0 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import {inject as service} from '@ember/service';
export default class LaunchRoute extends AuthenticatedRoute {
@service session;
beforeModel() {
super.beforeModel(...arguments);
if (!this.session.user.isOwnerOnly) {
return this.transitionTo('home');
}
}
}

View File

@ -1,32 +0,0 @@
<div class="fullscreen-wizard-container" {{will-destroy this.reset}}>
<div class="pt7 pb5 pl12 pr12 flex justify-between items-center">
<div class="flex flex-column">
<div class="ttu gh-launch-wizard-step-indicator">{{this.currentStep.position}}</div>
<h2>{{this.currentStep.title}}</h2>
</div>
<button type="button" class="close gh-btn gh-btn-outline" {{on "click" this.close}} data-test-button="close-wizard">
<span>Cancel</span>
</button>
</div>
<div class="gh-launch-wizard-content">
<div class="gh-launch-wizard-content-left">
{{component (concat "gh-launch-wizard/" this.step)
nextStep=this.goNextStep
backStep=this.goBackStep
skipStep=this.skipStep
refreshPreview=this.refreshPreview
updatePreview=this.updatePreview
replacePreviewContents=this.replacePreviewContents
storeData=this.storeData
getData=this.getData
}}
</div>
<div class="gh-launch-wizard-content-right">
<GhBrowserPreview class="gh-launch-wizard-preview-container" @icon={{this.settings.icon}} @title={{this.config.blogTitle}}>
<GhSiteIframe class="gh-launch-wizard-preview" @src={{this.previewSrc}} @guid={{this.previewGuid}} {{did-insert this.registerPreviewIframe}}></GhSiteIframe>
</GhBrowserPreview>
</div>
</div>
</div>

View File

@ -1,30 +0,0 @@
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');
});
describe('when logged in', function () {
beforeEach(async function () {
let role = this.server.create('role', {name: 'Owner'});
this.server.create('user', {roles: [role]});
return await authenticateSession();
});
it('can visit /launch', async function () {
await visit('/launch');
expect(currentURL()).to.equal('/launch');
});
});
});