fix: react-doctor errors → 0 errors, 1649 warnings (46/100)
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
This commit is contained in:
@@ -164,10 +164,7 @@ function HighlightedText({ text, query }: { text: string; query: string }) {
|
||||
|
||||
export default function CommandPalette() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [selected, setSelected] = useState(0);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const router = useRouter();
|
||||
const [toggleCount, setToggleCount] = useState(0);
|
||||
|
||||
// Open/close on Cmd+K / Ctrl+K. Toggle so the same shortcut closes it.
|
||||
useEffect(() => {
|
||||
@@ -175,66 +172,58 @@ export default function CommandPalette() {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
|
||||
e.preventDefault();
|
||||
setOpen((o) => !o);
|
||||
setToggleCount((c) => c + 1);
|
||||
}
|
||||
}
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
return () => document.removeEventListener("keydown", onKeyDown);
|
||||
}, []);
|
||||
|
||||
// Reset on open, focus the input, and lock body scroll.
|
||||
if (!open) return null;
|
||||
return <CommandPaletteDialog key={toggleCount} onClose={() => setOpen(false)} />;
|
||||
}
|
||||
|
||||
function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [selected, setSelected] = useState(0);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const router = useRouter();
|
||||
|
||||
// Lock body scroll while open; focus the input on next frame.
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
document.body.style.overflow = "";
|
||||
return;
|
||||
}
|
||||
setQuery("");
|
||||
setSelected(0);
|
||||
document.body.style.overflow = "hidden";
|
||||
// Defer focus to next frame so the input is mounted and the modal
|
||||
// animation has started (keeps the focus ring from flickering in).
|
||||
const raf = requestAnimationFrame(() => inputRef.current?.focus());
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [open]);
|
||||
}, []);
|
||||
|
||||
// Global Escape while open (covers the case where focus is not on input).
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setOpen(false);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
document.addEventListener("keydown", onKey);
|
||||
return () => document.removeEventListener("keydown", onKey);
|
||||
}, [open]);
|
||||
}, [onClose]);
|
||||
|
||||
// Filtering
|
||||
const results = useMemo(() => filterEntries(query), [query]);
|
||||
|
||||
// Reset selection on every query change so the top result is always active.
|
||||
useEffect(() => {
|
||||
setSelected(0);
|
||||
}, [query]);
|
||||
|
||||
// Clamp selection to results length
|
||||
useEffect(() => {
|
||||
if (selected >= results.length && results.length > 0) {
|
||||
setSelected(results.length - 1);
|
||||
} else if (results.length === 0) {
|
||||
setSelected(0);
|
||||
}
|
||||
}, [results, selected]);
|
||||
// Always render a valid selection index (clamped to results length).
|
||||
const safeSelected =
|
||||
results.length === 0 ? 0 : Math.min(selected, results.length - 1);
|
||||
|
||||
const navigate = useCallback(
|
||||
(href: string) => {
|
||||
setOpen(false);
|
||||
onClose();
|
||||
router.push(href);
|
||||
},
|
||||
[router]
|
||||
[router, onClose]
|
||||
);
|
||||
|
||||
// In-input keyboard nav. Attached to the input itself so it works whether
|
||||
@@ -250,19 +239,17 @@ export default function CommandPalette() {
|
||||
setSelected((s) => (s - 1 + results.length) % results.length);
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const entry = results[selected];
|
||||
const entry = results[safeSelected];
|
||||
if (entry) navigate(entry.href);
|
||||
}
|
||||
};
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Command palette"
|
||||
onClick={() => setOpen(false)}
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
@@ -277,10 +264,6 @@ export default function CommandPalette() {
|
||||
animation: "cp-fade-in 180ms ease-out",
|
||||
}}
|
||||
>
|
||||
{/* Local keyframes — scoped via the dialog's runtime so they don't
|
||||
leak into the global stylesheet. The panel now fades in without
|
||||
the scale(0.98) start (removed — the slight scale combined with
|
||||
the backdrop fade read as a "pop" when the palette opened). */}
|
||||
<style>{`
|
||||
@keyframes cp-fade-in {
|
||||
from { opacity: 0; }
|
||||
@@ -348,6 +331,16 @@ export default function CommandPalette() {
|
||||
color: "var(--admin-text-primary)",
|
||||
fontFamily: "inherit",
|
||||
}}
|
||||
// Restore the focus ring only for keyboard users. Mouse clicks
|
||||
// hide it; tabbing into the input keeps a visible outline.
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.outline = "2px solid var(--admin-accent)";
|
||||
e.currentTarget.style.outlineOffset = "2px";
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.outline = "none";
|
||||
e.currentTarget.style.outlineOffset = "0";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -361,15 +354,6 @@ export default function CommandPalette() {
|
||||
padding: "0.375rem",
|
||||
}}
|
||||
>
|
||||
{/*
|
||||
TODO (next pass): recent items
|
||||
- Persist a small ring buffer of last-visited entries in localStorage
|
||||
under "rc-cmdk-recent" (cap at 5).
|
||||
- Read on mount, render as a "Recent" section above "Pages" when
|
||||
present and the query is empty.
|
||||
- Update on `navigate()` after a successful route push.
|
||||
Skipped for v1 per the design spec.
|
||||
*/}
|
||||
{results.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
@@ -384,7 +368,7 @@ export default function CommandPalette() {
|
||||
) : (
|
||||
results.map((entry, i) => {
|
||||
const Icon = ICON_MAP[entry.iconName] ?? Search;
|
||||
const isSelected = i === selected;
|
||||
const isSelected = i === safeSelected;
|
||||
return (
|
||||
<button
|
||||
key={entry.id}
|
||||
|
||||
Reference in New Issue
Block a user