Files
Game_Calendar_FE/src/components/features/games/GameDetail.tsx
Harun CAN 1f123b9f65 main
2026-01-30 04:48:46 +03:00

204 lines
9.9 KiB
TypeScript

'use client';
import { Box, Container, Heading, Text, Image, Badge, Flex, Grid, GridItem, Button, Icon, Stack, Tag, TagLabel, AspectRatio } from '@chakra-ui/react';
import { Game, gamesApi } from '@/lib/api/games';
import { notificationsApi } from '@/lib/api/notifications';
import { useEffect, useState } from 'react';
import { FaPlaystation, FaXbox, FaDesktop, FaGamepad, FaStar, FaBuilding, FaCalendar, FaSync, FaBell } from 'react-icons/fa'; // Assuming react-icons is installed, or use lucid-react if available in project
import { useQuery } from '@tanstack/react-query'; // Assuming react-query is used
import { toaster } from '@/components/ui/feedback/toaster'; // Updated path
// Import UI components (assuming they exist or use basic Chakra)
// I will use basic Chakra v3 components. For icons, I'll fallback to text if icons missing or Import from react-icons if available.
// The prompted file said Chakra UI v3.
interface GameDetailProps {
slug: string;
}
export function GameDetail({ slug }: GameDetailProps) {
const { data: response, isLoading, error, refetch } = useQuery({
queryKey: ['game', slug],
queryFn: () => gamesApi.getBySlug(slug),
});
useEffect(() => {
console.log('GameDetail mounted for slug:', slug);
}, [slug]);
const [isSyncing, setIsSyncing] = useState(false);
const [isSubscribing, setIsSubscribing] = useState(false);
const handleSync = async () => {
setIsSyncing(true);
try {
await gamesApi.sync(slug);
toaster.create({ title: 'Game synced successfully', type: 'success' });
refetch();
} catch (e) {
toaster.create({ title: 'Failed to sync game', type: 'error' });
} finally {
setIsSyncing(false);
}
};
const handleSubscribe = async () => {
setIsSubscribing(true);
try {
if (!game?.id) return;
await notificationsApi.subscribe(game.id);
toaster.create({ title: 'Subscribed to alerts!', type: 'success' });
} catch (e: any) {
// Handle "Already subscribed" specifically if possible, else generic error
const msg = e.response?.data?.message || 'Failed to subscribe';
toaster.create({ title: msg, type: msg.includes('Already') ? 'info' : 'error' });
} finally {
setIsSubscribing(false);
}
};
if (isLoading) return <Box p={10}>Loading...</Box>;
if (error || !response?.data?.data) return (
<Container centerContent py={20}>
<Heading>Game not found</Heading>
<Button mt={4} onClick={handleSync} loading={isSyncing}>Try Syncing from External Source</Button>
</Container>
);
const game = response.data.data;
return (
<Box pb={20}>
{/* Hero Section */}
<Box position="relative" h={{ base: "400px", md: "500px" }} w="full" overflow="hidden">
{/* Background Blur */}
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
bgImage={`url(${game.coverImage})`}
bgSize="cover"
bgPos="center"
filter="blur(20px) brightness(0.4)"
zIndex={0}
/>
<Container maxW="container.xl" position="relative" zIndex={1} h="full" display="flex" alignItems="center">
<Grid templateColumns={{ base: "1fr", md: "300px 1fr" }} gap={8} alignItems="center" w="full">
<GridItem display={{ base: "none", md: "block" }}>
<Image
src={game.coverImage?.replace('t_thumb', 't_cover_big_2x')} // Try to get higher res if possible by replacing IGDB logic if used, else just src
alt={game.title}
borderRadius="xl"
boxShadow="2xl"
objectFit="cover"
h="400px"
w="full"
/>
</GridItem>
<GridItem color="white">
<Stack>
<Heading size="4xl" fontWeight="black" letterSpacing="tight">{game.title}</Heading>
<Flex gap={4} alignItems="center" flexWrap="wrap">
{game.rating && (
<Badge colorPalette="yellow" size="lg" variant="solid">
<Icon as={FaStar} mr={1} /> {Math.round(game.rating).toFixed(1)}
</Badge>
)}
{game.releaseDate && (
<Badge colorPalette="gray" size="lg" variant="surface">
<Icon as={FaCalendar} mr={2} />
{new Date(game.releaseDate).toLocaleDateString()}
</Badge>
)}
</Flex>
<Flex gap={2} mt={4} flexWrap="wrap">
{game.platforms?.map(p => (
<Tag.Root key={p.platform.slug} size="lg" variant="subtle">
<Tag.Label>{p.platform.name}</Tag.Label>
</Tag.Root>
))}
</Flex>
<Flex gap={2} mt={2} flexWrap="wrap">
{game.genres?.map(g => (
<Badge key={g.genre.slug} variant="outline" colorPalette="purple">
{g.genre.name}
</Badge>
))}
</Flex>
<Flex mt={6} gap={4}>
<Button onClick={handleSubscribe} loading={isSubscribing} colorPalette="purple" variant="solid">
<Icon as={FaBell} mr={2} /> Receive Alerts
</Button>
<Button onClick={handleSync} loading={isSyncing} variant="ghost" colorPalette="whiteAlpha">
<Icon as={FaSync} mr={2} /> Refresh Data
</Button>
</Flex>
</Stack>
</GridItem>
</Grid>
</Container>
</Box>
{/* Content Section */}
<Container maxW="container.xl" py={12}>
<Grid templateColumns={{ base: "1fr", lg: "2fr 1fr" }} gap={12}>
<GridItem>
<Heading size="xl" mb={6}>About</Heading>
<Text fontSize="lg" lineHeight="tall" color="fg.muted">
{game.description || "No description available."}
</Text>
{game.screenshots && game.screenshots.length > 0 && (
<Box mt={12}>
<Heading size="xl" mb={6}>Gallery</Heading>
<Grid templateColumns={{ base: "1fr", md: "repeat(2, 1fr)" }} gap={4}>
{game.screenshots.map((shot, idx) => (
<GridItem key={idx}>
<AspectRatio ratio={16 / 9} borderRadius="lg" overflow="hidden">
<Image
src={shot.url.replace('t_thumb', 't_screenshot_med')}
alt={`Screenshot ${idx}`}
objectFit="cover"
transition="transform 0.2s"
_hover={{ transform: 'scale(1.05)' }}
/>
</AspectRatio>
</GridItem>
))}
</Grid>
</Box>
)}
</GridItem>
<GridItem>
<Box p={6} borderRadius="xl" borderWidth="1px" borderColor="border">
<Heading size="md" mb={6}>Information</Heading>
<Stack gap={4}>
<Box>
<Text fontWeight="bold" mb={1} color="fg.muted">Developer</Text>
<Text fontSize="lg">{game.developer || "Unknown"}</Text>
</Box>
<Box>
<Text fontWeight="bold" mb={1} color="fg.muted">Publisher</Text>
<Text fontSize="lg">{game.publisher || "Unknown"}</Text>
</Box>
<Box>
<Text fontWeight="bold" mb={1} color="fg.muted">Release Date</Text>
<Text fontSize="lg">{game.releaseDate ? new Date(game.releaseDate).toLocaleDateString(undefined, { dateStyle: 'long' }) : 'TBD'}</Text>
</Box>
</Stack>
</Box>
</GridItem>
</Grid>
</Container>
</Box>
);
}