feat(client): update donate page UI (#52524)

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com>
This commit is contained in:
Ahmad Abdolsaheb
2023-12-13 12:18:35 +03:00
committed by GitHub
parent 575aa172ad
commit b3bb9e7827
8 changed files with 301 additions and 116 deletions
+23 -3
View File
@@ -472,7 +472,7 @@
"thank-you": "Thank you for being a supporter.",
"success-card-update": "Your card has been updated successfully.",
"additional": "You can make an additional one-time donation of any amount using this link: <0>{{url}}</0>",
"help-more": "Help us do more",
"help-more": "Help Our Charity Do More",
"error": "Something went wrong with your donation.",
"error-card-update": "Something went wrong with updating your card.",
"error-2": "Something is not right. Please contact donors@freecodecamp.org",
@@ -528,7 +528,7 @@
"card-number": "Your Card Number:",
"expiration": "Expiration Date:",
"secure-donation": "Secure donation",
"faq": "Frequently asked questions",
"faq": "Frequently asked questions:",
"only-you": "Only you can see this message. Congratulations on earning this certification. It's no easy task. Running freeCodeCamp isn't easy either. Nor is it cheap. Help us help you and many other people around the world. Make a tax-deductible supporting donation to our charity today.",
"get-help": "How can I get help with my donations?",
"how-transparent": "How transparent is freeCodeCamp.org?",
@@ -565,7 +565,27 @@
"anything-else": "Is there anything else I can learn about donating to freeCodeCamp.org?",
"other-support": "If there is some other way you'd like to support our charity and its mission that isn't listed here, or if you have any questions at all, please email Quincy at quincy@freecodecamp.org.",
"bear-progress-alt": "Illustration of an adorable teddy bear with a pleading expression holding an empty money jar.",
"bear-completion-alt": "Illustration of an adorable teddy bear holding a large trophy."
"bear-completion-alt": "Illustration of an adorable teddy bear holding a large trophy.",
"crucial-contribution": "Your contribution will be crucial in creating resources that empower millions of people to learn new skills and support their families.",
"if-another-monthly": "If you want to make another monthly donation, please proceed with selecting your monthly donation amount.",
"support-benefits-title": "Benefits from becoming a Supporter:",
"support-benefits-1": "No more donation prompt popups",
"support-benefits-2": "You'll get a Supporter badge",
"support-benefits-3": "Your profile image will get a golden halo around it",
"support-benefits-4": "You'll gain access to special Supporter Discord channels",
"support-benefits-5": "And more benefits to come in 2024",
"current-initiatives-title": "Current Initiatives:",
"current-initiatives-1": "Creating new JavaScript and Python curricula",
"current-initiatives-2": "Creating English and math curricula",
"current-initiatives-3": "Translating our curriculum and tutorials into 32 languages",
"current-initiatives-4": "Creating a free accredited computer science bachelor's degree",
"community-achivements-title": "Our Community Achievements This Year:",
"community-achivements-1": "Published <0>114</0> full-length courses on YouTube.",
"community-achivements-2": "Published <0>1,045</0> text-based coding tutorials and <0>20</0> free books through freeCodeCamp Press.",
"community-achivements-3": "Merged <0>2,753</0> code contributions into our open source repositories on GitHub",
"community-achivements-4": "Translated <0>2,106,203</0> words to make our curriculum and tutorials more accessible to speakers of many world languages",
"as-you-see": "As you can see, we're getting things done. So you can rest assured that we'll put your donations to good use.",
"get-benefits": "Get the benefits and the knowledge that youre helping our charity change education for the better. Become a Supporter today."
},
"report": {
"sign-in": "You need to be signed in to report a user",
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { useTranslation, Trans } from 'react-i18next';
import Caret from '../../assets/icons/caret';
import { Spacer } from '../helpers';
const POBOX = (
<>
@@ -14,10 +15,12 @@ const POBOX = (
</>
);
export const DonationText = (): JSX.Element => {
export const CtaText = (): JSX.Element => {
const { t } = useTranslation();
return (
<>
<h1 data-playwright-test-label='main-head'>{t('donate.help-more')}</h1>
<Spacer size='medium' />
<p data-playwright-test-label='donate-text-1'>{t('donate.efficiency')}</p>
<p data-playwright-test-label='donate-text-2'>
{t('donate.why-donate-1')}
@@ -29,17 +32,37 @@ export const DonationText = (): JSX.Element => {
);
};
export const DonationOptionsAlertText = (): JSX.Element => {
export const ThankYouMessage = ({
askForDonation
}: {
askForDonation: boolean;
}): JSX.Element => {
const { t } = useTranslation();
return (
<p data-cy='donate.bigger-donation'>
<Trans>donate.bigger-donation</Trans>{' '}
<Trans i18nKey='donate.other-ways'>
<a data-cy='donate-link' href={t('links:donate.other-ways-url')}>
placeholder
</a>
</Trans>
</p>
<>
<h1 data-playwright-test-label='main-head' data-cy='donate.thank-you'>
{t('donate.thank-you')}
</h1>
{askForDonation && (
<>
<Spacer size='medium' />
<p data-cy='donate.crucial-contribution'>
{t('donate.crucial-contribution')}
</p>
<p data-cy='donate.bigger-donation'>
{t('donate.bigger-donation')}{' '}
<Trans i18nKey='donate.other-ways'>
<a data-cy='donate-link' href={t('links:donate.other-ways-url')}>
placeholder
</a>
</Trans>
</p>
<p data-cy='donate.make-another-monthly'>
{t('donate.if-another-monthly')}
</p>
</>
)}
</>
);
};
@@ -167,6 +190,87 @@ export const DonationFaqText = (): JSX.Element => {
];
return (
<>{faqItems.map((item, iterator) => FaqItem(item.Q, item.A, iterator))}</>
<>
<h2 data-playwright-test-label='faq-head'>{t('donate.faq')}</h2>
<Spacer size='small' />
{faqItems.map((item, iterator) => FaqItem(item.Q, item.A, iterator))}
</>
);
};
export const SupportBenefitsText = (): JSX.Element => {
const { t } = useTranslation();
return (
<>
<h2>{t('donate.support-benefits-title')}</h2>
<ul>
<li>{t('donate.support-benefits-1')}</li>
<li>{t('donate.support-benefits-2')}</li>
<li>{t('donate.support-benefits-3')}</li>
<li>{t('donate.support-benefits-4')}</li>
<li>{t('donate.support-benefits-5')}</li>
</ul>
</>
);
};
export const CurrentInitiativesText = (): JSX.Element => {
const { t } = useTranslation();
return (
<>
<h2>{t('donate.current-initiatives-title')}</h2>
<ul>
<li>{t('donate.current-initiatives-1')}</li>
<li>{t('donate.current-initiatives-2')}</li>
<li>{t('donate.current-initiatives-3')}</li>
<li>{t('donate.current-initiatives-4')}</li>
</ul>
</>
);
};
export const CommunityAchievementsText = (): JSX.Element => {
const { t } = useTranslation();
return (
<>
<h2>{t('donate.community-achivements-title')}</h2>
<ul>
<li>
<Trans i18nKey='donate.community-achivements-1'>
<b>placeholder</b>
</Trans>
</li>
<li>
<Trans i18nKey='donate.community-achivements-2'>
<b>placeholder</b>
</Trans>
</li>
<li>
<Trans i18nKey='donate.community-achivements-3'>
<b>placeholder</b>
</Trans>
</li>
<li>
<Trans i18nKey='donate.community-achivements-4'>
<b>placeholder</b>
</Trans>
</li>
</ul>
</>
);
};
export const GetSupporterBenefitsText = ({
isDonating
}: {
isDonating: boolean;
}): JSX.Element => {
const { t } = useTranslation();
return (
<>
<Spacer size='large' />
<p>{t('donate.as-you-see')}</p>
{!isDonating ? <p>{t('donate.get-benefits')}</p> : null}
</>
);
};
+70 -35
View File
@@ -88,19 +88,6 @@
margin-bottom: 8px;
}
.donate-page-wrapper .paypal-buttons-container {
margin-bottom: 0;
}
@media (min-width: 1200px) {
/*
This style allows multi columns in the same row.
It will become a functionality once the Grid system is updated.
*/
.donate-page-wrapper > div > div {
float: left;
}
}
.donate-input-element {
padding-top: 8px;
}
@@ -243,12 +230,6 @@ li.disabled > a {
color: var(--gray-15) !important;
}
.donate-page-wrapper h2,
.donate-page-wrapper h3,
[name='payment-method'] {
font-family: var(--font-family-sans-serif);
}
@media (max-width: 400px) {
.donate-tabs > .nav-pills > li > a {
font-size: 0.8rem;
@@ -403,10 +384,6 @@ li.disabled > a {
margin-top: 0;
height: 39px;
}
.donate-page-wrapper .form-payment-methods {
height: 22px;
width: 220px;
}
.donation-icon-container {
margin: 40px;
}
@@ -417,13 +394,6 @@ li.disabled > a {
}
}
@media screen and (min-width: 1200px) {
.donate-page-wrapper .form-payment-methods {
height: 25px;
width: 250px;
}
}
.paypal-button-onetime {
border: 1px solid #ffc439;
padding: 2px;
@@ -584,11 +554,6 @@ a.patreon-button:hover {
font-weight: bold;
}
.donate-page-wrapper [role='alert'] a:hover {
color: #327290;
background-color: #acdef3;
}
.separator {
display: flex;
align-items: center;
@@ -670,3 +635,73 @@ a.patreon-button:hover {
background-color: var(--blue70);
border-color: var(--blue70);
}
/* donation page */
.donate-page-container h1,
.donate-page-container h2,
.donate-page-container h3 {
font-family: var(--font-family-sans-serif);
}
.donate-page-container h1 {
font-size: 2.5rem;
}
.donate-page-container .donation-section > div {
margin-top: 40px;
margin-bottom: 40px;
}
.donate-page-container .donation-section {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
min-height: 60vh;
}
.donate-page-container .paypal-buttons-container {
margin-bottom: 0;
}
.dark-palette .gradient-container {
background: linear-gradient(
-10deg,
rgb(7 40 94) 35%,
rgba(237, 202, 216, 0) 75%,
rgb(127 30 52) 100%
),
radial-gradient(circle, rgb(112 0 48) 0%, rgb(0 63 136) 100%);
}
.light-palette .gradient-container {
background: linear-gradient(
-10deg,
rgb(223 243 255) 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%
);
}
@media screen and (min-width: 991px) {
.donate-page-container .form-payment-methods {
height: 22px;
width: 220px;
}
}
@media screen and (min-width: 1200px) {
.donate-page-container .form-payment-methods {
height: 25px;
width: 250px;
}
.donate-page-container .donation-section {
flex-direction: row;
}
}
+81 -48
View File
@@ -1,4 +1,4 @@
import { Alert, Container, Col, Row } from '@freecodecamp/ui';
import { Container, Col, Row } from '@freecodecamp/ui';
import type { TFunction } from 'i18next';
import React, { useEffect } from 'react';
import Helmet from 'react-helmet';
@@ -10,15 +10,23 @@ import { createSelector } from 'reselect';
import MultiTierDonationForm from '../components/Donation/multi-tier-donation-form';
import {
DonationText,
DonationOptionsAlertText,
DonationFaqText
CtaText,
ThankYouMessage,
DonationFaqText,
SupportBenefitsText,
CurrentInitiativesText,
CommunityAchievementsText,
GetSupporterBenefitsText
} from '../components/Donation/donation-text-components';
import { Spacer, Loader } from '../components/helpers';
import CampersImage from '../components/landing/components/campers-image';
import { executeGA } from '../redux/actions';
import { signInLoadingSelector, userSelector } from '../redux/selectors';
import {
signInLoadingSelector,
userSelector,
donationFormStateSelector
} from '../redux/selectors';
import { PaymentContext } from '../../../shared/config/donation-settings';
import { DonateFormState } from '../redux/types';
export interface ExecuteGaArg {
event: string;
@@ -31,14 +39,21 @@ interface DonatePageProps {
isDonating?: boolean;
showLoading: boolean;
t: TFunction;
donationFormState: DonateFormState;
}
const mapStateToProps = createSelector(
userSelector,
signInLoadingSelector,
({ isDonating }: { isDonating: boolean }, showLoading: boolean) => ({
donationFormStateSelector,
(
{ isDonating }: { isDonating: boolean },
showLoading: boolean,
donationFormState: DonateFormState
) => ({
isDonating,
showLoading
showLoading,
donationFormState
})
);
@@ -50,7 +65,8 @@ function DonatePage({
executeGA = () => {},
isDonating = false,
showLoading,
t
t,
donationFormState
}: DonatePageProps) {
useEffect(() => {
executeGA({
@@ -65,48 +81,65 @@ function DonatePage({
) : (
<>
<Helmet title={`${t('donate.title')} | freeCodeCamp.org`} />
<Container className='donate-page-wrapper'>
<Spacer size='medium' />
<Container fluid={true} className='gradient-container'>
<Container className='donate-page-container'>
<Row className={'donation-section'}>
<Col lg={6} lgOffset={0} md={8} mdOffset={1} sm={12}>
{isDonating ? (
<ThankYouMessage askForDonation={!donationFormState.success} />
) : (
<CtaText />
)}
</Col>
<Col lg={6} lgOffset={0} md={12}>
<MultiTierDonationForm
paymentContext={PaymentContext.DonatePage}
/>
</Col>
</Row>
</Container>
</Container>
<Container className='donate-page-container'>
<Row>
<Col lg={6} lgOffset={0} md={8} mdOffset={2} sm={10} smOffset={1}>
{isDonating ? (
<h2
data-playwright-test-label='main-head'
className='text-center'
>
{t('donate.thank-you')}
</h2>
) : (
<h2
data-playwright-test-label='main-head'
className='text-center'
>
{t('donate.help-more')}
</h2>
)}
<Spacer size='medium' />
{isDonating ? (
<Alert variant='info' data-cy='donate-alert'>
<p data-cy='donate.thank-you'>{t('donate.thank-you')}</p>
<br />
<DonationOptionsAlertText />
</Alert>
) : null}
<DonationText />
<MultiTierDonationForm paymentContext={PaymentContext.DonatePage} />
<Spacer size='exLarge' />
<hr />
<h2 data-playwright-test-label='faq-head' className={'text-center'}>
{t('donate.faq')}
</h2>
<Spacer size='medium' />
<DonationFaqText />
</Col>
<Col lg={6}>
<CampersImage pageName='donate' />
<Col lg={6} lgOffset={0} md={8} mdOffset={2} sm={10}>
<Spacer size='large' />
<SupportBenefitsText />
</Col>
</Row>
<Spacer size='medium' />
<Row>
<Col lg={6} lgOffset={0} md={8} mdOffset={2} sm={10}>
<Spacer size='large' />
<CurrentInitiativesText />
</Col>
</Row>
<Row>
<Col lg={6} lgOffset={0} md={8} mdOffset={2} sm={10}>
<Spacer size='large' />
<CommunityAchievementsText />
</Col>
</Row>
<Row>
<Col lg={6} lgOffset={0} md={8} mdOffset={2} sm={10}>
<GetSupporterBenefitsText isDonating={Boolean(isDonating)} />
</Col>
</Row>
</Container>
<Container fluid={true}>
<Row>
<Col sm={12}>
<Spacer size='large' />
<hr />
<Spacer size='large' />
</Col>
</Row>
</Container>
<Container className='donate-page-container'>
<Row>
<Col lg={10} lgOffset={0} md={8} mdOffset={2} sm={10}>
<DonationFaqText />
</Col>
</Row>
<Spacer size='large' />
</Container>
</>
);
@@ -4,15 +4,16 @@ describe('Donate page', () => {
cy.login();
});
it('Donor alert should be visible for donor', () => {
it('Donor CTA should be visible for donor', () => {
cy.visit('/donate');
cy.get('[data-cy="donate-alert"]').should('be.visible');
cy.get('[data-cy="donate.thank-you"]').should(
'have.text',
'Thank you for being a supporter.'
);
cy.get('[data-cy="donate.crucial-contribution"]').should(
'have.text',
'Your contribution will be crucial in creating resources that empower millions of people to learn new skills and support their families.'
);
cy.get('[data-cy="donate.bigger-donation"]').should(
'have.text',
"Want to make a bigger one-time donation, mail us a check, or give in other ways? Here are many other ways you can support our charity's mission."
@@ -22,5 +23,9 @@ describe('Donate page', () => {
'href',
'https://www.freecodecamp.org/news/how-to-donate-to-free-code-camp'
);
cy.get('[data-cy="donate.make-another-monthly"]').should(
'have.text',
'If you want to make another monthly donation, please proceed with selecting your monthly donation amount.'
);
});
});
-13
View File
@@ -149,19 +149,6 @@ test.describe('Donate Page', () => {
await expect(donateText3).toHaveText(translations.donate['why-donate-2']);
});
test('should render the campers image in desktop browsers', async ({
isMobile
}) => {
const figure = page.getByTestId(pageElements.campersImage);
if (isMobile) {
await expect(figure).not.toBeVisible();
return;
}
await expect(figure).toBeVisible();
});
test('should display the faq heading', async () => {
const faqHead = page.getByTestId(pageElements.faqHeading);
await expect(faqHead).toHaveText(translations.donate.faq);
+2 -1
View File
@@ -36,7 +36,8 @@ const MediumClasses = {
4: 'min-[992px]:w-1/3',
6: 'min-[992px]:w-1/2',
8: 'min-[992px]:w-2/3',
10: 'min-[992px]:w-5/6'
10: 'min-[992px]:w-5/6',
12: 'min-[992px]:w-full'
};
const MediumOffsetClasses = {
+1 -1
View File
@@ -3,7 +3,7 @@ export interface ColProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
xs?: 6 | 8 | 10 | 12;
sm?: 2 | 4 | 5 | 6 | 8 | 10 | 12;
md?: 4 | 6 | 8 | 10;
md?: 4 | 6 | 8 | 10 | 12;
lg?: 6 | 8 | 10;
xsOffset?: 1 | 2 | 3;
smOffset?: 1 | 2 | 3 | 4;