diff --git a/ghost/admin/app/components/gh-members-lab-setting.hbs b/ghost/admin/app/components/gh-members-lab-setting.hbs index 09ad490409..46d85aff49 100644 --- a/ghost/admin/app/components/gh-members-lab-setting.hbs +++ b/ghost/admin/app/components/gh-members-lab-setting.hbs @@ -1,13 +1,29 @@ -
+
-
+ {{#if (and this.feature.labs.members (or (enable-developer-experiments) this.config.portal))}} +
Portal
+
+
+
+

Portal settings

+

Customize members modal signup flow

+
+
+ +
+
+
+ {{/if}} + +
Payments
+
{{#if this.stripeDirect}} -
-
+
+

Connect to Stripe

Configure API keys to create subscriptions and take payments

-
+
@@ -52,8 +68,8 @@
{{/liquid-if}} {{else}} -
-
+
+

Connect to Stripe

{{#if this.stripeConnectAccountId}} {{#if this.hasActiveStripeSubscriptions}} @@ -77,7 +93,7 @@

Connect to Stripe to create subscriptions and take payments

{{/if}}
-
+
{{#if this.stripeConnectAccountId}} {{else}} @@ -87,7 +103,7 @@
{{#liquid-if this.membersStripeOpen}} -
+
@@ -138,29 +154,13 @@
{{/liquid-if}} {{/if}} -
- {{#if (or (enable-developer-experiments) this.config.portal)}} -
-
-
-

Portal settings

-

Customize members modal signup flow

-
-
- -
-
-
- {{/if}} - -
-
-
+
+

Subscription pricing

Set monthly and yearly recurring subscription prices

-
+
@@ -221,13 +221,14 @@ {{/liquid-if}}
-
-
-
+
Access
+
+
+

Allow free member signup

If disabled, members can only be signed up via payment checkout or API integration

-
+
-
-
-
-
+
+

Default post access

When a new post is created, who should have access to it?

-
+
@@ -281,19 +280,20 @@ {{/liquid-if}}
-
-
-
+
Email
+
+
+

Email addresses

Contact information used for newsletters and member login emails

-
+
{{#liquid-if this.membersFromOpen}} -
+
@@ -377,15 +377,14 @@
{{/liquid-if}} -
- {{#unless this.mailgunIsConfigured}} -
-
-
+ + {{#unless this.mailgunIsConfigured}} +
+

Email newsletter settings

The Mailgun API is used for bulk email newsletter delivery. Why is this required?

-
+
@@ -394,54 +393,54 @@ {{#liquid-if this.membersEmailOpen}}
-
- - -
- - {{region.flag}} {{region.name}} - -
-
- - - - -
- - - - - - Find your Mailgun API keys here » - +
+ + +
+ + {{region.flag}} {{region.name}} + +
+ + + + +
+ + + + + + Find your Mailgun API keys here » + +
{{/liquid-if}} -
- {{/unless}} + {{/unless}} +
{{#if this.showDisconnectStripeConnectModal}} @@ -456,6 +455,9 @@ {{#if this.showPortalSettings}} {{/if}} @@ -464,4 +466,4 @@ @confirm={{action "leavePortalSettings"}} @close={{action "closeLeaveSettingsModal"}} @modifier="action wide" /> -{{/if}} +{{/if}} \ No newline at end of file diff --git a/ghost/admin/app/components/gh-members-lab-setting.js b/ghost/admin/app/components/gh-members-lab-setting.js index 4983a47979..95aba5c3c2 100644 --- a/ghost/admin/app/components/gh-members-lab-setting.js +++ b/ghost/admin/app/components/gh-members-lab-setting.js @@ -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); } }, diff --git a/ghost/admin/app/components/modal-portal-settings.hbs b/ghost/admin/app/components/modal-portal-settings.hbs index 076e2607fb..a0ab9970f2 100644 --- a/ghost/admin/app/components/modal-portal-settings.hbs +++ b/ghost/admin/app/components/modal-portal-settings.hbs @@ -90,6 +90,10 @@
+ {{else}} +
+ You need to to take payments +
{{/if}}
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}} -
Members (BETA)
-
-
-
-
-
-
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}}
Migration options
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 @@ +
+ +

+ Labs + {{svg-jar "arrow-right"}} + Members +

+
+ +
+
+ +
+ + {{#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"]');