mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-08-22 08:34:13 -05:00
Add popular hacks section to homepage
This commit is contained in:
139
src/app/page.tsx
139
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<string, string[]>();
|
||||
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<string, string>();
|
||||
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<string, string[]>();
|
||||
(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<string, string>();
|
||||
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<string, string>();
|
||||
(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 (
|
||||
<div>
|
||||
<section className="relative overflow-hidden">
|
||||
@@ -45,7 +147,7 @@ export default function Home() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mx-auto max-w-screen-2xl px-6 py-6 sm:py-12">
|
||||
<section className={`mx-auto max-w-screen-2xl px-6 ${hackData.length > 0 ? "pt-6 sm:pt-12" : "py-6 sm:py-12"}`}>
|
||||
<div className="grid gap-6 sm:grid-cols-3">
|
||||
<div className="card p-5">
|
||||
<div className="text-[15px] font-semibold tracking-tight">Curated discovery</div>
|
||||
@@ -68,6 +170,39 @@ export default function Home() {
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{hackData.length > 0 && (
|
||||
<section className="mx-auto max-w-screen-2xl px-6 py-12">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">Popular ROM hacks</h2>
|
||||
<p className="mt-1 text-sm text-foreground/70">Most downloaded patches</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/discover"
|
||||
className="text-sm font-medium text-foreground/80 hover:text-foreground hover:underline"
|
||||
>
|
||||
View all <FaArrowRightLong className="inline ml-1" size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{hackData.map((hack) => (
|
||||
<HackCard key={hack.slug} hack={hack} />
|
||||
))}
|
||||
</div>
|
||||
<div className="sm:hidden flex justify-center mt-6">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="w-48"
|
||||
>
|
||||
<Link href="/discover" className="inline-flex items-center">
|
||||
View all
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user