diff --git a/client/src/components/Header/components/nav-links.tsx b/client/src/components/Header/components/nav-links.tsx index 8f3b6774399..9212130c4b2 100644 --- a/client/src/components/Header/components/nav-links.tsx +++ b/client/src/components/Header/components/nav-links.tsx @@ -13,6 +13,7 @@ import { openSignoutModal, toggleTheme } from '../../../redux/actions'; import { Link } from '../../helpers'; import { LocalStorageThemes } from '../../../redux/types'; import { themeSelector } from '../../../redux/selectors'; +import SupporterBadge from '../../../assets/icons/supporter-badge'; export interface NavLinksProps { displayMenu: boolean; @@ -35,6 +36,42 @@ const mapStateToProps = createSelector( (theme: LocalStorageThemes) => ({ theme }) ); +interface DonateButtonProps { + isUserDonating: boolean | undefined; + handleMenuKeyDown: (event: React.KeyboardEvent) => void; +} + +const DonateButton = ({ + isUserDonating, + handleMenuKeyDown +}: DonateButtonProps) => { + const { t } = useTranslation(); + return ( +
  • + + {isUserDonating ? ( + <> + {t('buttons.supporters')} + + + ) : ( + <>{t('buttons.donate')} + )} + +
  • + ); +}; + function NavLinks({ menuButtonRef, openSignoutModal, @@ -45,7 +82,7 @@ function NavLinks({ toggleTheme }: NavLinksProps) { const { t } = useTranslation(); - const { username: currentUserName } = user || {}; + const { isDonating: isUserDonating, username: currentUserName } = user || {}; // the accessibility tree just needs a little more time to pick up the change. // This function allows us to set aria-expanded to false and then delay just a bit before setting focus on the button @@ -106,6 +143,10 @@ function NavLinks({ data-playwright-test-label='header-menu' className={`nav-list${displayMenu ? ' display-menu' : ''}`} > +
  • {t('buttons.curriculum')} diff --git a/client/src/components/Header/components/universal-nav.css b/client/src/components/Header/components/universal-nav.css index f0ac48ce7f1..95c83745950 100644 --- a/client/src/components/Header/components/universal-nav.css +++ b/client/src/components/Header/components/universal-nav.css @@ -84,9 +84,8 @@ /** * Site header language list - * Using ~ so it still works if more items are added between the button and list. */ -.lang-button-nav[aria-expanded='true'] ~ .nav-list { +.lang-button-nav[aria-expanded='true'] + .nav-list { -ms-overflow-style: none; display: block; max-height: calc(100vh - var(--header-height)); @@ -95,7 +94,7 @@ top: calc(var(--header-height)); } -.lang-button-nav[aria-expanded='true'] ~ .nav-list::-webkit-scrollbar { +.lang-button-nav[aria-expanded='true'] + .nav-list::-webkit-scrollbar { display: none; } @@ -259,33 +258,6 @@ li > button.nav-link-signout:not([aria-disabled='true']):is(:hover, :focus) { border: 1px solid var(--gray-00); } -.nav-donate-btn .menu-btn-icon { - display: inline-flex; - align-items: center; -} - -.nav-donate-btn .menu-btn-text { - display: none; -} - -@media (min-width: 601px) { - .nav-donate-btn .menu-btn-icon { - display: none; - } - .nav-donate-btn .menu-btn-text { - display: inline-block; - } -} - -.nav-donate-btn .fa-heart { - color: #ff5e5e; - transition: transform 0.2s ease; -} - -.nav-donate-btn:hover .fa-heart { - transform: scale(1.2); -} - /** * User thumbnail */ @@ -395,7 +367,7 @@ li > button.nav-link-signout:not([aria-disabled='true']):is(:hover, :focus) { menu is collapsed. */ .universal-nav-right #toggle-button-nav[aria-expanded='false'] - ~ .fcc_searchBar { + + .fcc_searchBar { display: none; } @@ -403,7 +375,7 @@ li > button.nav-link-signout:not([aria-disabled='true']):is(:hover, :focus) { menu is collapsed. */ .universal-nav-right #toggle-button-nav[aria-expanded='false'] - ~ .fcc_searchBar + + .fcc_searchBar .ais-Hits { display: none; } @@ -451,17 +423,12 @@ li > button.nav-link-signout:not([aria-disabled='true']):is(:hover, :focus) { /** * Handle submenu containers collapsed and expanded states - - * We use ~ because the Donate button is now between the toggle button - * and the menu/search elements, so they are no longer right next to each other. */ -#universal-nav button[aria-expanded='false'] ~ .nav-list, -#universal-nav button[aria-expanded='false'] ~ .fcc_searchBar { +#universal-nav button[aria-expanded='false'] + div { display: none; } -#universal-nav button[aria-expanded='true'] ~ .nav-list, -#universal-nav button[aria-expanded='true'] ~ .fcc_searchBar { +#universal-nav button[aria-expanded='true'] + div { display: block; } diff --git a/client/src/components/Header/components/universal-nav.test.tsx b/client/src/components/Header/components/universal-nav.test.tsx deleted file mode 100644 index f4067dbbe8c..00000000000 --- a/client/src/components/Header/components/universal-nav.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; -import { render, screen, within } from '@testing-library/react'; -import { describe, it, expect, vi } from 'vitest'; -import { Provider } from 'react-redux'; -import { createStore } from '../../../redux/create-store'; -import UniversalNav from './universal-nav'; - -vi.mock('@loadable/component', () => ({ - default: () => { - const LazyComponent = () => null; - LazyComponent.displayName = 'Loadable'; - return LazyComponent; - } -})); - -vi.mock('../../../utils/get-words'); -vi.mock('../../../analytics'); - -const baseProps = { - displayMenu: false, - showMenu: vi.fn(), - hideMenu: vi.fn(), - menuButtonRef: { current: null } as React.RefObject, - searchBarRef: { current: null } as React.RefObject, - fetchState: { pending: false }, - pathname: '/learn' -}; - -const baseUser = { - username: 'test-user', - picture: 'https://freecodecamp.org/image.png', - yearsTopContributor: [] -}; - -const nonDonatingUser = { ...baseUser, isDonating: false }; -const donatingUser = { ...baseUser, isDonating: true }; - -const getByLabel = (label: string) => - within(screen.getByRole('navigation')).getByRole('link', { - hidden: true, - name: (_: string, el: Element) => - el.getAttribute('data-playwright-test-label') === label - }); - -const queryByLabel = (label: string) => - within(screen.getByRole('navigation')).queryByRole('link', { - hidden: true, - name: (_: string, el: Element) => - el.getAttribute('data-playwright-test-label') === label - }); - -const renderNav = ( - user: typeof baseUser & { isDonating: boolean }, - pending = false -) => - render( - - - - ); - -describe('', () => { - describe.each([ - { - label: 'non-donating user', - user: nonDonatingUser, - visibleBtn: { testLabel: 'header-donate-button', href: '/donate' }, - hiddenBtn: { testLabel: 'header-support-button' } - }, - { - label: 'donating user', - user: donatingUser, - visibleBtn: { testLabel: 'header-support-button', href: '/supporters' }, - hiddenBtn: { testLabel: 'header-donate-button' } - } - ])('$label', ({ user, visibleBtn, hiddenBtn }) => { - it(`renders ${visibleBtn.testLabel}`, () => { - renderNav(user); - expect(getByLabel(visibleBtn.testLabel)).toBeInTheDocument(); - }); - - it(`links to ${visibleBtn.href}`, () => { - renderNav(user); - expect(getByLabel(visibleBtn.testLabel)).toHaveAttribute( - 'href', - visibleBtn.href - ); - }); - - it(`does not render ${hiddenBtn.testLabel}`, () => { - renderNav(user); - expect(queryByLabel(hiddenBtn.testLabel)).not.toBeInTheDocument(); - }); - }); - - describe('Loading state', () => { - it('renders no donate or supporters button when pending', () => { - renderNav(nonDonatingUser, true); - expect(queryByLabel('header-donate-button')).not.toBeInTheDocument(); - expect(queryByLabel('header-support-button')).not.toBeInTheDocument(); - }); - }); -}); diff --git a/client/src/components/Header/components/universal-nav.tsx b/client/src/components/Header/components/universal-nav.tsx index ab46b0d0c76..5225dcd8295 100644 --- a/client/src/components/Header/components/universal-nav.tsx +++ b/client/src/components/Header/components/universal-nav.tsx @@ -1,5 +1,3 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faHeart } from '@fortawesome/free-solid-svg-icons'; import Loadable from '@loadable/component'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,7 +6,6 @@ import { isLanding } from '../../../utils/path-parsers'; import { Link, SkeletonSprite } from '../../helpers'; import { SEARCH_EXPOSED_WIDTH } from '../../../../config/misc'; import FreeCodeCampLogo from '../../../assets/icons/freecodecamp-logo'; -import SupporterBadge from '../../../assets/icons/supporter-badge'; import MenuButton from './menu-button'; import NavLinks from './nav-links'; import AuthOrProfile from './auth-or-profile'; @@ -57,8 +54,6 @@ const UniversalNav = ({ ) : ( ); - - const isDonating: boolean = user?.isDonating; return (