From feb80916ccb1652d0e93a2084e0c10f9ac3eb7ff Mon Sep 17 00:00:00 2001
From: Ategon <benjamin@barbeau.net>
Date: Mon, 20 Jan 2025 00:37:30 -0500
Subject: [PATCH] Add post times

---
 next.config.ts                   |  12 ++-
 src/components/posts/index.tsx   | 170 ++++++++++++++++++++++++++++---
 src/components/streams/index.tsx |   7 +-
 src/types/PostSort.ts            |   2 +-
 src/types/PostTimes.ts           |  13 +++
 5 files changed, 188 insertions(+), 16 deletions(-)
 create mode 100644 src/types/PostTimes.ts

diff --git a/next.config.ts b/next.config.ts
index e9ffa30..c57540f 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,17 @@
 import type { NextConfig } from "next";
 
 const nextConfig: NextConfig = {
-  /* config options here */
+  images: {
+    remotePatterns: [
+      {
+        protocol: "https",
+        hostname: "static-cdn.jtvnw.net",
+        port: "",
+        pathname: "/**",
+        search: "",
+      },
+    ],
+  },
 };
 
 export default nextConfig;
diff --git a/src/components/posts/index.tsx b/src/components/posts/index.tsx
index 0b05471..d4c1665 100644
--- a/src/components/posts/index.tsx
+++ b/src/components/posts/index.tsx
@@ -1,6 +1,6 @@
 "use client";
 
-import { useEffect, useState } from "react";
+import { ReactNode, useEffect, useState } from "react";
 import PostCard from "./PostCard";
 import { PostType } from "@/types/PostType";
 import {
@@ -14,12 +14,31 @@ import { PostSort } from "@/types/PostSort";
 import { PostStyle } from "@/types/PostStyle";
 import { getCookie } from "@/helpers/cookie";
 import { UserType } from "@/types/UserType";
-import { LoaderCircle } from "lucide-react";
+import {
+  Calendar,
+  Calendar1,
+  CalendarArrowDown,
+  CalendarCog,
+  CalendarDays,
+  CalendarFold,
+  CalendarRange,
+  Clock1,
+  Clock2,
+  Clock3,
+  Clock4,
+  ClockArrowDown,
+  ClockArrowUp,
+  LoaderCircle,
+  Sparkles,
+  Trophy,
+} from "lucide-react";
 import { toast } from "react-toastify";
+import { PostTime } from "@/types/PostTimes";
 
 export default function Posts() {
   const [posts, setPosts] = useState<PostType[]>();
   const [sort, setSort] = useState<PostSort>("newest");
+  const [time, setTime] = useState<PostTime>("all");
   const [style, setStyle] = useState<PostStyle>("cozy");
   const [user, setUser] = useState<UserType>();
   const [loading, setLoading] = useState<boolean>(true);
@@ -45,8 +64,8 @@ export default function Posts() {
         // Fetch posts with userSlug if user is available
         const postsResponse = await fetch(
           process.env.NEXT_PUBLIC_MODE === "PROD"
-            ? `https://d2jam.com/api/v1/posts?sort=${sort}&user=${userData.slug}`
-            : `http://localhost:3005/api/v1/posts?sort=${sort}&user=${userData.slug}`
+            ? `https://d2jam.com/api/v1/posts?sort=${sort}&user=${userData.slug}&time=${time}`
+            : `http://localhost:3005/api/v1/posts?sort=${sort}&user=${userData.slug}&time=${time}`
         );
         setPosts(await postsResponse.json());
         setLoading(false);
@@ -56,8 +75,8 @@ export default function Posts() {
         // Fetch posts without userSlug if user is not available
         const postsResponse = await fetch(
           process.env.NEXT_PUBLIC_MODE === "PROD"
-            ? `https://d2jam.com/api/v1/posts?sort=${sort}`
-            : `http://localhost:3005/api/v1/posts?sort=${sort}`
+            ? `https://d2jam.com/api/v1/posts?sort=${sort}&time=${time}`
+            : `http://localhost:3005/api/v1/posts?sort=${sort}&time=${time}`
         );
         setPosts(await postsResponse.json());
         setLoading(false);
@@ -65,7 +84,94 @@ export default function Posts() {
     };
 
     loadUserAndPosts();
-  }, [sort]);
+  }, [sort, time]);
+
+  const sorts: Record<
+    PostSort,
+    { name: string; icon: ReactNode; description: string }
+  > = {
+    top: {
+      name: "Top",
+      icon: <Trophy />,
+      description: "Shows the most liked posts first",
+    },
+    newest: {
+      name: "Newest",
+      icon: <ClockArrowUp />,
+      description: "Shows the newest posts first",
+    },
+    oldest: {
+      name: "Oldest",
+      icon: <ClockArrowDown />,
+      description: "Shows the oldest posts first",
+    },
+  };
+
+  const times: Record<
+    PostTime,
+    { name: string; icon: ReactNode; description: string }
+  > = {
+    hour: {
+      name: "Hour",
+      icon: <Clock1 />,
+      description: "Shows posts from the last hour",
+    },
+    three_hours: {
+      name: "Three Hours",
+      icon: <Clock2 />,
+      description: "Shows posts from the last three hours",
+    },
+    six_hours: {
+      name: "Six Hours",
+      icon: <Clock3 />,
+      description: "Shows posts from the last six hours",
+    },
+    twelve_hours: {
+      name: "Twelve Hours",
+      icon: <Clock4 />,
+      description: "Shows posts from the last twelve hours",
+    },
+    day: {
+      name: "Day",
+      icon: <Calendar />,
+      description: "Shows posts from the last day",
+    },
+    week: {
+      name: "Week",
+      icon: <CalendarDays />,
+      description: "Shows posts from the last week",
+    },
+    month: {
+      name: "Month",
+      icon: <CalendarRange />,
+      description: "Shows posts from the last month",
+    },
+    three_months: {
+      name: "Three Months",
+      icon: <CalendarFold />,
+      description: "Shows posts from the last three months",
+    },
+    six_months: {
+      name: "Six Months",
+      icon: <CalendarCog />,
+      description: "Shows posts from the last six months",
+    },
+    nine_months: {
+      name: "Nine Months",
+      icon: <CalendarArrowDown />,
+      description: "Shows posts from the last nine months",
+    },
+    year: {
+      name: "Year",
+      icon: <Calendar1 />,
+      description: "Shows posts from the last year",
+    },
+    all: {
+      name: "All Times",
+      icon: <Sparkles />,
+      description: "Shows all posts",
+    },
+  };
 
   return (
     <div>
@@ -78,7 +184,7 @@ export default function Posts() {
                 className="text-xs bg-white dark:bg-[#252525] !duration-250 !ease-linear !transition-all text-[#333] dark:text-white"
                 variant="faded"
               >
-                {sort.charAt(0).toUpperCase() + sort.slice(1)}
+                {sorts[sort]?.name}
               </Button>
             </DropdownTrigger>
             <DropdownMenu
@@ -87,9 +193,42 @@ export default function Posts() {
               }}
               className="text-[#333] dark:text-white"
             >
-              <DropdownItem key="newest">Newest</DropdownItem>
-              <DropdownItem key="top">Top</DropdownItem>
-              <DropdownItem key="oldest">Oldest</DropdownItem>
+              {Object.entries(sorts).map(([key, sort]) => (
+                <DropdownItem
+                  key={key}
+                  startContent={sort.icon}
+                  description={sort.description}
+                >
+                  {sort.name}
+                </DropdownItem>
+              ))}
+            </DropdownMenu>
+          </Dropdown>
+          <Dropdown>
+            <DropdownTrigger>
+              <Button
+                size="sm"
+                className="text-xs bg-white dark:bg-[#252525] !duration-250 !ease-linear !transition-all text-[#333] dark:text-white"
+                variant="faded"
+              >
+                {times[time]?.name}
+              </Button>
+            </DropdownTrigger>
+            <DropdownMenu
+              onAction={(key) => {
+                setTime(key as PostTime);
+              }}
+              className="text-[#333] dark:text-white"
+            >
+              {Object.entries(times).map(([key, sort]) => (
+                <DropdownItem
+                  key={key}
+                  startContent={sort.icon}
+                  description={sort.description}
+                >
+                  {sort.name}
+                </DropdownItem>
+              ))}
             </DropdownMenu>
           </Dropdown>
           <Button
@@ -138,10 +277,15 @@ export default function Posts() {
         </div>
       ) : (
         <div className="flex flex-col gap-3 p-4">
-          {posts &&
+          {posts && posts.length > 0 ? (
             posts.map((post) => (
               <PostCard key={post.id} post={post} style={style} user={user} />
-            ))}
+            ))
+          ) : (
+            <p className="text-center text-[#333] dark:text-white transition-color duration-250 ease-linear">
+              No posts match your filters
+            </p>
+          )}
         </div>
       )}
     </div>
