import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import { useTheme } from '../lib/theme-context';
import MobileNavigation from './MobileNavigation';
import DailyEmberWidget from './DailyEmberWidget';
import TopSearchBar from './ui/TopSearchBar';
import CommandPaletteModal from './ui/CommandPaletteModal';
// User Profile Dropdown Component
function UserProfileDropdown({ user, onMobileMenuClose }) {
// Hook order is fixed for both branches; do not move this below the
// null-user early return — see rules-of-hooks (AGENTS.md Gotcha #11.5).
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
// Logged-out: replace avatar + email + dropdown with a Sign-in CTA.
if (!user) {
return (
))}
>
);
}
export default function Layout({ children, user = null, showSearch = false }) {
const router = useRouter();
const { theme, toggleTheme } = useTheme();
const [searchQuery, setSearchQuery] = useState('');
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Command palette (redesign-v2 sub-convoy #3, 2026-06-04). Opens
// globally on ⌘K / Ctrl+K for authenticated users. The keyboard
// listener is attached at Layout-scope (not _app.js) so it doesn't
// fire on the public marketing pages that don't mount .
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
useEffect(() => {
if (!user) return undefined;
const handler = (event) => {
const isShortcut =
(event.metaKey || event.ctrlKey) &&
!event.shiftKey &&
!event.altKey &&
event.key.toLowerCase() === 'k';
if (isShortcut) {
event.preventDefault();
setIsCommandPaletteOpen((prev) => !prev);
}
};
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
}, [user]);
return (
{/* Mobile Navigation - Bottom bar for mobile */}
setIsMobileMenuOpen(true)}
/>
{/* Mobile Overlay — Liquid Glass scrim consistent with . */}
{isMobileMenuOpen && (
setIsMobileMenuOpen(false)}
/>
)}
{/* Mobile Menu Drawer - Slides in from left when "More" is tapped */}
{/* Mobile Header with Close Button */}
DeckHearth
{/* Mobile Navigation Content */}
{/* Mobile Bottom Section */}
{/* User Profile Dropdown */}
setIsMobileMenuOpen(false)}
/>
{/* Icon Buttons Row - Support and Dark Mode */}
{/* Desktop Sidebar - Hidden on mobile. Liquid Glass mid-tint, ambient
elevation + rim-light edges; the page background visibly cools
through the rail. */}
{/* Desktop Header — flame logomark + two-tone wordmark.
redesign-v2 sub-convoy #2 (2026-06-04): replaces the prior
"DH" monogram + plain text. Mockup shows a gradient flame
icon on the left and "Deck" + "Hearth" where "Hearth" is
gradient-text-flame so the wordmark reads as a single brand
unit with the ember accent. */}
{/* Daily Ember Widget — gamification card from the
redesign-v2 mockup. Only rendered for authenticated
users (the hook returns demo data until the real
backend ships; an unauthenticated user has no
meaningful "today" to track). */}
{user && }
{/* User Profile Dropdown */}
{}}
/>
{/* Icon Buttons Row - Support and Dark Mode */}
{/* Global top bar — redesign-v2 sub-convoy #3 (2026-06-04).
Rendered for ALL authenticated pages so the search +
notifications + user-menu chrome is consistent everywhere
(operator decision § 7.3 of the umbrella convoy: "sweep to
all authenticated pages in this convoy"). Unauthenticated
visitors see no top bar — public landing has its own
marketing header. The legacy `showSearch` prop is honored
via fall-through but no longer drives visibility; it can
be removed in a cleanup convoy along with the dead
`searchQuery` state. */}
{user && (
setIsCommandPaletteOpen(true)}
/>
)}
{/* Page Content */}
{children}