backend integration started
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
import { Link, NavLink, Outlet, useLocation } from 'react-router-dom';
|
||||
import { Bell, ChevronDown, ChevronLeft, LogOut, Menu, Search, X } from 'lucide-react';
|
||||
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
||||
import { ChevronDown, ChevronLeft, LogOut, Menu, X } from 'lucide-react';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useAuth } from '@/auth/AuthContext';
|
||||
import { ROLE_LABEL } from '@/auth/roles';
|
||||
@@ -87,29 +88,12 @@ export function AppShell({
|
||||
const { user, signOut } = useAuth();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [isSearchFocused, setIsSearchFocused] = useState(false);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isAssistantOpen, setIsAssistantOpen] = useState(true);
|
||||
const [isNavOpen, setIsNavOpen] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// ⌘K / Ctrl-K focuses search, the same global shortcut the reference uses.
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
searchRef.current?.focus();
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, []);
|
||||
|
||||
const isSearchWide = isSearchFocused || query !== '';
|
||||
|
||||
// Escape closes the account menu. A menu that can only be dismissed by
|
||||
// finding the trigger again is a trap for anyone on a keyboard, and this one
|
||||
// sits over the page rather than beside it.
|
||||
@@ -181,6 +165,7 @@ export function AppShell({
|
||||
/>
|
||||
</Link>
|
||||
|
||||
|
||||
{/* Below md the scope control moves into the navigation sheet. On a
|
||||
390px phone the logo, the selector and the right-hand cluster add
|
||||
up to 503px and push the header 113px past the viewport, so the
|
||||
@@ -243,75 +228,21 @@ export function AppShell({
|
||||
</nav>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
|
||||
{/* Search: 176px at rest, 256px once focused or filled. */}
|
||||
<form
|
||||
role="search"
|
||||
className="show-from-md"
|
||||
onSubmit={(event) => event.preventDefault()}
|
||||
style={{
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
width: isSearchWide ? 256 : 176,
|
||||
transition: 'width .2s cubic-bezier(.16,1,.3,1)',
|
||||
}}
|
||||
>
|
||||
<Search
|
||||
size={14}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 10,
|
||||
color: 'var(--color-ink-4)',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
ref={searchRef}
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
onFocus={() => setIsSearchFocused(true)}
|
||||
onBlur={() => setIsSearchFocused(false)}
|
||||
placeholder="Search"
|
||||
aria-label="Search the console"
|
||||
style={{
|
||||
height: 32,
|
||||
width: '100%',
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${isSearchFocused ? 'var(--color-brand)' : 'var(--color-line)'}`,
|
||||
background: isSearchFocused
|
||||
? 'var(--color-surface)'
|
||||
: 'var(--color-surface-subtle)',
|
||||
paddingLeft: 32,
|
||||
paddingRight: 40,
|
||||
fontSize: 13,
|
||||
color: 'var(--color-ink-1)',
|
||||
outline: 'none',
|
||||
transition: 'border-color .2s, background .2s',
|
||||
boxShadow: isSearchFocused ? '0 0 0 2px rgba(102,37,130,.15)' : 'none',
|
||||
}}
|
||||
/>
|
||||
<kbd
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
borderRadius: 4,
|
||||
border: '1px solid var(--color-line)',
|
||||
background: 'var(--color-surface)',
|
||||
padding: '0 4px',
|
||||
fontSize: 10,
|
||||
fontWeight: 500,
|
||||
color: 'var(--color-ink-4)',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
⌘K
|
||||
</kbd>
|
||||
</form>
|
||||
{/* No global search.
|
||||
|
||||
There was a search box here with `⌘K` on it, and its `onSubmit`
|
||||
was `preventDefault()` and nothing else — it advertised a
|
||||
console-wide search that did not exist and had no endpoint
|
||||
behind it. The per-page search boxes on Sales, Users, Stores and
|
||||
the catalogue are real and stay. A control that looks like it
|
||||
works costs more trust than a missing one. */}
|
||||
|
||||
{headerActions}
|
||||
|
||||
<IconButton label="Notifications, 2 unread" hasUnread>
|
||||
<Bell size={16} />
|
||||
</IconButton>
|
||||
{/* No notification bell. It was labelled "2 unread" with the dot
|
||||
painted unconditionally, for every user on every page, forever —
|
||||
and it had no click handler and no notifications endpoint behind
|
||||
it anywhere in the API. */}
|
||||
|
||||
{/* Assistant button moved to floating pill */}
|
||||
|
||||
@@ -473,7 +404,14 @@ export function AppShell({
|
||||
row from md where it becomes a side column. */}
|
||||
<div className="admin-body app-gutter">
|
||||
<main style={{ minWidth: 0, flex: 1, padding: '24px 0 48px' }}>
|
||||
<Outlet />
|
||||
{/* Scoped to the page, not the shell: a page that throws should leave
|
||||
the nav, the account menu and the workspace switcher usable, so
|
||||
you can walk to a screen that works instead of reloading blind.
|
||||
Keyed by pathname so navigating away clears a caught error —
|
||||
without that, one broken page latches the whole outlet. */}
|
||||
<ErrorBoundary key={pathname} area="this page">
|
||||
<Outlet />
|
||||
</ErrorBoundary>
|
||||
</main>
|
||||
|
||||
{isAssistantOpen || isMobile ? (
|
||||
|
||||
Reference in New Issue
Block a user