diff --git a/client/app/components/rewind/Rewind.tsx b/client/app/components/rewind/Rewind.tsx index 8e1908c..2553b35 100644 --- a/client/app/components/rewind/Rewind.tsx +++ b/client/app/components/rewind/Rewind.tsx @@ -8,9 +8,16 @@ interface Props { } export default function Rewind(props: Props) { - const artistimg = props.stats.top_artists[0].image; - const albumimg = props.stats.top_albums[0].image; - const trackimg = props.stats.top_tracks[0].image; + const artistimg = props.stats.top_artists[0]?.image; + const albumimg = props.stats.top_albums[0]?.image; + const trackimg = props.stats.top_tracks[0]?.image; + if ( + !props.stats.top_artists[0] || + !props.stats.top_albums[0] || + !props.stats.top_tracks[0] + ) { + return

Not enough data exists to create a Rewind for this period :(

; + } return (

{props.stats.title}

diff --git a/client/app/routes/RewindPage.tsx b/client/app/routes/RewindPage.tsx index b14e5fc..2d60697 100644 --- a/client/app/routes/RewindPage.tsx +++ b/client/app/routes/RewindPage.tsx @@ -1,52 +1,199 @@ import Rewind from "~/components/rewind/Rewind"; import type { Route } from "./+types/Home"; -import { type RewindStats } from "api/api"; -import { useState } from "react"; +import { imageUrl, type RewindStats } from "api/api"; +import { useEffect, useState } from "react"; import type { LoaderFunctionArgs } from "react-router"; import { useLoaderData } from "react-router"; import { getRewindYear } from "~/utils/utils"; +import { useNavigate } from "react-router"; +import { average } from "color.js"; +import { ChevronLeft, ChevronRight } from "lucide-react"; + +// TODO: Bind year and month selectors to what data actually exists + +const months = [ + "Full Year", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; export async function clientLoader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); - const year = url.searchParams.get("year") || getRewindYear(); + const year = parseInt(url.searchParams.get("year") || "0") || getRewindYear(); + const month = parseInt(url.searchParams.get("month") || "0"); - const res = await fetch(`/apis/web/v1/summary?year=${year}`); + const res = await fetch(`/apis/web/v1/summary?year=${year}&month=${month}`); if (!res.ok) { throw new Response("Failed to load summary", { status: 500 }); } const stats: RewindStats = await res.json(); - stats.title = `Your ${year} Rewind`; + stats.title = `Your ${month === 0 ? "" : months[month]} ${year} Rewind`; return { stats }; } -export function meta({}: Route.MetaArgs) { - return [ - { title: `Rewind - Koito` }, - { name: "description", content: "Rewind - Koito" }, - ]; -} - export default function RewindPage() { + const currentParams = new URLSearchParams(location.search); + let year = parseInt(currentParams.get("year") || "0"); + let month = parseInt(currentParams.get("month") || "0"); + const navigate = useNavigate(); const [showTime, setShowTime] = useState(false); const { stats: stats } = useLoaderData<{ stats: RewindStats }>(); + + const [bgColor, setBgColor] = useState("(--color-bg)"); + + useEffect(() => { + if (!stats.top_artists[0]) return; + + const img = (stats.top_artists[0] as any)?.image; + if (!img) return; + + average(imageUrl(img, "small"), { amount: 1 }).then((color) => { + setBgColor(`rgba(${color[0]},${color[1]},${color[2]},0.4)`); + }); + }, [stats]); + + const updateParams = (params: Record) => { + const nextParams = new URLSearchParams(location.search); + + for (const key in params) { + const val = params[key]; + + if (val !== null && val !== "0") { + nextParams.set(key, val); + } else { + nextParams.delete(key); + } + } + + const url = `/rewind?${nextParams.toString()}`; + + navigate(url, { replace: false }); + }; + + const navigateMonth = (direction: "prev" | "next") => { + if (direction === "next") { + if (month === 12) { + month = 0; + } else { + month += 1; + } + } else { + if (month === 0) { + month = 12; + } else { + month -= 1; + } + } + + updateParams({ + year: year.toString(), + month: month.toString(), + }); + }; + const navigateYear = (direction: "prev" | "next") => { + if (direction === "next") { + year += 1; + } else { + year -= 1; + } + + updateParams({ + year: year.toString(), + month: month.toString(), + }); + }; + + const pgTitle = `${stats.title} - Koito`; + return ( -
- {stats.title} - Koito - - -
-
- - setShowTime(!showTime)} - > +
+
+ {pgTitle} + + +
+ {stats !== undefined && ( + + )} +
+
+ +

+ {months[month]} +

+ +
+
+ +

{year}

+ +
+
+
+ + setShowTime(!showTime)} + > +
- {stats !== undefined && }
-
+
); }