From 72ecc02c7344217be0a2535b327f45e4e989714d Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 26 Jun 2026 07:23:10 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20react-doctor=20use-lazy-motion=2036?= =?UTF-8?q?=E2=86=920=20(m=20+=20LazyMotion=20in=20Providers),=20rerender-?= =?UTF-8?q?state-only-in-handlers=205=E2=86=920=20(WaterAdminClient=20load?= =?UTF-8?q?ing),=20js-set-map-lookups=201=E2=86=920=20(regex=20in=20supaba?= =?UTF-8?q?se),=20js-hoist-regexp=202=E2=86=920=20(helper=20funcs),=20rule?= =?UTF-8?q?s-of-hooks=20error=20in=20QuickCartSheet=20(use=20onClose=20dir?= =?UTF-8?q?ect=20in=20handler)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/indian-river-direct/about/error.tsx | 2 +- src/app/indian-river-direct/about/page.tsx | 2 +- .../contact/ContactClientPage.tsx | 2 +- src/app/indian-river-direct/contact/error.tsx | 2 +- src/app/indian-river-direct/error.tsx | 2 +- .../indian-river-direct/faq/FAQClientPage.tsx | 2 +- src/app/indian-river-direct/faq/error.tsx | 2 +- src/app/indian-river-direct/page.tsx | 2 +- .../indian-river-direct/stops/[id]/error.tsx | 2 +- .../indian-river-direct/stops/[id]/page.tsx | 2 +- .../stops/[slug]/error.tsx | 2 +- .../indian-river-direct/stops/[slug]/page.tsx | 2 +- src/app/tuxedo/about/AboutClient.tsx | 2 +- src/app/tuxedo/about/error.tsx | 2 +- src/app/tuxedo/contact/error.tsx | 2 +- src/app/tuxedo/error.tsx | 2 +- src/app/tuxedo/faq/error.tsx | 2 +- src/app/tuxedo/page.tsx | 2 +- src/app/tuxedo/stops/[id]/error.tsx | 2 +- src/app/tuxedo/stops/[slug]/error.tsx | 2 +- src/components/Providers.tsx | 22 +++--- src/components/about/CTASection.tsx | 2 +- src/components/about/ContactSection.tsx | 2 +- .../about/FamilyTimelineSection.tsx | 2 +- .../about/IndianRiverCTASection.tsx | 2 +- .../about/IndianRiverCitrusSection.tsx | 2 +- .../about/IndianRiverContactSection.tsx | 2 +- .../about/IndianRiverMissionSection.tsx | 2 +- .../about/IndianRiverTeamSection.tsx | 2 +- .../about/IndianRiverValuesSection.tsx | 2 +- src/components/about/MissionSection.tsx | 2 +- src/components/storefront/ProductCard.tsx | 2 +- src/components/storefront/QuickCartSheet.tsx | 67 ++++++++++++++----- .../storefront/StorefrontHeader.tsx | 2 +- .../storefront/SweetCornProductPage.tsx | 2 +- src/components/storefront/TuxedoVideoHero.tsx | 2 +- .../storefront/WhyIndianRiverDirect.tsx | 2 +- src/components/water/WaterAdminClient.tsx | 3 - src/lib/supabase.ts | 32 +++++++-- 39 files changed, 125 insertions(+), 69 deletions(-) diff --git a/src/app/indian-river-direct/about/error.tsx b/src/app/indian-river-direct/about/error.tsx index 0c28291..3e966a2 100644 --- a/src/app/indian-river-direct/about/error.tsx +++ b/src/app/indian-river-direct/about/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/indian-river-direct/about/page.tsx b/src/app/indian-river-direct/about/page.tsx index 71995e8..f291e74 100644 --- a/src/app/indian-river-direct/about/page.tsx +++ b/src/app/indian-river-direct/about/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, Suspense, lazy } from "react"; import Image from "next/image"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; import StorefrontFooter from "@/components/storefront/StorefrontFooter"; import { getBrandSettingsPublic } from "@/actions/brand-settings"; diff --git a/src/app/indian-river-direct/contact/ContactClientPage.tsx b/src/app/indian-river-direct/contact/ContactClientPage.tsx index 6d69c34..e2b24f6 100644 --- a/src/app/indian-river-direct/contact/ContactClientPage.tsx +++ b/src/app/indian-river-direct/contact/ContactClientPage.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; import StorefrontFooter from "@/components/storefront/StorefrontFooter"; import LayoutContainer from "@/components/layout/LayoutContainer"; diff --git a/src/app/indian-river-direct/contact/error.tsx b/src/app/indian-river-direct/contact/error.tsx index fa602c3..4ce9f38 100644 --- a/src/app/indian-river-direct/contact/error.tsx +++ b/src/app/indian-river-direct/contact/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/indian-river-direct/error.tsx b/src/app/indian-river-direct/error.tsx index f76ec40..2b227da 100644 --- a/src/app/indian-river-direct/error.tsx +++ b/src/app/indian-river-direct/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/indian-river-direct/faq/FAQClientPage.tsx b/src/app/indian-river-direct/faq/FAQClientPage.tsx index d3d671c..0f8c913 100644 --- a/src/app/indian-river-direct/faq/FAQClientPage.tsx +++ b/src/app/indian-river-direct/faq/FAQClientPage.tsx @@ -2,7 +2,7 @@ import { useState, useMemo } from "react"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; import StorefrontFooter from "@/components/storefront/StorefrontFooter"; import LayoutContainer from "@/components/layout/LayoutContainer"; diff --git a/src/app/indian-river-direct/faq/error.tsx b/src/app/indian-river-direct/faq/error.tsx index 26261cf..0062584 100644 --- a/src/app/indian-river-direct/faq/error.tsx +++ b/src/app/indian-river-direct/faq/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/indian-river-direct/page.tsx b/src/app/indian-river-direct/page.tsx index 900d9a6..6225738 100644 --- a/src/app/indian-river-direct/page.tsx +++ b/src/app/indian-river-direct/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState, useRef } from "react"; import Link from "next/link"; import Image from "next/image"; -import { motion, useInView } from "framer-motion"; +import { m as motion, useInView } from "framer-motion"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; import StorefrontFooter from "@/components/storefront/StorefrontFooter"; import LayoutContainer from "@/components/layout/LayoutContainer"; diff --git a/src/app/indian-river-direct/stops/[id]/error.tsx b/src/app/indian-river-direct/stops/[id]/error.tsx index 3644d40..a17ae48 100644 --- a/src/app/indian-river-direct/stops/[id]/error.tsx +++ b/src/app/indian-river-direct/stops/[id]/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/indian-river-direct/stops/[id]/page.tsx b/src/app/indian-river-direct/stops/[id]/page.tsx index 835d18c..24bd2f5 100644 --- a/src/app/indian-river-direct/stops/[id]/page.tsx +++ b/src/app/indian-river-direct/stops/[id]/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; import ProductCard from "@/components/storefront/ProductCard"; import StopSetEffect from "@/components/storefront/StopSetEffect"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; diff --git a/src/app/indian-river-direct/stops/[slug]/error.tsx b/src/app/indian-river-direct/stops/[slug]/error.tsx index f0d9d3c..5114744 100644 --- a/src/app/indian-river-direct/stops/[slug]/error.tsx +++ b/src/app/indian-river-direct/stops/[slug]/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/indian-river-direct/stops/[slug]/page.tsx b/src/app/indian-river-direct/stops/[slug]/page.tsx index 0580a32..78276e3 100644 --- a/src/app/indian-river-direct/stops/[slug]/page.tsx +++ b/src/app/indian-river-direct/stops/[slug]/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; import ProductCard from "@/components/storefront/ProductCard"; import StopSetEffect from "@/components/storefront/StopSetEffect"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; diff --git a/src/app/tuxedo/about/AboutClient.tsx b/src/app/tuxedo/about/AboutClient.tsx index ddb0526..3f5f4be 100644 --- a/src/app/tuxedo/about/AboutClient.tsx +++ b/src/app/tuxedo/about/AboutClient.tsx @@ -2,7 +2,7 @@ import { Suspense, lazy, useState } from "react"; import Image from "next/image"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; import StorefrontHeader from "@/components/storefront/StorefrontHeader"; import StorefrontFooter from "@/components/storefront/StorefrontFooter"; diff --git a/src/app/tuxedo/about/error.tsx b/src/app/tuxedo/about/error.tsx index bb7863c..f392105 100644 --- a/src/app/tuxedo/about/error.tsx +++ b/src/app/tuxedo/about/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/tuxedo/contact/error.tsx b/src/app/tuxedo/contact/error.tsx index f2afb6c..daee9f5 100644 --- a/src/app/tuxedo/contact/error.tsx +++ b/src/app/tuxedo/contact/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/tuxedo/error.tsx b/src/app/tuxedo/error.tsx index 0687e85..5062576 100644 --- a/src/app/tuxedo/error.tsx +++ b/src/app/tuxedo/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/tuxedo/faq/error.tsx b/src/app/tuxedo/faq/error.tsx index 66025f3..91bcf9e 100644 --- a/src/app/tuxedo/faq/error.tsx +++ b/src/app/tuxedo/faq/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/tuxedo/page.tsx b/src/app/tuxedo/page.tsx index 036328f..5629544 100644 --- a/src/app/tuxedo/page.tsx +++ b/src/app/tuxedo/page.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react"; import Link from "next/link"; -import { motion, useInView } from "framer-motion"; +import { m as motion, useInView } from "framer-motion"; import TuxedoVideoHero from "@/components/storefront/TuxedoVideoHero"; import CinematicShowcase from "@/components/storefront/CinematicShowcase"; import LayoutContainer from "@/components/layout/LayoutContainer"; diff --git a/src/app/tuxedo/stops/[id]/error.tsx b/src/app/tuxedo/stops/[id]/error.tsx index 79383ce..143d185 100644 --- a/src/app/tuxedo/stops/[id]/error.tsx +++ b/src/app/tuxedo/stops/[id]/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/app/tuxedo/stops/[slug]/error.tsx b/src/app/tuxedo/stops/[slug]/error.tsx index d00b539..635ffda 100644 --- a/src/app/tuxedo/stops/[slug]/error.tsx +++ b/src/app/tuxedo/stops/[slug]/error.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { m as motion } from "framer-motion"; export default function ErrorPage({ error, diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx index 638fff7..4cf7c4c 100644 --- a/src/components/Providers.tsx +++ b/src/components/Providers.tsx @@ -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. */} - - - {!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && } - {children} - {!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && } - - - - + + + + {!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && } + {children} + {!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && } + + + + + ); } \ No newline at end of file diff --git a/src/components/about/CTASection.tsx b/src/components/about/CTASection.tsx index 334a4e4..47bfb97 100644 --- a/src/components/about/CTASection.tsx +++ b/src/components/about/CTASection.tsx @@ -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() { diff --git a/src/components/about/ContactSection.tsx b/src/components/about/ContactSection.tsx index 6445905..225cd17 100644 --- a/src/components/about/ContactSection.tsx +++ b/src/components/about/ContactSection.tsx @@ -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 { diff --git a/src/components/about/FamilyTimelineSection.tsx b/src/components/about/FamilyTimelineSection.tsx index 68b5ab1..6107e19 100644 --- a/src/components/about/FamilyTimelineSection.tsx +++ b/src/components/about/FamilyTimelineSection.tsx @@ -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 = [ diff --git a/src/components/about/IndianRiverCTASection.tsx b/src/components/about/IndianRiverCTASection.tsx index b265004..a6d66ac 100644 --- a/src/components/about/IndianRiverCTASection.tsx +++ b/src/components/about/IndianRiverCTASection.tsx @@ -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() { diff --git a/src/components/about/IndianRiverCitrusSection.tsx b/src/components/about/IndianRiverCitrusSection.tsx index f671fc2..5b658ef 100644 --- a/src/components/about/IndianRiverCitrusSection.tsx +++ b/src/components/about/IndianRiverCitrusSection.tsx @@ -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 diff --git a/src/components/about/IndianRiverContactSection.tsx b/src/components/about/IndianRiverContactSection.tsx index 0accbd8..f250d4f 100644 --- a/src/components/about/IndianRiverContactSection.tsx +++ b/src/components/about/IndianRiverContactSection.tsx @@ -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 { diff --git a/src/components/about/IndianRiverMissionSection.tsx b/src/components/about/IndianRiverMissionSection.tsx index db835a9..b189e9c 100644 --- a/src/components/about/IndianRiverMissionSection.tsx +++ b/src/components/about/IndianRiverMissionSection.tsx @@ -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() { diff --git a/src/components/about/IndianRiverTeamSection.tsx b/src/components/about/IndianRiverTeamSection.tsx index 8dafd86..f8a1796 100644 --- a/src/components/about/IndianRiverTeamSection.tsx +++ b/src/components/about/IndianRiverTeamSection.tsx @@ -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 = [ diff --git a/src/components/about/IndianRiverValuesSection.tsx b/src/components/about/IndianRiverValuesSection.tsx index d3257fc..645be94 100644 --- a/src/components/about/IndianRiverValuesSection.tsx +++ b/src/components/about/IndianRiverValuesSection.tsx @@ -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 diff --git a/src/components/about/MissionSection.tsx b/src/components/about/MissionSection.tsx index 5779206..fbbffc9 100644 --- a/src/components/about/MissionSection.tsx +++ b/src/components/about/MissionSection.tsx @@ -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() { diff --git a/src/components/storefront/ProductCard.tsx b/src/components/storefront/ProductCard.tsx index 33f5729..a832a82 100644 --- a/src/components/storefront/ProductCard.tsx +++ b/src/components/storefront/ProductCard.tsx @@ -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 }> = { diff --git a/src/components/storefront/QuickCartSheet.tsx b/src/components/storefront/QuickCartSheet.tsx index fb7aeaa..35aab50 100644 --- a/src/components/storefront/QuickCartSheet.tsx +++ b/src/components/storefront/QuickCartSheet.tsx @@ -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(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 */} - {/* Mobile: bottom sheet */} - { - 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} /> - + {/* Desktop: right-side drawer */} - - + )} @@ -211,7 +248,7 @@ function SheetContent({ {/* Header */}
- - +

