diff --git a/resources/js/components/partials/header.test.tsx b/resources/js/components/partials/header.test.tsx index 8a10e456..99029d62 100644 --- a/resources/js/components/partials/header.test.tsx +++ b/resources/js/components/partials/header.test.tsx @@ -1,10 +1,11 @@ -import { dashboard } from '@/routes'; +import { dashboard, home } from '@/routes'; import { render, screen } from '@testing-library/react'; import type React from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import Header from './header'; const mocks = vi.hoisted(() => ({ + url: '/roadmap', pageProps: { auth: { user: { @@ -24,11 +25,12 @@ vi.mock('@inertiajs/react', () => ({ children: React.ReactNode; href: string | { url: string }; }) => {children}, - usePage: () => ({ props: mocks.pageProps }), + usePage: () => ({ url: mocks.url, props: mocks.pageProps }), })); describe('Header', () => { beforeEach(() => { + mocks.url = '/roadmap'; vi.stubGlobal( 'fetch', vi.fn(() => new Promise(() => {})), @@ -45,4 +47,26 @@ describe('Header', () => { expect(dashboardLinks).toHaveLength(2); expect(dashboardLinks[0]?.getAttribute('href')).toBe(dashboard().url); }); + + it('links the logo home on every page but the landing one', () => { + render(
); + + const logoLinks = screen.getAllByRole('link', { + name: 'Whisper Money', + }); + + expect(logoLinks).toHaveLength(2); + expect(logoLinks[0]?.getAttribute('href')).toBe(home().url); + }); + + it('does not link the logo on the landing page', () => { + mocks.url = '/?ref=newsletter'; + + render(
); + + expect(screen.getAllByText('Whisper Money')).toHaveLength(2); + expect( + screen.queryByRole('link', { name: 'Whisper Money' }), + ).toBeNull(); + }); }); diff --git a/resources/js/components/partials/header.tsx b/resources/js/components/partials/header.tsx index 3357229a..9a5f94fd 100644 --- a/resources/js/components/partials/header.tsx +++ b/resources/js/components/partials/header.tsx @@ -1,9 +1,10 @@ -import { dashboard, login, roadmap } from '@/routes'; +import { cn, resolveUrl } from '@/lib/utils'; +import { dashboard, home, login, roadmap } from '@/routes'; import { type SharedData } from '@/types'; import { __ } from '@/utils/i18n'; import { Link, usePage } from '@inertiajs/react'; import { BirdIcon, Github, LogIn, MapIcon, StarIcon } from 'lucide-react'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, type ReactNode } from 'react'; import DiscordIcon from '../icons/DiscordIcon'; import { Button } from '../ui/button'; import { Separator } from '../ui/separator'; @@ -27,6 +28,56 @@ function useGitHubStars(): number | null { return stars; } +/** + * Below `lg` the nav only has room for the icons, so the label stays for + * screen readers instead of disappearing with the text. + */ +function NavButton({ + label, + badge, + children, +}: { + label: string; + badge?: ReactNode; + children: ReactNode; +}) { + return ( + + ); +} + +/** + * The logo links back to the landing page, except on the landing page itself. + */ +function Logo({ className }: { className?: string }) { + const { url } = usePage(); + const isLandingPage = url.split('?')[0] === resolveUrl(home()); + const classes = cn('flex shrink-0 items-center font-mono', className); + const content = ( + <> + + Whisper Money + + ); + + if (isLandingPage) { + return
{content}
; + } + + return ( + + {content} + + ); +} + type Props = { canRegister?: boolean; hideExternalButtons?: boolean; @@ -43,59 +94,8 @@ export default function Header({ <> {/* Mobile pill header */}
-
- - - Whisper Money - -
+