feat(website): add Group Rides Hub navigation links for authenticated riders
This commit is contained in:
parent
30539108fa
commit
126f2c6c5f
|
|
@ -78,6 +78,13 @@ export default function AccountPage() {
|
||||||
<div className="flex flex-wrap items-center gap-3 pt-2 lg:pt-0">
|
<div className="flex flex-wrap items-center gap-3 pt-2 lg:pt-0">
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
|
<Link
|
||||||
|
href="/group-rides"
|
||||||
|
className="px-4 py-2 rounded-xl bg-gradient-to-r from-brand-cyan/20 to-blue-600/20 hover:from-brand-cyan/30 hover:to-blue-600/30 text-brand-cyan text-sm font-semibold border border-brand-cyan/40 hover:border-brand-cyan transition flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[18px]">groups</span>
|
||||||
|
<span>Group Rides Hub</span>
|
||||||
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleQuickSync}
|
onClick={handleQuickSync}
|
||||||
|
|
@ -241,6 +248,13 @@ export default function AccountPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-3 pt-2">
|
<div className="flex flex-wrap gap-3 pt-2">
|
||||||
|
<Link
|
||||||
|
href="/group-rides"
|
||||||
|
className="px-4 py-2 rounded-lg bg-brand-cyan/15 text-brand-cyan border border-brand-cyan/40 hover:bg-brand-cyan/25 text-xs font-semibold transition inline-flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[16px]">groups</span>
|
||||||
|
<span>Group Rides Hub</span>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/clubs/manage"
|
href="/clubs/manage"
|
||||||
className="px-4 py-2 rounded-lg bg-secondary/15 text-secondary border border-secondary/40 hover:bg-secondary/25 text-xs font-semibold transition inline-flex items-center gap-1.5"
|
className="px-4 py-2 rounded-lg bg-secondary/15 text-secondary border border-secondary/40 hover:bg-secondary/25 text-xs font-semibold transition inline-flex items-center gap-1.5"
|
||||||
|
|
|
||||||
|
|
@ -123,16 +123,25 @@ export function FounderRides({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="flex items-center gap-2">
|
||||||
className="flex items-center gap-2 px-3.5 py-2 rounded-xl bg-surface-container/50 border border-brand-border/40 text-slate-400 font-body text-xs cursor-not-allowed select-none"
|
<Link
|
||||||
title="Founder Portal ride planning is not wired yet"
|
href="/group-rides"
|
||||||
aria-disabled="true"
|
className="flex items-center gap-2 px-3.5 py-2 rounded-xl bg-brand-cyan/15 hover:bg-brand-cyan/25 border border-brand-cyan/40 text-brand-cyan font-body text-xs font-semibold transition"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-[17px] text-slate-500">add_road</span>
|
<span className="material-symbols-outlined text-[17px]">groups</span>
|
||||||
<span className="text-slate-400">Plan Club Ride</span>
|
<span>Live Pack Radar & Hub</span>
|
||||||
<span className="font-mono text-[9px] px-1.5 py-0.5 rounded bg-surface-container text-slate-400 border border-brand-border/60">
|
</Link>
|
||||||
SOON
|
<div
|
||||||
</span>
|
className="flex items-center gap-2 px-3.5 py-2 rounded-xl bg-surface-container/50 border border-brand-border/40 text-slate-400 font-body text-xs cursor-not-allowed select-none"
|
||||||
|
title="Founder Portal ride planning is not wired yet"
|
||||||
|
aria-disabled="true"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[17px] text-slate-500">add_road</span>
|
||||||
|
<span className="text-slate-400">Plan Club Ride</span>
|
||||||
|
<span className="font-mono text-[9px] px-1.5 py-0.5 rounded bg-surface-container text-slate-400 border border-brand-border/60">
|
||||||
|
SOON
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,18 @@ export function FounderTopBar({
|
||||||
<span>Rider Account</span>
|
<span>Rider Account</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/group-rides"
|
||||||
|
onClick={() => setUserMenuOpen(false)}
|
||||||
|
role="menuitem"
|
||||||
|
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-slate-200 hover:text-brand-cyan hover:bg-surface-container transition-colors text-xs font-semibold"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[18px] text-brand-cyan">
|
||||||
|
groups
|
||||||
|
</span>
|
||||||
|
<span>Group Rides</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/clubs/manage"
|
href="/clubs/manage"
|
||||||
onClick={() => setUserMenuOpen(false)}
|
onClick={() => setUserMenuOpen(false)}
|
||||||
|
|
|
||||||
|
|
@ -109,63 +109,85 @@ export function SiteHeaderClient({ signedIn, role }: SiteHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{signedIn ? (
|
{signedIn ? (
|
||||||
<div className="relative" ref={userMenuRef}>
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<Link
|
||||||
type="button"
|
href="/group-rides"
|
||||||
onClick={() => setUserMenuOpen(!userMenuOpen)}
|
className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-surface-container-high hover:bg-surface-container border border-outline-variant/30 text-xs font-semibold text-on-surface hover:text-primary-container transition-colors"
|
||||||
className={`w-9 h-9 sm:w-11 sm:h-11 rounded-full bg-surface-container-high border border-outline-variant/40 flex items-center justify-center transition-all shrink-0 cursor-pointer ${
|
title="Group Rides Hub"
|
||||||
userMenuOpen
|
|
||||||
? 'ring-2 ring-primary-container bg-surface-container shadow-[0_0_16px_rgba(0,242,254,0.25)]'
|
|
||||||
: 'hover:ring-2 hover:ring-primary-container'
|
|
||||||
}`}
|
|
||||||
title="Rider Menu"
|
|
||||||
aria-label="Open rider menu"
|
|
||||||
aria-expanded={userMenuOpen}
|
|
||||||
aria-haspopup="menu"
|
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-primary-container text-[20px] sm:text-[22px]">
|
<span className="material-symbols-outlined text-[16px] text-primary-container">groups</span>
|
||||||
person
|
<span>Group Rides</span>
|
||||||
</span>
|
</Link>
|
||||||
</button>
|
|
||||||
|
|
||||||
{userMenuOpen && (
|
<div className="relative" ref={userMenuRef}>
|
||||||
<div
|
<button
|
||||||
role="menu"
|
type="button"
|
||||||
aria-orientation="vertical"
|
onClick={() => setUserMenuOpen(!userMenuOpen)}
|
||||||
className="absolute right-0 mt-2 w-64 rounded-2xl bg-surface-container-lowest/95 backdrop-blur-2xl border border-outline-variant/40 shadow-2xl p-2 z-50 animate-in fade-in slide-in-from-top-2 flex flex-col gap-1"
|
className={`w-9 h-9 sm:w-11 sm:h-11 rounded-full bg-surface-container-high border border-outline-variant/40 flex items-center justify-center transition-all shrink-0 cursor-pointer ${
|
||||||
|
userMenuOpen
|
||||||
|
? 'ring-2 ring-primary-container bg-surface-container shadow-[0_0_16px_rgba(0,242,254,0.25)]'
|
||||||
|
: 'hover:ring-2 hover:ring-primary-container'
|
||||||
|
}`}
|
||||||
|
title="Rider Menu"
|
||||||
|
aria-label="Open rider menu"
|
||||||
|
aria-expanded={userMenuOpen}
|
||||||
|
aria-haspopup="menu"
|
||||||
>
|
>
|
||||||
<div className="px-3 py-2 border-b border-outline-variant/20 mb-1">
|
<span className="material-symbols-outlined text-primary-container text-[20px] sm:text-[22px]">
|
||||||
<span className="font-headline-sm text-xs font-semibold text-on-surface block">
|
person
|
||||||
Rider Account
|
</span>
|
||||||
</span>
|
</button>
|
||||||
<span className="font-label-caps text-[10px] text-primary-fixed-dim">
|
|
||||||
{isAdmin ? 'Staff / Administrator' : 'Telemetry Connected'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Link
|
{userMenuOpen && (
|
||||||
href="/account"
|
<div
|
||||||
onClick={() => setUserMenuOpen(false)}
|
role="menu"
|
||||||
role="menuitem"
|
aria-orientation="vertical"
|
||||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-on-surface hover:text-primary-container hover:bg-surface-container transition-colors text-xs font-semibold"
|
className="absolute right-0 mt-2 w-64 rounded-2xl bg-surface-container-lowest/95 backdrop-blur-2xl border border-outline-variant/40 shadow-2xl p-2 z-50 animate-in fade-in slide-in-from-top-2 flex flex-col gap-1"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-[18px] text-primary-container">
|
<div className="px-3 py-2 border-b border-outline-variant/20 mb-1">
|
||||||
person
|
<span className="font-headline-sm text-xs font-semibold text-on-surface block">
|
||||||
</span>
|
Rider Account
|
||||||
<span>Rider Account</span>
|
</span>
|
||||||
</Link>
|
<span className="font-label-caps text-[10px] text-primary-fixed-dim">
|
||||||
|
{isAdmin ? 'Staff / Administrator' : 'Telemetry Connected'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/clubs/manage"
|
href="/account"
|
||||||
onClick={() => setUserMenuOpen(false)}
|
onClick={() => setUserMenuOpen(false)}
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-on-surface hover:text-secondary hover:bg-surface-container transition-colors text-xs font-semibold"
|
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-on-surface hover:text-primary-container hover:bg-surface-container transition-colors text-xs font-semibold"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-[18px] text-secondary">
|
<span className="material-symbols-outlined text-[18px] text-primary-container">
|
||||||
shield
|
person
|
||||||
</span>
|
</span>
|
||||||
<span>Founder Portal</span>
|
<span>Rider Account</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/group-rides"
|
||||||
|
onClick={() => setUserMenuOpen(false)}
|
||||||
|
role="menuitem"
|
||||||
|
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-on-surface hover:text-primary-container hover:bg-surface-container transition-colors text-xs font-semibold"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[18px] text-primary-container">
|
||||||
|
groups
|
||||||
|
</span>
|
||||||
|
<span>Group Rides</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/clubs/manage"
|
||||||
|
onClick={() => setUserMenuOpen(false)}
|
||||||
|
role="menuitem"
|
||||||
|
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-on-surface hover:text-secondary hover:bg-surface-container transition-colors text-xs font-semibold"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[18px] text-secondary">
|
||||||
|
shield
|
||||||
|
</span>
|
||||||
|
<span>Founder Portal</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -205,6 +227,7 @@ export function SiteHeaderClient({ signedIn, role }: SiteHeaderProps) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-1.5 sm:gap-space-sm">
|
<div className="flex items-center gap-1.5 sm:gap-space-sm">
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -289,6 +312,14 @@ export function SiteHeaderClient({ signedIn, role }: SiteHeaderProps) {
|
||||||
<span className="material-symbols-outlined text-[20px] text-primary-container">person</span>
|
<span className="material-symbols-outlined text-[20px] text-primary-container">person</span>
|
||||||
<span>Rider Account</span>
|
<span>Rider Account</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/group-rides"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className="px-space-md py-2.5 rounded-xl font-label-md text-label-md transition-colors min-h-[44px] flex items-center gap-2 text-on-surface hover:bg-surface-container"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[20px] text-primary-container">groups</span>
|
||||||
|
<span>Group Rides</span>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/clubs/manage"
|
href="/clubs/manage"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@ import { JoinRideModal } from '../components/group-rides/join-ride-modal';
|
||||||
import { CreateRideModal } from '../components/group-rides/create-ride-modal';
|
import { CreateRideModal } from '../components/group-rides/create-ride-modal';
|
||||||
import { GroupRidesDirectory } from '../components/group-rides/group-rides-directory';
|
import { GroupRidesDirectory } from '../components/group-rides/group-rides-directory';
|
||||||
import { INITIAL_GROUP_RIDES } from '../components/group-rides/fixtures';
|
import { INITIAL_GROUP_RIDES } from '../components/group-rides/fixtures';
|
||||||
|
import { SiteHeaderClient } from '../components/site-header-client';
|
||||||
|
|
||||||
|
vi.mock('next/navigation', () => ({
|
||||||
|
usePathname: () => '/',
|
||||||
|
useRouter: () => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn() }),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Group Rides Interactive Page & Components', () => {
|
describe('Group Rides Interactive Page & Components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -303,4 +309,39 @@ describe('Group Rides Interactive Page & Components', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 6. Authenticated Navigation to Group Rides
|
||||||
|
// ============================================================
|
||||||
|
describe('Authenticated Navigation to Group Rides', () => {
|
||||||
|
it('renders Group Rides quick button and user dropdown link for signed-in users', () => {
|
||||||
|
render(<SiteHeaderClient signedIn={true} role="rider" />);
|
||||||
|
|
||||||
|
// Quick header button for signed-in users
|
||||||
|
const quickGroupRidesBtn = screen.getByTitle('Group Rides Hub');
|
||||||
|
expect(quickGroupRidesBtn).toBeDefined();
|
||||||
|
expect(quickGroupRidesBtn.getAttribute('href')).toBe('/group-rides');
|
||||||
|
|
||||||
|
// Open Rider Menu dropdown
|
||||||
|
const riderMenuBtn = screen.getByRole('button', { name: /open rider menu/i });
|
||||||
|
fireEvent.click(riderMenuBtn);
|
||||||
|
|
||||||
|
// Verify Group Rides link exists inside dropdown menu
|
||||||
|
const groupRidesLinks = screen.getAllByRole('menuitem', { name: /group rides/i });
|
||||||
|
expect(groupRidesLinks.length).toBeGreaterThan(0);
|
||||||
|
expect(groupRidesLinks[0].getAttribute('href')).toBe('/group-rides');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Group Rides in mobile navigation drawer for signed-in users', () => {
|
||||||
|
render(<SiteHeaderClient signedIn={true} role="rider" />);
|
||||||
|
|
||||||
|
const hamburgerBtn = screen.getByRole('button', { name: /open navigation menu/i });
|
||||||
|
fireEvent.click(hamburgerBtn);
|
||||||
|
|
||||||
|
const groupRidesNavLinks = screen.getAllByRole('link', { name: /group rides/i });
|
||||||
|
const mobileDrawerLink = groupRidesNavLinks.find(link => link.getAttribute('href') === '/group-rides');
|
||||||
|
expect(mobileDrawerLink).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue