Skip to content

Commit a9bc6fd

Browse files
committed
Fix paid subscribers carousel click behavior - disabled auto-scroll and drag, added scroll wheel support
1 parent a5f097e commit a9bc6fd

File tree

5 files changed

+135
-96
lines changed

5 files changed

+135
-96
lines changed

src/components/charts/LineRaceChart/LineRaceChart.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ export const LineRaceChart: React.FC = () => {
380380
};
381381

382382
let content = `
383-
<div style="padding: 4px; min-width: 145px;">
383+
<div style="padding: 6px;">
384384
<div style="color: rgba(0, 255, 255, 0.9); font-weight: 500; margin-bottom: 8px; border-bottom: 1px solid rgba(0, 255, 255, 0.2); padding-bottom: 6px;">
385385
${monthName}
386386
</div>
@@ -395,10 +395,10 @@ export const LineRaceChart: React.FC = () => {
395395
const value = param.value[1];
396396

397397
content += `
398-
<div style="display: flex; align-items: center; margin-bottom: 4px;">
399-
<span style="width: 10px; height: 10px; border-radius: 50%; ${gradientStyle}; display: inline-block; margin-right: 8px;"></span>
400-
<span style="color: rgba(255, 255, 255, 0.85); flex: 1;">${param.seriesName}: </span>
401-
<span style="color: ${color}; font-weight: 600; margin-left: auto;">${value.toLocaleString()}</span>
398+
<div style="display: flex; align-items: center; margin-bottom: 4px; white-space: nowrap;">
399+
<span style="width: 10px; height: 10px; border-radius: 50%; ${gradientStyle}; display: inline-block; margin-right: 8px; flex-shrink: 0;"></span>
400+
<span style="color: rgba(255, 255, 255, 0.85); margin-right: 8px;">${param.seriesName}:</span>
401+
<span style="color: ${color}; font-weight: 600;">${value.toLocaleString()}</span>
402402
</div>
403403
`;
404404
});

src/components/common/SplideCarousel/SplideCarousel.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,22 @@ export const SplideCarousel = forwardRef<Splide, PropsWithChildren<BaseCarouselP
4444
gap: '1rem',
4545
drag: 'free',
4646
type: type,
47-
autoScroll: {
48-
speed: autoSpeed,
49-
},
5047
...props,
5148
};
5249

50+
// Only add autoScroll if explicitly enabled and not false
51+
if (props.autoScroll !== false && autoSpeed) {
52+
options.autoScroll = {
53+
speed: autoSpeed,
54+
};
55+
}
56+
57+
// Only add AutoScroll extension if autoScroll is not explicitly disabled
58+
const extensions = props.autoScroll !== false ? { AutoScroll } : {};
59+
5360
return (
5461
//MUST use SlideTtrack around slides to work
55-
<Splide options={options} hasTrack={false} extensions={{ AutoScroll }} ref={ref}>
62+
<Splide options={options} hasTrack={false} extensions={extensions} ref={ref}>
5663
{children}
5764
</Splide>
5865
);

src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, useState, useMemo } from 'react';
1+
import React, { useRef, useState, useMemo, useEffect } from 'react';
22
import ReactDOM from 'react-dom';
33
import { Splide, SplideSlide, SplideTrack } from '@splidejs/react-splide';
44
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
@@ -71,6 +71,7 @@ export const PaidSubscribers: React.FC = () => {
7171
};
7272

7373
const sliderRef = useRef<Splide>(null);
74+
const carouselContainerRef = useRef<HTMLDivElement>(null);
7475
const { isTablet: isTabletOrHigher } = useResponsive();
7576
const { t } = useTranslation();
7677

@@ -86,6 +87,32 @@ export const PaidSubscribers: React.FC = () => {
8687
}
8788
};
8889

90+
// Add mouse wheel scrolling support
91+
useEffect(() => {
92+
const handleWheel = (e: WheelEvent) => {
93+
if (!sliderRef.current?.splide) return;
94+
95+
e.preventDefault();
96+
97+
if (e.deltaY > 0) {
98+
// Scroll down - go next
99+
sliderRef.current.splide.go('+1');
100+
} else if (e.deltaY < 0) {
101+
// Scroll up - go previous
102+
sliderRef.current.splide.go('-1');
103+
}
104+
};
105+
106+
const container = carouselContainerRef.current;
107+
if (container) {
108+
container.addEventListener('wheel', handleWheel, { passive: false });
109+
110+
return () => {
111+
container.removeEventListener('wheel', handleWheel);
112+
};
113+
}
114+
}, []);
115+
89116
// Determine whether to use carousel with looping based on count
90117
const shouldUseLoop = subscribers.length >= 7;
91118

@@ -268,15 +295,12 @@ export const PaidSubscribers: React.FC = () => {
268295

269296
// Carousel view for 7+ subscribers
270297
return (
271-
<S.ComponentWrapper>
298+
<S.ComponentWrapper ref={carouselContainerRef}>
272299
<SplideCarousel
273300
ref={sliderRef}
274301
type={shouldUseLoop ? 'loop' : undefined}
275-
drag="free"
302+
drag={false}
276303
gap=".2rem"
277-
snap="false"
278-
autoSpeed={isTabletOrHigher ? 0.7 : 0.8}
279-
flickPower="500"
280304
breakpoints={{
281305
8000: {
282306
perPage: 10,

src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ export const CreatorButton = styled.button<StoriesInternalProps>`
1919
overflow: hidden;
2020
position: relative;
2121
border: 3px solid ${(props) => (!props.$viewed ? 'rgba(0, 255, 255, 0.5)' : 'rgba(255, 255, 255, 0.2)')};
22+
pointer-events: auto !important;
23+
z-index: 10;
24+
25+
&:hover {
26+
transform: scale(1.05);
27+
transition: transform 0.2s ease;
28+
}
2229
`;
2330

2431
export const Avatar = styled.img`

