mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 11:54:33 +03:00
✨ Added referrals invite notification (#16187)
no issue - Ghost users that make >= $100 MRR will see a dismissible notification that invites them to the Ghost Referral program - Only applies to Admin and Owner users and when Stripe is setup and connected in live mode - By saving a `referralInviteDismissed` property to the users' `accessibility` JSON object we can determine if the notification has been dismissed and won't show it again - Added new `gh-referral-invite` component
This commit is contained in:
parent
63d216c321
commit
488d9bb135
@ -1,11 +1,13 @@
|
|||||||
<div class="gh-nav-bottom">
|
<div class="gh-nav-bottom">
|
||||||
{{#if this.hasThemeErrors}}
|
{{#if this.hasThemeErrors}}
|
||||||
<button type="button" class="gh-theme-error-toast" {{on "click" (fn this.openThemeErrors null)}}>
|
<button type="button" class="gh-footer-toast gh-theme-error-toast" {{on "click" (fn this.openThemeErrors null)}}>
|
||||||
<span class="gh-notification-title">Your theme contains errors</span>
|
<span class="gh-footer-toast-title gh-notification-title">Your theme contains errors</span>
|
||||||
<span class="gh-theme-error-p">Some functionality on your site may be limited →</span>
|
<span class="gh-footer-toast-p">Some functionality on your site may be limited →</span>
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<GhReferralInvite @hasThemeErrors={{this.hasThemeErrors}} />
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="pe-all">
|
<div class="pe-all">
|
||||||
<GhBasicDropdown @horizontalPosition="left" @verticalPosition="above" @calculatePosition={{this.userDropdownPosition}} as |dropdown|>
|
<GhBasicDropdown @horizontalPosition="left" @verticalPosition="above" @calculatePosition={{this.userDropdownPosition}} as |dropdown|>
|
||||||
|
10
ghost/admin/app/components/gh-referral-invite.hbs
Normal file
10
ghost/admin/app/components/gh-referral-invite.hbs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{{#if this.showReferralInvite}}
|
||||||
|
<div class="gh-referral-toast">
|
||||||
|
<button class="gh-referral-toast-close" type="button" {{on "click" this.dismissReferralInvite}}>×</button>
|
||||||
|
<a href="https://referrals.ghost.org/?ref=admin" target="_blank" rel="noopener noreferrer">
|
||||||
|
<strong>You qualify for our invite-only referral program.</strong>
|
||||||
|
<p class="gh-footer-toast-p">Refer people to Ghost and earn a <strong>30% share</strong> of the subscription revenue, every single month.</p>
|
||||||
|
<div class="gh-btn gh-btn-black gh-referral-toast-button" type="button"><span>Find out more →</span></div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
68
ghost/admin/app/components/gh-referral-invite.js
Normal file
68
ghost/admin/app/components/gh-referral-invite.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import envConfig from 'ghost-admin/config/environment';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
import {task} from 'ember-concurrency';
|
||||||
|
|
||||||
|
export default class GhReferralInvite extends Component {
|
||||||
|
@service session;
|
||||||
|
@service dashboardStats;
|
||||||
|
@service feature;
|
||||||
|
@service membersUtils;
|
||||||
|
@service settings;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.loadCurrentMRR.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAdminOrOwner() {
|
||||||
|
return this.session.user.isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReferralNotificationNotDismissed() {
|
||||||
|
return !this.feature.accessibility.referralInviteDismissed;
|
||||||
|
}
|
||||||
|
|
||||||
|
get stripeLiveModeEnabled() {
|
||||||
|
// allow testing mode when not in a production environment
|
||||||
|
const isDevModeStripeEnabled = envConfig.environment !== 'production' && this.membersUtils.isStripeEnabled;
|
||||||
|
const isLiveEnabled = this.settings.stripeConnectLivemode;
|
||||||
|
return isDevModeStripeEnabled || isLiveEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasReachedMRR() {
|
||||||
|
return this.dashboardStats.currentMRR / 100 >= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showReferralInvite() {
|
||||||
|
// Conditions to see the referral invite
|
||||||
|
// 1. Needs to be Owner or Admin
|
||||||
|
// 2. Stripe is setup and enabled in live mode
|
||||||
|
// 3. MRR is > $100
|
||||||
|
// 4. Notification has not yet been dismissed by the user
|
||||||
|
return !this.args.hasThemeErrors && this.isAdminOrOwner && this.isReferralNotificationNotDismissed && this.stripeLiveModeEnabled && this.hasReachedMRR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*loadCurrentMRR() {
|
||||||
|
if (this.isAdminOrOwnern) {
|
||||||
|
try {
|
||||||
|
yield this.dashboardStats.loadMrrStats();
|
||||||
|
} catch (error) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
dismissReferralInvite(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (!this.feature.referralInviteDismissed) {
|
||||||
|
this.feature.referralInviteDismissed = moment().tz(this.settings.timezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,9 @@ export default class FeatureService extends Service {
|
|||||||
@feature('nightShift', {user: true, onChange: '_setAdminTheme'})
|
@feature('nightShift', {user: true, onChange: '_setAdminTheme'})
|
||||||
nightShift;
|
nightShift;
|
||||||
|
|
||||||
|
// user-specific referral invitation
|
||||||
|
@feature('referralInviteDismissed', {user: true}) referralInviteDismissed;
|
||||||
|
|
||||||
// labs flags
|
// labs flags
|
||||||
@feature('urlCache') urlCache;
|
@feature('urlCache') urlCache;
|
||||||
@feature('beforeAfterCard') beforeAfterCard;
|
@feature('beforeAfterCard') beforeAfterCard;
|
||||||
|
@ -810,6 +810,10 @@ input:focus,
|
|||||||
background: color-mod(var(--dark-main-bg-color) l(+2%));
|
background: color-mod(var(--dark-main-bg-color) l(+2%));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast-close:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.nightshift-toggle {
|
.nightshift-toggle {
|
||||||
background: var(--lightgrey);
|
background: var(--lightgrey);
|
||||||
}
|
}
|
||||||
@ -1486,4 +1490,4 @@ kbd {
|
|||||||
|
|
||||||
.gh-mention-your-post-link {
|
.gh-mention-your-post-link {
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
}
|
}
|
||||||
|
@ -2149,8 +2149,8 @@ section.gh-ds h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Theme error toast and modal */
|
/* Footer toast in navbar */
|
||||||
.gh-theme-error-toast {
|
.gh-footer-toast {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
@ -2159,10 +2159,87 @@ section.gh-ds h2 {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: var(--red);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-theme-error-toast .gh-notification-title {
|
.gh-footer-toast-title.gh-notification-title {
|
||||||
margin: 0 0 6px 0;
|
margin: 0 0 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Theme error toast and modal */
|
||||||
|
.gh-theme-error-toast {
|
||||||
|
background: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ghost Referrals invite toast */
|
||||||
|
.gh-referral-toast {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
padding: 25px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
color: var(--black);
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: rgb(75 225 226 / 28%) -7px -6px 42px 8px, rgb(202 103 255 / 32%) 7px 6px 42px 8px;
|
||||||
|
transition: all 0.6s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast a {
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast:hover {
|
||||||
|
transform: translateY(-2px) scale(1.01);
|
||||||
|
box-shadow: rgb(75 225 226 / 38%) -7px -4px 42px 10px, rgb(202 103 255 / 42%) 7px 8px 42px 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
color: #ddd;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast-close:hover {
|
||||||
|
color: #15171A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast a > strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast a > p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1.35em;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast a > p strong {
|
||||||
|
color: #ff247d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-referral-toast-button span {
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-align-last: center;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user