Moved members settings to new page (#1736)

no refs

Moves members related settings in Labs to its own page to improve the overall UX and make it more consistent with the rest of the Admin.
This commit is contained in:
Peter Zimon 2020-10-22 12:39:00 +02:00 committed by GitHub
parent f1bab33296
commit 6ecba355eb
17 changed files with 389 additions and 205 deletions

View File

@ -1,13 +1,29 @@
<div class="flex flex-column b--whitegrey bt">
<div class="flex flex-column b--whitegrey bt mb5">
<section class="bb b--whitegrey pa5">
{{#if (and this.feature.labs.members (or (enable-developer-experiments) this.config.portal))}}
<div class="gh-setting-header">Portal</div>
<section class="flex flex-column br3 shadow-1 bg-grouped-table mt1 pa5 relative gh-settings-portal-section">
<div class="gh-setting-last gh-setting-first relative">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Portal settings</h4>
<p class="gh-setting-desc pa0 ma0">Customize members modal signup flow</p>
</div>
<div class="gh-setting-action">
<button type="button" class="gh-btn gh-btn-outline blue" {{action (toggle "showPortalSettings" this)}} data-test-toggle-membersFrom><span> Customize </span></button>
</div>
</div>
</section>
{{/if}}
<div class="gh-setting-header">Payments</div>
<section class="flex flex-column br3 shadow-1 bg-grouped-table mt1 pa5">
{{#if this.stripeDirect}}
<div class="flex justify-between">
<div>
<div class="gh-setting-first">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Connect to Stripe</h4>
<p class="gh-setting-desc pa0 ma0">Configure API keys to create subscriptions and take payments</p>
</div>
<div>
<div class="gh-setting-action">
<button type="button" class="gh-btn" {{action (toggle "membersStripeOpen" this)}} data-test-toggle-membersstripe><span>{{if this.membersStripeOpen "Close" "Expand"}}</span></button>
</div>
</div>
@ -52,8 +68,8 @@
</div>
{{/liquid-if}}
{{else}}
<div class="flex justify-between">
<div>
<div class="gh-setting-first">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Connect to Stripe</h4>
{{#if this.stripeConnectAccountId}}
{{#if this.hasActiveStripeSubscriptions}}
@ -77,7 +93,7 @@
<p class="gh-setting-desc pa0 ma0">Connect to Stripe to create subscriptions and take payments</p>
{{/if}}
</div>
<div>
<div class="gh-setting-action">
{{#if this.stripeConnectAccountId}}
<button type="button" class="gh-btn" {{action "openDisconnectStripeModal"}}><span>Disconnect</span></button>
{{else}}
@ -87,7 +103,7 @@
</div>
{{#liquid-if this.membersStripeOpen}}
<div class="mb4 mt6">
<div class="mt2">
<div class="flex flex-column flex-row-l items-start justify-between">
<div class="w-100 w-50-l">
<label class="fw6 f8">Generate secure key</label>
@ -138,29 +154,13 @@
</div>
{{/liquid-if}}
{{/if}}
</section>
{{#if (or (enable-developer-experiments) this.config.portal)}}
<section class="bb b--whitegrey pa5">
<div class="flex justify-between">
<div>
<h4 class="gh-setting-title">Portal settings</h4>
<p class="gh-setting-desc pa0 ma0">Customize members modal signup flow</p>
</div>
<div>
<button type="button" class="gh-btn" {{action (toggle "showPortalSettings" this)}} data-test-toggle-membersFrom><span> Customize </span></button>
</div>
</div>
</section>
{{/if}}
<section class="bb b--whitegrey pa5">
<div class="flex justify-between">
<div>
<div class="gh-setting-last">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Subscription pricing</h4>
<p class="gh-setting-desc pa0 ma0">Set monthly and yearly recurring subscription prices</p>
</div>
<div>
<div class="gh-setting-action">
<button type="button" class="gh-btn" {{action (toggle "membersPricingOpen" this)}} data-test-toggle-memberspricing><span>{{if this.membersPricingOpen "Close" "Expand"}}</span></button>
</div>
</div>
@ -221,13 +221,14 @@
{{/liquid-if}}
</section>
<section class="bb b--whitegrey pa5">
<div class="flex justify-between">
<div>
<div class="gh-setting-header">Access</div>
<section class="flex flex-column br3 shadow-1 bg-grouped-table mt1 pa5">
<div class="gh-setting-first">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Allow free member signup</h4>
<p class="gh-setting-desc pa0 ma0">If disabled, members can only be signed up via payment checkout or API integration</p>
</div>
<div>
<div class="gh-setting-action">
<div class="for-switch">
<label class="switch" for="members-allow-self-signup" {{action "toggleSelfSignup" bubbles="false"}}>
<input type="checkbox" checked={{this.allowSelfSignup}} class="gh-input" onclick={{action "toggleSelfSignup"}} data-test-checkbox="members-allow-self-signup">
@ -236,15 +237,13 @@
</div>
</div>
</div>
</section>
<section class="bb b--whitegrey pa5">
<div class="flex justify-between">
<div>
<div class="gh-setting-last">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Default post access</h4>
<p class="gh-setting-desc pa0 ma0">When a new post is created, who should have access to it?</p>
</div>
<div>
<div class="gh-setting-action">
<button type="button" class="gh-btn" {{action (toggle "membersPostAccessOpen" this)}} data-test-toggle-memberspostaccess><span>{{if this.membersPostAccessOpen "Close" "Expand"}}</span></button>
</div>
</div>
@ -281,19 +280,20 @@
{{/liquid-if}}
</section>
<section class="bb b--whitegrey pa5">
<div class="flex justify-between">
<div>
<div class="gh-setting-header">Email</div>
<section class="flex flex-column br3 shadow-1 bg-grouped-table mt1 pa5">
<div class="gh-setting-first">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Email addresses</h4>
<p class="gh-setting-desc pa0 ma0">Contact information used for newsletters and member login emails</p>
</div>
<div>
<div class="gh-setting-action">
<button type="button" class="gh-btn" {{action (toggle "membersFromOpen" this)}} data-test-toggle-membersFrom><span>{{if this.membersFromOpen "Close" "Expand"}}</span></button>
</div>
</div>
{{#liquid-if this.membersFromOpen}}
<div class="mt8">
<div class="mt2">
<GhFormGroup>
<label class="fw6 f8">Support email address</label>
<div class="flex items-center justify-center mt1">
@ -377,15 +377,14 @@
</div>
</div>
{{/liquid-if}}
</section>
{{#unless this.mailgunIsConfigured}}
<section class="bb b--whitegrey pa5">
<div class="flex justify-between">
<div>
{{#unless this.mailgunIsConfigured}}
<div class="gh-setting-last">
<div class="gh-setting-content">
<h4 class="gh-setting-title">Email newsletter settings</h4>
<p class="gh-setting-desc pa0 ma0">The <a href="https://www.mailgun.com/" target="_blank" rel="nofollow noopener">Mailgun API</a> is used for bulk email newsletter delivery. <a href="https://ghost.org/faq/mailgun-newsletters/" target="_blank" rel="noopener">Why is this required?</a></p>
</div>
<div>
<div class="gh-setting-action">
<button type="button" class="gh-btn" {{action (toggle "membersEmailOpen" this)}} data-test-toggle-membersemail>
<span>{{if this.membersEmailOpen "Close" "Expand"}}</span>
</button>
@ -394,54 +393,54 @@
{{#liquid-if this.membersEmailOpen}}
<div class="flex flex-column w-100 w-50-l flex mt8">
<div class="flex items-center">
<GhFormGroup @class="gh-labs-mailgun-region">
<label class="fw6 f8">Mailgun region</label>
<div class="mt1">
<PowerSelect
@options={{this.mailgunRegions}}
@selected={{this.mailgunRegion}}
@onChange={{action "setMailgunRegion"}}
@searchEnabled={{false}}
@triggerComponent="gh-power-select/trigger"
as |region|
>
{{region.flag}} {{region.name}}
</PowerSelect>
</div>
</GhFormGroup>
<GhFormGroup>
<label class="fw6 f8">Mailgun domain</label>
<GhTextInput
@value={{readonly this.mailgunSettings.domain}}
@input={{action "setMailgunDomain"}}
@class="mt1"
data-test-mailgun-domain-input={{true}}
/>
</GhFormGroup>
</div>
<div class="nt5 mb5">
<a href="https://app.mailgun.com/app/sending/domains" target="_blank" class="mt1 fw4 f8">
Find your Mailgun region and domain here &raquo;
</a>
</div>
<GhFormGroup>
<label class="fw6 f8">Mailgun API key</label>
<GhTextInput
@type="password"
@value={{readonly this.mailgunSettings.apiKey}}
@input={{action "setMailgunApiKey"}}
@class="mt1 password" @autocomplete="new-password"
data-test-mailgun-api-key-input={{true}}
/>
<a href="https://app.mailgun.com/app/account/security/api_keys" target="_blank" class="mt1 fw4 f8">
Find your Mailgun API keys here &raquo;
</a>
<div class="flex items-center">
<GhFormGroup @class="gh-labs-mailgun-region">
<label class="fw6 f8">Mailgun region</label>
<div class="mt1">
<PowerSelect
@options={{this.mailgunRegions}}
@selected={{this.mailgunRegion}}
@onChange={{action "setMailgunRegion"}}
@searchEnabled={{false}}
@triggerComponent="gh-power-select/trigger"
as |region|
>
{{region.flag}} {{region.name}}
</PowerSelect>
</div>
</GhFormGroup>
<GhFormGroup>
<label class="fw6 f8">Mailgun domain</label>
<GhTextInput
@value={{readonly this.mailgunSettings.domain}}
@input={{action "setMailgunDomain"}}
@class="mt1"
data-test-mailgun-domain-input={{true}}
/>
</GhFormGroup>
</div>
<div class="nt5 mb5">
<a href="https://app.mailgun.com/app/sending/domains" target="_blank" class="mt1 fw4 f8">
Find your Mailgun region and domain here &raquo;
</a>
</div>
<GhFormGroup>
<label class="fw6 f8">Mailgun API key</label>
<GhTextInput
@type="password"
@value={{readonly this.mailgunSettings.apiKey}}
@input={{action "setMailgunApiKey"}}
@class="mt1 password" @autocomplete="new-password"
data-test-mailgun-api-key-input={{true}}
/>
<a href="https://app.mailgun.com/app/account/security/api_keys" target="_blank" class="mt1 fw4 f8">
Find your Mailgun API keys here &raquo;
</a>
</GhFormGroup>
</div>
{{/liquid-if}}
</section>
{{/unless}}
{{/unless}}
</section>
</div>
{{#if this.showDisconnectStripeConnectModal}}
@ -456,6 +455,9 @@
{{#if this.showPortalSettings}}
<GhFullscreenModal @modal="portal-settings"
@model={{hash
openStripeSettings=(action "openStripeSettings")
}}
@close={{action "closePortalSettings"}}
@modifier="full-overlay portal-settings" />
{{/if}}
@ -464,4 +466,4 @@
@confirm={{action "leavePortalSettings"}}
@close={{action "closeLeaveSettingsModal"}}
@modifier="action wide" />
{{/if}}
{{/if}}

View File

@ -76,6 +76,8 @@ export default Component.extend({
stripeConnectAccountName: reads('settings.stripeConnectDisplayName'),
stripeConnectLivemode: reads('settings.stripeConnectLivemode'),
portalSettingsBorderColor: reads('settings.accentColor'),
selectedReplyAddress: computed('settings.membersReplyAddress', function () {
return REPLY_ADDRESSES.findBy('value', this.get('settings.membersReplyAddress'));
}),
@ -306,6 +308,10 @@ export default Component.extend({
this.settings.rollbackAttributes();
this.set('showPortalSettings', false);
this.set('showLeaveSettingsModal', false);
},
openStripeSettings() {
this.set('membersStripeOpen', true);
}
},

View File

@ -90,6 +90,10 @@
</label>
</div>
</div>
{{else}}
<div class="gh-portal-setting-no-stripe">
You need to <button class="gh-btn gh-btn-link blue" {{action "openStripeSettings"}}>connect to Stripe</button> to take payments
</div>
{{/if}}
</div>
<div class="gh-portal-setting-section divider-top" onclick={{action "switchToSignupPage"}}>

View File

@ -256,6 +256,11 @@ export default ModalComponent.extend({
this.set('showLeaveSettingsModal', false);
},
openStripeSettings() {
this.model.openStripeSettings();
this.closeModal();
},
leaveSettings() {
this.closeModal();
}

View File

@ -8,7 +8,6 @@ import {
isRequestEntityTooLargeError,
isUnsupportedMediaTypeError
} from 'ghost-admin/services/ajax';
import {computed} from '@ember/object';
import {isBlank} from '@ember/utils';
import {isArray as isEmberArray} from '@ember/array';
import {run} from '@ember/runloop';
@ -43,9 +42,6 @@ export default Controller.extend({
session: service(),
settings: service(),
queryParams: ['fromAddressUpdate', 'supportAddressUpdate'],
fromAddressUpdate: null,
supportAddressUpdate: null,
importErrors: null,
importSuccessful: false,
showDeleteAllModal: false,
@ -72,20 +68,6 @@ export default Controller.extend({
this.yamlAccept = [...this.yamlMimeType, ...Array.from(this.yamlExtension, extension => '.' + extension)];
},
fromAddress: computed(function () {
return this.parseEmailAddress(this.settings.get('membersFromAddress'));
}),
supportAddress: computed(function () {
return this.parseEmailAddress(this.settings.get('membersSupportAddress'));
}),
blogDomain: computed('config.blogDomain', function () {
let blogDomain = this.config.blogDomain || '';
const domainExp = blogDomain.replace('https://', '').replace('http://', '').match(new RegExp('^([^/:?#]+)(?:[/:?#]|$)', 'i'));
return (domainExp && domainExp[1]) || '';
}),
actions: {
onUpload(file) {
let formData = new FormData();
@ -179,18 +161,6 @@ export default Controller.extend({
.closest('.gh-setting-action')
.find('input[type="file"]')
.click();
},
setDefaultContentVisibility(value) {
this.set('settings.defaultContentVisibility', value);
},
setStripeConnectIntegrationTokenSetting(stripeConnectIntegrationToken) {
this.set('settings.stripeConnectIntegrationToken', stripeConnectIntegrationToken);
},
setEmailAddress(type, emailAddress) {
this.set(type, emailAddress);
}
},
@ -235,23 +205,6 @@ export default Controller.extend({
return RSVP.resolve();
},
parseEmailAddress(address) {
const emailAddress = address || 'noreply';
// Adds default domain as site domain
if (emailAddress.indexOf('@') < 0 && this.blogDomain) {
return `${emailAddress}@${this.blogDomain}`;
}
return emailAddress;
},
saveSettings: task(function* () {
const response = yield this.settings.save();
// Reset from address value on save
this.set('fromAddress', this.parseEmailAddress(this.settings.get('membersFromAddress')));
this.set('supportAddress', this.parseEmailAddress(this.settings.get('membersSupportAddress')));
return response;
}).drop(),
redirectUploadResult: task(function* (success) {
this.set('redirectSuccess', success);
this.set('redirectFailure', !success);
@ -277,10 +230,5 @@ export default Controller.extend({
reset() {
this.set('importErrors', null);
this.set('importSuccessful', false);
this.set('fromAddressUpdate', null);
this.set('supportAddressUpdate', null);
// stripeConnectIntegrationToken is not a persisted value so we don't want
// to keep it around across transitions
this.settings.set('stripeConnectIntegrationToken', undefined);
}
});

View File

@ -0,0 +1,89 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default Controller.extend({
ajax: service(),
config: service(),
feature: service(),
ghostPaths: service(),
notifications: service(),
session: service(),
settings: service(),
queryParams: ['fromAddressUpdate', 'supportAddressUpdate'],
fromAddressUpdate: null,
supportAddressUpdate: null,
importErrors: null,
importSuccessful: false,
showDeleteAllModal: false,
submitting: false,
uploadButtonText: 'Import',
importMimeType: null,
jsonExtension: null,
jsonMimeType: null,
yamlExtension: null,
yamlMimeType: null,
yamlAccept: null,
init() {
this._super(...arguments);
},
fromAddress: computed(function () {
return this.parseEmailAddress(this.settings.get('membersFromAddress'));
}),
supportAddress: computed(function () {
return this.parseEmailAddress(this.settings.get('membersSupportAddress'));
}),
blogDomain: computed('config.blogDomain', function () {
let blogDomain = this.config.blogDomain || '';
const domainExp = blogDomain.replace('https://', '').replace('http://', '').match(new RegExp('^([^/:?#]+)(?:[/:?#]|$)', 'i'));
return (domainExp && domainExp[1]) || '';
}),
actions: {
setDefaultContentVisibility(value) {
this.set('settings.defaultContentVisibility', value);
},
setStripeConnectIntegrationTokenSetting(stripeConnectIntegrationToken) {
this.set('settings.stripeConnectIntegrationToken', stripeConnectIntegrationToken);
},
setEmailAddress(type, emailAddress) {
this.set(type, emailAddress);
}
},
parseEmailAddress(address) {
const emailAddress = address || 'noreply';
// Adds default domain as site domain
if (emailAddress.indexOf('@') < 0 && this.blogDomain) {
return `${emailAddress}@${this.blogDomain}`;
}
return emailAddress;
},
saveSettings: task(function* () {
const response = yield this.settings.save();
// Reset from address value on save
this.set('fromAddress', this.parseEmailAddress(this.settings.get('membersFromAddress')));
this.set('supportAddress', this.parseEmailAddress(this.settings.get('membersSupportAddress')));
return response;
}).drop(),
reset() {
this.set('fromAddressUpdate', null);
this.set('supportAddressUpdate', null);
// stripeConnectIntegrationToken is not a persisted value so we don't want
// to keep it around across transitions
this.settings.set('stripeConnectIntegrationToken', undefined);
}
});

View File

@ -45,6 +45,7 @@ Router.map(function () {
this.route('settings.general', {path: '/settings/general'});
this.route('settings.labs', {path: '/settings/labs'});
this.route('settings.labs.members', {path: '/settings/labs/members'});
this.route('settings.code-injection', {path: '/settings/code-injection'});
this.route('settings.design', {path: '/settings/design'}, function () {
this.route('uploadtheme');

View File

@ -5,14 +5,6 @@ import {inject as service} from '@ember/service';
export default AuthenticatedRoute.extend(CurrentUserSettings, {
settings: service(),
notifications: service(),
queryParams: {
fromAddressUpdate: {
replace: true
},
supportAddressUpdate: {
replace: true
}
},
beforeModel() {
this._super(...arguments);
@ -25,20 +17,6 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, {
return this.settings.reload();
},
setupController(controller) {
if (controller.fromAddressUpdate === 'success') {
this.notifications.showAlert(
`Newsletter email address has been updated`.htmlSafe(),
{type: 'success', key: 'members.settings.from-address.updated'}
);
} else if (controller.supportAddressUpdate === 'success') {
this.notifications.showAlert(
`Support email address has been updated`.htmlSafe(),
{type: 'success', key: 'members.settings.support-address.updated'}
);
}
},
resetController(controller, isExiting) {
if (isExiting) {
controller.reset();

View File

@ -0,0 +1,53 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import CurrentUserSettings from 'ghost-admin/mixins/current-user-settings';
import {inject as service} from '@ember/service';
export default AuthenticatedRoute.extend(CurrentUserSettings, {
settings: service(),
notifications: service(),
queryParams: {
fromAddressUpdate: {
replace: true
},
supportAddressUpdate: {
replace: true
}
},
beforeModel() {
this._super(...arguments);
return this.get('session.user')
.then(this.transitionAuthor())
.then(this.transitionEditor());
},
model() {
return this.settings.reload();
},
setupController(controller) {
if (controller.fromAddressUpdate === 'success') {
this.notifications.showAlert(
`Newsletter email address has been updated`.htmlSafe(),
{type: 'success', key: 'members.settings.from-address.updated'}
);
} else if (controller.supportAddressUpdate === 'success') {
this.notifications.showAlert(
`Support email address has been updated`.htmlSafe(),
{type: 'success', key: 'members.settings.support-address.updated'}
);
}
},
resetController(controller, isExiting) {
if (isExiting) {
controller.reset();
}
},
buildRouteInfoMetadata() {
return {
titleToken: 'Settings - Labs - Members'
};
}
});

View File

@ -141,7 +141,12 @@
overflow-y: hidden;
transition: all 0.2s ease-in-out;
opacity: 0;
margin-top: 18px;
margin-top: 16px;
margin-bottom: 0;
}
.gh-members-connect-savecontainer.expanded {
margin-bottom: 20px;
}
.gh-members-connect-savecontainer.expanded {

View File

@ -61,6 +61,7 @@
.gh-portal-settings-sidebar {
padding: 0px 24px 20px;
width: 342px;
}
.gh-portal-settings-form {
@ -500,3 +501,14 @@
display: none;
background: color-mod(var(--darkgrey) a(0.8));
}
.gh-portal-setting-no-stripe {
padding: 20px;
margin-bottom: 28px;
font-size: 1.3rem;
text-align: center;
background: var(--whitegrey-l2);
border: 1px solid var(--whitegrey);
border-radius: 4px;
color: var(--midgrey);
}

View File

@ -71,6 +71,8 @@
}
.gh-setting-action {
display: flex;
align-items: center;
flex-shrink: 0;
margin: 1px 0 0 0;
}
@ -94,6 +96,32 @@
border: 1px solid var(--lightgrey);
}
.gh-setting-liquid-section .liquid-container,
.gh-setting-liquid-section .liquid-child {
padding: 0 20px;
margin: 0 -20px;
}
.gh-settings-portal-section {
box-shadow:
0 0 1px rgba(0,0,0,.07),
0 1.5px 1.2px -11px rgba(0, 0, 0, 0.028),
0 5.1px 4px -11px rgba(0, 0, 0, 0.042),
0 23px 18px -16px rgba(0, 0, 0, 0.07)
;
}
.gh-settings-portal-border {
position: absolute;
content: "";
top: -5px;
right: -5px;
left: -5px;
bottom: -5px;
border: 1px solid var(--blue);
border-radius: 8px;
}
/* Images */
@ -645,6 +673,10 @@
font-size: 10px;
}
.gh-setting-linkrow:hover {
background: var(--whitegrey-l2);
}
/* Themes
/* ---------------------------------------------------------- */

View File

@ -385,11 +385,22 @@ svg.gh-btn-icon-right {
color: color-mod(var(--yellow) l(-10%));
}
.gh-btn-outline {
.gh-btn-outline,
.gh-btn-outline:hover {
background: none;
box-shadow: none;
}
.gh-btn-outline.blue {
border-color: var(--blue);
color: var(--blue);
}
.gh-btn-outline.blue:hover {
border-color: color-mod(var(--blue) l(-10%));
color: color-mod(var(--blue) l(-10%));
}
.gh-btn-textfield-group span {
height: 36px;
line-height: 36px;
@ -485,6 +496,10 @@ Usage: CTA buttons grouped together horizontally.
box-shadow: none;
}
.gh-btn-link.blue {
color: var(--blue);
}
/* Spin Buttons!
/* ---------------------------------------------------------- */

View File

@ -9,43 +9,25 @@
<p class="gh-box gh-box-tip">{{svg-jar "idea"}}This is a testing ground for new or experimental features. They may change, break or inexplicably disappear at any time.</p>
{{#if this.session.user.isOwner}}
<div class="gh-setting-header">Members (BETA) </div>
<div class="flex flex-column br3 shadow-1 bg-grouped-table mt2">
<div class="gh-setting-first gh-setting-last">
<div class="gh-members-setting-content">
<div class="flex">
<div class="flex flex-column flex-grow-1">
<div class="gh-setting-title pl5 pt5">Enable members</div>
<div class="gh-setting-desc pl5 pb5">Create registered members and take subscription payments — <a href="https://ghost.org/docs/members/" target="_blank" rel="noopener">Find out more</a></div>
</div>
<div class="gh-setting-action">
<div class="for-switch pa5">
<GhFeatureFlag @flag="members" @testKey="enable-members"/>
</div>
</div>
<div class="gh-setting-header"></div>
<LinkTo @route="settings.labs.members">
<div class="flex flex-column br3 shadow-1 bg-grouped-table mt2 pa5 gh-setting-linkrow">
<div class="gh-setting-first gh-setting-last">
<div class="gh-setting-content">
<div class="gh-setting-title">Members</div>
<div class="gh-setting-desc">Create registered members and take subscription payments</div>
</div>
{{#liquid-if this.feature.labs.members}}
<GhMembersLabSetting
@settings={{this.settings}}
@fromAddress={{this.fromAddress}}
@supportAddress={{this.supportAddress}}
@setDefaultContentVisibility={{action "setDefaultContentVisibility"}}
@setStripeConnectIntegrationTokenSetting={{action "setStripeConnectIntegrationTokenSetting"}}
@setEmailAddress={{action "setEmailAddress"}}
/>
<div class="mt5 pl5 pr5 pb5">
<GhTaskButton @buttonText="Save members settings"
@task={{this.saveSettings}}
@successText="Saved"
@runningText="Saving"
@class="gh-btn gh-btn-blue gh-btn-icon"
data-test-button="save-members-settings"
/>
<div class="gh-setting-action flex items-center midgrey">
{{#if this.feature.labs.members}}
<span class="gh-badge">Enabled</span>
{{else}}
<span>Configure</span>
{{/if}}
{{svg-jar "arrow-right" class="w6 h6 fill-midgrey pa1 nr2 ml2"}}
</div>
{{/liquid-if}}
</div>
</div>
</div>
</LinkTo>
{{/if}}
<div class="gh-setting-header">Migration options</div>
<div class="flex flex-column br3 shadow-1 bg-grouped-table pa5 mt2">

View File

@ -0,0 +1,52 @@
<section class="gh-canvas">
<GhCanvasHeader class="gh-canvas-header">
<h2 class="gh-canvas-title" data-test-screen-title>
<LinkTo @route="settings.labs">Labs</LinkTo>
<span>{{svg-jar "arrow-right"}}</span>
Members
</h2>
<section class="view-actions">
<GhTaskButton @buttonText="Save settings"
@task={{this.saveSettings}}
@successText="Saved"
@runningText="Saving"
@class="gh-btn gh-btn-blue gh-btn-icon"
data-test-button="save-members-settings"
/>
</section>
</GhCanvasHeader>
<section class="view-container settings-debug">
{{#if this.session.user.isOwner}}
<div class="flex flex-column br3 shadow-1 bg-grouped-table mt2 pa5">
<div class="gh-setting-first gh-setting-last">
<div class="gh-members-setting-content">
<div class="gh-setting-title">Enable members</div>
<div class="gh-setting-desc">Create registered members and take subscription payments — <a href="https://ghost.org/docs/members/" target="_blank" rel="noopener">Find out more</a></div>
</div>
<div class="gh-setting-action">
<div class="for-switch">
<GhFeatureFlag @flag="members" @testKey="enable-members"/>
</div>
</div>
</div>
</div>
<div class="gh-setting-liquid-section">
{{#liquid-if this.feature.labs.members}}
<GhMembersLabSetting
@settings={{this.settings}}
@fromAddress={{this.fromAddress}}
@supportAddress={{this.supportAddress}}
@setDefaultContentVisibility={{action "setDefaultContentVisibility"}}
@setStripeConnectIntegrationTokenSetting={{action "setStripeConnectIntegrationTokenSetting"}}
@setEmailAddress={{action "setEmailAddress"}}
/>
{{/liquid-if}}
</div>
{{/if}}
</section>
</section>

View File

@ -43,7 +43,7 @@ describe('Acceptance: Members', function () {
});
it('shows sidebar link which navigates to members list', async function () {
await visit('/settings/labs');
await visit('/settings/labs/members');
await click('#labs-members');
await visit('/');

View File

@ -314,7 +314,7 @@ describe('Acceptance: Settings - Labs', function () {
});
it('sets the mailgunBaseUrl to the default', async function () {
await visit('/settings/labs');
await visit('/settings/labs/members');
await click('[data-test-toggle="enable-members"]');