feat: update donation page copy (#67900)

This commit is contained in:
Kristofer Koishigawa
2026-06-23 01:05:56 +12:00
committed by GitHub
parent 23b11e1411
commit 6259a9cd4d
5 changed files with 239 additions and 83 deletions
+43 -27
View File
@@ -813,7 +813,7 @@
"error-2": "Something is not right. Please contact donors@freecodecamp.org",
"error-3": "Please try again or contact donors@freecodecamp.org",
"free-tech": "Your donations will support free technology education for people all over the world.",
"visit-supporters": "Visit supporters page to learn about your supporter benefits.",
"visit-supporters": "Visit the Supporters page to learn about your Supporter benefits.",
"gift-frequency": "Select gift frequency:",
"gift-amount": "Select gift amount:",
"confirm": "Confirm your donation:",
@@ -827,6 +827,7 @@
"your-donation": "Your ${{usd}} donation will provide {{hours}} hours of learning to people around the world.",
"your-donation-2": "Your ${{usd}} donation will provide {{hours}} hours of learning to people around the world each month.",
"your-donation-3": "Your ${{usd}} donation will provide {{hours}} hours of learning to people around the world each year.",
"donation-policy-disclaimer": "Donations to freeCodeCamp are voluntary charitable contributions and are non-refundable. Monthly donations continue until canceled, and you can cancel future donations at any time by contacting donors@freecodecamp.org.",
"become-supporter": "Become a Supporter",
"progress-modal-cta-1": "Donate now to help our charity build a free accredited Computer Science degree for all.",
"progress-modal-cta-2": "Donate now to sponsor 53 hours of server time for our charity's website.",
@@ -873,15 +874,19 @@
"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?",
"offer-refunds": "Does freeCodeCamp offer refunds?",
"donations-are-voluntary": "Donations to freeCodeCamp are voluntary charitable contributions and are non-refundable. Once a donation has been processed, we do not offer refunds.",
"cancel-future-donations": "If you started a monthly donation, you can cancel future donations at any time by forwarding a copy of your donation receipt to donors@freecodecamp.org and telling us you would like to cancel.",
"without-your-authorization": "If you believe a donation was made without your authorization, or if you see a duplicate charge, please email donors@freecodecamp.org so we can review it.",
"how-transparent": "How transparent is freeCodeCamp?",
"very-transparent": "Very. We have a Platinum transparency rating from GuideStar.org.",
"download-irs": "You can <0>download our IRS Determination Letter here</0>.",
"download-990": "You can <0>download our most recent 990 (annual tax report) here</0>.",
"how-efficient": "How efficient is freeCodeCamp?",
"fcc-budget": "freeCodeCamp's budget is much smaller than most comparable charities. We haven't brought in professional fundraisers. Instead, Quincy does everything himself.",
"fcc-budget": "freeCodeCamp's budget is much smaller than most comparable charities. We do not use professional fundraising firms. We keep our fundraising operations small so more of our budget can support our educational mission.",
"help-millions": "However, on a budget of only a few hundred thousand dollars per year, we have been able to help millions of people.",
"how-one-time": "How can I make a one-time donation?",
"one-time": "If you'd prefer to make one-time donations, you can support freeCodeCamp's mission whenever you have cash to spare. You can use <0>this link to donate whatever amount feels right through PayPal</0>.",
"one-time": "If you prefer to make one-time donations, you can support freeCodeCamp's mission whenever you have cash to spare. You can use <0>this link to donate any amount through PayPal</0>.",
"wire-transfer": "You can also send money to freeCodeCamp directly through a wire transfer. If you need our wire details, email Quincy at quincy@freecodecamp.org",
"does-crypto": "Does freeCodeCamp accept donations in Bitcoin or other cryptocurrencies?",
"yes-cryptocurrency": "Yes. Please email Quincy at quincy@freecodecamp.org and he can send you freeCodeCamp's wallet information. He can also provide you with a donation receipt if you need one for your taxes.",
@@ -891,45 +896,56 @@
"employers-vary": "This varies from employer to employer, and our charity is already listed in many of the big donation-matching databases.",
"some-volunteer": "Some people are able to volunteer for freeCodeCamp and their employer matches by donating a fixed amount per hour they volunteer. Other employers will match any donations the donors make up to a certain amount",
"help-matching-gift": "If you need help with this, please email Quincy directly: quincy@freecodecamp.org",
"how-endowment": "How can I set up an Endowment Gift to freeCodeCamp.org?",
"endowment": "This would be a huge help. Since this is a more manual process, Quincy can help walk you through it personally. Please email him directly at quincy@freecodecamp.org.",
"how-legacy": "How can I set up a Legacy gift to freeCodeCamp.org?",
"how-endowment": "How can I set up an endowment gift to freeCodeCamp?",
"endowment": "Endowment gifts can help sustain freeCodeCamp's mission over the long term. Since this is a more manual process, Quincy can help walk you through it personally. Please email him directly at quincy@freecodecamp.org.",
"how-legacy": "How can I set up a legacy gift to freeCodeCamp?",
"we-honored": "We would be honored to put such a gift to good use helping people around the world learn to code. Depending on where you live, this may also be tax exempt.",
"legacy-gift-message": "I give, devise, and bequeath [the sum of _____ USD (or other currency) OR _____ percent of the rest and residue of my estate] to freeCodeCamp.org (Free Code Camp, Inc. tax identification number 82-0779546), a charitable corporation organized under the laws of the State of Delaware, United States, currently located at 3905 Hedgcoxe Rd, PO Box 250352, Plano, Texas, 75025 United States, to be used for its general charitable purposes at its discretion.",
"thank-wikimedia": "We would like to thank the Wikimedia Foundation for providing this formal language for us to use.",
"legacy-gift-questions": "If you have any questions about this process, please email Quincy at quincy@freecodecamp.org.",
"how-stock": "How can I donate stock to freeCodeCamp.org?",
"how-stock": "How can I donate stock to freeCodeCamp?",
"welcome-stock": "We would welcome your stock donations. Please email Quincy directly and he can help you with this, and share our charity's brokerage account details: quincy@freecodecamp.org.",
"how-receipt": "Can I get a donation receipt so that I can deduct my donation from my taxes?",
"just-forward": "Absolutely. Just forward the receipt from your transaction to donors@freecodecamp.org, tell us you'd like a receipt and any special instructions you may have, and we'll reply with a receipt for you.",
"how-update": "I set up a monthly donation, but I need to update or stop the monthly recurrence. How can I do this?",
"take-care-of-this": "Just forward one of your monthly donation receipts to donors@freecodecamp.org and tell us what you'd like us to do. We'll take care of this for you and send you confirmation.",
"anything-else": "Is there anything else I can learn about donating to freeCodeCamp.org?",
"how-update": "I set up a monthly donation. How can I update, pause, or cancel it?",
"take-care-of-this": "Forward a copy of your donation receipt to donors@freecodecamp.org and tell us what you would like to change.",
"help-update-change-cancel": "We can help you update your monthly donation amount, change your payment method, pause your monthly donation, or cancel future monthly donations. Canceling a monthly donation stops future charges. Donations that have already been processed are non-refundable.",
"anything-else": "Is there anything else I can learn about donating to freeCodeCamp?",
"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.",
"how-will-donation-appear": "How will my donation appear on my bank or card statement?",
"as-freecodecamp-inc": "Your donation may appear on your bank or card statement as \"freeCodeCamp\", \"Free Code Camp\", \"Free Code Camp, Inc.\", or the name of one of our payment processors.",
"do-not-recognize": "If you do not recognize a charge, please email donors@freecodecamp.org before filing a dispute. We can help you identify the donation, update or cancel future monthly donations, and answer any questions.",
"are-benefits-a-product": "Are Supporter benefits a product I am buying?",
"benefits-are-thanks": "No. Donations to freeCodeCamp are charitable contributions. Supporter benefits, such as removed donation prompt popups, a Supporter badge, profile styling, and access to Supporter Discord channels, are our way of thanking donors.",
"benefits-not-sold-separately": "These benefits may change over time and are not sold separately. Your donation supports freeCodeCamp's charitable mission of helping people learn to code for free.",
"is-donation-tax-deductible": "Is my donation tax-deductible?",
"freecodecamp-is-a-charitable-organization": "Free Code Camp, Inc. is a tax-exempt 501(c)(3) charitable organization in the United States. Our tax identification number is 82-0779546.",
"donations-may-be-deductible": "Donations may be tax-deductible for donors in the United States, depending on your individual tax situation. Donors outside the United States should check the rules in their country.",
"annual-donation-receipt": "We cannot provide tax advice, but we can provide an annual donation receipt. If you need help with that, email us at donors@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.",
"flying-bear": "Illustration of an adorable teddy bear wearing a graduation cap and flying with a Supporter badge.",
"crucial-contribution": "Your contributions are crucial in creating resources that empower millions of people to learn new skills and support their families.",
"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 - <0>join our Discord</0> and use the <1>/supporter</1> command to get access",
"support-benefits-5": "And more benefits to come soon",
"support-benefits-title": "Benefits of becoming a Supporter:",
"support-benefits-1": "No more donation prompt popups while signed in",
"support-benefits-2": "A Supporter badge on your profile page",
"support-benefits-3": "A golden halo around your profile image",
"support-benefits-4": "Access to special Supporter Discord channels - <0>join our Discord</0> and use the <1>/supporter</1> command to get access",
"support-benefits-disclaimer": "Supporter benefits are our way of thanking donors and may change over time.",
"exclusive-features": "Here is the list of exclusive features for you as a Supporter:",
"current-initiatives-title": "Current Initiatives:",
"current-initiatives-title": "Your donations help us:",
"your-donation-helps-followings": "Your donation makes the following initiatives possible:",
"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-achievements-title": "Our Community Achievements This Year:",
"community-achievements-1": "Published <0>193</0> full-length courses on YouTube.",
"community-achievements-2": "Published <0>850</0> text-based coding tutorials and <0>5</0> free books through freeCodeCamp Press.",
"current-initiatives-1": "Build new JavaScript and Python curricula",
"current-initiatives-2": "Create free English and math curricula",
"current-initiatives-3": "Translate our curriculum and tutorials into 32 languages",
"current-initiatives-4": "Develop a free accredited computer science bachelor's degree",
"community-achievements-title": "Our recent community achievements:",
"community-achievements-1": "Published <0>193</0> full-length courses on YouTube",
"community-achievements-2": "Published <0>850</0> text-based coding tutorials and <0>5</0> free books through freeCodeCamp Press",
"community-achievements-3": "Merged <0>2,455</0> code contributions into our open source repositories on GitHub",
"community-achievements-4": "Translated <0>1.5</0> million 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.",
"careful-with-every-donation": "We are careful with every donation and put donor support directly toward our charitable mission.",
"get-benefits": "Get the benefits and the knowledge that you're helping our charity change education for the better. Become a Supporter today.",
"modal-benefits-title": "Support us",
"help-us-more-certifications": "Help us build more certifications",
"remove-donation-popups": "Remove donation popups",
@@ -104,6 +104,53 @@ export const DonationFaqText = (): JSX.Element => {
const { t } = useTranslation();
const faqItems = [
{ Q: t('donate.get-help'), A: <p>{t('donate.forward-receipt')}</p> },
{
Q: t('donate.offer-refunds'),
A: (
<>
<p>{t('donate.donations-are-voluntary')}</p>
<p>{t('donate.cancel-future-donations')}</p>
<p>{t('donate.without-your-authorization')}</p>
</>
)
},
{
Q: t('donate.how-update'),
A: (
<>
<p>{t('donate.take-care-of-this')}</p>
<p>{t('donate.help-update-change-cancel')}</p>
</>
)
},
{
Q: t('donate.how-will-donation-appear'),
A: (
<>
<p>{t('donate.as-freecodecamp-inc')}</p>
<p>{t('donate.do-not-recognize')}</p>
</>
)
},
{
Q: t('donate.are-benefits-a-product'),
A: (
<>
<p>{t('donate.benefits-are-thanks')}</p>
<p>{t('donate.benefits-not-sold-separately')}</p>
</>
)
},
{
Q: t('donate.is-donation-tax-deductible'),
A: (
<>
<p>{t('donate.freecodecamp-is-a-charitable-organization')}</p>
<p>{t('donate.donations-may-be-deductible')}</p>
<p>{t('donate.annual-donation-receipt')}</p>
</>
)
},
{
Q: t('donate.how-transparent'),
A: (
@@ -183,7 +230,6 @@ export const DonationFaqText = (): JSX.Element => {
)
},
{ Q: t('donate.how-stock'), A: <p>{t('donate.welcome-stock')}</p> },
{ Q: t('donate.how-update'), A: <p>{t('donate.forward-receipt')}</p> },
{
Q: t('donate.anything-else'),
A: <p>{t('donate.other-support')}</p>
@@ -213,6 +259,9 @@ export const SupportBenefitsText = ({
: t('donate.support-benefits-title')}
</h2>
<BenefitsList />
<p className='support-benefits-disclaimer'>
{t('donate.support-benefits-disclaimer')}
</p>
</>
);
};
@@ -236,7 +285,6 @@ const BenefitsList = (): JSX.Element => {
<code>placeholder</code>
</Trans>
</li>
<li>{t('donate.support-benefits-5')}</li>
</ul>
);
};
@@ -304,12 +352,24 @@ export const GetSupporterBenefitsText = ({
return (
<>
<Spacer size='l' />
<p>{t('donate.as-you-see')}</p>
<p>{t('donate.careful-with-every-donation')}</p>
{!isDonating ? <p>{t('donate.get-benefits')}</p> : null}
</>
);
};
export const DonationPolicyDisclaimer = (): JSX.Element => {
const { t } = useTranslation();
return (
<p
className='donation-policy-disclaimer'
data-playwright-test-label='donation-policy-disclaimer'
>
{t('donate.donation-policy-disclaimer')}
</p>
);
};
export const ModalBenefitList = () => {
const { t } = useTranslation();
const isA11yFeatureEnabled = useFeature('a11y-donation-modal').on;
@@ -992,6 +992,12 @@ p.donation-animation-bullet-1-b {
margin-top: 80px;
}
.donation-policy-disclaimer {
margin-top: 1rem;
font-size: 0.9rem;
text-align: center;
}
@media (max-width: 768px) {
.donation-animation-bullet-points p {
font-size: 150%;
+8 -4
View File
@@ -14,7 +14,8 @@ import {
SupportBenefitsText,
CurrentInitiativesText,
CommunityAchievementsText,
GetSupporterBenefitsText
GetSupporterBenefitsText,
DonationPolicyDisclaimer
} from '../components/Donation/donation-text-components';
import { Loader } from '../components/helpers';
import {
@@ -81,9 +82,12 @@ function DonatePage({
</Col>
<Col lg={6} lgOffset={0} md={8} mdOffset={1} sm={12}>
{!isDonating || donationFormState.success ? (
<MultiTierDonationForm
paymentContext={PaymentContext.DonatePage}
/>
<>
<MultiTierDonationForm
paymentContext={PaymentContext.DonatePage}
/>
<DonationPolicyDisclaimer />
</>
) : null}
</Col>
</Row>
+119 -49
View File
@@ -64,6 +64,13 @@ function donatePageTests() {
await expect(faqHead).toHaveText(translations.donate.faq);
});
test('should display the donation policy disclaimer', async ({ page }) => {
const disclaimer = page.getByTestId('donation-policy-disclaimer');
await expect(disclaimer).toHaveText(
translations.donate['donation-policy-disclaimer']
);
});
test('should display the faq list with buttons', async ({ page }) => {
const faq1 = page.getByRole('button', {
name: translations.donate['get-help']
@@ -76,10 +83,83 @@ function donatePageTests() {
await faq1.click();
const faq2 = page.getByRole('button', {
name: translations.donate['how-transparent']
name: translations.donate['offer-refunds']
});
await expect(faq2).toBeVisible();
await faq2.click();
await expect(
page.getByText(translations.donate['donations-are-voluntary'])
).toBeVisible();
await expect(
page.getByText(translations.donate['cancel-future-donations'])
).toBeVisible();
await expect(
page.getByText(translations.donate['without-your-authorization'])
).toBeVisible();
await faq2.click();
const faq3 = page.getByRole('button', {
name: translations.donate['how-update']
});
await expect(faq3).toBeVisible();
await faq3.click();
await expect(
page.getByText(translations.donate['take-care-of-this'])
).toBeVisible();
await expect(
page.getByText(translations.donate['help-update-change-cancel'])
).toBeVisible();
await faq3.click();
const faq4 = page.getByRole('button', {
name: translations.donate['how-will-donation-appear']
});
await expect(faq4).toBeVisible();
await faq4.click();
await expect(
page.getByText(translations.donate['as-freecodecamp-inc'])
).toBeVisible();
await expect(
page.getByText(translations.donate['do-not-recognize'])
).toBeVisible();
await faq4.click();
const faq5 = page.getByRole('button', {
name: translations.donate['are-benefits-a-product']
});
await expect(faq5).toBeVisible();
await faq5.click();
await expect(
page.getByText(translations.donate['benefits-are-thanks'])
).toBeVisible();
await expect(
page.getByText(translations.donate['benefits-not-sold-separately'])
).toBeVisible();
await faq5.click();
const faq6 = page.getByRole('button', {
name: translations.donate['is-donation-tax-deductible']
});
await expect(faq6).toBeVisible();
await faq6.click();
await expect(
page.getByText(
translations.donate['freecodecamp-is-a-charitable-organization']
)
).toBeVisible();
await expect(
page.getByText(translations.donate['donations-may-be-deductible'])
).toBeVisible();
await expect(
page.getByText(translations.donate['annual-donation-receipt'])
).toBeVisible();
await faq6.click();
const faq7 = page.getByRole('button', {
name: translations.donate['how-transparent']
});
await expect(faq7).toBeVisible();
await faq7.click();
await expect(
page.getByText(translations.donate['very-transparent'])
).toBeVisible();
@@ -91,51 +171,51 @@ function donatePageTests() {
'You can download our most recent 990 (annual tax report) here.'
)
).toBeVisible();
await faq2.click();
await faq7.click();
const faq3 = page.getByRole('button', {
const faq8 = page.getByRole('button', {
name: translations.donate['how-efficient']
});
await expect(faq3).toBeVisible();
await faq3.click();
await expect(faq8).toBeVisible();
await faq8.click();
await expect(
page.getByText(translations.donate['fcc-budget'])
).toBeVisible();
await expect(
page.getByText(translations.donate['help-millions'])
).toBeVisible();
await faq3.click();
await faq8.click();
const faq4 = page.getByRole('button', {
const faq9 = page.getByRole('button', {
name: translations.donate['how-one-time']
});
await expect(faq4).toBeVisible();
await faq4.click();
await expect(faq9).toBeVisible();
await faq9.click();
await expect(
page.getByText(
"If you'd prefer to make one-time donations, you can support freeCodeCamp's mission whenever you have cash to spare. You can use this link to donate whatever amount feels right through PayPal."
"If you prefer to make one-time donations, you can support freeCodeCamp's mission whenever you have cash to spare. You can use this link to donate any amount through PayPal."
)
).toBeVisible();
await expect(
page.getByText(translations.donate['wire-transfer'])
).toBeVisible();
await faq4.click();
await faq9.click();
const faq5 = page.getByRole('button', {
const faq10 = page.getByRole('button', {
name: translations.donate['does-crypto']
});
await expect(faq5).toBeVisible();
await faq5.click();
await expect(faq10).toBeVisible();
await faq10.click();
await expect(
page.getByText(translations.donate['yes-cryptocurrency'])
).toBeVisible();
await faq5.click();
await faq10.click();
const faq6 = page.getByRole('button', {
const faq11 = page.getByRole('button', {
name: translations.donate['can-check']
});
await expect(faq6).toBeVisible();
await faq6.click();
await expect(faq11).toBeVisible();
await faq11.click();
await expect(
page.getByText(translations.donate['yes-check'])
).toBeVisible();
@@ -143,13 +223,13 @@ function donatePageTests() {
await expect(page.getByText('3905 Hedgcoxe Rd')).toBeVisible();
await expect(page.getByText('PO Box 250352')).toBeVisible();
await expect(page.getByText('Plano, TX 75025')).toBeVisible();
await faq6.click();
await faq11.click();
const faq7 = page.getByRole('button', {
const faq12 = page.getByRole('button', {
name: translations.donate['how-matching-gift']
});
await expect(faq7).toBeVisible();
await faq7.click();
await expect(faq12).toBeVisible();
await faq12.click();
await expect(
page.getByText(translations.donate['employers-vary'])
).toBeVisible();
@@ -159,23 +239,23 @@ function donatePageTests() {
await expect(
page.getByText(translations.donate['help-matching-gift'])
).toBeVisible();
await faq7.click();
await faq12.click();
const faq8 = page.getByRole('button', {
const faq13 = page.getByRole('button', {
name: translations.donate['how-endowment']
});
await expect(faq8).toBeVisible();
await faq8.click();
await expect(faq13).toBeVisible();
await faq13.click();
await expect(
page.getByText(translations.donate['endowment'])
).toBeVisible();
await faq8.click();
await faq13.click();
const faq9 = page.getByRole('button', {
const faq14 = page.getByRole('button', {
name: translations.donate['how-legacy']
});
await expect(faq9).toBeVisible();
await faq9.click();
await expect(faq14).toBeVisible();
await faq14.click();
await expect(
page.getByText(translations.donate['we-honored'])
).toBeVisible();
@@ -188,37 +268,27 @@ function donatePageTests() {
await expect(
page.getByText(translations.donate['legacy-gift-questions'])
).toBeVisible();
await faq9.click();
await faq14.click();
const faq10 = page.getByRole('button', {
const faq15 = page.getByRole('button', {
name: translations.donate['how-stock']
});
await expect(faq10).toBeVisible();
await faq10.click();
await expect(faq15).toBeVisible();
await faq15.click();
await expect(
page.getByText(translations.donate['welcome-stock'])
).toBeVisible();
await faq10.click();
await faq15.click();
const faq11 = page.getByRole('button', {
name: translations.donate['how-update']
});
await expect(faq11).toBeVisible();
await faq11.click();
await expect(
page.getByText(translations.donate['forward-receipt'])
).toBeVisible();
await faq11.click();
const faq12 = page.getByRole('button', {
const faq16 = page.getByRole('button', {
name: translations.donate['anything-else']
});
await expect(faq12).toBeVisible();
await faq12.click();
await expect(faq16).toBeVisible();
await faq16.click();
await expect(
page.getByText(translations.donate['other-support'])
).toBeVisible();
await faq12.click();
await faq16.click();
});
test('should make $5 tier selectable', async ({ page }) => {