Skip to content

Commit 96fec95

Browse files
committed
Add loading page with progress bar, refactor all pages to use PageContainer with Previous/Next navigation, fix genres data fetching, add genre honorable mentions, re-enable localStorage caching
1 parent 5b43dc5 commit 96fec95

34 files changed

Lines changed: 1471 additions & 413 deletions

package-lock.json

Lines changed: 542 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"build": "tsc -b && vite build",
1010
"lint": "eslint .",
1111
"preview": "vite preview",
12+
"validate": "tsx src/scripts/validate-data.ts",
1213
"prepare-husky": "husky",
1314
"prepare": "husky"
1415
},
@@ -41,6 +42,7 @@
4142
"@types/react": "^18.3.18",
4243
"@types/react-dom": "^18.3.5",
4344
"@vitejs/plugin-react": "^4.3.4",
45+
"dotenv": "^17.2.3",
4446
"eslint": "^9.17.0",
4547
"eslint-plugin-react-hooks": "^5.0.0",
4648
"eslint-plugin-react-refresh": "^0.4.16",
@@ -49,6 +51,7 @@
4951
"lint-staged": "^15.3.0",
5052
"prettier": "^3.4.2",
5153
"tailwindcss": "^3.4.17",
54+
"tsx": "^4.21.0",
5255
"typescript": "~5.6.2",
5356
"typescript-eslint": "^8.18.2",
5457
"vite": "^6.0.5"

src/App.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@ import TopTenPage from "./components/pages/TopTenPage";
2929
import { useEffect } from "react";
3030
import ActivityCalendarPage from "./components/pages/ActivityCalendarPage";
3131
import Navigation from "./components/Navigation";
32+
import { LoadingPage } from "./components/pages/LoadingPage";
3233

33-
// Create a client
34-
const queryClient = new QueryClient();
34+
const queryClient = new QueryClient({
35+
defaultOptions: {
36+
queries: {
37+
staleTime: Infinity,
38+
gcTime: Infinity,
39+
refetchOnWindowFocus: false,
40+
refetchOnMount: false,
41+
refetchOnReconnect: false,
42+
retry: 1,
43+
},
44+
},
45+
});
3546

