- Updated BillingController to use Promise.all for concurrent fetching of payment methods and WHMCS client ID, improving performance. - Modified package.json in the portal to enable turbopack for faster development builds. - Removed unused useInvoicesFilter hook from the billing feature, streamlining the codebase. - Enhanced useCarousel hook to support auto-play functionality and pause on user interaction, improving user experience in the landing page carousel.
123 lines
3.1 KiB
TypeScript
123 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
|
export function useCarousel<T>({ items, autoPlayMs = 5000 }: { items: T[]; autoPlayMs?: number }) {
|
|
const total = items.length;
|
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
const [direction, setDirection] = useState<"next" | "prev">("next");
|
|
const activeIndexRef = useRef(activeIndex);
|
|
activeIndexRef.current = activeIndex;
|
|
const touchXRef = useRef(0);
|
|
const pausedRef = useRef(false);
|
|
|
|
const goTo = useCallback((i: number) => {
|
|
setDirection(i > activeIndexRef.current ? "next" : "prev");
|
|
setActiveIndex(i);
|
|
}, []);
|
|
|
|
const goNext = useCallback(() => {
|
|
setDirection("next");
|
|
setActiveIndex(prev => (prev + 1) % total);
|
|
}, [total]);
|
|
|
|
const goPrev = useCallback(() => {
|
|
setDirection("prev");
|
|
setActiveIndex(prev => (prev - 1 + total) % total);
|
|
}, [total]);
|
|
|
|
const reset = useCallback(() => {
|
|
setActiveIndex(0);
|
|
setDirection("next");
|
|
}, []);
|
|
|
|
const onTouchStart = useCallback((e: React.TouchEvent) => {
|
|
const touch = e.touches[0];
|
|
if (touch) touchXRef.current = touch.clientX;
|
|
}, []);
|
|
|
|
// Auto-play: pause on user interaction, resume after delay
|
|
const interactionTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
|
|
const pauseAutoPlay = useCallback(() => {
|
|
pausedRef.current = true;
|
|
clearTimeout(interactionTimerRef.current);
|
|
interactionTimerRef.current = setTimeout(() => {
|
|
pausedRef.current = false;
|
|
}, autoPlayMs * 2);
|
|
}, [autoPlayMs]);
|
|
|
|
const goToWithPause = useCallback(
|
|
(i: number) => {
|
|
pauseAutoPlay();
|
|
goTo(i);
|
|
},
|
|
[goTo, pauseAutoPlay]
|
|
);
|
|
|
|
const goNextWithPause = useCallback(() => {
|
|
pauseAutoPlay();
|
|
goNext();
|
|
}, [goNext, pauseAutoPlay]);
|
|
|
|
const goPrevWithPause = useCallback(() => {
|
|
pauseAutoPlay();
|
|
goPrev();
|
|
}, [goPrev, pauseAutoPlay]);
|
|
|
|
const onTouchEndWithPause = useCallback(
|
|
(e: React.TouchEvent) => {
|
|
const touch = e.changedTouches[0];
|
|
if (!touch) return;
|
|
const diff = touchXRef.current - touch.clientX;
|
|
if (Math.abs(diff) > 50) {
|
|
pauseAutoPlay();
|
|
if (diff > 0) goNext();
|
|
else goPrev();
|
|
}
|
|
},
|
|
[goNext, goPrev, pauseAutoPlay]
|
|
);
|
|
|
|
const onKeyDownWithPause = useCallback(
|
|
(e: React.KeyboardEvent) => {
|
|
if (e.key === "ArrowLeft") {
|
|
pauseAutoPlay();
|
|
goPrev();
|
|
} else if (e.key === "ArrowRight") {
|
|
pauseAutoPlay();
|
|
goNext();
|
|
}
|
|
},
|
|
[goPrev, goNext, pauseAutoPlay]
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (total <= 1) return;
|
|
const id = setInterval(() => {
|
|
if (!pausedRef.current) {
|
|
setDirection("next");
|
|
setActiveIndex(prev => (prev + 1) % total);
|
|
}
|
|
}, autoPlayMs);
|
|
return () => clearInterval(id);
|
|
}, [total, autoPlayMs]);
|
|
|
|
return {
|
|
items,
|
|
total,
|
|
activeIndex,
|
|
direction,
|
|
goTo: goToWithPause,
|
|
goNext: goNextWithPause,
|
|
goPrev: goPrevWithPause,
|
|
reset,
|
|
onTouchStart,
|
|
onTouchEnd: onTouchEndWithPause,
|
|
onKeyDown: onKeyDownWithPause,
|
|
};
|
|
}
|
|
|
|
/** @deprecated Use `useCarousel` instead */
|
|
export const useInfiniteCarousel = useCarousel;
|