mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-06-10 09:20:44 -05:00
Add archive distinction to HackCard and DiscoverBrowser
This commit is contained in:
parent
06318ef715
commit
147d25342e
|
|
@ -6,6 +6,7 @@ import HackCard from "@/components/HackCard";
|
|||
import Button from "@/components/Button";
|
||||
import { sortOrderedTags } from "@/utils/format";
|
||||
import { getCoverSignedUrls } from "@/app/hack/actions";
|
||||
import { HackCardAttributes } from "@/components/HackCard";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
alternates: {
|
||||
|
|
@ -27,7 +28,7 @@ export default async function Home() {
|
|||
.order("downloads", { ascending: false })
|
||||
.limit(6);
|
||||
|
||||
let hackData: any[] = [];
|
||||
let hackData: HackCardAttributes[] = [];
|
||||
if (popularHacks && popularHacks.length > 0) {
|
||||
const slugs = popularHacks.map((h) => h.slug);
|
||||
|
||||
|
|
@ -110,6 +111,7 @@ export default async function Home() {
|
|||
version: mappedVersions.get(r.slug) || "Pre-release",
|
||||
summary: r.summary,
|
||||
description: r.description,
|
||||
isArchive: r.original_author != null && r.current_patch === null,
|
||||
}));
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { CATEGORY_ICONS } from "@/components/Icons/tagCategories";
|
|||
import { useBaseRoms } from "@/contexts/BaseRomContext";
|
||||
import { sortOrderedTags, OrderedTag } from "@/utils/format";
|
||||
import { getCoverSignedUrls } from "@/app/hack/actions";
|
||||
import { HackCardAttributes } from "@/components/HackCard";
|
||||
|
||||
|
||||
export default function DiscoverBrowser() {
|
||||
|
|
@ -21,7 +22,7 @@ export default function DiscoverBrowser() {
|
|||
const [selectedTags, setSelectedTags] = React.useState<string[]>([]);
|
||||
const [selectedBaseRoms, setSelectedBaseRoms] = React.useState<string[]>([]);
|
||||
const [sort, setSort] = React.useState("popular");
|
||||
const [hacks, setHacks] = React.useState<any[]>([]);
|
||||
const [hacks, setHacks] = React.useState<HackCardAttributes[]>([]);
|
||||
const [tagGroups, setTagGroups] = React.useState<Record<string, string[]>>({});
|
||||
const [ungroupedTags, setUngroupedTags] = React.useState<string[]>([]);
|
||||
const [loadingHacks, setLoadingHacks] = React.useState(true);
|
||||
|
|
@ -46,19 +47,24 @@ export default function DiscoverBrowser() {
|
|||
const run = async () => {
|
||||
setLoadingHacks(true);
|
||||
setLoadingTags(true);
|
||||
let orderBy: string | undefined = undefined;
|
||||
let query = supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,downloads,created_by,updated_at,current_patch,original_author");
|
||||
|
||||
if (sort === "popular") {
|
||||
orderBy = "downloads";
|
||||
// When sorting by popularity, always show non-archive hacks first.
|
||||
// Archives are defined as rows where original_author IS NOT NULL and current_patch IS NULL,
|
||||
// so ordering by current_patch with NULLS LAST effectively pushes archives to the end.
|
||||
query = query
|
||||
.order("downloads", { ascending: false })
|
||||
.order("current_patch", { ascending: false, nullsFirst: false });
|
||||
} else if (sort === "updated") {
|
||||
orderBy = "updated_at";
|
||||
query = query.order("updated_at", { ascending: false });
|
||||
} else {
|
||||
orderBy = "created_at";
|
||||
query = query.order("created_at", { ascending: false });
|
||||
}
|
||||
|
||||
const { data: rows } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,downloads,created_by,updated_at,current_patch,original_author")
|
||||
.order(orderBy, { ascending: false });
|
||||
const { data: rows } = await query;
|
||||
const slugs = (rows || []).map((r) => r.slug);
|
||||
const { data: coverRows } = await supabase
|
||||
.from("hack_covers")
|
||||
|
|
@ -132,6 +138,7 @@ export default function DiscoverBrowser() {
|
|||
version: mappedVersions.get(r.slug) || "Pre-release",
|
||||
summary: r.summary,
|
||||
description: r.description,
|
||||
isArchive: r.original_author != null && r.current_patch === null,
|
||||
}));
|
||||
|
||||
setHacks(mapped);
|
||||
|
|
@ -177,15 +184,15 @@ export default function DiscoverBrowser() {
|
|||
}
|
||||
// AND filter across selected tags: hack must include all selectedTags
|
||||
if (selectedTags.length > 0) {
|
||||
out = out.filter((h) => selectedTags.every((t) => h.tags.includes(t)));
|
||||
out = out.filter((h) => selectedTags.every((t) => h.tags.some((tag) => tag.name === t)));
|
||||
}
|
||||
// OR filter across base roms: hack's baseRomId must be in selectedBaseRoms
|
||||
if (selectedBaseRoms.length > 0) {
|
||||
out = out.filter((h) => selectedBaseRoms.includes(h.baseRomId));
|
||||
out = out.filter((h) => h.baseRomId && selectedBaseRoms.includes(h.baseRomId));
|
||||
}
|
||||
// Filter to hacks whose base ROM is ready (linked with permission or cached)
|
||||
if (onlyReady) {
|
||||
out = out.filter((h) => readyBaseRomIds.has(h.baseRomId));
|
||||
out = out.filter((h) => !h.isArchive && h.baseRomId && readyBaseRomIds.has(h.baseRomId));
|
||||
}
|
||||
return out;
|
||||
}, [hacks, query, selectedTags, selectedBaseRoms, onlyReady, readyBaseRomIds]);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ import useEmblaCarousel from "embla-carousel-react";
|
|||
import { usePathname } from "next/navigation";
|
||||
import { FaRegImages } from "react-icons/fa6";
|
||||
import { ImDownload } from "react-icons/im";
|
||||
import { FaArchive } from "react-icons/fa";
|
||||
|
||||
type CardHack = {
|
||||
export interface HackCardAttributes {
|
||||
slug: string;
|
||||
title: string;
|
||||
author: string;
|
||||
|
|
@ -23,15 +24,19 @@ type CardHack = {
|
|||
version: string;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
isArchive?: boolean;
|
||||
};
|
||||
|
||||
export default function HackCard({ hack, clickable = true, className = "" }: { hack: CardHack; clickable?: boolean; className?: string }) {
|
||||
export default function HackCard({ hack, clickable = true, className = "" }: { hack: HackCardAttributes; clickable?: boolean; className?: string }) {
|
||||
const isArchive = !!hack.isArchive;
|
||||
const { isLinked, hasPermission, hasCached } = useBaseRoms();
|
||||
const match = baseRoms.find((r) => r.id === hack.baseRomId);
|
||||
const baseId = match?.id ?? undefined;
|
||||
const baseName = match?.name ?? undefined;
|
||||
const linked = baseId ? isLinked(baseId) : false;
|
||||
const ready = baseId ? hasPermission(baseId) || hasCached(baseId) : false;
|
||||
|
||||
// Only compute base ROM readiness for non-archive hacks
|
||||
const linked = !isArchive && baseId ? isLinked(baseId) : false;
|
||||
const ready = !isArchive && baseId ? hasPermission(baseId) || hasCached(baseId) : false;
|
||||
const images = (hack.covers && hack.covers.length > 0 ? hack.covers : []).filter(Boolean);
|
||||
const isCarousel = images.length > 1;
|
||||
const pathname = usePathname();
|
||||
|
|
@ -121,7 +126,7 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
|||
) : null}
|
||||
|
||||
<div className="absolute left-3 top-3 z-10 flex gap-2">
|
||||
{hack.tags.slice(0, 2).map((t) => (
|
||||
{hack.tags.slice(0, isArchive ? 3 : 2).map((t) => (
|
||||
<span
|
||||
key={t.name}
|
||||
className="rounded-full px-2 py-0.5 text-xs ring-1 ring-foreground/20 dark:ring-foreground/30 bg-background/70 text-foreground/90 backdrop-blur-md"
|
||||
|
|
@ -129,18 +134,25 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
|||
{t.name}
|
||||
</span>
|
||||
))}
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-xs ring-1 backdrop-blur-md ${
|
||||
ready
|
||||
? "bg-emerald-600/60 text-white ring-emerald-700/80 dark:bg-emerald-500/25 dark:text-emerald-100 dark:ring-emerald-400/90"
|
||||
: linked
|
||||
? "bg-amber-600/60 text-white ring-amber-700/80 dark:bg-amber-500/50 dark:text-amber-100 dark:ring-amber-400/90"
|
||||
: "bg-red-600/60 text-white ring-red-700/80 dark:bg-red-500/50 dark:text-red-100 dark:ring-red-400/90"
|
||||
}`}
|
||||
>
|
||||
{ready ? "Ready" : linked ? "Permission needed" : "Base ROM needed"}
|
||||
</span>
|
||||
{!isArchive && (
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-xs ring-1 backdrop-blur-md ${
|
||||
ready
|
||||
? "bg-emerald-600/60 text-white ring-emerald-700/80 dark:bg-emerald-500/25 dark:text-emerald-100 dark:ring-emerald-400/90"
|
||||
: linked
|
||||
? "bg-amber-600/60 text-white ring-amber-700/80 dark:bg-amber-500/50 dark:text-amber-100 dark:ring-amber-400/90"
|
||||
: "bg-red-600/60 text-white ring-red-700/80 dark:bg-red-500/50 dark:text-red-100 dark:ring-red-400/90"
|
||||
}`}
|
||||
>
|
||||
{ready ? "Ready" : linked ? "Permission needed" : "Base ROM needed"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isArchive && (
|
||||
<div className="absolute right-3 top-3 z-10">
|
||||
<FaArchive size={20} className="text-foreground/60" />
|
||||
</div>
|
||||
)}
|
||||
{isCarousel && (
|
||||
<div className="absolute inset-x-0 bottom-2 z-10 flex items-center justify-center gap-3">
|
||||
{images.map((_, i) => (
|
||||
|
|
@ -164,8 +176,8 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
|||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="min-w-0 w-full">
|
||||
<div className={`flex items-center gap-2 ${isArchive ? "justify-between" : "justify-start"}`}>
|
||||
<h3 className="line-clamp-1 text-[15px] font-semibold tracking-tight">
|
||||
{hack.title}
|
||||
</h3>
|
||||
|
|
@ -175,10 +187,12 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
|||
</div>
|
||||
<p className="mt-1 text-xs text-foreground/60">By {hack.author}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-sm text-foreground/70">
|
||||
{!isArchive && (
|
||||
<div className="flex items-center gap-1 text-sm text-foreground/70">
|
||||
<ImDownload size={16} />
|
||||
<span>{formatCompactNumber(hack.downloads)}</span>
|
||||
</div>
|
||||
<span>{formatCompactNumber(hack.downloads)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-2 line-clamp-2 text-sm text-foreground/70">
|
||||
{(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user