3647
// Layout component that wraps all routes
3748
function ScrollToTop() {
@@ -73,6 +84,10 @@ const router = createBrowserRouter([
7384
path: "/configure",
7485
element: <ServerConfigurationPage />,
7586
},
87+
{
88+
path: "/loading",
89+
element: <LoadingPage />,
90+
},
7691
{
7792
path: "/movies",
7893
element: <MoviesReviewPage />,

src/components/PageContainer.tsx

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,56 @@
11
import { ReactNode } from "react";
2-
import { Box, Button } from "@radix-ui/themes";
2+
import { Box, Button, Flex } from "@radix-ui/themes";
33
import { useNavigate } from "react-router-dom";
44
import { styled } from "@stitches/react";
55

66
interface PageContainerProps {
77
children: ReactNode;
88
backgroundColor?: string;
99
nextPage?: string;
10+
previousPage?: string;
1011
}
1112

1213
const PageContainer = ({
1314
children,
1415
backgroundColor = "var(--purple-8)",
1516
nextPage,
17+
previousPage,
1618
}: PageContainerProps) => {
1719
const navigate = useNavigate();
1820

1921
return (
20-
<Box style={{ backgroundColor }} className="min-h-screen pb-12">
21-
{children}
22+
<Box style={{ backgroundColor, minHeight: "100vh", display: "flex", flexDirection: "column" }}>
23+
<div style={{ flex: 1, paddingBottom: "5rem" }}>
24+
{children}
25+
</div>
2226

23-
{nextPage && (
27+
{(nextPage || previousPage) && (
2428
<NavButtonContainer>
25-
<Button
26-
size={"4"}
27-
style={{ width: "100%" }}
28-
onClick={() => {
29-
void navigate(nextPage);
30-
}}
31-
>
32-
Next
33-
</Button>
29+
<Flex gap="2" style={{ width: "100%" }}>
30+
{previousPage && (
31+
<Button
32+
size={"4"}
33+
color="gray"
34+
style={{ flex: 1 }}
35+
onClick={() => {
36+
void navigate(previousPage);
37+
}}
38+
>
39+
Previous
40+
</Button>
41+
)}
42+
{nextPage && (
43+
<Button
44+
size={"4"}
45+
style={{ flex: 1 }}
46+
onClick={() => {
47+
void navigate(nextPage);
48+
}}
49+
>
50+
Next
51+
</Button>
52+
)}
53+
</Flex>
3454
</NavButtonContainer>
3555
)}
3656
</Box>
@@ -43,6 +63,7 @@ const NavButtonContainer = styled("div", {
4363
left: 0,
4464
right: 0,
4565
zIndex: 10,
66+
padding: "0 1rem 1rem 1rem",
4667
});
4768

4869
export default PageContainer;
Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { ResponsiveCalendar } from "@nivo/calendar";
22
import { Card } from "@/components/ui/card";
3-
import { Button, Box } from "@radix-ui/themes";
4-
import { useNavigate } from "react-router-dom";
3+
import { Container } from "@radix-ui/themes";
54
import { useErrorBoundary } from "react-error-boundary";
65
import { subYears, format } from "date-fns";
76
import { Title } from "../ui/styled";
87
import { motion } from "framer-motion";
98
import { useCalendar } from "@/hooks/queries/useCalendar";
109
import { LoadingSpinner } from "../LoadingSpinner";
1110
import { useIsMobile } from "@/hooks/useIsMobile";
11+
import PageContainer from "../PageContainer";
1212

1313
const NEXT_PAGE = "/";
1414

1515
export default function ActivityCalendarPage() {
16-
const navigate = useNavigate();
1716
const { showBoundary } = useErrorBoundary();
1817
const isMobile = useIsMobile();
1918
const { data, isLoading, error } = useCalendar();
@@ -30,42 +29,35 @@ export default function ActivityCalendarPage() {
3029
const toDate = format(new Date(), "yyyy-MM-dd");
3130

3231
return (
33-
<Box
34-
style={{ backgroundColor: "var(--gray-8)" }}
35-
className="min-h-screen p-8"
36-
>
37-
<Card className="p-6">
38-
<Title as={motion.h2} className="text-2xl font-bold mb-4">
39-
Your Viewing Activity
40-
</Title>
41-
<div style={{ height: isMobile ? "300px" : "500px" }}>
42-
<ResponsiveCalendar
43-
data={data ?? []}
44-
from={fromDate}
45-
to={toDate}
46-
emptyColor="#eeeeee"
47-
colors={["#61cdbb", "#97e3d5", "#e8c1a0", "#f47560"]}
48-
margin={
49-
isMobile
50-
? { top: 20, right: 20, bottom: 20, left: 20 }
51-
: { top: 40, right: 40, bottom: 40, left: 40 }
52-
}
53-
yearSpacing={40}
54-
monthBorderColor="#ffffff"
55-
dayBorderWidth={2}
56-
dayBorderColor="#ffffff"
57-
/>
58-
</div>
59-
</Card>
60-
<Button
61-
size={"4"}
62-
style={{ width: "100%", marginTop: "1rem" }}
63-
onClick={() => {
64-
void navigate(NEXT_PAGE);
65-
}}
66-
>
67-
Finish
68-
</Button>
69-
</Box>
32+
<PageContainer backgroundColor="var(--indigo-8)" nextPage={NEXT_PAGE} previousPage="/device-stats">
33+
<Container size="4" p="4">
34+
<Card className="p-6">
35+
<Title as={motion.h2} className="text-2xl font-bold mb-4">
36+
Your Viewing Activity
37+
</Title>
38+
<p style={{ fontSize: "1.125rem", color: "var(--gray-11)", marginBottom: "1rem" }}>
39+
A calendar view of your daily viewing patterns
40+
</p>
41+
<div style={{ height: isMobile ? "300px" : "500px" }}>
42+
<ResponsiveCalendar
43+
data={data ?? []}
44+
from={fromDate}
45+
to={toDate}
46+
emptyColor="#eeeeee"
47+
colors={["#61cdbb", "#97e3d5", "#e8c1a0", "#f47560"]}
48+
margin={
49+
isMobile
50+
? { top: 20, right: 20, bottom: 20, left: 20 }
51+
: { top: 40, right: 40, bottom: 40, left: 40 }
52+
}
53+
yearSpacing={40}
54+
monthBorderColor="#ffffff"
55+
dayBorderWidth={2}
56+
dayBorderColor="#ffffff"
57+
/>
58+
</div>
59+
</Card>
60+
</Container>
61+
</PageContainer>
7062
);
7163
}

src/components/pages/AudioReviewPage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ export default function AudioReviewPage() {
3131
}
3232

3333
return (
34-
<PageContainer backgroundColor="var(--red-8)" nextPage={NEXT_PAGE}>
34+
<PageContainer backgroundColor="var(--red-8)" nextPage={NEXT_PAGE} previousPage="/shows">
3535
<Container size="4" p="4">
3636
<Grid gap="6">
3737
<div style={{ textAlign: "center" }}>
3838
<Title as={motion.h1} variants={itemVariants}>
3939
You Listened to {audios.length} Songs
4040
</Title>
41+
<p style={{ fontSize: "1.125rem", color: "var(--gray-11)", marginTop: "0.5rem" }}>
42+
Your music listening history
43+
</p>
4144
{audios.length > MAX_DISPLAY_ITEMS && (
4245
<p style={{ color: "var(--gray-12)" }}>
4346
Showing top {MAX_DISPLAY_ITEMS} songs

src/components/pages/CriticallyAcclaimedPage.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import {
22
Container,
33
Grid,
4-
Box,
5-
Button,
64
Text,
75
Card,
86
Flex,
@@ -17,8 +15,9 @@ import { useShows } from "@/hooks/queries/useShows";
1715
import { LoadingSpinner } from "../LoadingSpinner";
1816
import { ContentImage } from "../ContentImage";
1917
import { getTopRatedContent, TopContent } from "@/lib/rating-helpers";
18+
import PageContainer from "../PageContainer";
2019

21-
const NEXT_PAGE = "/actors";
20+
const NEXT_PAGE = "/oldest-movie";
2221

2322
export default function CriticallyAcclaimedPage() {
2423
const { showBoundary } = useErrorBoundary();
@@ -49,13 +48,16 @@ export default function CriticallyAcclaimedPage() {
4948
}
5049

5150
return (
52-
<Box style={{ backgroundColor: "var(--teal-8)" }} className="min-h-screen">
51+
<PageContainer backgroundColor="var(--cyan-8)" nextPage={NEXT_PAGE} previousPage="/tv">
5352
<Container size="4" p="4">
5453
<Grid gap="6">
5554
<div style={{ textAlign: "center" }}>
5655
<Title as={motion.h1} variants={itemVariants}>
5756
Critically Acclaimed Content You Watched
5857
</Title>
58+
<p style={{ fontSize: "1.125rem", color: "var(--gray-11)", marginTop: "0.5rem" }}>
59+
Highly-rated movies and shows from your viewing history
60+
</p>
5961
</div>
6062

6163
<Grid columns={{ initial: "1", sm: "2" }} gap="4">
@@ -85,15 +87,6 @@ export default function CriticallyAcclaimedPage() {
8587
</Grid>
8688
</Grid>
8789
</Container>
88-
<Button
89-
size={"4"}
90-
style={{ width: "100%" }}
91-
onClick={() => {
92-
void navigate(NEXT_PAGE);
93-
}}
94-
>
95-
Next
96-
</Button>
97-
</Box>
90+
</PageContainer>
9891
);
9992
}

src/components/pages/DeviceStatsPage.tsx

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useRef } from "react";
2-
import { Container, Grid, Box, Button } from "@radix-ui/themes";
2+
import { Container, Grid } from "@radix-ui/themes";
33
import { motion } from "framer-motion";
44
import { useErrorBoundary } from "react-error-boundary";
55
import { useNavigate } from "react-router-dom";
@@ -9,8 +9,9 @@ import { itemVariants } from "@/lib/styled-variants";
99
import { useDeviceStats } from "@/hooks/queries/useDeviceStats";
1010
import { LoadingSpinner } from "../LoadingSpinner";
1111
import { PieChart } from "../charts/PieChart";
12+
import PageContainer from "../PageContainer";
1213

13-
const NEXT_PAGE = "/oldest-movie";
14+
const NEXT_PAGE = "/punch-card";
1415

1516
const CHART_COLORS = {
1617
devices: d3.schemeSet3,
@@ -61,16 +62,16 @@ export default function DeviceStatsPage() {
6162
);
6263

6364
return (
64-
<Box
65-
style={{ backgroundColor: "var(--purple-8)" }}
66-
className="min-h-screen"
67-
>
65+
<PageContainer backgroundColor="var(--violet-8)" nextPage={NEXT_PAGE} previousPage="/unfinished-shows">
6866
<Container size="4" p="4" ref={containerRef}>
6967
<Grid gap="6">
7068
<div style={{ textAlign: "center" }}>
7169
<Title as={motion.h1} variants={itemVariants}>
7270
Your Viewing Devices
7371
</Title>
72+
<p style={{ fontSize: "1.125rem", color: "var(--gray-11)", marginTop: "0.5rem" }}>
73+
Where you watch your content across different devices and apps
74+
</p>
7475
</div>
7576

7677
<Grid columns={{ initial: "1", md: "2" }} gap="4">
@@ -95,15 +96,6 @@ export default function DeviceStatsPage() {
9596
</Grid>
9697
</Grid>
9798
</Container>
98-
<Button
99-
size={"4"}
100-
style={{ width: "100%" }}
101-
onClick={() => {
102-
void navigate(NEXT_PAGE);
103-
}}
104-
>
105-
Next
106-
</Button>
107-
</Box>
99+
</PageContainer>
108100
);
109101
}

0 commit comments

Comments
 (0)