diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index f72b93b..5ecf92e 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import { Download, Heart, Eye } from "lucide-react"; +import { Download, Heart } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -13,7 +13,7 @@ interface ModCardProps { followers: number; categories: string[]; projectType: string; - viewMode?: "grid" | "list"; + viewMode?: "grid" | "list" | "compact"; source?: "modrinth" | "curseforge"; } @@ -34,10 +34,36 @@ const typeLabels: Record = { const ModCard = ({ id, slug, title, description, iconUrl, downloads, followers, categories, projectType, viewMode = "grid", source = "modrinth" }: ModCardProps) => { const linkPath = source === "curseforge" ? `/curseforge/${id}` : `/mod/${slug || id}`; + + // Compact view — minimal row + if (viewMode === "compact") { + return ( + +
+
+ {iconUrl ? ( + {title} + ) : ( +
🧊
+ )} +
+

+ {title} +

+ + + {formatNumber(downloads)} + +
+ + ); + } + + // List view — horizontal card if (viewMode === "list") { return ( - +
{iconUrl ? ( @@ -51,7 +77,7 @@ const ModCard = ({ id, slug, title, description, iconUrl, downloads, followers,

{title}

- + {typeLabels[projectType] || projectType}
@@ -73,9 +99,10 @@ const ModCard = ({ id, slug, title, description, iconUrl, downloads, followers, ); } + // Grid view — card return ( - +
{iconUrl ? ( @@ -89,7 +116,7 @@ const ModCard = ({ id, slug, title, description, iconUrl, downloads, followers,

{title}

- + {typeLabels[projectType] || projectType}
diff --git a/src/components/ModGrid.tsx b/src/components/ModGrid.tsx index 2d9bd30..bcc1149 100644 --- a/src/components/ModGrid.tsx +++ b/src/components/ModGrid.tsx @@ -1,8 +1,7 @@ import ModCard from "./ModCard"; import { Skeleton } from "@/components/ui/skeleton"; -import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import { LayoutGrid, List, Monitor, Smartphone } from "lucide-react"; +import { LayoutGrid, List, AlignJustify, Monitor, Smartphone } from "lucide-react"; import { useState } from "react"; interface Mod { @@ -37,7 +36,14 @@ const ModGrid = ({ onPlatformChange, selectedPlatform = "all", }: ModGridProps) => { - const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); + const [viewMode, setViewMode] = useState<"grid" | "list" | "compact">("grid"); + + const gridClass = + viewMode === "grid" + ? "grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" + : viewMode === "list" + ? "flex flex-col gap-2" + : "grid gap-1.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"; return (
@@ -70,25 +76,25 @@ const ModGrid = ({ val && setViewMode(val as "grid" | "list")} + onValueChange={(val) => val && setViewMode(val as "grid" | "list" | "compact")} className="rounded-lg border border-border bg-secondary/50 p-0.5" > - + - + + + + )} {isLoading ? ( -
+
{Array.from({ length: 6 }).map((_, i) => (
@@ -105,13 +111,10 @@ const ModGrid = ({ لا توجد إضافات حالياً
) : ( -
+
{mods.map((mod) => ( { {project.logo?.url ? ( {project.name} ) : ( -
🟠
+
🧊
)}
-
-

{project.name}

- CurseForge -
+

{project.name}

{project.summary}

@@ -95,7 +92,6 @@ const CurseForgeDetails = () => {
- {/* Files / Versions */} الملفات diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 8bf638d..bd0c843 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -17,63 +17,67 @@ const Index = () => { }, }); - const { data: discoverData, isLoading: loadingDiscover } = useQuery({ - queryKey: ["discover", randomKeyword], - queryFn: () => searchMods(randomKeyword, 0, 12), + const { data: discoverMods, isLoading: loadingDiscover } = useQuery({ + queryKey: ["discover-all", randomKeyword], + queryFn: async () => { + const [modrinthData, cfData] = await Promise.all([ + searchMods(randomKeyword, 0, 8), + searchCurseForge(randomKeyword, 0, 8), + ]); + + const modrinthMods = modrinthData?.hits?.map((hit: any) => ({ + id: hit.project_id, + slug: hit.slug, + title: hit.title, + description: hit.description, + icon_url: hit.icon_url, + downloads: hit.downloads, + followers: hit.follows, + categories: hit.categories || [], + project_type: hit.project_type, + source: "modrinth" as const, + })) || []; + + const cfMods = cfData?.data?.map((mod: any) => ({ + id: String(mod.id), + slug: String(mod.id), + title: mod.name, + description: mod.summary, + icon_url: mod.logo?.url, + downloads: mod.downloadCount, + followers: mod.thumbsUpCount || 0, + categories: mod.categories?.map((c: any) => c.name) || [], + project_type: "mod", + source: "curseforge" as const, + })) || []; + + // Interleave results for a mixed feel + const merged = []; + const maxLen = Math.max(modrinthMods.length, cfMods.length); + for (let i = 0; i < maxLen; i++) { + if (i < modrinthMods.length) merged.push(modrinthMods[i]); + if (i < cfMods.length) merged.push(cfMods[i]); + } + return merged; + }, }); - const { data: cfData, isLoading: loadingCF } = useQuery({ - queryKey: ["curseforge-discover", randomKeyword], - queryFn: () => searchCurseForge(randomKeyword, 0, 12), - }); - - const discoverMods = discoverData?.hits?.map((hit: any) => ({ - id: hit.project_id, - slug: hit.slug, - title: hit.title, - description: hit.description, - icon_url: hit.icon_url, - downloads: hit.downloads, - followers: hit.follows, - categories: hit.categories || [], - project_type: hit.project_type, - source: "modrinth" as const, - })) || []; - - const curseForgeMods = cfData?.data?.map((mod: any) => ({ - id: String(mod.id), - slug: String(mod.id), - title: mod.name, - description: mod.summary, - icon_url: mod.logo?.url, - downloads: mod.downloadCount, - followers: mod.thumbsUpCount || 0, - categories: mod.categories?.map((c: any) => c.name) || [], - project_type: "mod", - source: "curseforge" as const, - })) || []; - return (
-
+
-
diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx index c945e47..c0d6a9d 100644 --- a/src/pages/SearchPage.tsx +++ b/src/pages/SearchPage.tsx @@ -8,7 +8,6 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Search, Languages, Loader2 } from "lucide-react"; import { searchMods, searchCurseForge, translateQuery } from "@/lib/api"; import { toast } from "sonner"; @@ -22,7 +21,6 @@ const SearchPage = () => { const [isTranslating, setIsTranslating] = useState(false); const [platform, setPlatform] = useState("all"); const [lastSubmit, setLastSubmit] = useState(0); - const [source, setSource] = useState<"all" | "modrinth" | "curseforge">("all"); const facets = platform === "java" ? '[["project_type:mod"],["categories:fabric","categories:forge","categories:neoforge","categories:quilt"]]' @@ -31,19 +29,14 @@ const SearchPage = () => { : ""; const { data, isLoading } = useQuery({ - queryKey: ["search", initialQuery, translatedText, platform, source], + queryKey: ["search", initialQuery, translatedText, platform], queryFn: async () => { const searchTerm = translatedText || initialQuery; - const modrinthPromise = source !== "curseforge" - ? searchMods(searchTerm, 0, 20, facets) - : Promise.resolve({ hits: [], total_hits: 0 }); - - const cfPromise = source !== "modrinth" - ? searchCurseForge(searchTerm, 0, 20) - : Promise.resolve({ data: [], pagination: { totalCount: 0 } }); - - const [modrinthResult, cfResult] = await Promise.all([modrinthPromise, cfPromise]); + const [modrinthResult, cfResult] = await Promise.all([ + searchMods(searchTerm, 0, 20, facets), + searchCurseForge(searchTerm, 0, 20), + ]); const totalHits = (modrinthResult.total_hits || 0) + (cfResult.pagination?.totalCount || 0); @@ -54,8 +47,8 @@ const SearchPage = () => { if (translated !== initialQuery) { setTranslatedText(translated); const [mr, cr] = await Promise.all([ - source !== "curseforge" ? searchMods(translated, 0, 20, facets) : Promise.resolve({ hits: [], total_hits: 0 }), - source !== "modrinth" ? searchCurseForge(translated, 0, 20) : Promise.resolve({ data: [], pagination: { totalCount: 0 } }), + searchMods(translated, 0, 20, facets), + searchCurseForge(translated, 0, 20), ]); setIsTranslating(false); return { modrinth: mr, curseforge: cr }; @@ -129,7 +122,13 @@ const SearchPage = () => { source: "curseforge" as const, })) || []; - const mods = [...modrinthMods, ...cfMods]; + // Interleave results + const mods: typeof modrinthMods = []; + const maxLen = Math.max(modrinthMods.length, cfMods.length); + for (let i = 0; i < maxLen; i++) { + if (i < modrinthMods.length) mods.push(modrinthMods[i]); + if (i < cfMods.length) mods.push(cfMods[i]); + } const totalHits = (data?.modrinth?.total_hits || 0) + (data?.curseforge?.pagination?.totalCount || 0); return ( @@ -185,26 +184,6 @@ const SearchPage = () => { )}
- {/* Source filter */} -
- val && setSource(val as "all" | "modrinth" | "curseforge")} - className="rounded-lg border border-border bg-secondary/50 p-0.5" - > - - الكل - - - Modrinth - - - CurseForge - - -
- {initialQuery ? ( <>