backend integration started

This commit is contained in:
2026-08-26 18:37:28 +05:30
parent 517a99d577
commit 3545819be8
50 changed files with 3461 additions and 3241 deletions

View File

@@ -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 ? (