Skip to content

Commit aa03bf8

Browse files
committed
chore: improve mobile behavior
1 parent 01785fe commit aa03bf8

2 files changed

Lines changed: 73 additions & 53 deletions

File tree

src/App.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import "@radix-ui/themes/styles.css";
22
import { ErrorBoundary } from "react-error-boundary";
33
import { Theme } from "@radix-ui/themes";
4-
import { createBrowserRouter, RouterProvider, Outlet } from "react-router-dom";
4+
import { createBrowserRouter, RouterProvider, Outlet, useLocation } from "react-router-dom";
55
import SplashPage from "./components/pages/SplashPage";
66
import ServerConfigurationPage from "./components/pages/ServerConfigurationPage";
77
import MoviesReviewPage from "./components/pages/MoviesReviewPage";
@@ -15,11 +15,26 @@ import MusicVideoPage from "./components/pages/MusicVideoPage";
1515
import GenreReviewPage from "./components/pages/GenreReviewPage";
1616
import HolidayReviewPage from "./components/pages/HolidayReviewPage";
1717
import MinutesPlayedPerDayPage from "./components/pages/MinutesPlayedPerDayPage";
18+
import { useEffect } from "react";
1819

1920
// Layout component that wraps all routes
21+
function ScrollToTop() {
22+
const { pathname } = useLocation();
23+
24+
useEffect(() => {
25+
window.scrollTo({
26+
top: 0,
27+
behavior: 'smooth'
28+
});
29+
}, [pathname]);
30+
31+
return null;
32+
}
33+
2034
function RootLayout() {
2135
return (
2236
<ErrorBoundary fallback={<div>Something went wrong</div>}>
37+
<ScrollToTop />
2338
<Theme>
2439
<Outlet />
2540
</Theme>

src/components/pages/MinutesPlayedPerDayPage.tsx

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ interface PlaybackData {
1111
date: string;
1212
minutes: number;
1313
}
14-
1514
const NEXT_PAGE = "/";
1615
export default function MinutesPlayedPerDayPage() {
1716
const { showBoundary } = useErrorBoundary();
18-
const navigate = useNavigate();
1917
const [isLoading, setIsLoading] = useState(true);
2018
const [playbackData, setPlaybackData] = useState<PlaybackData[]>([]);
19+
const containerRef = useRef<HTMLDivElement>(null);
2120
const svgRef = useRef<SVGSVGElement>(null);
21+
const navigate = useNavigate();
22+
23+
// Format time display helper
2224
const formatTimeDisplay = (minutes: number): string => {
2325
const hours = Math.floor(minutes / 60);
2426
const remainingMinutes = minutes % 60;
@@ -33,13 +35,13 @@ export default function MinutesPlayedPerDayPage() {
3335

3436
return `${hours} ${hours === 1 ? "hour" : "hours"} ${remainingMinutes} ${remainingMinutes === 1 ? "minute" : "minutes"}`;
3537
};
38+
3639
// Fetch data
3740
useEffect(() => {
3841
const fetchData = async () => {
3942
setIsLoading(true);
4043
try {
4144
const data = await getMinutesPlayedPerDay();
42-
// Sort data chronologically for the graph
4345
setPlaybackData(
4446
data.sort(
4547
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
@@ -55,16 +57,26 @@ export default function MinutesPlayedPerDayPage() {
5557
}, []);
5658

5759
// Create/Update D3 visualization
58-
useEffect(() => {
59-
if (!playbackData.length || !svgRef.current) return;
60+
const updateChart = () => {
61+
if (!playbackData.length || !svgRef.current || !containerRef.current)
62+
return;
6063

6164
// Clear any existing SVG content
6265
d3.select(svgRef.current).selectAll("*").remove();
6366

64-
// Set up dimensions
65-
const margin = { top: 20, right: 30, bottom: 40, left: 60 };
66-
const width = 800 - margin.left - margin.right;
67-
const height = 400 - margin.top - margin.bottom;
67+
// Get container width
68+
const containerWidth = containerRef.current.clientWidth;
69+
70+
// Set responsive dimensions
71+
const margin = {
72+
top: 20,
73+
right: 20,
74+
bottom: containerWidth < 600 ? 60 : 40, // More space for labels on mobile
75+
left: containerWidth < 600 ? 50 : 60,
76+
};
77+
const width = containerWidth - margin.left - margin.right;
78+
const height =
79+
Math.min(400, containerWidth * 0.6) - margin.top - margin.bottom;
6880

6981
// Create SVG
7082
const svg = d3
@@ -116,13 +128,15 @@ export default function MinutesPlayedPerDayPage() {
116128
.attr("stroke-width", 2)
117129
.attr("d", line);
118130

119-
// Add axes
131+
// Configure axes with responsive settings
120132
const xAxis = d3
121133
.axisBottom(xScale)
122-
.ticks(d3.timeMonth.every(1))
134+
.ticks(containerWidth < 600 ? 4 : d3.timeMonth.every(1))
123135
.tickFormat((d) => {
124136
if (d instanceof Date) {
125-
return d3.timeFormat("%b %Y")(d);
137+
return containerWidth < 600
138+
? d3.timeFormat("%b")(d) // Short month name on mobile
139+
: d3.timeFormat("%b %Y")(d);
126140
}
127141
return "";
128142
});
@@ -132,6 +146,7 @@ export default function MinutesPlayedPerDayPage() {
132146
.ticks(5)
133147
.tickFormat((d: d3.NumberValue) => `${d.valueOf()}m`);
134148

149+
// Add x-axis
135150
svg
136151
.append("g")
137152
.attr("transform", `translate(0,${height})`)
@@ -140,8 +155,9 @@ export default function MinutesPlayedPerDayPage() {
140155
.style("text-anchor", "end")
141156
.attr("dx", "-.8em")
142157
.attr("dy", ".15em")
143-
.attr("transform", "rotate(-45)");
158+
.attr("transform", containerWidth < 600 ? "rotate(-45)" : "rotate(-45)");
144159

160+
// Add y-axis
145161
svg.append("g").call(yAxis);
146162

147163
// Add hover effects
@@ -154,7 +170,10 @@ export default function MinutesPlayedPerDayPage() {
154170
.style("padding", "8px")
155171
.style("border-radius", "4px")
156172
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
157-
.style("display", "none");
173+
.style("display", "none")
174+
.style("z-index", "1000")
175+
.style("pointer-events", "none")
176+
.style("font-size", containerWidth < 600 ? "12px" : "14px");
158177

159178
const dots = svg
160179
.selectAll(".dot")
@@ -164,7 +183,7 @@ export default function MinutesPlayedPerDayPage() {
164183
.attr("class", "dot")
165184
.attr("cx", (d) => xScale(new Date(d.date)))
166185
.attr("cy", (d) => yScale(d.minutes))
167-
.attr("r", 4)
186+
.attr("r", containerWidth < 600 ? 3 : 4)
168187
.attr("fill", "var(--yellow-9)")
169188
.style("opacity", 0);
170189

@@ -175,15 +194,12 @@ export default function MinutesPlayedPerDayPage() {
175194
.style("fill", "none")
176195
.style("pointer-events", "all")
177196
.on("mousemove", function (event: PointerEvent) {
178-
const [xPos] = d3.pointer(event);
197+
const [xPos] = d3.pointer(event, this);
179198
const xDate = xScale.invert(xPos);
180-
const bisectByDate = d3.bisector<PlaybackData, Date>(
199+
const bisectLeft = d3.bisector<PlaybackData, Date>(
181200
(d) => new Date(d.date),
182-
);
183-
const bisectLeft = (data: PlaybackData[], date: Date) =>
184-
bisectByDate.left(data, date);
201+
).left;
185202
const index = bisectLeft(playbackData, xDate);
186-
187203
const d = playbackData[index];
188204

189205
if (d) {
@@ -194,20 +210,31 @@ export default function MinutesPlayedPerDayPage() {
194210
.style("display", "block")
195211
.style("left", `${event.pageX + 10}px`)
196212
.style("top", `${event.pageY - 10}px`)
197-
.html(
198-
`Date: ${d.date}<br>Time: ${formatTimeDisplay(d.minutes)}`,
199-
);
213+
.html(`Date: ${d.date}<br>${formatTimeDisplay(d.minutes)}`);
200214
}
201215
})
202216
.on("mouseout", () => {
203217
dots.style("opacity", 0);
204218
tooltip.style("display", "none");
205219
});
206220

207-
// Cleanup function
208221
return () => {
209222
tooltip.remove();
210223
};
224+
};
225+
226+
// Update chart on window resize
227+
useEffect(() => {
228+
const handleResize = () => {
229+
updateChart();
230+
};
231+
232+
window.addEventListener("resize", handleResize);
233+
updateChart();
234+
235+
return () => {
236+
window.removeEventListener("resize", handleResize);
237+
};
211238
}, [playbackData]);
212239

213240
if (isLoading) {
@@ -220,25 +247,7 @@ export default function MinutesPlayedPerDayPage() {
220247
minHeight: "100vh",
221248
}}
222249
>
223-
<Box
224-
style={{
225-
backgroundColor: "var(--green-8)",
226-
minHeight: "100vh",
227-
minWidth: "100vw",
228-
}}
229-
className="min-h-screen"
230-
>
231-
<Box
232-
style={{
233-
backgroundColor: "var(--green-8)",
234-
minHeight: "100vh",
235-
minWidth: "100vw",
236-
}}
237-
className="min-h-screen"
238-
>
239-
<Spinner size="3" />
240-
</Box>
241-
</Box>
250+
<Spinner size="3" />
242251
</div>
243252
);
244253
}
@@ -247,14 +256,7 @@ export default function MinutesPlayedPerDayPage() {
247256
const averageMinutes = Math.round(totalMinutes / playbackData.length);
248257

249258
return (
250-
<Box
251-
style={{
252-
backgroundColor: "var(--green-8)",
253-
minHeight: "100vh",
254-
minWidth: "100vw",
255-
}}
256-
className="min-h-screen"
257-
>
259+
<Box className="min-h-screen">
258260
<Container size="4" p="4">
259261
<Grid gap="6">
260262
<div style={{ textAlign: "center" }}>
@@ -267,11 +269,14 @@ export default function MinutesPlayedPerDayPage() {
267269
</div>
268270

269271
<div
272+
ref={containerRef}
270273
style={{
271274
backgroundColor: "white",
272275
padding: "20px",
273276
borderRadius: "8px",
274277
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
278+
width: "100%",
279+
overflowX: "hidden",
275280
}}
276281
>
277282
<svg ref={svgRef}></svg>

0 commit comments

Comments
 (0)