diff --git a/src/components/streams/index.tsx b/src/components/streams/index.tsx
index 9029056..5d51fc4 100644
--- a/src/components/streams/index.tsx
+++ b/src/components/streams/index.tsx
@@ -2,6 +2,8 @@
 
 import { useEffect, useState } from "react";
 import { FeaturedStreamerType } from "@/types/FeaturedStreamerType";
+import { Image } from "@nextui-org/react";
+import NextImage from "next/image";
 
 export default function Streams() {
   const [streamers, setStreamers] = useState<FeaturedStreamerType[]>([]);
@@ -64,10 +66,13 @@ export default function Streams() {
           margin: "0 auto",
         }}
       >
-        <img
+        <Image
+          as={NextImage}
           src={currentStreamer.thumbnailUrl}
           alt={`${currentStreamer.userName}'s thumbnail`}
           style={{ width: "100%", borderRadius: "4px", marginBottom: "10px" }}
+          width={320}
+          height={180}
         />
         <a
           href={`https://twitch.tv/${currentStreamer.userName}`}
diff --git a/src/types/PostSort.ts b/src/types/PostSort.ts
index ac07621..22c0a2e 100644
--- a/src/types/PostSort.ts
+++ b/src/types/PostSort.ts
@@ -1 +1 @@
-export type PostSort = "newest" | "oldest";
+export type PostSort = "newest" | "oldest" | "top";
diff --git a/src/types/PostTimes.ts b/src/types/PostTimes.ts
new file mode 100644
index 0000000..d228d72
--- /dev/null
+++ b/src/types/PostTimes.ts
@@ -0,0 +1,13 @@
+export type PostTime =
+  | "hour"
+  | "three_hours"
+  | "six_hours"
+  | "twelve_hours"
+  | "day"
+  | "week"
+  | "month"
+  | "three_months"
+  | "six_months"
+  | "nine_months"
+  | "year"
+  | "all";