mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
feat(client): expose nav donate button for non-donor (#49705)
Co-authored-by: Muhammed Mustafa <muhammed@freecodecamp.org>
This commit is contained in:
@@ -4,6 +4,7 @@ client/public/**
|
||||
api-server/src/public/**
|
||||
api-server/lib/**
|
||||
config/i18n.js
|
||||
config/misc.js
|
||||
config/certification-settings.js
|
||||
config/donation-settings.js
|
||||
config/superblock-order.js
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -163,6 +163,7 @@ config/client/frame-runner.json
|
||||
config/client/test-evaluator.json
|
||||
config/curriculum.json
|
||||
config/i18n.js
|
||||
config/misc.js
|
||||
config/certification-settings.js
|
||||
config/donation-settings.js
|
||||
config/superblock-order.js
|
||||
|
||||
@@ -7,6 +7,7 @@ curriculum/challenges/_meta/*/*
|
||||
curriculum/challenges/**/*
|
||||
config/**/*.json
|
||||
config/i18n.js
|
||||
config/misc.js
|
||||
config/certification-settings.js
|
||||
config/donation-settings.js
|
||||
config/superblock-order.js
|
||||
|
||||
@@ -43,7 +43,7 @@ const MenuButton = ({
|
||||
return (
|
||||
<button
|
||||
aria-expanded={displayMenu}
|
||||
className={`toggle-button-nav${
|
||||
className={`exposed-button-nav${
|
||||
displayMenu ? ' reverse-toggle-color' : ''
|
||||
}`}
|
||||
id='toggle-button-nav'
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import {
|
||||
faCheckSquare,
|
||||
faHeart,
|
||||
faSquare,
|
||||
faExternalLinkAlt
|
||||
faExternalLinkAlt,
|
||||
faHeart
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { Fragment, useRef } from 'react';
|
||||
import Media from 'react-responsive';
|
||||
import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { useFeature } from '@growthbook/growthbook-react';
|
||||
import { connect } from 'react-redux';
|
||||
import { clientLocale, radioLocation } from '../../../../../config/env.json';
|
||||
import { DONATE_NAV_EXPOSED_WIDTH } from '../../../../../config/misc';
|
||||
import {
|
||||
availableLangs,
|
||||
LangNames,
|
||||
@@ -50,6 +53,63 @@ const mapDispatchToProps = {
|
||||
openSignoutModal
|
||||
};
|
||||
|
||||
interface DonateButtonProps {
|
||||
isUserDonating: boolean | undefined;
|
||||
handleMenuKeyDown: (event: React.KeyboardEvent<HTMLAnchorElement>) => void;
|
||||
t: TFunction;
|
||||
}
|
||||
|
||||
type DonateItemProps = Pick<DonateButtonProps, 'handleMenuKeyDown'> & {
|
||||
donateText: string;
|
||||
};
|
||||
|
||||
const DonateItem = ({ handleMenuKeyDown, donateText }: DonateItemProps) => (
|
||||
<li key='donate'>
|
||||
<Link
|
||||
className='nav-link'
|
||||
onKeyDown={handleMenuKeyDown}
|
||||
sameTab={false}
|
||||
to='/donate'
|
||||
data-test-label='dropdown-donate-button'
|
||||
nav-donate-button
|
||||
>
|
||||
{donateText}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
|
||||
const ThankYouMessage = ({ message }: { message: string }) => (
|
||||
<li className='nav-link nav-link-flex nav-link-header' key='donate'>
|
||||
{message}
|
||||
<FontAwesomeIcon icon={faHeart} />
|
||||
</li>
|
||||
);
|
||||
|
||||
const DonateButton = ({
|
||||
isUserDonating,
|
||||
handleMenuKeyDown,
|
||||
t
|
||||
}: DonateButtonProps) => {
|
||||
const exposeUniversalDonateButton = useFeature('expose_donate_button').on;
|
||||
if (isUserDonating) return <ThankYouMessage message={t('donate.thanks')} />;
|
||||
else if (exposeUniversalDonateButton)
|
||||
return (
|
||||
<Media maxWidth={DONATE_NAV_EXPOSED_WIDTH}>
|
||||
<DonateItem
|
||||
handleMenuKeyDown={handleMenuKeyDown}
|
||||
donateText={t('buttons.donate')}
|
||||
/>
|
||||
</Media>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<DonateItem
|
||||
handleMenuKeyDown={handleMenuKeyDown}
|
||||
donateText={t('buttons.donate')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function NavLinks({
|
||||
menuButtonRef,
|
||||
hideLanguageMenu,
|
||||
@@ -313,7 +373,7 @@ function NavLinks({
|
||||
openSignoutModal();
|
||||
};
|
||||
|
||||
const currentUserDonating = user?.isDonating;
|
||||
const isUserDonating = user?.isDonating;
|
||||
const currentUserName = user?.username;
|
||||
const currentUserTheme = user?.theme;
|
||||
const { pending } = fetchState;
|
||||
@@ -327,25 +387,11 @@ function NavLinks({
|
||||
isLanguageMenuDisplayed ? ' display-lang-menu' : ''
|
||||
}`}
|
||||
>
|
||||
{currentUserDonating ? (
|
||||
<li key='donate'>
|
||||
<div className='nav-link nav-link-flex nav-link-header'>
|
||||
<span>{t('donate.thanks')}</span>
|
||||
<FontAwesomeIcon icon={faHeart} />
|
||||
</div>
|
||||
</li>
|
||||
) : (
|
||||
<li key='donate'>
|
||||
<Link
|
||||
className='nav-link'
|
||||
onKeyDown={handleMenuKeyDown}
|
||||
sameTab={false}
|
||||
to='/donate'
|
||||
>
|
||||
{t('buttons.donate')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<DonateButton
|
||||
t={t}
|
||||
isUserDonating={isUserDonating}
|
||||
handleMenuKeyDown={handleMenuKeyDown}
|
||||
/>
|
||||
<li key='learn'>
|
||||
<Link className='nav-link' onKeyDown={handleMenuKeyDown} to='/learn'>
|
||||
{t('buttons.curriculum')}
|
||||
|
||||
@@ -322,7 +322,7 @@ button.nav-link[aria-disabled='true'] {
|
||||
margin-inline-end: 25px;
|
||||
}
|
||||
|
||||
.toggle-button-nav {
|
||||
.exposed-button-nav {
|
||||
padding: 2px 14px;
|
||||
border: 1px solid var(--gray-00);
|
||||
font-size: 18px;
|
||||
@@ -334,18 +334,21 @@ button.nav-link[aria-disabled='true'] {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toggle-button-nav:hover {
|
||||
background-color: var(--theme-color);
|
||||
color: var(--gray-00);
|
||||
.exposed-button-nav:hover,
|
||||
.exposed-button-nav:hover:focus {
|
||||
background-color: var(--gray-00);
|
||||
color: var(--gray-90);
|
||||
border: 1px solid var(--gray-00);
|
||||
}
|
||||
|
||||
.toggle-button-nav:focus {
|
||||
.exposed-button-nav:focus {
|
||||
outline: 3px solid var(--blue-mid);
|
||||
outline-offset: 0;
|
||||
background-color: var(--gray-90);
|
||||
color: var(--gray-00);
|
||||
}
|
||||
|
||||
.toggle-button-nav:focus:not(:focus-visible) {
|
||||
.exposed-button-nav:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -409,12 +412,12 @@ button.nav-link[aria-disabled='true'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reverse-toggle-color {
|
||||
.exposed-button-nav.reverse-toggle-color {
|
||||
background-color: var(--gray-00);
|
||||
color: var(--theme-color);
|
||||
}
|
||||
|
||||
.reverse-toggle-color:hover {
|
||||
.exposed-button-nav.reverse-toggle-color:hover {
|
||||
background-color: var(--gray-00);
|
||||
color: var(--theme-color);
|
||||
}
|
||||
@@ -568,7 +571,7 @@ button.nav-link[aria-disabled='true'] {
|
||||
#universal-nav .login-btn-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
.toggle-button-nav {
|
||||
.exposed-button-nav {
|
||||
padding: 2px 8px;
|
||||
}
|
||||
#universal-nav-logo svg {
|
||||
|
||||
@@ -2,8 +2,13 @@ import Loadable from '@loadable/component';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Media from 'react-responsive';
|
||||
import { useFeature } from '@growthbook/growthbook-react';
|
||||
import { isLanding } from '../../../utils/path-parsers';
|
||||
import { Link, SkeletonSprite } from '../../helpers';
|
||||
import {
|
||||
SEARCH_EXPOSED_WIDTH,
|
||||
DONATE_NAV_EXPOSED_WIDTH
|
||||
} from '../../../../../config/misc';
|
||||
import { User } from '../../../redux/prop-types';
|
||||
import MenuButton from './menu-button';
|
||||
import NavLinks from './nav-links';
|
||||
@@ -16,8 +21,6 @@ const SearchBarOptimized = Loadable(
|
||||
() => import('../../search/searchBar/search-bar-optimized')
|
||||
);
|
||||
|
||||
const MAX_MOBILE_WIDTH = 980;
|
||||
|
||||
interface UniversalNavProps {
|
||||
displayMenu: boolean;
|
||||
isLanguageMenuDisplayed: boolean;
|
||||
@@ -45,6 +48,8 @@ export const UniversalNav = ({
|
||||
const { pending } = fetchState;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const exposeDonateButton = useFeature('expose_donate_button').on;
|
||||
|
||||
const search =
|
||||
typeof window !== `undefined` && isLanding(window.location.pathname) ? (
|
||||
<SearchBarOptimized innerRef={searchBarRef} />
|
||||
@@ -61,7 +66,7 @@ export const UniversalNav = ({
|
||||
<div
|
||||
className={`universal-nav-left${displayMenu ? ' display-search' : ''}`}
|
||||
>
|
||||
<Media minWidth={MAX_MOBILE_WIDTH + 1}>{search}</Media>
|
||||
<Media minWidth={SEARCH_EXPOSED_WIDTH + 1}>{search}</Media>
|
||||
</div>
|
||||
<div className='universal-nav-middle'>
|
||||
<Link id='universal-nav-logo' to='/learn'>
|
||||
@@ -75,6 +80,18 @@ export const UniversalNav = ({
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!user?.isDonating && exposeDonateButton && (
|
||||
<Media minWidth={DONATE_NAV_EXPOSED_WIDTH + 1}>
|
||||
<Link
|
||||
sameTab={false}
|
||||
to='/donate'
|
||||
data-test-label='nav-donate-button'
|
||||
className='exposed-button-nav'
|
||||
>
|
||||
{t('buttons.donate')}
|
||||
</Link>
|
||||
</Media>
|
||||
)}
|
||||
<MenuButton
|
||||
displayMenu={displayMenu}
|
||||
hideMenu={hideMenu}
|
||||
@@ -82,7 +99,7 @@ export const UniversalNav = ({
|
||||
showMenu={showMenu}
|
||||
user={user}
|
||||
/>
|
||||
<Media maxWidth={MAX_MOBILE_WIDTH}>{search}</Media>
|
||||
<Media maxWidth={SEARCH_EXPOSED_WIDTH}>{search}</Media>
|
||||
<NavLinks
|
||||
displayMenu={displayMenu}
|
||||
fetchState={fetchState}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
exports.defaultUserImage = 'https://freecodecamp.com/sample-image.png';
|
||||
exports.MAX_MOBILE_WIDTH = 767;
|
||||
4
config/misc.ts
Normal file
4
config/misc.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const defaultUserImage = 'https://freecodecamp.com/sample-image.png';
|
||||
export const MAX_MOBILE_WIDTH = 767;
|
||||
export const SEARCH_EXPOSED_WIDTH = 980;
|
||||
export const DONATE_NAV_EXPOSED_WIDTH = 600;
|
||||
@@ -4,7 +4,7 @@ const navBarselectors = {
|
||||
navigationLinks: '.nav-list',
|
||||
avatarContainer: '.avatar-container',
|
||||
defaultAvatar: '.avatar-container',
|
||||
menuButton: '.toggle-button-nav',
|
||||
menuButton: '#toggle-button-nav',
|
||||
avatarImage: '.avatar-container .avatar'
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const { clientLocale } = envData;
|
||||
|
||||
const selectors: { [key: string]: string } = {
|
||||
'navigation-list': '.nav-list',
|
||||
'toggle-button': '.toggle-button-nav',
|
||||
'toggle-button': '#toggle-button-nav',
|
||||
'language-menu': '.nav-lang-menu',
|
||||
'exit-lang-menu': "[data-value='exit-lang-menu']",
|
||||
'lang-menu-option': 'button.nav-lang-menu-option',
|
||||
|
||||
Reference in New Issue
Block a user