feat(client): expose nav donate button for non-donor (#49705)

Co-authored-by: Muhammed Mustafa <muhammed@freecodecamp.org>
This commit is contained in:
Ahmad Abdolsaheb
2023-03-21 11:01:44 +03:00
committed by GitHub
parent 92a5a48534
commit d3396a2017
11 changed files with 111 additions and 40 deletions

View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
exports.defaultUserImage = 'https://freecodecamp.com/sample-image.png';
exports.MAX_MOBILE_WIDTH = 767;

4
config/misc.ts Normal file
View 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;

View File

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

View File

@@ -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',