Added Tailwind CSS and tw-animate-css imports to globals.css. Introduced a custom dark variant and an extensive @theme inline block with CSS variables for colors, radii, and animations. Included keyframes for shimmer-slide, spin-around, gradient, shine, and background-position-spin to enable consistent theming and dynamic UI effects across the application.
187 lines
5.4 KiB
TypeScript
187 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useMemo } from "react";
|
|
import { useTranslations } from "next-intl";
|
|
import { ItemCard } from "@/components/item-card";
|
|
import { MarketplaceFilters } from "@/components/marketplace-filters";
|
|
import { featuredItems } from "@/lib/mock-data";
|
|
import { BlurFade } from "@/components/ui/blur-fade";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
|
|
export function FeaturedItems() {
|
|
const t = useTranslations("home.featured");
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
const [filters, setFilters] = useState<{
|
|
rarity: string[];
|
|
category: string[];
|
|
priceRange: { min: number; max: number };
|
|
}>({
|
|
rarity: [],
|
|
category: [],
|
|
priceRange: { min: 0, max: 100000 },
|
|
});
|
|
const [sortBy, setSortBy] = useState("name");
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
// Filtrar e ordenar itens
|
|
const filteredItems = useMemo(() => {
|
|
let filtered = [...featuredItems];
|
|
|
|
// Filtro por busca
|
|
if (searchQuery) {
|
|
filtered = filtered.filter((item) =>
|
|
item.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
);
|
|
}
|
|
|
|
// Filtro por raridade
|
|
if (filters.rarity.length > 0) {
|
|
filtered = filtered.filter((item) =>
|
|
filters.rarity.includes(item.rarity)
|
|
);
|
|
}
|
|
|
|
// Filtro por categoria
|
|
if (filters.category.length > 0) {
|
|
filtered = filtered.filter((item) =>
|
|
filters.category.includes(item.category)
|
|
);
|
|
}
|
|
|
|
// Filtro por faixa de preço
|
|
filtered = filtered.filter(
|
|
(item) =>
|
|
item.price.buy >= filters.priceRange.min &&
|
|
item.price.buy <= filters.priceRange.max
|
|
);
|
|
|
|
// Ordenação
|
|
filtered.sort((a, b) => {
|
|
switch (sortBy) {
|
|
case "name":
|
|
return a.name.localeCompare(b.name);
|
|
case "price-asc":
|
|
return a.price.buy - b.price.buy;
|
|
case "price-desc":
|
|
return b.price.buy - a.price.buy;
|
|
case "rarity":
|
|
const rarityOrder = {
|
|
common: 0,
|
|
uncommon: 1,
|
|
rare: 2,
|
|
epic: 3,
|
|
legendary: 4,
|
|
};
|
|
return rarityOrder[a.rarity] - rarityOrder[b.rarity];
|
|
case "trending":
|
|
return b.priceHistory.highest - a.priceHistory.highest;
|
|
case "recent":
|
|
// Simular ordenação por data (mais recente primeiro)
|
|
return Math.random() - 0.5;
|
|
default:
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
return filtered;
|
|
}, [searchQuery, filters, sortBy]);
|
|
|
|
const handleSearch = (query: string) => {
|
|
setSearchQuery(query);
|
|
};
|
|
|
|
const handleFilter = (newFilters: any) => {
|
|
setFilters(newFilters);
|
|
};
|
|
|
|
const handleSort = (sortValue: string) => {
|
|
setSortBy(sortValue);
|
|
};
|
|
|
|
// Skeleton para loading
|
|
const LoadingSkeleton = () => (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
{Array.from({ length: 8 }).map((_, index) => (
|
|
<div key={index} className="space-y-4">
|
|
<Skeleton className="h-48 w-full rounded-xl" />
|
|
<div className="space-y-2">
|
|
<Skeleton className="h-6 w-3/4" />
|
|
<Skeleton className="h-4 w-1/2" />
|
|
<Skeleton className="h-4 w-1/3" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Skeleton className="h-4 w-full" />
|
|
<Skeleton className="h-4 w-2/3" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<section className="py-20 bg-muted/30">
|
|
<div className="container">
|
|
<BlurFade delay={0.2}>
|
|
<div className="text-center mb-16">
|
|
<h2 className="text-4xl font-bold mb-4">{t("title")}</h2>
|
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
|
|
Descubra os melhores itens do servidor com preços atualizados em
|
|
tempo real
|
|
</p>
|
|
</div>
|
|
</BlurFade>
|
|
|
|
{/* Filtros e busca */}
|
|
<div className="mb-12">
|
|
<MarketplaceFilters
|
|
onSearch={handleSearch}
|
|
onFilter={handleFilter}
|
|
onSort={handleSort}
|
|
totalItems={filteredItems.length}
|
|
/>
|
|
</div>
|
|
|
|
{/* Grid de itens */}
|
|
{isLoading ? (
|
|
<LoadingSkeleton />
|
|
) : (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
{filteredItems.map((item, index) => (
|
|
<BlurFade key={item.id} delay={0.2 + index * 0.05}>
|
|
<ItemCard item={item} />
|
|
</BlurFade>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Mensagem quando não há resultados */}
|
|
{filteredItems.length === 0 && !isLoading && (
|
|
<div className="text-center py-12">
|
|
<div className="text-6xl mb-4">🔍</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Nenhum item encontrado
|
|
</h3>
|
|
<p className="text-muted-foreground mb-4">
|
|
Tente ajustar os filtros ou fazer uma nova busca
|
|
</p>
|
|
<button
|
|
onClick={() => {
|
|
setSearchQuery("");
|
|
setFilters({
|
|
rarity: [],
|
|
category: [],
|
|
priceRange: { min: 0, max: 100000 },
|
|
});
|
|
setSortBy("name");
|
|
}}
|
|
className="text-primary hover:underline"
|
|
>
|
|
Limpar todos os filtros
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|