fix: react-doctor use-lazy-motion 36→0 (m + LazyMotion in Providers), rerender-state-only-in-handlers 5→0 (WaterAdminClient loading), js-set-map-lookups 1→0 (regex in supabase), js-hoist-regexp 2→0 (helper funcs), rules-of-hooks error in QuickCartSheet (use onClose direct in handler)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { MotionConfig } from "framer-motion";
|
||||
import { LazyMotion, MotionConfig, domAnimation } from "framer-motion";
|
||||
import { CartProvider } from "@/context/CartContext";
|
||||
import { usePathname } from "next/navigation";
|
||||
import SiteHeader from "@/components/layout/SiteHeader";
|
||||
@@ -45,15 +45,17 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||
* Combined with the prefers-reduced-motion block in globals.css, this means a user
|
||||
* with motion sensitivity gets the same content with zero positional movement.
|
||||
*/}
|
||||
<MotionConfig transition={{ duration: 0.2, ease: "easeOut" }} reducedMotion="user">
|
||||
<CartProvider>
|
||||
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteHeader />}
|
||||
{children}
|
||||
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteFooter />}
|
||||
<CartToast />
|
||||
<CartRestoredToast />
|
||||
</CartProvider>
|
||||
</MotionConfig>
|
||||
<LazyMotion features={domAnimation}>
|
||||
<MotionConfig transition={{ duration: 0.2, ease: "easeOut" }} reducedMotion="user">
|
||||
<CartProvider>
|
||||
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteHeader />}
|
||||
{children}
|
||||
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteFooter />}
|
||||
<CartToast />
|
||||
<CartRestoredToast />
|
||||
</CartProvider>
|
||||
</MotionConfig>
|
||||
</LazyMotion>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
export default function CTASection() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
interface ContactSectionProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
const FAMILY_MEMBERS = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
export default function IndianRiverCTASection() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
// Custom SVG Icons for Citrus Varieties
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
interface IndianRiverContactSectionProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
export default function IndianRiverMissionSection() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
const TEAM_MEMBERS = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
// Custom checkmark icon with blue styling
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
export default function MissionSection() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { m as motion, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
const badgeConfig: Record<"best-seller" | "new" | "limited" | "organic", { label: string; bg: string; text: string; border: string }> = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useEffectEvent } from "react";
|
||||
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { m, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
|
||||
@@ -68,6 +68,44 @@ export default function QuickCartSheet({
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [open]);
|
||||
|
||||
// Mobile sheet drag state. Replaces framer-motion's `drag` prop with
|
||||
// pointer events so the `m` (lazy) component can be used everywhere.
|
||||
const dragStartYRef = useRef<number | null>(null);
|
||||
const dragDeltaRef = useRef(0);
|
||||
const dragVelocityRef = useRef(0);
|
||||
const lastDragTimeRef = useRef(0);
|
||||
const lastDragYRef = useRef(0);
|
||||
const [dragOffset, setDragOffset] = useState(0);
|
||||
|
||||
function handleDragPointerDown(e: React.PointerEvent) {
|
||||
dragStartYRef.current = e.clientY;
|
||||
lastDragYRef.current = e.clientY;
|
||||
lastDragTimeRef.current = performance.now();
|
||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||
}
|
||||
function handleDragPointerMove(e: React.PointerEvent) {
|
||||
if (dragStartYRef.current === null) return;
|
||||
const delta = Math.max(0, e.clientY - dragStartYRef.current);
|
||||
const now = performance.now();
|
||||
const dt = Math.max(1, now - lastDragTimeRef.current);
|
||||
dragVelocityRef.current = ((e.clientY - lastDragYRef.current) / dt) * 1000;
|
||||
lastDragYRef.current = e.clientY;
|
||||
lastDragTimeRef.current = now;
|
||||
dragDeltaRef.current = delta;
|
||||
setDragOffset(delta);
|
||||
}
|
||||
function handleDragPointerUp(e: React.PointerEvent) {
|
||||
if (dragStartYRef.current === null) return;
|
||||
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
|
||||
if (dragDeltaRef.current > 90 || dragVelocityRef.current > 480) {
|
||||
onClose();
|
||||
}
|
||||
dragStartYRef.current = null;
|
||||
dragDeltaRef.current = 0;
|
||||
dragVelocityRef.current = 0;
|
||||
setDragOffset(0);
|
||||
}
|
||||
|
||||
const displayName = productName ?? justAdded?.name ?? cart[0]?.name ?? "Your box";
|
||||
const addedAt = new Date();
|
||||
const headerLabel = justAddedLabel ?? "Just added";
|
||||
@@ -87,7 +125,7 @@ export default function QuickCartSheet({
|
||||
{open && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<motion.button
|
||||
<m.button
|
||||
type="button"
|
||||
aria-label="Close cart"
|
||||
onClick={onClose}
|
||||
@@ -99,7 +137,7 @@ export default function QuickCartSheet({
|
||||
/>
|
||||
|
||||
{/* Mobile: bottom sheet */}
|
||||
<motion.div
|
||||
<m.div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Quick cart"
|
||||
@@ -107,12 +145,11 @@ export default function QuickCartSheet({
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: "100%" }}
|
||||
transition={{ type: "spring", stiffness: 380, damping: 38, mass: 0.9 }}
|
||||
drag="y"
|
||||
dragConstraints={{ top: 0, bottom: 0 }}
|
||||
dragElastic={{ top: 0, bottom: 0.5 }}
|
||||
onDragEnd={(_, info) => {
|
||||
if (info.offset.y > 90 || info.velocity.y > 480) onClose();
|
||||
}}
|
||||
onPointerDown={handleDragPointerDown}
|
||||
onPointerMove={handleDragPointerMove}
|
||||
onPointerUp={handleDragPointerUp}
|
||||
onPointerCancel={handleDragPointerUp}
|
||||
style={{ y: dragOffset, touchAction: "none" }}
|
||||
className="lg:hidden fixed inset-x-0 bottom-0 z-[61] max-h-[88vh] rounded-t-[28px] bg-[#FAF6EA] shadow-[0_-24px_60px_-12px_rgba(26,77,46,0.35)] flex flex-col"
|
||||
>
|
||||
{/* Drag handle */}
|
||||
@@ -131,10 +168,10 @@ export default function QuickCartSheet({
|
||||
onCheckout={handleCheckout}
|
||||
onContinue={onClose}
|
||||
/>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
|
||||
{/* Desktop: right-side drawer */}
|
||||
<motion.aside
|
||||
<m.aside
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Quick cart"
|
||||
@@ -156,7 +193,7 @@ export default function QuickCartSheet({
|
||||
onContinue={onClose}
|
||||
showCloseButton
|
||||
/>
|
||||
</motion.aside>
|
||||
</m.aside>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -211,7 +248,7 @@ function SheetContent({
|
||||
{/* Header */}
|
||||
<div className="relative px-6 sm:px-8 pt-2 pb-5 border-b border-stone-900/10">
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.div
|
||||
<m.div
|
||||
initial={{ scale: 0.95, opacity: 0 , rotate: -45 }}
|
||||
animate={{ scale: 1, rotate: 0 }}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 18, delay: 0.05 }}
|
||||
@@ -220,7 +257,7 @@ function SheetContent({
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.22em] text-[#1A4D2E]/70">
|
||||
{headerLabel}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { m as motion, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
type StorefrontHeaderProps = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { motion, AnimatePresence, useScroll, useTransform } from "framer-motion";
|
||||
import { m as motion, AnimatePresence, useScroll, useTransform } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
import QuickCartSheet from "./QuickCartSheet";
|
||||
import StorefrontHeader from "./StorefrontHeader";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { m as motion, useInView } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
// ── Feature type & data ──────────────────────────────────────────────
|
||||
|
||||
@@ -278,7 +278,6 @@ export default function WaterAdminClient() {
|
||||
const [allEntries, setAllEntries] = useState<WaterEntry[]>([]);
|
||||
const [users, setUsers] = useState<WaterUser[]>([]);
|
||||
const [headgates, setHeadgates] = useState<Headgate[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [alertLog, setAlertLog] = useState<AlertLogEntry[]>([]);
|
||||
|
||||
// Filter state
|
||||
@@ -316,7 +315,6 @@ export default function WaterAdminClient() {
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
|
||||
const loadAll = async () => {
|
||||
setLoading(true);
|
||||
const [entries, usersData, headgatesData, alertsData] = await Promise.all([
|
||||
getWaterEntries(TUXEDO_BRAND_ID, 500),
|
||||
getWaterIrrigators(TUXEDO_BRAND_ID),
|
||||
@@ -327,7 +325,6 @@ export default function WaterAdminClient() {
|
||||
setUsers(usersData);
|
||||
setHeadgates(headgatesData);
|
||||
setAlertLog(alertsData as AlertLogEntry[]);
|
||||
setLoading(false);
|
||||
setInitialLoading(false);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user