From c5987734116cfaa3c33fa6e3629dc0dd1961bee3 Mon Sep 17 00:00:00 2001 From: Jared Schoeny Date: Sat, 8 Nov 2025 12:43:05 -1000 Subject: [PATCH] Add popular hacks section to homepage --- src/app/page.tsx | 139 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 8edb549..9c1c991 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,9 @@ import { Metadata } from "next"; import Link from "next/link"; import { FaArrowRightLong } from "react-icons/fa6"; +import { createClient } from "@/utils/supabase/server"; +import HackCard from "@/components/HackCard"; +import Button from "@/components/Button"; export const metadata: Metadata = { alternates: { @@ -8,7 +11,106 @@ export const metadata: Metadata = { }, }; -export default function Home() { +// TODO: Lower to 3600 (1 hour) once we get more traffic +export const revalidate = 604800; // 1 week in seconds + +export default async function Home() { + const supabase = await createClient(); + + // Fetch top 6 approved hacks ordered by downloads + const { data: popularHacks } = await supabase + .from("hacks") + .select("slug,title,summary,description,base_rom,downloads,created_by,current_patch") + .eq("approved", true) + .order("downloads", { ascending: false }) + .limit(6); + + let hackData: any[] = []; + if (popularHacks && popularHacks.length > 0) { + const slugs = popularHacks.map((h) => h.slug); + + // Fetch covers + const { data: coverRows } = await supabase + .from("hack_covers") + .select("hack_slug,url,position") + .in("hack_slug", slugs) + .order("position", { ascending: true }); + + const coversBySlug = new Map(); + if (coverRows && coverRows.length > 0) { + const { data: imagesData } = await supabase.storage + .from("hack-covers") + .createSignedUrls(coverRows.map((c) => c.url), 60 * 5); + if (imagesData) { + const urlToSignedUrl = new Map(); + imagesData.forEach((d, idx) => { + if (d.signedUrl) urlToSignedUrl.set(coverRows[idx].url, d.signedUrl); + }); + + coverRows.forEach((c) => { + const arr = coversBySlug.get(c.hack_slug) || []; + const signed = urlToSignedUrl.get(c.url); + if (signed) { + arr.push(signed); + coversBySlug.set(c.hack_slug, arr); + } + }); + } + } + + // Fetch tags + const { data: tagRows } = await supabase + .from("hack_tags") + .select("hack_slug,tags(name,category)") + .in("hack_slug", slugs); + const tagsBySlug = new Map(); + (tagRows || []).forEach((r: any) => { + if (!r.tags?.name) return; + const arr = tagsBySlug.get(r.hack_slug) || []; + arr.push(r.tags.name); + tagsBySlug.set(r.hack_slug, arr); + }); + + // Fetch versions + let mappedVersions = new Map(); + await Promise.all( + popularHacks.map(async (r) => { + if (r.current_patch) { + const { data: currentPatch } = await supabase + .from("patches") + .select("version") + .eq("id", r.current_patch) + .maybeSingle(); + mappedVersions.set(r.slug, currentPatch?.version || "Pre-release"); + } else { + mappedVersions.set(r.slug, "Pre-release"); + } + }) + ); + + // Fetch profiles + const userIds = [...new Set(popularHacks.map((h) => h.created_by).filter(Boolean))]; + const { data: profiles } = await supabase + .from("profiles") + .select("id,username") + .in("id", userIds); + const usernameById = new Map(); + (profiles || []).forEach((p) => usernameById.set(p.id, p.username ? `@${p.username}` : "Unknown")); + + // Map data to HackCard format + hackData = popularHacks.map((r) => ({ + slug: r.slug, + title: r.title, + author: usernameById.get(r.created_by as string) || "Unknown", + covers: coversBySlug.get(r.slug) || [], + tags: tagsBySlug.get(r.slug) || [], + downloads: r.downloads, + baseRomId: r.base_rom, + version: mappedVersions.get(r.slug) || "Pre-release", + summary: r.summary, + description: r.description, + })); + } return (
@@ -45,7 +147,7 @@ export default function Home() {
-
+
0 ? "pt-6 sm:pt-12" : "py-6 sm:py-12"}`}>
Curated discovery
@@ -68,6 +170,39 @@ export default function Home() {
+ + {hackData.length > 0 && ( +
+
+
+

Popular ROM hacks

+

Most downloaded patches

+
+ + View all + +
+
+ {hackData.map((hack) => ( + + ))} +
+
+ +
+
+ )} ); }