src/hooks/useBitcoinRatesWithRange.ts

Lines changed: 82 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -54,87 +54,8 @@ export const useBitcoinRatesWithRange = (options: BitcoinRatesOptions) => {
5454
const abortControllerRef = useRef<AbortController | null>(null);
5555
const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);
5656

57-
const fetchBitcoinRates = useCallback(async () => {
58-
// Check cache first for this specific range
59-
const cached = rangeCache.get(options.timeRange);
60-
const now = Date.now();
61-
62-
// Always check cache duration
63-
const shouldUseFreshData = !cached ||
64-
(now - cached.timestamp) > CACHE_DURATION;
65-
66-
if (!shouldUseFreshData && cached) {
67-
console.log(`[useBitcoinRatesWithRange] Using cached data for ${options.timeRange}`);
68-
return cached.data;
69-
}
70-
71-
// Cancel any existing request
72-
if (abortControllerRef.current) {
73-
abortControllerRef.current.abort();
74-
}
75-
76-
// Create new abort controller for this request
77-
abortControllerRef.current = new AbortController();
78-
79-
try {
80-
const token = readToken();
81-
if (!token) {
82-
throw new Error('No authentication token found');
83-
}
84-
85-
const params = getApiParams(options.timeRange);
86-
87-
// Try range-specific endpoint first, fallback to last-30-days
88-
let endpoint = `${config.baseURL}/api/bitcoin-rates/range/${options.timeRange}`;
89-
90-
// For now, keep using the last-30-days endpoint until backend is updated
91-
// Once backend supports the /range/:range endpoint, remove this line
92-
endpoint = `${config.baseURL}/api/bitcoin-rates/last-30-days`;
93-
94-
console.log(`[useBitcoinRatesWithRange] Fetching ${options.timeRange} data from ${endpoint}`);
95-
96-
const response = await fetch(endpoint, {
97-
method: 'GET',
98-
headers: {
99-
'Content-Type': 'application/json',
100-
'Authorization': `Bearer ${token}`,
101-
},
102-
signal: abortControllerRef.current.signal,
103-
});
104-
105-
if (!response.ok) {
106-
if (response.status === 401) {
107-
handleLogout();
108-
throw new Error('Authentication failed. You have been logged out.');
109-
}
110-
111-
throw new Error(`Network response was not ok (status: ${response.status})`);
112-
}
113-
114-
const data = await response.json();
115-
console.log(`[useBitcoinRatesWithRange] Data received for ${options.timeRange}`);
116-
117-
const processedData = processBitcoinData(data, options.timeRange);
118-
119-
// Cache the result
120-
rangeCache.set(options.timeRange, {
121-
data: processedData,
122-
timestamp: now,
123-
});
124-
125-
setLastUpdate(new Date());
126-
return processedData;
127-
} catch (err: any) {
128-
if (err.name === 'AbortError') {
129-
console.log('[useBitcoinRatesWithRange] Request was aborted');
130-
return [];
131-
}
132-
throw err;
133-
}
134-
}, [options.timeRange, handleLogout]);
135-
13657
// Process and filter data based on time range
137-
const processBitcoinData = (data: any[], range: TimeRange): Earning[] => {
58+
const processBitcoinData = useCallback((data: any[], range: TimeRange): Earning[] => {
13859
console.log(`[processBitcoinData] Processing ${data.length} data points for range: ${range}`);
13960

14061
if (data.length === 0) {
@@ -266,7 +187,87 @@ export const useBitcoinRatesWithRange = (options: BitcoinRatesOptions) => {
266187
console.log(`[processBitcoinData] Final: ${aggregated.length} points for ${range} view`);
267188

268189
return aggregated;
269-
};
190+
}, []);
191+
192+
// Smart aggregation that maintains data shape while reducing points
193+
const fetchBitcoinRates = useCallback(async () => {
194+
// Check cache first for this specific range
195+
const cached = rangeCache.get(options.timeRange);
196+
const now = Date.now();
197+
198+
// Always check cache duration
199+
const shouldUseFreshData = !cached ||
200+
(now - cached.timestamp) > CACHE_DURATION;
201+
202+
if (!shouldUseFreshData && cached) {
203+
console.log(`[useBitcoinRatesWithRange] Using cached data for ${options.timeRange}`);
204+
return cached.data;
205+
}
206+
207+
// Cancel any existing request
208+
if (abortControllerRef.current) {
209+
abortControllerRef.current.abort();
210+
}
211+
212+
// Create new abort controller for this request
213+
abortControllerRef.current = new AbortController();
214+
215+
try {
216+
const token = readToken();
217+
if (!token) {
218+
throw new Error('No authentication token found');
219+
}
220+
221+
const params = getApiParams(options.timeRange);
222+
223+
// Try range-specific endpoint first, fallback to last-30-days
224+
let endpoint = `${config.baseURL}/api/bitcoin-rates/range/${options.timeRange}`;
225+
226+
// For now, keep using the last-30-days endpoint until backend is updated
227+
// Once backend supports the /range/:range endpoint, remove this line
228+
endpoint = `${config.baseURL}/api/bitcoin-rates/last-30-days`;
229+
230+
console.log(`[useBitcoinRatesWithRange] Fetching ${options.timeRange} data from ${endpoint}`);
231+
232+
const response = await fetch(endpoint, {
233+
method: 'GET',
234+
headers: {
235+
'Content-Type': 'application/json',
236+
'Authorization': `Bearer ${token}`,
237+
},
238+
signal: abortControllerRef.current.signal,
239+
});
240+
241+
if (!response.ok) {
242+
if (response.status === 401) {
243+
handleLogout();
244+
throw new Error('Authentication failed. You have been logged out.');
245+
}
246+
247+
throw new Error(`Network response was not ok (status: ${response.status})`);
248+
}
249+
250+
const data = await response.json();
251+
console.log(`[useBitcoinRatesWithRange] Data received for ${options.timeRange}`);
252+
253+
const processedData = processBitcoinData(data, options.timeRange);
254+
255+
// Cache the result
256+
rangeCache.set(options.timeRange, {
257+
data: processedData,
258+
timestamp: now,
259+
});
260+
261+
setLastUpdate(new Date());
262+
return processedData;
263+
} catch (err: any) {
264+
if (err.name === 'AbortError') {
265+
console.log('[useBitcoinRatesWithRange] Request was aborted');
266+
return [];
267+
}
268+
throw err;
269+
}
270+
}, [options.timeRange, handleLogout, processBitcoinData]);
270271

271272
// Smart aggregation that maintains data shape while reducing points
272273
const smartAggregateData = (data: Earning[], targetPoints: number): Earning[] => {

0 commit comments

Comments
 (0)