39420-vm/src/pages/SearchPage.tsx
gpt-engineer-app[bot] db8b488075 Added device support and filters
Co-authored-by: felix-fx-top <253056634+felix-fx-top@users.noreply.github.com>
2026-03-30 13:56:41 +00:00

195 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import Navbar from "@/components/Navbar";
import ModGrid from "@/components/ModGrid";
import Footer from "@/components/Footer";
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 { Search, Languages, Loader2 } from "lucide-react";
import { searchMods, translateQuery } from "@/lib/api";
import { toast } from "sonner";
const SearchPage = () => {
const [searchParams, setSearchParams] = useSearchParams();
const initialQuery = searchParams.get("q") || "";
const [query, setQuery] = useState(initialQuery);
const [autoTranslate, setAutoTranslate] = useState(true);
const [translatedText, setTranslatedText] = useState<string | null>(null);
const [isTranslating, setIsTranslating] = useState(false);
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, platform],
queryFn: async () => {
const searchTerm = translatedText || initialQuery;
const result = await searchMods(searchTerm, 0, 20, facets);
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, 0, 20, facets);
setIsTranslating(false);
return translatedResult;
}
} catch {
toast.error("فشل في ترجمة البحث");
}
setIsTranslating(false);
}
return result;
},
enabled: !!initialQuery,
});
const handleSearch = useCallback((e: React.FormEvent) => {
e.preventDefault();
const trimmed = query.trim().slice(0, 200);
if (!trimmed) return;
const now = Date.now();
if (now - lastSubmit < 1000) {
toast.error("انتظر قليلاً قبل البحث مرة أخرى");
return;
}
setLastSubmit(now);
setTranslatedText(null);
setSearchParams({ q: trimmed });
}, [query, lastSubmit, setSearchParams]);
const handleManualTranslate = async () => {
if (!initialQuery) return;
setIsTranslating(true);
try {
const { translated } = await translateQuery(initialQuery);
if (translated !== initialQuery) {
setTranslatedText(translated);
toast.success(`تمت الترجمة: "${translated}"`);
} else {
toast.info("النص بالفعل باللغة الإنجليزية");
}
} catch {
toast.error("فشل في ترجمة البحث");
}
setIsTranslating(false);
};
const mods = data?.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,
})) || [];
return (
<div className="flex min-h-screen flex-col">
<Navbar />
<main className="flex-1">
<div className="container mx-auto px-3 py-6 sm:px-4 sm:py-8">
<h1 className="mb-4 text-2xl font-black sm:mb-6 sm:text-3xl">البحث عن إضافات</h1>
<form onSubmit={handleSearch} className="mb-3 flex gap-2 sm:mb-4 sm:gap-3">
<div className="relative flex-1">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="ابحث عن مود، حزمة موارد، خريطة..."
className="bg-secondary pr-10 text-sm sm:text-base"
maxLength={200}
/>
<Search className="absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
</div>
<Button type="submit" className="font-bold text-sm sm:text-base">
بحث
</Button>
</form>
{/* Translation controls */}
<div className="mb-4 flex flex-col gap-3 sm:mb-6 sm:flex-row sm:items-center">
<div className="flex items-center gap-2">
<Switch
id="auto-translate"
checked={autoTranslate}
onCheckedChange={setAutoTranslate}
/>
<Label htmlFor="auto-translate" className="text-xs text-muted-foreground sm:text-sm">
ترجمة تلقائية عند عدم وجود نتائج
</Label>
</div>
{initialQuery && (
<Button
variant="outline"
size="sm"
onClick={handleManualTranslate}
disabled={isTranslating}
className="gap-1 text-xs sm:text-sm"
>
{isTranslating ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Languages className="h-3 w-3" />
)}
ترجم للإنجليزية
</Button>
)}
</div>
{initialQuery ? (
<>
<p className="mb-3 text-xs text-muted-foreground sm:mb-4 sm:text-sm">
نتائج البحث عن: <span className="font-bold text-foreground">"{initialQuery}"</span>
{translatedText && (
<span className="mr-2 text-primary">
تمت الترجمة إلى: "{translatedText}"
</span>
)}
{data && ` (${data.total_hits || 0} نتيجة)`}
</p>
{isTranslating ? (
<div className="flex items-center justify-center gap-2 py-20 text-muted-foreground">
<Loader2 className="h-5 w-5 animate-spin" />
جاري الترجمة والبحث...
</div>
) : (
<ModGrid
mods={mods}
isLoading={isLoading}
showViewToggle
showPlatformFilter
selectedPlatform={platform}
onPlatformChange={setPlatform}
/>
)}
</>
) : (
<div className="py-20 text-center text-muted-foreground">
ابدأ بالبحث عن إضافتك المفضلة
</div>
)}
</div>
</main>
<Footer />
</div>
);
};
export default SearchPage;