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:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState, Suspense, lazy } from "react";
|
import { useEffect, useState, Suspense, lazy } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
||||||
import { getBrandSettingsPublic } from "@/actions/brand-settings";
|
import { getBrandSettingsPublic } from "@/actions/brand-settings";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
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 StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import ProductCard from "@/components/storefront/ProductCard";
|
import ProductCard from "@/components/storefront/ProductCard";
|
||||||
import StopSetEffect from "@/components/storefront/StopSetEffect";
|
import StopSetEffect from "@/components/storefront/StopSetEffect";
|
||||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import ProductCard from "@/components/storefront/ProductCard";
|
import ProductCard from "@/components/storefront/ProductCard";
|
||||||
import StopSetEffect from "@/components/storefront/StopSetEffect";
|
import StopSetEffect from "@/components/storefront/StopSetEffect";
|
||||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Suspense, lazy, useState } from "react";
|
import { Suspense, lazy, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||||
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import Link from "next/link";
|
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 TuxedoVideoHero from "@/components/storefront/TuxedoVideoHero";
|
||||||
import CinematicShowcase from "@/components/storefront/CinematicShowcase";
|
import CinematicShowcase from "@/components/storefront/CinematicShowcase";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
|
|
||||||
export default function ErrorPage({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
import { MotionConfig } from "framer-motion";
|
import { LazyMotion, MotionConfig, domAnimation } from "framer-motion";
|
||||||
import { CartProvider } from "@/context/CartContext";
|
import { CartProvider } from "@/context/CartContext";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import SiteHeader from "@/components/layout/SiteHeader";
|
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
|
* 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.
|
* with motion sensitivity gets the same content with zero positional movement.
|
||||||
*/}
|
*/}
|
||||||
<MotionConfig transition={{ duration: 0.2, ease: "easeOut" }} reducedMotion="user">
|
<LazyMotion features={domAnimation}>
|
||||||
<CartProvider>
|
<MotionConfig transition={{ duration: 0.2, ease: "easeOut" }} reducedMotion="user">
|
||||||
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteHeader />}
|
<CartProvider>
|
||||||
{children}
|
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteHeader />}
|
||||||
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteFooter />}
|
{children}
|
||||||
<CartToast />
|
{!isBrandRoute && !isLandingPage && !isAuthPage && !isAdminRoute && !isStandalonePage && <SiteFooter />}
|
||||||
<CartRestoredToast />
|
<CartToast />
|
||||||
</CartProvider>
|
<CartRestoredToast />
|
||||||
</MotionConfig>
|
</CartProvider>
|
||||||
|
</MotionConfig>
|
||||||
|
</LazyMotion>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
export default function CTASection() {
|
export default function CTASection() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
interface ContactSectionProps {
|
interface ContactSectionProps {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
const FAMILY_MEMBERS = [
|
const FAMILY_MEMBERS = [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
export default function IndianRiverCTASection() {
|
export default function IndianRiverCTASection() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
// Custom SVG Icons for Citrus Varieties
|
// Custom SVG Icons for Citrus Varieties
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
interface IndianRiverContactSectionProps {
|
interface IndianRiverContactSectionProps {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
export default function IndianRiverMissionSection() {
|
export default function IndianRiverMissionSection() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
const TEAM_MEMBERS = [
|
const TEAM_MEMBERS = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
// Custom checkmark icon with blue styling
|
// Custom checkmark icon with blue styling
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
export default function MissionSection() {
|
export default function MissionSection() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { m as motion, AnimatePresence } from "framer-motion";
|
||||||
import { useCart } from "@/context/CartContext";
|
import { useCart } from "@/context/CartContext";
|
||||||
|
|
||||||
const badgeConfig: Record<"best-seller" | "new" | "limited" | "organic", { label: string; bg: string; text: string; border: string }> = {
|
const badgeConfig: Record<"best-seller" | "new" | "limited" | "organic", { label: string; bg: string; text: string; border: string }> = {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useEffectEvent } from "react";
|
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { m, AnimatePresence } from "framer-motion";
|
||||||
import { useCart } from "@/context/CartContext";
|
import { useCart } from "@/context/CartContext";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
|
|
||||||
@@ -68,6 +68,44 @@ export default function QuickCartSheet({
|
|||||||
return () => window.removeEventListener("keydown", onKey);
|
return () => window.removeEventListener("keydown", onKey);
|
||||||
}, [open]);
|
}, [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 displayName = productName ?? justAdded?.name ?? cart[0]?.name ?? "Your box";
|
||||||
const addedAt = new Date();
|
const addedAt = new Date();
|
||||||
const headerLabel = justAddedLabel ?? "Just added";
|
const headerLabel = justAddedLabel ?? "Just added";
|
||||||
@@ -87,7 +125,7 @@ export default function QuickCartSheet({
|
|||||||
{open && (
|
{open && (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<motion.button
|
<m.button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Close cart"
|
aria-label="Close cart"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -99,7 +137,7 @@ export default function QuickCartSheet({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Mobile: bottom sheet */}
|
{/* Mobile: bottom sheet */}
|
||||||
<motion.div
|
<m.div
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-label="Quick cart"
|
aria-label="Quick cart"
|
||||||
@@ -107,12 +145,11 @@ export default function QuickCartSheet({
|
|||||||
animate={{ y: 0 }}
|
animate={{ y: 0 }}
|
||||||
exit={{ y: "100%" }}
|
exit={{ y: "100%" }}
|
||||||
transition={{ type: "spring", stiffness: 380, damping: 38, mass: 0.9 }}
|
transition={{ type: "spring", stiffness: 380, damping: 38, mass: 0.9 }}
|
||||||
drag="y"
|
onPointerDown={handleDragPointerDown}
|
||||||
dragConstraints={{ top: 0, bottom: 0 }}
|
onPointerMove={handleDragPointerMove}
|
||||||
dragElastic={{ top: 0, bottom: 0.5 }}
|
onPointerUp={handleDragPointerUp}
|
||||||
onDragEnd={(_, info) => {
|
onPointerCancel={handleDragPointerUp}
|
||||||
if (info.offset.y > 90 || info.velocity.y > 480) onClose();
|
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"
|
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 */}
|
{/* Drag handle */}
|
||||||
@@ -131,10 +168,10 @@ export default function QuickCartSheet({
|
|||||||
onCheckout={handleCheckout}
|
onCheckout={handleCheckout}
|
||||||
onContinue={onClose}
|
onContinue={onClose}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</m.div>
|
||||||
|
|
||||||
{/* Desktop: right-side drawer */}
|
{/* Desktop: right-side drawer */}
|
||||||
<motion.aside
|
<m.aside
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-label="Quick cart"
|
aria-label="Quick cart"
|
||||||
@@ -156,7 +193,7 @@ export default function QuickCartSheet({
|
|||||||
onContinue={onClose}
|
onContinue={onClose}
|
||||||
showCloseButton
|
showCloseButton
|
||||||
/>
|
/>
|
||||||
</motion.aside>
|
</m.aside>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
@@ -211,7 +248,7 @@ function SheetContent({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="relative px-6 sm:px-8 pt-2 pb-5 border-b border-stone-900/10">
|
<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">
|
<div className="flex items-center gap-3">
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ scale: 0.95, opacity: 0 , rotate: -45 }}
|
initial={{ scale: 0.95, opacity: 0 , rotate: -45 }}
|
||||||
animate={{ scale: 1, rotate: 0 }}
|
animate={{ scale: 1, rotate: 0 }}
|
||||||
transition={{ type: "spring", stiffness: 500, damping: 18, delay: 0.05 }}
|
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}>
|
<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" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||||
</svg>
|
</svg>
|
||||||
</motion.div>
|
</m.div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-[10px] font-bold uppercase tracking-[0.22em] text-[#1A4D2E]/70">
|
<p className="text-[10px] font-bold uppercase tracking-[0.22em] text-[#1A4D2E]/70">
|
||||||
{headerLabel}
|
{headerLabel}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { m as motion, AnimatePresence } from "framer-motion";
|
||||||
import { useCart } from "@/context/CartContext";
|
import { useCart } from "@/context/CartContext";
|
||||||
|
|
||||||
type StorefrontHeaderProps = {
|
type StorefrontHeaderProps = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
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 { useCart } from "@/context/CartContext";
|
||||||
import QuickCartSheet from "./QuickCartSheet";
|
import QuickCartSheet from "./QuickCartSheet";
|
||||||
import StorefrontHeader from "./StorefrontHeader";
|
import StorefrontHeader from "./StorefrontHeader";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { m as motion } from "framer-motion";
|
||||||
import { gsap } from "gsap";
|
import { gsap } from "gsap";
|
||||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { motion, useInView } from "framer-motion";
|
import { m as motion, useInView } from "framer-motion";
|
||||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||||
|
|
||||||
// ── Feature type & data ──────────────────────────────────────────────
|
// ── Feature type & data ──────────────────────────────────────────────
|
||||||
|
|||||||
@@ -278,7 +278,6 @@ export default function WaterAdminClient() {
|
|||||||
const [allEntries, setAllEntries] = useState<WaterEntry[]>([]);
|
const [allEntries, setAllEntries] = useState<WaterEntry[]>([]);
|
||||||
const [users, setUsers] = useState<WaterUser[]>([]);
|
const [users, setUsers] = useState<WaterUser[]>([]);
|
||||||
const [headgates, setHeadgates] = useState<Headgate[]>([]);
|
const [headgates, setHeadgates] = useState<Headgate[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [alertLog, setAlertLog] = useState<AlertLogEntry[]>([]);
|
const [alertLog, setAlertLog] = useState<AlertLogEntry[]>([]);
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
@@ -316,7 +315,6 @@ export default function WaterAdminClient() {
|
|||||||
const [initialLoading, setInitialLoading] = useState(true);
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
|
|
||||||
const loadAll = async () => {
|
const loadAll = async () => {
|
||||||
setLoading(true);
|
|
||||||
const [entries, usersData, headgatesData, alertsData] = await Promise.all([
|
const [entries, usersData, headgatesData, alertsData] = await Promise.all([
|
||||||
getWaterEntries(TUXEDO_BRAND_ID, 500),
|
getWaterEntries(TUXEDO_BRAND_ID, 500),
|
||||||
getWaterIrrigators(TUXEDO_BRAND_ID),
|
getWaterIrrigators(TUXEDO_BRAND_ID),
|
||||||
@@ -327,7 +325,6 @@ export default function WaterAdminClient() {
|
|||||||
setUsers(usersData);
|
setUsers(usersData);
|
||||||
setHeadgates(headgatesData);
|
setHeadgates(headgatesData);
|
||||||
setAlertLog(alertsData as AlertLogEntry[]);
|
setAlertLog(alertsData as AlertLogEntry[]);
|
||||||
setLoading(false);
|
|
||||||
setInitialLoading(false);
|
setInitialLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+26
-6
@@ -176,6 +176,12 @@ class MockQueryBuilder<T extends Record<string, unknown> = Record<string, unknow
|
|||||||
const inSet = filter.op === "in" && Array.isArray(filter.value)
|
const inSet = filter.op === "in" && Array.isArray(filter.value)
|
||||||
? new Set(filter.value as unknown[])
|
? new Set(filter.value as unknown[])
|
||||||
: null;
|
: null;
|
||||||
|
const likeRe = filter.op === "like" && typeof filter.value === "string"
|
||||||
|
? likeRegexFor(filter.value as string)
|
||||||
|
: null;
|
||||||
|
const ilikeRe = filter.op === "ilike" && typeof filter.value === "string"
|
||||||
|
? ilikeRegexFor(filter.value as string)
|
||||||
|
: null;
|
||||||
const likeNeedle = filter.op === "like" && typeof filter.value === "string"
|
const likeNeedle = filter.op === "like" && typeof filter.value === "string"
|
||||||
? (filter.value as string).replace(/%/g, "")
|
? (filter.value as string).replace(/%/g, "")
|
||||||
: null;
|
: null;
|
||||||
@@ -205,16 +211,14 @@ class MockQueryBuilder<T extends Record<string, unknown> = Record<string, unknow
|
|||||||
case "like":
|
case "like":
|
||||||
return (
|
return (
|
||||||
typeof rowValue === "string" &&
|
typeof rowValue === "string" &&
|
||||||
likeNeedle !== null &&
|
likeRe !== null &&
|
||||||
likeNeedle.length > 0 &&
|
likeRe.test(rowValue)
|
||||||
rowValue.includes(likeNeedle)
|
|
||||||
);
|
);
|
||||||
case "ilike":
|
case "ilike":
|
||||||
return (
|
return (
|
||||||
typeof rowValue === "string" &&
|
typeof rowValue === "string" &&
|
||||||
ilikeNeedle !== null &&
|
ilikeRe !== null &&
|
||||||
ilikeNeedle.length > 0 &&
|
ilikeRe.test(rowValue.toLowerCase())
|
||||||
rowValue.toLowerCase().includes(ilikeNeedle)
|
|
||||||
);
|
);
|
||||||
case "in":
|
case "in":
|
||||||
return inSet !== null && inSet.has(rowValue);
|
return inSet !== null && inSet.has(rowValue);
|
||||||
@@ -333,6 +337,22 @@ interface UserAttributes {
|
|||||||
data?: Record<string, unknown>;
|
data?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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() {
|
function createMockClient() {
|
||||||
// The query builder is mutable, so we can't simply return a class
|
// The query builder is mutable, so we can't simply return a class
|
||||||
// instance + spread mutation methods. The cleanest way to support
|
// instance + spread mutation methods. The cleanest way to support
|
||||||
|
|||||||
Reference in New Issue
Block a user