Three console issues observed while idle on an offer:
1. Heartbeat flooded the console with 401s. The lock-keepalive interval
fired apiFetch(...).catch(() => {}) every 10s; apiFetch RESOLVES with a
401 (it doesn't throw), so .catch never saw it and the interval never
stopped. Once the refresh token had also expired, every tick 401'd
forever. Now we inspect the response and clearInterval on a 401 (which
only occurs after a failed refresh = session truly gone); the healthy
path still returns 200 and keeps ticking (verified in Chrome).
2. Duplicate React key "item-1" in the line-items table. The form/items/
sections useState initializers restored the create-mode draft
UNCONDITIONALLY — even in edit mode — and reused the draft's stale
item _key values while itemKeyCounter restarts at 0 each load, so an
added row collided. Now the draft is only restored when !isEdit, and
restored items are re-keyed with fresh unique _keys.
3. framer-motion "motion() is deprecated" warning — PageEnter used the
deprecated motion(Box) factory; switched to motion.create(Box).
tsc -b --noEmit, npm run build, vitest 152/152 clean. Console verified
clean in Chrome (only benign dev notices remain).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import { Children, isValidElement, type ReactNode } from "react";
|
|
import { motion, useReducedMotion, type Variants } from "framer-motion";
|
|
import Box from "@mui/material/Box";
|
|
import type { SxProps, Theme } from "@mui/material/styles";
|
|
|
|
const MotionBox = motion.create(Box);
|
|
|
|
const container: Variants = {
|
|
hidden: {},
|
|
show: { transition: { staggerChildren: 0.06, delayChildren: 0.04 } },
|
|
};
|
|
|
|
const itemVariant: Variants = {
|
|
hidden: { opacity: 0, y: 16 },
|
|
show: {
|
|
opacity: 1,
|
|
y: 0,
|
|
transition: { duration: 0.45, ease: [0.16, 1, 0.3, 1] },
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Consistent page entrance. Use as a page's OUTERMOST wrapper (in place of the
|
|
* page's root `<Box>`): each top-level child rises + fades in, staggered, so the
|
|
* whole page comes in the same way everywhere — no element appears instantly.
|
|
* Wrapping each child in a block `motion.div` is layout-safe for the vertical
|
|
* section stacks our pages use (header → filters → card/table); nested grids/
|
|
* flex inside a section are untouched. Honors prefers-reduced-motion.
|
|
*/
|
|
export default function PageEnter({
|
|
children,
|
|
sx,
|
|
}: {
|
|
children: ReactNode;
|
|
sx?: SxProps<Theme>;
|
|
}) {
|
|
const reduce = useReducedMotion();
|
|
if (reduce) return <Box sx={sx}>{children}</Box>;
|
|
|
|
return (
|
|
<MotionBox variants={container} initial="hidden" animate="show" sx={sx}>
|
|
{Children.map(children, (child) =>
|
|
isValidElement(child) ? (
|
|
<motion.div variants={itemVariant}>{child}</motion.div>
|
|
) : (
|
|
child
|
|
),
|
|
)}
|
|
</MotionBox>
|
|
);
|
|
}
|