Files
iddaai-fe/src/components/leagues/leagues-content.tsx
T
fahricansecer 0d194f7409
Deploy Iddaai Frontend / build-and-deploy (push) Failing after 41s
main
2026-05-04 18:01:01 +03:00

440 lines
22 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.
"use client";
import {
Box,
Flex,
Heading,
Text,
Card,
VStack,
HStack,
Badge,
Spinner,
Input,
Grid,
GridItem,
Icon,
} from "@chakra-ui/react";
import { useTranslations } from "next-intl";
import { useColorModeValue } from "@/components/ui/color-mode";
import { SlideUp } from "@/components/motion";
import {
useCountries,
useLeagues,
useSearchTeams,
} from "@/lib/api/leagues/use-hooks";
import type { CountryDto, LeagueDto, TeamDto } from "@/lib/api/leagues/types";
import { LuSearch, LuGlobe, LuTrophy, LuUsers, LuArrowRight, LuMapPin } from "react-icons/lu";
import { useMemo, useState } from "react";
import { useDebounce } from "@/hooks/use-debounce";
import { Link } from "@/i18n/navigation";
import { InputGroup } from "@/components/ui/forms/input-group";
import { Link as ChakraLink } from "@chakra-ui/react";
export default function LeaguesContent() {
const t = useTranslations("leagues");
const tMatches = useTranslations("matches");
const bgGradient = useColorModeValue(
"linear(to-r, primary.500, primary.700)",
"linear(to-r, primary.600, primary.900)"
);
const cardBg = useColorModeValue("white", "gray.900");
const borderColor = useColorModeValue("gray.200", "gray.800");
const hoverBg = useColorModeValue("gray.50", "whiteAlpha.50");
const [activeTab, setActiveTab] = useState<"leagues" | "teams">("leagues");
const [sportFilter, setSportFilter] = useState<string>("");
const [selectedCountryId, setSelectedCountryId] = useState<string | null>(null);
const [teamSearchQuery, setTeamSearchQuery] = useState("");
const debouncedTeamQuery = useDebounce(teamSearchQuery, 300);
const [countrySearchQuery, setCountrySearchQuery] = useState("");
const debouncedCountryQuery = useDebounce(countrySearchQuery, 300);
const countries = useCountries();
const leagues = useLeagues(
sportFilter
? { sport: sportFilter as "football" | "basketball" }
: undefined,
);
const searchTeams = useSearchTeams(
debouncedTeamQuery.length >= 2 ? { q: debouncedTeamQuery } : { q: "" },
);
const filteredCountries = useMemo(() => {
if (!countries.data?.data) return [];
if (!debouncedCountryQuery) return countries.data.data;
return countries.data.data.filter((c) =>
c.name.toLowerCase().includes(debouncedCountryQuery.toLowerCase())
);
}, [countries.data?.data, debouncedCountryQuery]);
const displayedLeagues = useMemo(() => {
let sourceLeagues: LeagueDto[] = leagues.data?.data || [];
if (selectedCountryId) {
sourceLeagues = sourceLeagues.filter(l => l.countryId === selectedCountryId);
}
// Apply sport filter if selected
if (sportFilter) {
return sourceLeagues.filter(l => l.sport === sportFilter);
}
return sourceLeagues;
}, [selectedCountryId, leagues.data?.data, sportFilter]);
return (
<Box minH="calc(100vh - 80px)">
{/* Hero Section */}
<Box bgGradient={bgGradient} color="white" pt={16} pb={20} px={6} position="relative" overflow="hidden">
<Box position="absolute" top="-20%" right="-10%" opacity={0.1} transform="rotate(15deg)">
<LuTrophy size={400} />
</Box>
<Box maxW="7xl" mx="auto" position="relative" zIndex={1}>
<SlideUp>
<VStack align="center" gap={4} textAlign="center" maxW="3xl" mx="auto">
<Badge colorScheme="whiteAlpha" variant="subtle" size="lg" px={4} py={1} rounded="full">
{t("title")}
</Badge>
<Heading as="h1" fontSize={{ base: "3xl", md: "5xl" }} fontWeight="800" letterSpacing="tight">
{activeTab === "leagues" ? t("countries-leagues") : tMatches("search-teams")}
</Heading>
<Text fontSize="lg" color="whiteAlpha.800" maxW="xl">
{activeTab === "leagues"
? "Explore top football and basketball leagues around the world. Filter by country and analyze historical matches."
: "Find your favorite teams across all leagues. Get deep insights and head-to-head statistics."}
</Text>
</VStack>
</SlideUp>
</Box>
</Box>
{/* Main Content Area - Pulled up to overlap hero */}
<Box maxW="7xl" mx="auto" px={6} mt={-10} position="relative" zIndex={2} pb={20}>
<SlideUp transition={{ delay: 0.1, duration: 0.5, ease: [0.25, 0.1, 0.25, 1] }}>
<Card.Root bg={cardBg} shadow="xl" borderRadius="2xl" borderWidth="1px" borderColor={borderColor} overflow="hidden">
{/* Tab Navigation */}
<Flex borderBottomWidth="1px" borderColor={borderColor} bg={useColorModeValue("gray.50", "whiteAlpha.50")}>
<Flex flex={1}>
<Box
flex={1} py={4} textAlign="center" cursor="pointer"
borderBottomWidth="2px"
borderColor={activeTab === "leagues" ? "primary.500" : "transparent"}
color={activeTab === "leagues" ? "primary.500" : "fg.muted"}
fontWeight={activeTab === "leagues" ? "bold" : "medium"}
onClick={() => setActiveTab("leagues")}
transition="all 0.2s"
_hover={{ bg: hoverBg }}
>
<HStack justify="center" gap={2}>
<LuGlobe />
<Text>{t("countries-leagues")}</Text>
</HStack>
</Box>
<Box
flex={1} py={4} textAlign="center" cursor="pointer"
borderBottomWidth="2px"
borderColor={activeTab === "teams" ? "primary.500" : "transparent"}
color={activeTab === "teams" ? "primary.500" : "fg.muted"}
fontWeight={activeTab === "teams" ? "bold" : "medium"}
onClick={() => setActiveTab("teams")}
transition="all 0.2s"
_hover={{ bg: hoverBg }}
>
<HStack justify="center" gap={2}>
<LuUsers />
<Text>{tMatches("search-teams")}</Text>
</HStack>
</Box>
</Flex>
</Flex>
{/* LEAGUES TAB */}
{activeTab === "leagues" && (
<Flex direction={{ base: "column", lg: "row" }} minH="600px">
{/* Left Sidebar: Countries */}
<Box w={{ base: "full", lg: "320px" }} borderRightWidth={{ lg: "1px" }} borderColor={borderColor} bg={useColorModeValue("gray.50", "whiteAlpha.50")}>
<VStack align="stretch" h="full" gap={0}>
<Box p={4} borderBottomWidth="1px" borderColor={borderColor} bg={cardBg}>
<InputGroup startElement={<LuSearch color="gray.400" />} w="full">
<Input
placeholder={t("countries") + "..."}
variant="subtle"
borderRadius="full"
value={countrySearchQuery}
onChange={(e) => setCountrySearchQuery(e.target.value)}
/>
</InputGroup>
</Box>
<Box flex={1} overflowY="auto" maxH={{ base: "300px", lg: "600px" }} p={2}>
{countries.isLoading ? (
<Flex justify="center" py={10}><Spinner color="primary.500" /></Flex>
) : (
<VStack gap={1} align="stretch">
<Box
px={4} py={3} borderRadius="lg" cursor="pointer"
bg={selectedCountryId === null ? "primary.500" : "transparent"}
color={selectedCountryId === null ? "white" : "fg"}
_hover={{ bg: selectedCountryId === null ? "primary.600" : hoverBg }}
onClick={() => setSelectedCountryId(null)}
transition="all 0.2s"
>
<HStack justify="space-between">
<HStack gap={3}>
<LuGlobe />
<Text fontWeight={selectedCountryId === null ? "bold" : "medium"}>{t("all")}</Text>
</HStack>
<Badge size="sm" bg={selectedCountryId === null ? "whiteAlpha.300" : "gray.100"} color={selectedCountryId === null ? "white" : "fg"}>
{leagues.data?.data?.length || 0}
</Badge>
</HStack>
</Box>
{filteredCountries.map((country: CountryDto) => {
const isSelected = selectedCountryId === country.id;
return (
<Box
key={country.id}
px={4} py={3} borderRadius="lg" cursor="pointer"
bg={isSelected ? "primary.500" : "transparent"}
color={isSelected ? "white" : "fg"}
_hover={{ bg: isSelected ? "primary.600" : hoverBg }}
onClick={() => setSelectedCountryId(country.id)}
transition="all 0.2s"
>
<HStack justify="space-between">
<HStack gap={3}>
{country.flag ? (
<img src={country.flag} width="20" height="20" style={{ borderRadius: "50%", objectFit: "cover" }} alt={country.name} />
) : <LuMapPin />}
<Text fontWeight={isSelected ? "bold" : "medium"}>{country.name}</Text>
</HStack>
<Badge size="sm" bg={isSelected ? "whiteAlpha.300" : "gray.100"} color={isSelected ? "white" : "fg"}>
{leagues.data?.data?.filter(l => l.countryId === country.id).length || 0}
</Badge>
</HStack>
</Box>
);
})}
</VStack>
)}
</Box>
</VStack>
</Box>
{/* Right Area: Leagues Grid */}
<Box flex={1} p={{ base: 4, md: 8 }} bg={cardBg}>
{/* Top Filters */}
<Flex justify="space-between" align="center" mb={6} direction={{ base: "column", sm: "row" }} gap={4}>
<Heading size="md" fontWeight="bold">
{selectedCountryId
? `${countries.data?.data?.find(c => c.id === selectedCountryId)?.name} ${t("leagues")}`
: t("leagues")}
<Text as="span" color="fg.muted" ml={2} fontWeight="normal" fontSize="sm">
({displayedLeagues.length})
</Text>
</Heading>
<HStack gap={2} bg={useColorModeValue("gray.100", "gray.800")} p={1} borderRadius="full">
<Box
px={4} py={1.5} borderRadius="full" cursor="pointer" fontSize="sm" fontWeight="medium"
bg={!sportFilter ? "white" : "transparent"}
color={!sportFilter ? "black" : "fg.muted"}
shadow={!sportFilter ? "sm" : "none"}
onClick={() => setSportFilter("")}
transition="all 0.2s"
_dark={{ bg: !sportFilter ? "gray.600" : "transparent", color: !sportFilter ? "white" : "gray.400" }}
>
{t("all")}
</Box>
<Box
px={4} py={1.5} borderRadius="full" cursor="pointer" fontSize="sm" fontWeight="medium"
bg={sportFilter === "football" ? "green.500" : "transparent"}
color={sportFilter === "football" ? "white" : "fg.muted"}
shadow={sportFilter === "football" ? "sm" : "none"}
onClick={() => setSportFilter(sportFilter === "football" ? "" : "football")}
transition="all 0.2s"
>
{tMatches("football")}
</Box>
<Box
px={4} py={1.5} borderRadius="full" cursor="pointer" fontSize="sm" fontWeight="medium"
bg={sportFilter === "basketball" ? "orange.500" : "transparent"}
color={sportFilter === "basketball" ? "white" : "fg.muted"}
shadow={sportFilter === "basketball" ? "sm" : "none"}
onClick={() => setSportFilter(sportFilter === "basketball" ? "" : "basketball")}
transition="all 0.2s"
>
{tMatches("basketball")}
</Box>
</HStack>
</Flex>
{/* Leagues Grid */}
{leagues.isLoading ? (
<Flex justify="center" py={20}><Spinner size="xl" color="primary.500" borderWidth="3px" /></Flex>
) : displayedLeagues.length === 0 ? (
<Flex direction="column" align="center" justify="center" py={20} textAlign="center">
<Box bg="gray.100" _dark={{ bg: "gray.800" }} p={6} borderRadius="full" mb={4}>
<LuTrophy size={40} color="gray" />
</Box>
<Heading size="md" mb={2}>Bulunamadı</Heading>
<Text color="fg.muted">Seçili kriterlere uygun lig bulunamadı.</Text>
</Flex>
) : (
<Grid templateColumns={{ base: "1fr", md: "repeat(2, 1fr)", xl: "repeat(3, 1fr)" }} gap={4}>
{displayedLeagues.map((league: LeagueDto) => (
<GridItem key={league.id}>
<ChakraLink
as={Link}
href={`/leagues/${league.id}`}
display="block"
h="full"
p={5}
borderRadius="xl"
borderWidth="1px"
borderColor={borderColor}
bg={cardBg}
_hover={{
borderColor: "primary.300",
shadow: "md",
transform: "translateY(-2px)",
}}
transition="all 0.2s"
textDecoration="none"
color="inherit"
data-group
>
<Flex justify="space-between" align="flex-start" mb={4}>
<Box p={2} borderRadius="lg" bg={league.sport === "football" ? "green.50" : "orange.50"} _dark={{ bg: league.sport === "football" ? "green.900" : "orange.900" }}>
<LuTrophy size={20} color={league.sport === "football" ? "var(--chakra-colors-green-500)" : "var(--chakra-colors-orange-500)"} />
</Box>
<Badge size="sm" variant="subtle" colorScheme={league.sport === "football" ? "green" : "orange"}>
{league.sport}
</Badge>
</Flex>
<Heading size="sm" mb={1} lineClamp={1} _groupHover={{ color: "primary.500" }}>
{league.name}
</Heading>
<HStack color="fg.muted" fontSize="sm" gap={1}>
<LuMapPin size={14} />
<Text lineClamp={1}>{league.country?.name || "Global"}</Text>
</HStack>
{league.season && (
<Flex mt={4} pt={4} borderTopWidth="1px" borderColor={borderColor} justify="space-between" align="center">
<Text fontSize="xs" color="fg.muted" fontWeight="medium">SEZON: {league.season}</Text>
<Icon as={LuArrowRight} color="gray.400" _groupHover={{ color: "primary.500", transform: "translateX(4px)" }} transition="all 0.2s" />
</Flex>
)}
</ChakraLink>
</GridItem>
))}
</Grid>
)}
</Box>
</Flex>
)}
{/* TEAMS TAB */}
{activeTab === "teams" && (
<Box p={{ base: 4, md: 8 }}>
<Box maxW="2xl" mx="auto" mb={10}>
<InputGroup startElement={<LuSearch color="gray.400" size={20} />} w="full">
<Input
placeholder={tMatches("search-teams") + "..."}
value={teamSearchQuery}
onChange={(e) => setTeamSearchQuery(e.target.value)}
variant="outline"
borderRadius="xl"
fontSize="lg"
py={6}
boxShadow="sm"
_focus={{ boxShadow: "0 0 0 2px var(--chakra-colors-primary-500)" }}
/>
</InputGroup>
</Box>
{debouncedTeamQuery.length < 2 ? (
<Flex direction="column" align="center" justify="center" py={20} textAlign="center">
<Box bg="primary.50" _dark={{ bg: "primary.900" }} p={8} borderRadius="full" mb={6}>
<LuUsers size={64} color="var(--chakra-colors-primary-500)" />
</Box>
<Heading size="lg" mb={3}>{t("search-at-least-2")}</Heading>
<Text color="fg.muted" maxW="md">
Find detailed statistics, upcoming matches, and head-to-head analysis by searching for any team worldwide.
</Text>
</Flex>
) : searchTeams.isLoading ? (
<Flex justify="center" py={20}><Spinner size="xl" color="primary.500" borderWidth="3px" /></Flex>
) : searchTeams.data?.data?.length === 0 ? (
<Flex direction="column" align="center" justify="center" py={20} textAlign="center">
<Heading size="md" mb={2}>Takım Bulunamadı</Heading>
<Text color="fg.muted">"{debouncedTeamQuery}" aramasıyla eşleşen bir takım bulunamadı.</Text>
</Flex>
) : (
<Grid templateColumns={{ base: "1fr", md: "repeat(2, 1fr)", xl: "repeat(3, 1fr)" }} gap={4}>
{searchTeams.data?.data?.map((team: TeamDto) => (
<GridItem key={team.id}>
<ChakraLink
as={Link}
href={`/teams/${team.id}`}
display="flex"
alignItems="center"
p={4}
borderRadius="xl"
borderWidth="1px"
borderColor={borderColor}
bg={cardBg}
_hover={{
borderColor: "primary.300",
shadow: "md",
transform: "translateY(-2px)",
}}
transition="all 0.2s"
textDecoration="none"
color="inherit"
data-group
>
{team.logo ? (
<Box w={12} h={12} borderRadius="full" overflow="hidden" flexShrink={0} mr={4} bg="white" p={1} shadow="sm">
<img src={team.logo} width="100%" height="100%" style={{ objectFit: "contain" }} alt={team.name} />
</Box>
) : (
<Flex w={12} h={12} borderRadius="full" bg="gray.100" _dark={{ bg: "gray.700" }} align="center" justify="center" flexShrink={0} mr={4}>
<LuUsers size={20} color="gray" />
</Flex>
)}
<VStack align="start" gap={0} flex={1}>
<Heading size="sm" lineClamp={1} _groupHover={{ color: "primary.500" }}>{team.name}</Heading>
<HStack color="fg.muted" fontSize="xs" gap={1}>
<LuMapPin size={12} />
<Text lineClamp={1}>{team.country || "Global"}</Text>
</HStack>
</VStack>
<Badge ml={2} size="sm" colorScheme={team.sport === "football" ? "green" : "orange"} variant="subtle">
{team.sport}
</Badge>
</ChakraLink>
</GridItem>
))}
</Grid>
)}
</Box>
)}
</Card.Root>
</SlideUp>
</Box>
</Box>
);
}