✨ Added new FirstPromoter integration (#1825)
no issue Adds new FirstPromoter integration on the integrations page. FirstPromoter enables sites to launch their own members referral program, and integration allows Site admins to directly add their FirstPromoter tracking ID in the settings to enable FirstPromoter script on their site.
This commit is contained in:
parent
a1da989d21
commit
06d47f53e5
|
@ -0,0 +1,70 @@
|
|||
/* eslint-disable ghost/ember/alias-model-in-controller */
|
||||
import Controller from '@ember/controller';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
notifications: service(),
|
||||
settings: service(),
|
||||
|
||||
leaveSettingsTransition: null,
|
||||
|
||||
actions: {
|
||||
update(value) {
|
||||
this.settings.set('firstpromoter', value);
|
||||
},
|
||||
|
||||
save() {
|
||||
this.save.perform();
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.leaveSettingsTransition;
|
||||
|
||||
if (!transition && this.showLeaveSettingsModal) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.save.isRunning) {
|
||||
return this.save.last.then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.leaveSettingsTransition;
|
||||
let settings = this.settings;
|
||||
|
||||
if (!transition) {
|
||||
this.notifications.showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on settings model
|
||||
settings.rollbackAttributes();
|
||||
|
||||
return transition.retry();
|
||||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
try {
|
||||
yield this.settings.validate();
|
||||
return yield this.settings.save();
|
||||
} catch (error) {
|
||||
this.notifications.showAPIError(error);
|
||||
throw error;
|
||||
}
|
||||
}).drop()
|
||||
});
|
|
@ -26,6 +26,8 @@ export default Model.extend(ValidationEngine, {
|
|||
slack: attr('slack-settings'),
|
||||
amp: attr('boolean'),
|
||||
ampGtagId: attr('string'),
|
||||
firstpromoter: attr('boolean'),
|
||||
firstpromoterId: attr('string'),
|
||||
unsplash: attr('unsplash-settings', {
|
||||
defaultValue() {
|
||||
return {isActive: true};
|
||||
|
|
|
@ -59,6 +59,7 @@ Router.map(function () {
|
|||
});
|
||||
this.route('settings.integrations.slack', {path: '/settings/integrations/slack'});
|
||||
this.route('settings.integrations.amp', {path: '/settings/integrations/amp'});
|
||||
this.route('settings.integrations.firstpromoter', {path: '/settings/integrations/firstpromoter'});
|
||||
this.route('settings.integrations.unsplash', {path: '/settings/integrations/unsplash'});
|
||||
this.route('settings.integrations.zapier', {path: '/settings/integrations/zapier'});
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
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(),
|
||||
|
||||
beforeModel() {
|
||||
this._super(...arguments);
|
||||
return this.get('session.user')
|
||||
.then(this.transitionAuthor())
|
||||
.then(this.transitionEditor())
|
||||
.then(this.settings.reload());
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.controller.send('save');
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.controller;
|
||||
let modelIsDirty = this.settings.get('hasDirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
return {
|
||||
titleToken: 'FirstPromoter'
|
||||
};
|
||||
}
|
||||
|
||||
});
|
|
@ -27,7 +27,7 @@ export default Service.extend(_ProxyMixin, ValidationEngine, {
|
|||
_loadSettings() {
|
||||
if (!this._loadingPromise) {
|
||||
this._loadingPromise = this.store
|
||||
.queryRecord('setting', {group: 'site,theme,private,members,portal,newsletter,email,amp,labs,slack,unsplash,views'})
|
||||
.queryRecord('setting', {group: 'site,theme,private,members,portal,newsletter,email,amp,labs,slack,unsplash,views,firstpromoter'})
|
||||
.then((settings) => {
|
||||
this._loadingPromise = null;
|
||||
return settings;
|
||||
|
|
|
@ -899,6 +899,11 @@ p.theme-validation-details {
|
|||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.gh-setting-firstpromoter-liquid {
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.gh-setting-unsplash-checkbox {
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -153,7 +153,29 @@
|
|||
</article>
|
||||
</LinkTo>
|
||||
</div>
|
||||
|
||||
<div class="apps-grid-cell" data-test-app="firstpromoter">
|
||||
<LinkTo @route="settings.integrations.firstpromoter" data-test-link="firstpromoter">
|
||||
<article class="apps-card-app">
|
||||
<div class="apps-card-left">
|
||||
<figure class="apps-card-app-icon id-unsplash" style="background-image:url(assets/icons/firstpromoter.png); background-size:30px;"></figure>
|
||||
<div class="apps-card-meta">
|
||||
<h3 class="apps-card-app-title">FirstPromoter</h3>
|
||||
<p class="apps-card-app-desc">Launch your member referral program</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-card-right">
|
||||
<div class="apps-configured">
|
||||
{{#if this.settings.firstpromoter}}
|
||||
<span class="gh-badge" data-test-app-status>Active</span>
|
||||
{{else}}
|
||||
<span data-test-app-status>Configure</span>
|
||||
{{/if}}
|
||||
{{svg-jar "arrow-right"}}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</LinkTo>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<section class="gh-canvas">
|
||||
<GhCanvasHeader class="gh-canvas-header">
|
||||
<h2 class="gh-canvas-title" data-test-screen-title>
|
||||
<LinkTo @route="settings.integrations" data-test-link="integrations-back">Integrations</LinkTo>
|
||||
<span>{{svg-jar "arrow-right"}}</span>
|
||||
FirstPromoter
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
{{#if this.showLeaveSettingsModal}}
|
||||
<GhFullscreenModal @modal="leave-settings"
|
||||
@confirm={{action "leaveSettings"}}
|
||||
@close={{action "toggleLeaveSettingsModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container bt b--lightgrey-d1 pt5">
|
||||
<section class="app-grid">
|
||||
<div class="app-cell">
|
||||
<div class="bg-white mr3 display flex items-center justify-center br-pill shadow-1 pa3 dark-no-shadow">
|
||||
<img class="app-icon" src="assets/img/firstpromoter.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-cell">
|
||||
<h3>FirstPromoter</h3>
|
||||
<p>Launch your own member referral program</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="gh-setting-header gh-first-header">FirstPromoter configuration</div>
|
||||
<div class="flex flex-column br4 shadow-1 bg-grouped-table pa5 mt2">
|
||||
<div class="gh-setting-first">
|
||||
<div class="gh-setting-content">
|
||||
<div class="gh-setting-title">Enable FirstPromoter</div>
|
||||
<div class="gh-setting-desc">Enable <a href="https://firstpromoter.com/?fpr=ghost&fp_sid=admin" target="_blank">FirstPromoter</a> for tracking referrals</div>
|
||||
</div>
|
||||
<div class="gh-setting-action">
|
||||
<div class="for-checkbox">
|
||||
<label for="firstpromoter" class="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={{this.settings.firstpromoter}}
|
||||
id="firstpromoter"
|
||||
name="firstpromoter"
|
||||
onclick={{action "update" value="target.checked"}}
|
||||
data-test-firstpromoter-checkbox
|
||||
>
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#liquid-if this.settings.firstpromoter class="nl5 nr5"}}
|
||||
<div class="gh-setting-last gh-setting-firstpromoter-liquid">
|
||||
<div class="gh-setting-content gh-setting-content--no-action">
|
||||
<div class="gh-setting-title">FirstPromoter Tracking ID</div>
|
||||
<div class="gh-setting-desc"> Affiliate and referral tracking, find your ID here </div>
|
||||
<div class="gh-setting-content-extended">
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="firstpromoterId">
|
||||
<GhTextInput
|
||||
@placeholder="XXXXXXXX"
|
||||
@name="firstpromoter_id"
|
||||
@value={{this.settings.firstpromoterId}}
|
||||
@keyEvents={{hash
|
||||
Enter=(action "save")
|
||||
}}
|
||||
/>
|
||||
<GhErrorMessage @errors={{this.settings.errors}} @property="firstpromoterId"/>
|
||||
</GhFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/liquid-if}}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
Reference in New Issue