barsa 35ba8ab26a refactor: optimize billing controller and enhance carousel functionality
- 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.
2026-03-05 16:46:45 +09:00

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;