diff --git a/ghost/admin/app/components/modal-portal-settings.js b/ghost/admin/app/components/modal-portal-settings.js
index d1b648a356..7d9487f9d9 100644
--- a/ghost/admin/app/components/modal-portal-settings.js
+++ b/ghost/admin/app/components/modal-portal-settings.js
@@ -256,6 +256,11 @@ export default ModalComponent.extend({
this.set('showLeaveSettingsModal', false);
},
+ openStripeSettings() {
+ this.model.openStripeSettings();
+ this.closeModal();
+ },
+
leaveSettings() {
this.closeModal();
}
diff --git a/ghost/admin/app/controllers/settings/labs.js b/ghost/admin/app/controllers/settings/labs.js
index 74739d3dd1..958f8a00d0 100644
--- a/ghost/admin/app/controllers/settings/labs.js
+++ b/ghost/admin/app/controllers/settings/labs.js
@@ -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);
}
});
diff --git a/ghost/admin/app/controllers/settings/labs/members.js b/ghost/admin/app/controllers/settings/labs/members.js
new file mode 100644
index 0000000000..5a7041208b
--- /dev/null
+++ b/ghost/admin/app/controllers/settings/labs/members.js
@@ -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);
+ }
+});
diff --git a/ghost/admin/app/router.js b/ghost/admin/app/router.js
index 236fe0cb6a..06a0e77a6f 100644
--- a/ghost/admin/app/router.js
+++ b/ghost/admin/app/router.js
@@ -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');
diff --git a/ghost/admin/app/routes/settings/labs.js b/ghost/admin/app/routes/settings/labs.js
index a79d982ec7..84beb8f7c2 100644
--- a/ghost/admin/app/routes/settings/labs.js
+++ b/ghost/admin/app/routes/settings/labs.js
@@ -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();
diff --git a/ghost/admin/app/routes/settings/labs/members.js b/ghost/admin/app/routes/settings/labs/members.js
new file mode 100644
index 0000000000..b8e84bb152
--- /dev/null
+++ b/ghost/admin/app/routes/settings/labs/members.js
@@ -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'
+ };
+ }
+});
diff --git a/ghost/admin/app/styles/layouts/labs.css b/ghost/admin/app/styles/layouts/labs.css
index 4c2189bcc6..a22b82a625 100644
--- a/ghost/admin/app/styles/layouts/labs.css
+++ b/ghost/admin/app/styles/layouts/labs.css
@@ -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 {
diff --git a/ghost/admin/app/styles/layouts/portal-settings.css b/ghost/admin/app/styles/layouts/portal-settings.css
index 215dafa18e..eb4a115b64 100644
--- a/ghost/admin/app/styles/layouts/portal-settings.css
+++ b/ghost/admin/app/styles/layouts/portal-settings.css
@@ -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);
+}
\ No newline at end of file
diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css
index 79cdf1d34b..d472b7c457 100644
--- a/ghost/admin/app/styles/layouts/settings.css
+++ b/ghost/admin/app/styles/layouts/settings.css
@@ -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
/* ---------------------------------------------------------- */
diff --git a/ghost/admin/app/styles/patterns/buttons.css b/ghost/admin/app/styles/patterns/buttons.css
index b8c8580cc6..ca52d16d07 100644
--- a/ghost/admin/app/styles/patterns/buttons.css
+++ b/ghost/admin/app/styles/patterns/buttons.css
@@ -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!
/* ---------------------------------------------------------- */
diff --git a/ghost/admin/app/templates/settings/labs.hbs b/ghost/admin/app/templates/settings/labs.hbs
index 61b89aaa6e..8baf74a1fe 100644
--- a/ghost/admin/app/templates/settings/labs.hbs
+++ b/ghost/admin/app/templates/settings/labs.hbs
@@ -9,43 +9,25 @@
{{svg-jar "idea"}}This is a testing ground for new or experimental features. They may change, break or inexplicably disappear at any time.
{{#if this.session.user.isOwner}}
-
-
-
-
-
-
-
Enable members
-
Create registered members and take subscription payments —
Find out more
-
-
+
+
+
+
+
+
Members
+
Create registered members and take subscription payments
- {{#liquid-if this.feature.labs.members}}
-
-
-
+
+ {{#if this.feature.labs.members}}
+ Enabled
+ {{else}}
+ Configure
+ {{/if}}
+ {{svg-jar "arrow-right" class="w6 h6 fill-midgrey pa1 nr2 ml2"}}
- {{/liquid-if}}
-
+
{{/if}}
diff --git a/ghost/admin/app/templates/settings/labs/members.hbs b/ghost/admin/app/templates/settings/labs/members.hbs
new file mode 100644
index 0000000000..edae1834b0
--- /dev/null
+++ b/ghost/admin/app/templates/settings/labs/members.hbs
@@ -0,0 +1,52 @@
+
+
+
+
+
+ {{#if this.session.user.isOwner}}
+
+
+
+
Enable members
+
Create registered members and take subscription payments —
Find out more
+
+
+
+
+
+
+ {{#liquid-if this.feature.labs.members}}
+
+ {{/liquid-if}}
+
+
+ {{/if}}
+
+
+
\ No newline at end of file
diff --git a/ghost/admin/tests/acceptance/members-test.js b/ghost/admin/tests/acceptance/members-test.js
index 4346e4ea61..01a8565888 100644
--- a/ghost/admin/tests/acceptance/members-test.js
+++ b/ghost/admin/tests/acceptance/members-test.js
@@ -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('/');
diff --git a/ghost/admin/tests/acceptance/settings/labs-test.js b/ghost/admin/tests/acceptance/settings/labs-test.js
index 217f6479a4..a53d6a8f67 100644
--- a/ghost/admin/tests/acceptance/settings/labs-test.js
+++ b/ghost/admin/tests/acceptance/settings/labs-test.js
@@ -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"]');