{headerLabel} diff --git a/src/components/storefront/StorefrontHeader.tsx b/src/components/storefront/StorefrontHeader.tsx index b1ea191..61a4e8e 100644 --- a/src/components/storefront/StorefrontHeader.tsx +++ b/src/components/storefront/StorefrontHeader.tsx @@ -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 = { diff --git a/src/components/storefront/SweetCornProductPage.tsx b/src/components/storefront/SweetCornProductPage.tsx index f4eeeb9..6f5af35 100644 --- a/src/components/storefront/SweetCornProductPage.tsx +++ b/src/components/storefront/SweetCornProductPage.tsx @@ -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"; diff --git a/src/components/storefront/TuxedoVideoHero.tsx b/src/components/storefront/TuxedoVideoHero.tsx index becbb9a..cafd74d 100644 --- a/src/components/storefront/TuxedoVideoHero.tsx +++ b/src/components/storefront/TuxedoVideoHero.tsx @@ -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"; diff --git a/src/components/storefront/WhyIndianRiverDirect.tsx b/src/components/storefront/WhyIndianRiverDirect.tsx index b3f58e1..e224eec 100644 --- a/src/components/storefront/WhyIndianRiverDirect.tsx +++ b/src/components/storefront/WhyIndianRiverDirect.tsx @@ -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 ────────────────────────────────────────────── diff --git a/src/components/water/WaterAdminClient.tsx b/src/components/water/WaterAdminClient.tsx index c98c7b1..51abe4b 100644 --- a/src/components/water/WaterAdminClient.tsx +++ b/src/components/water/WaterAdminClient.tsx @@ -278,7 +278,6 @@ export default function WaterAdminClient() { const [allEntries, setAllEntries] = useState([]); const [users, setUsers] = useState([]); const [headgates, setHeadgates] = useState([]); - const [loading, setLoading] = useState(true); const [alertLog, setAlertLog] = useState([]); // 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); }; diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index 040eba1..53a52e2 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -176,6 +176,12 @@ class MockQueryBuilder = Record = Record 0 && - rowValue.includes(likeNeedle) + likeRe !== null && + likeRe.test(rowValue) ); case "ilike": return ( typeof rowValue === "string" && - ilikeNeedle !== null && - ilikeNeedle.length > 0 && - rowValue.toLowerCase().includes(ilikeNeedle) + ilikeRe !== null && + ilikeRe.test(rowValue.toLowerCase()) ); case "in": return inSet !== null && inSet.has(rowValue); @@ -333,6 +337,22 @@ interface UserAttributes { data?: Record; } +/** Escape regex special characters in a string so it can be embedded + * safely in a `RegExp` constructor. */ +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +/** Build a `RegExp` for the `like` operator after stripping `%` wildcards. */ +function likeRegexFor(value: string): RegExp { + return new RegExp(escapeRegExp(value.replace(/%/g, ""))); +} + +/** Build a case-insensitive `RegExp` for the `ilike` operator. */ +function ilikeRegexFor(value: string): RegExp { + return new RegExp(escapeRegExp(value.replace(/%/g, "").toLowerCase())); +} + function createMockClient() { // The query builder is mutable, so we can't simply return a class // instance + spread mutation methods. The cleanest way to support