From 19fb8d03c14ea23d8fda414e88e903634323cf90 Mon Sep 17 00:00:00 2001 From: Viet Nguyen <3805254+vnugent@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:43:01 -0800 Subject: [PATCH] refactor: make main banner suppressable feat: new sign up button and quick links --- package.json | 1 + .../[[...slug]]/components/PageAlert.tsx | 8 +- .../(default)/components/DesktopHeader.tsx | 125 ++++++++++-------- src/app/(default)/components/LandingCTA.tsx | 92 ------------- src/app/(default)/components/LandingHero.tsx | 20 ++- src/app/(default)/components/LoginButton.tsx | 18 --- src/app/(default)/components/MobileHeader.tsx | 2 +- src/app/(default)/page.tsx | 5 +- src/app/api/auth/[...nextauth]/route.ts | 7 +- src/components/GitHubStars.tsx | 4 +- src/components/Header.tsx | 20 --- src/components/broadcast/AppAlert.tsx | 55 -------- src/components/broadcast/SuppressButton.tsx | 14 ++ .../broadcast/__tests__/AppAlert.tsx | 59 ++++++--- .../media/__tests__/PhotoUploadError.tsx | 2 +- src/components/ui/NavMenuButton.tsx | 15 +-- yarn.lock | 5 + 17 files changed, 174 insertions(+), 278 deletions(-) delete mode 100644 src/app/(default)/components/LoginButton.tsx delete mode 100644 src/components/broadcast/AppAlert.tsx create mode 100644 src/components/broadcast/SuppressButton.tsx diff --git a/package.json b/package.json index 15b2836d8..38bd5c56e 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "graphql": "^16.2.0", "i18n-iso-countries": "^7.5.0", "immer": "^10.0.2", + "js-cookie": "^3.0.5", "lexical": "^0.7.5", "mapbox-gl": "^2.7.0", "maplibre-gl": "^4.3.2", diff --git a/src/app/(default)/climb/[[...slug]]/components/PageAlert.tsx b/src/app/(default)/climb/[[...slug]]/components/PageAlert.tsx index 31b0e7854..7d3c09aa4 100644 --- a/src/app/(default)/climb/[[...slug]]/components/PageAlert.tsx +++ b/src/app/(default)/climb/[[...slug]]/components/PageAlert.tsx @@ -3,9 +3,11 @@ import { Bulldozer } from '@phosphor-icons/react/dist/ssr' export const PageAlert: React.FC<{ id: string }> = ({ id }) => (
-
- - We're giving this page a facelift. +
+
+ + We're giving this page a facelift! +
Visit the previous version to make edits.
diff --git a/src/app/(default)/components/DesktopHeader.tsx b/src/app/(default)/components/DesktopHeader.tsx index 0d8abea93..7311ae42b 100644 --- a/src/app/(default)/components/DesktopHeader.tsx +++ b/src/app/(default)/components/DesktopHeader.tsx @@ -8,23 +8,27 @@ import { NavMenuItem, NavMenuItemProps } from '@/components/ui/NavMenuButton' import GitHubStars from '@/components/GitHubStars' import AuthenticatedProfileNavButton from '../../../components/AuthenticatedProfileNavButton' import Link from 'next/link' +import React from 'react' export const DesktopHeader: React.FC = () => { - const { status } = useSession() - const navListDefault: NavMenuItemProps[] = [ { to: 'https://community.openbeta.io', label: 'Forums' }, { - to: '/about', - label: 'About' + to: process.env.NEXT_PUBLIC_DISCORD_INVITE ?? '', + label: 'Discord' }, { to: 'https://opencollective.com/openbeta/contribute/t-shirt-31745', label: 'T-shirts' }, + { + to: '/about', + label: 'About us' + }, + { to: '/partner-with-us', label: 'Become a Partner' @@ -32,15 +36,10 @@ export const DesktopHeader: React.FC = () => { { to: 'https://docs.openbeta.io', label: 'Docs' - }, - { - onClick: () => { void signIn('auth0', { callbackUrl: '/api/user/me' }) }, - label: 'Login', - type: 'rounded-btn border bg-accent ring-0 border-b-2 border-b-neutral' } ] - const unauthenticatedMenu = navListDefault.map( + const topLevelNav = navListDefault.map( ({ onClick, label, to, type }: NavMenuItemProps, index) => ( { />) ) - unauthenticatedMenu.unshift( - - ) - - let nav - switch (status) { - case 'authenticated': - nav = - break - case 'loading': - nav = ( - <> -
- - ) - break - default: - nav = unauthenticatedMenu - } + topLevelNav.push() return ( -
-
-
- - -
|
- Maps +
+
+
{topLevelNav}
+
+
+ + +
|
+ Maps +
+
+ +
+
+
+
+ +
-
{nav}
-
-
) } const QuickLinks: React.FC = () => { return ( -
+
{[ { href: '/pulse', label: 'Pulse', - icon: + icon: }, { href: '/area/1d33c773-e381-5b8a-a13f-3dfd7991732b/south-africa', label: 'S.Africa', - icon: + icon: }, { href: '/area/2996145f-e1ba-5b56-9da8-30c64ccc3776/canada', label: 'Canada', - icon: + icon: }, { href: '/area/be9733db-21a2-53ec-86a2-3fb6fab552d9/germany', label: 'Germany', - icon: + icon: }, { href: '/area/1db1e8ba-a40e-587c-88a4-64f5ea814b8e/usa', label: 'USA', - icon: + icon: }, { href: '/a', label: 'All', - icon: + icon: } ].map(({ href, label, icon }) => ( {icon}{label} @@ -136,9 +125,39 @@ const QuickLinks: React.FC = () => { ) } -// Maps -// Pulse -// USA -// Canada -// S. Africa -// Germany +const SignupOrLogin: React.FC = () => { + const { status } = useSession() + if (status === 'loading') { + return () + } + if (status === 'authenticated') { + return + } + + return ( +
+ + +
+ ) +} + +export const LoginButton: React.FC = () => { + return ( + <> + + + ) +} + +export const SignupButton: React.FC = () => { + return ( + <> + + + ) +} diff --git a/src/app/(default)/components/LandingCTA.tsx b/src/app/(default)/components/LandingCTA.tsx index 48627fd3d..f5c63627b 100644 --- a/src/app/(default)/components/LandingCTA.tsx +++ b/src/app/(default)/components/LandingCTA.tsx @@ -1,99 +1,7 @@ -import clx from 'classnames' - -import { LoginButtonClient } from './LoginButton' -import { ShowEmailJS } from './ShowEmailJS' -import { ReactNode } from 'react' - -export const LandingCTA: React.FC = () => { - return ( -
-
- - - - -
-
- ) -} - -const Card4Coders: React.FC = () => { - return ( - -
  • ☑️ Fix a bug.
  • -
  • ☑️ Use OpenBeta API & data in your projects.
  • - - } - action={} - /> - ) -} - export const CodeContributorsAction: React.FC = () => ( <> Dev onboarding GitHub ) -const Card4All: React.FC = () => { - return ( - -
  • ☑️ Add missing climbs.
  • -
  • ☑️ Help us make your local climbing area's pages even better!
  • - - } - action={} - /> - ) -} - -const Leaders: React.FC = () => { - return ( - } - /> - ) -} - export const DonateButton: React.FC = () => (Donate) - -const Donate: React.FC = () => { - return ( - } - /> - ) -} - -interface CTACardProps { - title: string - body: string | ReactNode - action: React.ReactNode - className?: string -} - -const Card: React.FC = ({ title, body, action, className }) => { - return ( -
    -

    {title}

    -
    -
    -
    {body}
    -
    - {action} -
    -
    -
    -
    - ) -} diff --git a/src/app/(default)/components/LandingHero.tsx b/src/app/(default)/components/LandingHero.tsx index a2094e512..3e39e7587 100644 --- a/src/app/(default)/components/LandingHero.tsx +++ b/src/app/(default)/components/LandingHero.tsx @@ -1,10 +1,20 @@ +import { MiniAlert } from '@/components/broadcast/MiniAlert' +import { SignupButton } from './DesktopHeader' + export const LandingHero: React.FC = () => { return ( -
    -

    Share your climbing route knowledge!

    -
    - Join us to help improve this comprehensive climbing resource for the community. -
    +
    + +

    Share your climbing route knowledge!

    +
    +

    Join us to help improve this comprehensive
    climbing resource for the community.

    +
    + + + } + />
    ) } diff --git a/src/app/(default)/components/LoginButton.tsx b/src/app/(default)/components/LoginButton.tsx deleted file mode 100644 index a7f37d1fd..000000000 --- a/src/app/(default)/components/LoginButton.tsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client' -import { signIn } from 'next-auth/react' - -import { ArrowRightIcon } from '@heroicons/react/24/outline' - -/** - * Client-side button - */ -export const LoginButtonClient: React.FC<{ className: string, label: string }> = ({ className, label }) => { - return ( - - ) -} diff --git a/src/app/(default)/components/MobileHeader.tsx b/src/app/(default)/components/MobileHeader.tsx index 378d086a7..d75c1f4c8 100644 --- a/src/app/(default)/components/MobileHeader.tsx +++ b/src/app/(default)/components/MobileHeader.tsx @@ -12,7 +12,7 @@ export const MobileHeader: React.FC = () => { const { status } = useSession() const nav = status === 'authenticated' ? : return ( -
    +
    {nav} diff --git a/src/app/(default)/page.tsx b/src/app/(default)/page.tsx index cccbb252b..1c149e94a 100644 --- a/src/app/(default)/page.tsx +++ b/src/app/(default)/page.tsx @@ -18,11 +18,11 @@ export default async function Home (): Promise { const history = await getChangeHistoryServerSide() return ( <> -
    +
    -
    +
    }> @@ -34,7 +34,6 @@ export default async function Home (): Promise {
    - diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index efa420bc3..7185c6a08 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -24,7 +24,12 @@ export const authOptions: NextAuthOptions = { clientId, clientSecret, issuer, - authorization: { params: { audience: 'https://api.openbeta.io', scope: 'offline_access access_token_authz openid email profile read:current_user create:current_user_metadata update:current_user_metadata read:stats update:area_attrs' } }, + authorization: { + params: { + audience: 'https://api.openbeta.io', + scope: 'offline_access access_token_authz openid email profile read:current_user create:current_user_metadata update:current_user_metadata read:stats update:area_attrs' + } + }, client: { token_endpoint_auth_method: clientSecret.length === 0 ? 'none' : 'client_secret_basic' } diff --git a/src/components/GitHubStars.tsx b/src/components/GitHubStars.tsx index 25c6ce654..3cd053c60 100644 --- a/src/components/GitHubStars.tsx +++ b/src/components/GitHubStars.tsx @@ -9,8 +9,8 @@ const GitHubStars: React.FC = () => { const { data } = useSWRImmutable<{ stargazers_count: number }>('https://api.github.com/repos/openbeta/open-tacos', fetcher) return ( - - Star {data?.stargazers_count} + +  Star | {data?.stargazers_count} ) } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a0dacb7e7..0781a59cc 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,12 +1,10 @@ import React from 'react' -import Link from 'next/link' import MobileTabletAppBar from './MobileAppBar' import DesktopAppBar from './DesktopAppBar' import useResponsive from '../js/hooks/useResponsive' import PhotoUploadError from './media/PhotoUploadError' import { userMediaStore } from '../js/stores/media' -import AppAlert from './broadcast/AppAlert' const NAV_BAR_IDENTIFIER = 'tacos-nav-bar' @@ -33,24 +31,6 @@ export default function Header (props: HeaderProps): JSX.Element { showFilterBar={includeFilters} />}
    - -
    - • January 2023: Use this special  - - - Test area - -  for test driving the new edit feature Learn more -
    - - - } - /> ) } diff --git a/src/components/broadcast/AppAlert.tsx b/src/components/broadcast/AppAlert.tsx deleted file mode 100644 index bc2863167..000000000 --- a/src/components/broadcast/AppAlert.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useState, useEffect } from 'react' -import { XMarkIcon } from '@heroicons/react/20/solid' - -const STORAGE_KEY = 'alert.main' - -interface Props { - message: JSX.Element -} - -/** - * Main alert to be displayed under the nav bar. Users can disable or snooze the alert. - * @param message alert content - */ -export default function MiniAlert ({ message }: Props): JSX.Element | null { - const [open, setOpen] = useState(false) - - useEffect(() => { - const disabledFlag = localStorage.getItem(STORAGE_KEY) // indefinitely in this browser - const snoozedFlag = sessionStorage.getItem(STORAGE_KEY) // current session only - if (snoozedFlag === '1' || disabledFlag === '1') { - setOpen(false) - } else { - setOpen(true) - } - }) - return open - ? ( -
    -
    - {message} -
    -
    - - -
    -
    - ) - : null -} diff --git a/src/components/broadcast/SuppressButton.tsx b/src/components/broadcast/SuppressButton.tsx new file mode 100644 index 000000000..1cdfac2d4 --- /dev/null +++ b/src/components/broadcast/SuppressButton.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { XCircle } from '@phosphor-icons/react/dist/ssr' + +/** + * Suppress banner button + */ +export const SuppressButton: React.FC<{ onClick: React.MouseEventHandler }> = ({ onClick }) => ( + +) diff --git a/src/components/broadcast/__tests__/AppAlert.tsx b/src/components/broadcast/__tests__/AppAlert.tsx index d2979015a..fbe3d5895 100644 --- a/src/components/broadcast/__tests__/AppAlert.tsx +++ b/src/components/broadcast/__tests__/AppAlert.tsx @@ -1,31 +1,58 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { MiniAlertProps } from '../MiniAlert' +import React from 'react' -jest.mock('next/router') +const cookieGetter = jest.fn() +const cookieSetter = jest.fn() -jest.mock('../../DesktopAppBar') -jest.mock('../../MobileAppBar') -jest.mock('../../media/PhotoUploadError') +jest.mock('js-cookie', () => ({ + __esModule: 'true', + default: { + get: cookieGetter, + set: cookieSetter + } +})) -jest.mock('../../../js/stores/media') +let MiniAlertComponent: React.FC -let HeaderComponent - -describe('Test photo upload error popup', () => { +describe('Banner suppression', () => { beforeAll(async () => { // why async import? see https://github.com/facebook/jest/issues/10025#issuecomment-716789840 - const module = await import('../../Header') - HeaderComponent = module.default + const module = await import('../MiniAlert') + MiniAlertComponent = module.MiniAlert }) - it('shows app alert', async () => { - const user = userEvent.setup({ skipHover: true }) - - render() + it('doesn\'t show alert when cookie exists', async () => { + // cookie exists + cookieGetter.mockReturnValueOnce('foo') + render( + + important message +
    + } + />) + + expect(screen.queryAllByRole('button').length).toEqual(0) + }) - // there should be at least 2 buttons, temporarily dismiss & never show again - expect(screen.queryAllByRole('button').length).toBeGreaterThanOrEqual(2) + it('shows alert', async () => { + // Clear previous cookie setting if any + cookieGetter.mockClear() + const user = userEvent.setup({ skipHover: true }) + render( + + important message 2 +
    + } + />) + + // click the Suppress button await user.click(screen.getByRole('button', { name: /Don't show this again/i })) // alert dismissed diff --git a/src/components/media/__tests__/PhotoUploadError.tsx b/src/components/media/__tests__/PhotoUploadError.tsx index e0558d0a2..f879bfc22 100644 --- a/src/components/media/__tests__/PhotoUploadError.tsx +++ b/src/components/media/__tests__/PhotoUploadError.tsx @@ -5,7 +5,7 @@ jest.mock('next/router') jest.mock('../../DesktopAppBar') jest.mock('../../MobileAppBar') -jest.mock('../../broadcast/AppAlert') +jest.mock('../../broadcast/MiniAlert') const getPhotoUploadErrorMessageFn = jest.fn() const setPhotoUploadErrorMessageFn = jest.fn() diff --git a/src/components/ui/NavMenuButton.tsx b/src/components/ui/NavMenuButton.tsx index 896dfded2..3fd831b5c 100644 --- a/src/components/ui/NavMenuButton.tsx +++ b/src/components/ui/NavMenuButton.tsx @@ -1,5 +1,6 @@ 'use client' import React from 'react' +import clz from 'classnames' import { Button, ButtonVariant } from './BaseButton' interface NavMenuButtonProps { @@ -24,13 +25,11 @@ export interface NavMenuItemProps { export const NavMenuItem: React.FC = ({ label, to, type, onClick }) => { return ( -
  • - {label} - -
  • + {label} + ) } diff --git a/yarn.lock b/yarn.lock index 4cb06ef6e..a8cd5783a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6076,6 +6076,11 @@ js-cookie@^2.2.1: resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"