feat: add seasonal learn alert (#44335)

* feat:  add seasonal learn alert

* Update client/src/components/Intro/index.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* Update client/src/pages/learn.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2021-11-30 20:35:35 +03:00
committed by GitHub
parent 5b61c8a56f
commit 513f27e408
6 changed files with 84 additions and 4 deletions

View File

@ -32,6 +32,7 @@
"profile": "Profile", "profile": "Profile",
"news": "News", "news": "News",
"donate": "Donate", "donate": "Donate",
"support-our-nonprofit": "Support our non-profit",
"update-settings": "Update my account settings", "update-settings": "Update my account settings",
"sign-me-out": "Sign me out of freeCodeCamp", "sign-me-out": "Sign me out of freeCodeCamp",
"flag-user": "Flag This User's Account for Abuse", "flag-user": "Flag This User's Account for Abuse",
@ -290,6 +291,8 @@
}, },
"help-translate": "We are still translating the following certifications.", "help-translate": "We are still translating the following certifications.",
"help-translate-link": "Help us translate.", "help-translate-link": "Help us translate.",
"season-greetings": "Season's Greetings to you and your family.",
"if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.",
"project-preview-title": "Here's a preview of what you will build" "project-preview-title": "Here's a preview of what you will build"
}, },
"donate": { "donate": {

View File

@ -1,5 +1,7 @@
import { Alert } from '@freecodecamp/react-bootstrap';
import React from 'react'; import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { emailToABVariant } from '../../utils/A-B-tester';
import { randomQuote } from '../../utils/get-words'; import { randomQuote } from '../../utils/get-words';
import Login from '../Header/components/Login'; import Login from '../Header/components/Login';
import { Link, Spacer, Loader, FullWidthRow } from '../helpers'; import { Link, Spacer, Loader, FullWidthRow } from '../helpers';
@ -16,6 +18,8 @@ interface IntroProps {
pending?: boolean; pending?: boolean;
slug?: string; slug?: string;
username?: string; username?: string;
email?: string;
onAlertClick?: () => void;
} }
const Intro = ({ const Intro = ({
@ -24,9 +28,16 @@ const Intro = ({
pending, pending,
complete, complete,
completedChallengeCount, completedChallengeCount,
slug slug,
email,
onAlertClick
}: IntroProps): JSX.Element => { }: IntroProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const buttonVariation = (email: string | undefined): string => {
if (!email || emailToABVariant(email).isAVariant)
return t('buttons.donate');
return t('buttons.support-our-nonprofit');
};
if (pending && !complete) { if (pending && !complete) {
return ( return (
<> <>
@ -78,6 +89,24 @@ const Intro = ({
) : ( ) : (
'' ''
)} )}
<Alert bsStyle='info' className='annual-donation-alert'>
<p>
<b>{t('learn.season-greetings')}</b>
</p>
<p>{t('learn.if-getting-value')}</p>
<p className={'text-center'}>
<hr />
<Link
className='btn'
key='donate'
onClick={onAlertClick}
sameTab={false}
to='/donate'
>
{buttonVariation(email)}
</Link>
</p>
</Alert>
</> </>
); );
} else { } else {

View File

@ -8,6 +8,7 @@ interface LinkProps {
sameTab?: boolean; sameTab?: boolean;
state?: Record<string, unknown>; state?: Record<string, unknown>;
to: string; to: string;
onClick?: () => void;
} }
const Link = ({ const Link = ({

View File

@ -589,6 +589,32 @@ blockquote .small {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.alert .btn {
background-color: #d9edf7;
color: #31708f;
border-color: #31708f;
}
.alert .btn:hover,
.alert .btn:focus {
background-color: #31708f;
color: #d9edf7;
border-color: #31708f;
}
.annual-donation-alert {
background: linear-gradient(
-10deg,
rgba(217, 237, 247, 1) 35%,
rgba(237, 202, 216, 0) 75%,
rgb(255 215 224) 100%
),
radial-gradient(
circle,
rgba(255, 202, 225, 1) 0%,
rgba(218, 234, 252, 1) 100%
);
}
/* gatsby 404 */ /* gatsby 404 */
#search { #search {
background-color: var(--quaternary-background); background-color: var(--quaternary-background);

View File

@ -4,6 +4,7 @@ import React from 'react';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import Intro from '../components/Intro'; import Intro from '../components/Intro';
@ -13,7 +14,8 @@ import LearnLayout from '../components/layouts/learn';
import { import {
userFetchStateSelector, userFetchStateSelector,
isSignedInSelector, isSignedInSelector,
userSelector userSelector,
executeGA
} from '../redux'; } from '../redux';
interface FetchState { interface FetchState {
@ -23,6 +25,7 @@ interface FetchState {
} }
interface User { interface User {
email: string;
name: string; name: string;
username: string; username: string;
completedChallengeCount: number; completedChallengeCount: number;
@ -53,12 +56,18 @@ interface LearnPageProps {
fields: Slug; fields: Slug;
}; };
}; };
executeGA: (payload: Record<string, unknown>) => void;
} }
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators({ executeGA }, dispatch);
function LearnPage({ function LearnPage({
isSignedIn, isSignedIn,
user,
fetchState: { pending, complete }, fetchState: { pending, complete },
user: { name = '', completedChallengeCount = 0 }, user: { name = '', completedChallengeCount = 0 },
executeGA,
data: { data: {
challengeNode: { challengeNode: {
fields: { slug } fields: { slug }
@ -67,6 +76,16 @@ function LearnPage({
}: LearnPageProps) { }: LearnPageProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const onAlertClick = () => {
executeGA({
type: 'event',
data: {
category: 'Donation Related',
action: `learn donation alert click`
}
});
};
return ( return (
<LearnLayout> <LearnLayout>
<Helmet title={t('metaTags:title')} /> <Helmet title={t('metaTags:title')} />
@ -76,8 +95,10 @@ function LearnPage({
<Intro <Intro
complete={complete} complete={complete}
completedChallengeCount={completedChallengeCount} completedChallengeCount={completedChallengeCount}
email={user.email}
isSignedIn={isSignedIn} isSignedIn={isSignedIn}
name={name} name={name}
onAlertClick={onAlertClick}
pending={pending} pending={pending}
slug={slug} slug={slug}
/> />
@ -92,7 +113,7 @@ function LearnPage({
LearnPage.displayName = 'LearnPage'; LearnPage.displayName = 'LearnPage';
export default connect(mapStateToProps)(LearnPage); export default connect(mapStateToProps, mapDispatchToProps)(LearnPage);
export const query = graphql` export const query = graphql`
query FirstChallenge { query FirstChallenge {

View File

@ -94,7 +94,7 @@ const patreonDefaultPledgeAmount = 500;
const aBTestConfig = { const aBTestConfig = {
isTesting: true, isTesting: true,
type: 'DistributionTest' type: 'LearnAlertCTA'
}; };
module.exports = { module.exports = {