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:
Aileen Booker 2023-01-26 14:42:11 +00:00 committed by GitHub
parent 63d216c321
commit 488d9bb135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 9 deletions

View File

@ -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 &rarr;</span> <span class="gh-footer-toast-p">Some functionality on your site may be limited &rarr;</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|>

View 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}}>&#xd7;</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 &rarr;</span></div>
</a>
</div>
{{/if}}

View 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);
}
}
}

View File

@ -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;

View File

@ -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);
} }

View File

@ -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;
}