diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index 6977f00..36cea8b 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import { Download, Heart } from "lucide-react"; +import { Download, Heart, Eye } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -13,6 +13,7 @@ interface ModCardProps { followers: number; categories: string[]; projectType: string; + viewMode?: "grid" | "list"; } const formatNumber = (num: number) => { @@ -30,29 +31,68 @@ const typeLabels: Record = { plugin: "إضافة سيرفر", }; -const ModCard = ({ id, slug, title, description, iconUrl, downloads, followers, categories, projectType }: ModCardProps) => { +const ModCard = ({ id, slug, title, description, iconUrl, downloads, followers, categories, projectType, viewMode = "grid" }: ModCardProps) => { + if (viewMode === "list") { + return ( + + + +
+ {iconUrl ? ( + {title} + ) : ( +
🧊
+ )} +
+
+
+

+ {title} +

+ + {typeLabels[projectType] || projectType} + +
+

{description}

+
+
+ + + {formatNumber(downloads)} + + + + {formatNumber(followers)} + +
+
+
+ + ); + } + return ( - -
+ +
{iconUrl ? ( {title} ) : ( -
🧊
+
🧊
)}
-

+

{title}

- + {typeLabels[projectType] || projectType}
-

{description}

-
+

{description}

+
{formatNumber(downloads)} diff --git a/src/components/ModGrid.tsx b/src/components/ModGrid.tsx index 5bdaf67..323932a 100644 --- a/src/components/ModGrid.tsx +++ b/src/components/ModGrid.tsx @@ -1,5 +1,9 @@ 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 { useState } from "react"; interface Mod { id: string; @@ -17,22 +21,79 @@ interface ModGridProps { mods: Mod[]; isLoading?: boolean; title?: string; + showViewToggle?: boolean; + showPlatformFilter?: boolean; + onPlatformChange?: (platform: string) => void; + selectedPlatform?: string; } -const ModGrid = ({ mods, isLoading, title }: ModGridProps) => { +const ModGrid = ({ + mods, + isLoading, + title, + showViewToggle = true, + showPlatformFilter = false, + onPlatformChange, + selectedPlatform = "all", +}: ModGridProps) => { + const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); + return ( -
- {title && ( -

{title}

- )} +
+
+ {title && ( +

{title}

+ )} +
+ {showPlatformFilter && ( + val && onPlatformChange?.(val)} + className="rounded-lg border border-border bg-secondary/50 p-0.5" + > + + الكل + + + + جافا + + + + بيدروك + + + )} + {showViewToggle && ( + val && setViewMode(val as "grid" | "list")} + className="rounded-lg border border-border bg-secondary/50 p-0.5" + > + + + + + + + + )} +
+
+ {isLoading ? ( -
+
{Array.from({ length: 6 }).map((_, i) => ( -
- +
+
- - + +
@@ -43,7 +104,10 @@ const ModGrid = ({ mods, isLoading, title }: ModGridProps) => { لا توجد إضافات حالياً
) : ( -
+
{mods.map((mod) => ( { followers={mod.followers} categories={mod.categories || []} projectType={mod.project_type} + viewMode={viewMode} /> ))}
diff --git a/src/lib/api.ts b/src/lib/api.ts index 970ee99..b025142 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -36,11 +36,12 @@ export async function getUserProjects(username = "fxfelixzero") { return callFetchMods({ action: "user_projects", username }); } -export async function searchMods(query: string, offset = 0, limit = 20) { - // Sanitize client-side too +export async function searchMods(query: string, offset = 0, limit = 20, facets = "") { const cleanQuery = query.slice(0, 200).replace(/[<>{}]/g, "").trim(); if (!cleanQuery) return { hits: [], total_hits: 0 }; - return callFetchMods({ action: "search", query: cleanQuery, offset: String(offset), limit: String(limit) }); + const params: Record = { action: "search", query: cleanQuery, offset: String(offset), limit: String(limit) }; + if (facets) params.facets = facets; + return callFetchMods(params); } export async function getProject(id: string) { diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx index 512adf6..970439a 100644 --- a/src/pages/SearchPage.tsx +++ b/src/pages/SearchPage.tsx @@ -19,24 +19,28 @@ const SearchPage = () => { const [autoTranslate, setAutoTranslate] = useState(true); const [translatedText, setTranslatedText] = useState(null); const [isTranslating, setIsTranslating] = useState(false); - - // Spam prevention: track rapid submissions + const [platform, setPlatform] = useState("all"); const [lastSubmit, setLastSubmit] = useState(0); + const facets = platform === "java" + ? '[["project_type:mod"],["categories:fabric","categories:forge","categories:neoforge","categories:quilt"]]' + : platform === "bedrock" + ? '[["project_type:mod"],["categories:bedrock"]]' + : ""; + const { data, isLoading } = useQuery({ - queryKey: ["search", initialQuery, translatedText], + queryKey: ["search", initialQuery, translatedText, platform], queryFn: async () => { const searchTerm = translatedText || initialQuery; - const result = await searchMods(searchTerm); + const result = await searchMods(searchTerm, 0, 20, facets); - // If no results and autoTranslate is on, try translating if (autoTranslate && !translatedText && result.total_hits === 0 && initialQuery) { setIsTranslating(true); try { const { translated } = await translateQuery(initialQuery); if (translated !== initialQuery) { setTranslatedText(translated); - const translatedResult = await searchMods(translated); + const translatedResult = await searchMods(translated, 0, 20, facets); setIsTranslating(false); return translatedResult; } @@ -56,7 +60,6 @@ const SearchPage = () => { const trimmed = query.trim().slice(0, 200); if (!trimmed) return; - // Anti-spam: minimum 1s between searches const now = Date.now(); if (now - lastSubmit < 1000) { toast.error("انتظر قليلاً قبل البحث مرة أخرى"); @@ -100,34 +103,34 @@ const SearchPage = () => {
-
-

البحث عن إضافات

+
+

البحث عن إضافات

-
+
setQuery(e.target.value)} placeholder="ابحث عن مود، حزمة موارد، خريطة..." - className="bg-secondary pr-10" + className="bg-secondary pr-10 text-sm sm:text-base" maxLength={200} />
-
{/* Translation controls */} -
+
-
@@ -137,7 +140,7 @@ const SearchPage = () => { size="sm" onClick={handleManualTranslate} disabled={isTranslating} - className="gap-1" + className="gap-1 text-xs sm:text-sm" > {isTranslating ? ( @@ -151,7 +154,7 @@ const SearchPage = () => { {initialQuery ? ( <> -

+

نتائج البحث عن: "{initialQuery}" {translatedText && ( @@ -166,7 +169,14 @@ const SearchPage = () => { جاري الترجمة والبحث...

) : ( - + )} ) : (