This commit is contained in:
Harun CAN
2026-01-30 03:41:20 +03:00
parent d1e10b8b68
commit b662a88a65
16 changed files with 727 additions and 14 deletions

11
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@tanstack/react-query": "^5.90.16", "@tanstack/react-query": "^5.90.16",
"axios": "^1.13.1", "axios": "^1.13.1",
"date-fns": "^4.1.0",
"i18next": "^25.6.0", "i18next": "^25.6.0",
"next": "16.0.0", "next": "16.0.0",
"next-auth": "^4.24.13", "next-auth": "^4.24.13",
@@ -5368,6 +5369,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",

View File

@@ -15,6 +15,7 @@
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@tanstack/react-query": "^5.90.16", "@tanstack/react-query": "^5.90.16",
"axios": "^1.13.1", "axios": "^1.13.1",
"date-fns": "^4.1.0",
"i18next": "^25.6.0", "i18next": "^25.6.0",
"next": "16.0.0", "next": "16.0.0",
"next-auth": "^4.24.13", "next-auth": "^4.24.13",

View File

@@ -0,0 +1,114 @@
import { notFound } from 'next/navigation';
import { Container, Heading, Text, Box, Badge, HStack, VStack, Icon, Flex, Button } from '@chakra-ui/react';
import { MOCK_EVENTS } from '@/lib/api/mock-data';
import { FaCalendarAlt, FaClock, FaTwitch, FaYoutube } from 'react-icons/fa';
interface PageProps {
params: Promise<{
slug: string;
locale: string;
}>;
}
export default async function EventDetailsPage({ params }: PageProps) {
const { slug } = await params;
const event = MOCK_EVENTS.find((e) => e.slug === slug);
if (!event) {
notFound();
}
// Format date and time
const formattedDate = new Intl.DateTimeFormat('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric'
}).format(event.startTime);
const formattedTime = new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short'
}).format(event.startTime);
return (
<Container maxW="5xl" py={20} minH="100vh">
<VStack gap={8} align="center" textAlign="center">
{/* Event Type Badge */}
<Badge
colorScheme="pink"
fontSize="lg"
px={4}
py={2}
borderRadius="full"
variant="subtle"
>
{event.type}
</Badge>
{/* Title */}
<Heading
size="4xl"
bgGradient="linear(to-r, pink.400, purple.500)"
bgClip="text"
letterSpacing="tight"
>
{event.title}
</Heading>
{/* Date & Time Box */}
<Box
p={8}
bg="whiteAlpha.100"
borderRadius="2xl"
border="1px solid"
borderColor="whiteAlpha.200"
backdropFilter="blur(10px)"
minW={{ base: 'full', md: '500px' }}
>
<VStack gap={4}>
<HStack color="pink.300" gap={4}>
<Icon as={FaCalendarAlt} boxSize={6} />
<Text fontSize="2xl" fontWeight="bold">{formattedDate}</Text>
</HStack>
<HStack color="purple.300" gap={4}>
<Icon as={FaClock} boxSize={6} />
<Text fontSize="2xl">{formattedTime}</Text>
</HStack>
</VStack>
</Box>
{/* Description (Placeholder) */}
<Text fontSize="xl" maxW="2xl" color="whiteAlpha.800">
Join us for the {event.title}. Expect world premieres, developer interviews, and exclusive gameplay reveals. Don't miss out on the biggest gaming news!
</Text>
{/* Action Buttons */}
<HStack gap={4} pt={8}>
<Button
size="lg"
colorScheme="purple"
// leftIcon={<Icon as={FaTwitch} />}
// as="a" href="..."
>
<Icon as={FaTwitch} mr={2} />
Watch on Twitch
</Button>
<Button
size="lg"
colorScheme="red"
variant="outline"
// leftIcon={<Icon as={FaYoutube} />}
// as="a" href="..."
>
<Icon as={FaYoutube} mr={2} />
Watch on YouTube
</Button>
</HStack>
</VStack>
</Container>
);
}

View File

@@ -0,0 +1,120 @@
import { notFound } from 'next/navigation';
import { Container, Heading, Text, Box, Image, Badge, HStack, VStack, Icon, Flex } from '@chakra-ui/react';
import { MOCK_GAMES } from '@/lib/api/mock-data';
import { FaCalendar, FaGamepad } from 'react-icons/fa';
interface PageProps {
params: Promise<{
slug: string;
locale: string;
}>;
}
export default async function GameDetailsPage({ params }: PageProps) {
const { slug } = await params;
const game = MOCK_GAMES.find((g) => g.slug === slug);
if (!game) {
notFound();
}
// Format date
const formattedDate = new Intl.DateTimeFormat('en-US', {
dateStyle: 'long',
}).format(game.releaseDate);
return (
<Container maxW="6xl" py={12} minH="100vh">
{/* Hero / Cover Section */}
<Box
position="relative"
h={{ base: "300px", md: "500px" }}
w="full"
overflow="hidden"
borderRadius="2xl"
mb={8}
boxShadow="2xl"
>
<Image
src={game.coverImage}
alt={game.title}
objectFit="cover"
w="full"
h="full"
filter="brightness(0.7)"
/>
<Box
position="absolute"
bottom={0}
left={0}
w="full"
p={8}
bgGradient="linear(to-t, blackAlpha.900, transparent)"
>
<VStack align="flex-start" gap={4}>
<Heading size="3xl" color="white" textShadow="lg">{game.title}</Heading>
<HStack gap={4}>
<Badge colorScheme="purple" fontSize="0.9em" px={3} py={1} borderRadius="full">
{new Date() < game.releaseDate ? 'Upcoming' : 'Released'}
</Badge>
{game.platforms.map(p => (
<Badge key={p} variant="outline" colorScheme="cyan" px={2}>
{p}
</Badge>
))}
</HStack>
</VStack>
</Box>
</Box>
{/* Details Grid */}
<Flex direction={{ base: 'column', lg: 'row' }} gap={12}>
{/* Main Content */}
<Box flex="1">
<Heading size="lg" mb={4}>About</Heading>
<Text fontSize="lg" lineHeight="tall" color="whiteAlpha.800">
{/* Fallback description since mock doesn't have it yet */}
{game.title} is an anticipated title releasing on {formattedDate}.
Experience the next chapter in this gaming masterpiece.
Prepare to embark on a journey like no other.
</Text>
</Box>
{/* Sidebar Info */}
<Box w={{ base: 'full', lg: '350px' }}>
<VStack
align="stretch"
p={6}
bg="whiteAlpha.50"
borderRadius="xl"
border="1px solid"
borderColor="whiteAlpha.200"
gap={6}
>
<Heading size="md" mb={2}>Game Info</Heading>
<Box>
<HStack mb={1} color="gray.400">
<Icon as={FaCalendar} />
<Text fontSize="sm">Release Date</Text>
</HStack>
<Text fontSize="xl" fontWeight="bold">{formattedDate}</Text>
</Box>
<Box>
<HStack mb={1} color="gray.400">
<Icon as={FaGamepad} />
<Text fontSize="sm">Platforms</Text>
</HStack>
<Flex gap={2} wrap="wrap">
{game.platforms.map(p => (
<Badge key={p} colorScheme="green">{p}</Badge>
))}
</Flex>
</Box>
</VStack>
</Box>
</Flex>
</Container>
);
}

View File

@@ -1,14 +1,28 @@
import { getTranslations } from "next-intl/server"; import { Container, VStack, Heading, Text, Box } from '@chakra-ui/react';
import HomeCard from "@/components/site/home/home-card"; import { GameCalendar } from '@/components/features/calendar/game-calendar';
import { MOCK_EVENTS, MOCK_GAMES } from '@/lib/api/mock-data';
import { ThemeSwitcher } from '@/components/features/theme-switcher';
import { useTranslations } from 'next-intl';
export async function generateMetadata() { export default function HomePage() {
const t = await getTranslations(); // Static for now, in future useTranslations
// const t = useTranslations('dashboard');
return { return (
title: `${t("home")} | FCS`, <Container maxW="8xl" py={8} position="relative" minH="100vh">
}; <VStack spaceY={8} align="stretch">
} {/* Hero Section / GOTY Banner could go here */}
<Box>
export default function Home() { <Heading size="3xl" mb={2} color="white">Game Calendar</Heading>
return <HomeCard />; <Text fontSize="lg" color="whiteAlpha.800">Track releases, events, and showcases in one place.</Text>
</Box>
{/* Calendar */}
<GameCalendar games={MOCK_GAMES} events={MOCK_EVENTS} />
</VStack>
{/* Debug Switcher */}
<ThemeSwitcher />
</Container>
);
} }

View File

@@ -1,7 +1,41 @@
@import "https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap";
:root {
--font-bricolage: 'Bricolage Grotesque', sans-serif;
}
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
height: 100%;
} }
body { body {
overflow-x: hidden; overflow-x: hidden;
min-height: 100vh;
/* Use dynamic theme variable or fallback */
background-color: var(--app-background, var(--chakra-colors-gray-950));
background-image: var(--app-background-image, none);
background-size: cover;
background-attachment: fixed;
background-position: center;
transition: background-color 0.5s ease-in-out;
color: var(--chakra-colors-gray-100);
}
/* Scrollbar Styling for Gamer Aesthetic */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.3);
}
::-webkit-scrollbar-thumb {
background: var(--chakra-colors-primary-600);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--chakra-colors-primary-500);
} }

View File

@@ -1,4 +1,5 @@
import { Provider } from '@/components/ui/provider'; import { Provider } from '@/components/ui/provider';
import { DynamicThemeProvider } from '@/components/ui/dynamic-theme-provider';
import { Bricolage_Grotesque } from 'next/font/google'; import { Bricolage_Grotesque } from 'next/font/google';
import { hasLocale, NextIntlClientProvider } from 'next-intl'; import { hasLocale, NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
@@ -33,7 +34,11 @@ export default async function RootLayout({
</head> </head>
<body className={bricolage.variable}> <body className={bricolage.variable}>
<NextIntlClientProvider> <NextIntlClientProvider>
<Provider>{children}</Provider> <Provider>
<DynamicThemeProvider>
{children}
</DynamicThemeProvider>
</Provider>
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
import baseUrl from "@/config/base-url"; import baseUrl from "@/config/base-url";
import { authService } from "@/lib/api/Example/auth/service"; import { authService } from "@/lib/api/example/auth/service";
import NextAuth from "next-auth"; import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials"; import Credentials from "next-auth/providers/credentials";

View File

@@ -0,0 +1,85 @@
import { Box, HStack, Input, Button, Text, Icon, VStack } from '@chakra-ui/react';
import { FaSearch, FaFilter } from 'react-icons/fa';
import { Checkbox } from '@/components/ui/forms/checkbox';
import { InputGroup } from '@/components/ui/forms/input-group';
import { MenuRoot, MenuTrigger, MenuContent, MenuCheckboxItem, MenuSeparator } from '@/components/ui/overlays/menu';
export interface FilterState {
search: string;
platforms: string[];
showEvents: boolean;
}
interface FilterBarProps {
filters: FilterState;
onFilterChange: (filters: FilterState) => void;
}
export function FilterBar({ filters, onFilterChange }: FilterBarProps) {
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onFilterChange({ ...filters, search: e.target.value });
};
const togglePlatform = (platform: string) => {
const current = filters.platforms;
const next = current.includes(platform)
? current.filter(p => p !== platform)
: [...current, platform];
onFilterChange({ ...filters, platforms: next });
};
const toggleEvents = (checked: boolean) => {
onFilterChange({ ...filters, showEvents: checked });
};
return (
<HStack w="full" gap={4} mb={6} bg="whiteAlpha.100" p={4} borderRadius="xl" backdropFilter="blur(10px)">
<InputGroup maxW="400px" startElement={<Icon as={FaSearch} color="gray.400" />}>
<Input
placeholder="Search games..."
value={filters.search}
onChange={handleSearchChange}
variant="subtle"
bg="blackAlpha.300"
_hover={{ bg: "blackAlpha.400" }}
_focus={{ bg: "blackAlpha.400", borderColor: "purple.400" }}
/>
</InputGroup>
<MenuRoot closeOnSelect={false}>
<MenuTrigger asChild>
<Button variant="outline" colorScheme="purple">
Filters <Icon as={FaFilter} ml={2} />
</Button>
</MenuTrigger>
<MenuContent bg="gray.800" borderColor="whiteAlpha.200">
<Box px={4} py={2}>
<Text fontSize="xs" fontWeight="bold" color="gray.400" mb={2} textTransform="uppercase">Platforms</Text>
<VStack align="start" gap={2}>
{['PC', 'PS5', 'Xbox', 'Switch'].map(p => (
<MenuCheckboxItem
key={p}
value={p}
checked={filters.platforms.includes(p)}
onCheckedChange={(e) => togglePlatform(p)}
>
{p}
</MenuCheckboxItem>
))}
</VStack>
</Box>
<MenuSeparator />
<Box>
<MenuCheckboxItem
value="events"
checked={filters.showEvents}
onCheckedChange={(checked: boolean) => toggleEvents(checked)}
>
Show Events
</MenuCheckboxItem>
</Box>
</MenuContent>
</MenuRoot>
</HStack>
);
}

View File

@@ -0,0 +1,149 @@
'use client';
import { Box, Grid, Heading, Text, VStack, Badge, Flex, Image as ChakraImage, SimpleGrid } from '@chakra-ui/react';
import { useState } from 'react';
import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, addMonths, subMonths, getDay } from 'date-fns';
import { enUS, tr } from 'date-fns/locale';
import { useLocale } from 'next-intl';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { IconButton } from '@chakra-ui/react';
import { FilterBar, FilterState } from './filter-bar';
// import { Game, Event } from '@prisma/client'; // Removed dependency
// Note: In a real monorepo we'd share types. For now we will mock or use any.
interface CalendarProps {
games: any[]; // Replace with correct type
events: any[];
}
export function GameCalendar({ games = [], events = [] }: CalendarProps) {
const [currentDate, setCurrentDate] = useState(new Date());
const [filters, setFilters] = useState<FilterState>({
search: '',
platforms: ['PC', 'PS5', 'Xbox', 'Switch'],
showEvents: true
});
const locale = useLocale();
const dateLocale = locale === 'tr' ? tr : enUS;
const monthStart = startOfMonth(currentDate);
const monthEnd = endOfMonth(monthStart);
const daysInMonth = eachDayOfInterval({ start: monthStart, end: monthEnd });
// Add padding days for start of month
const startDayOfWeek = getDay(monthStart); // 0 (Sun) to 6 (Sat)
// Adjust for Monday start if needed. Let's assume standard Sunday start for grid.
const paddingDays = Array.from({ length: startDayOfWeek });
const nextMonth = () => setCurrentDate(addMonths(currentDate, 1));
const prevMonth = () => setCurrentDate(subMonths(currentDate, 1));
const getItemsForDay = (date: Date) => {
// Filter Games
const dayGames = games.filter(g => {
if (!g.releaseDate || !isSameDay(new Date(g.releaseDate), date)) return false;
// Search filter
if (filters.search && !g.title.toLowerCase().includes(filters.search.toLowerCase())) return false;
// Platform filter (if game has platforms)
if (g.platforms && g.platforms.length > 0) {
const hasPlatform = g.platforms.some((p: string) => filters.platforms.includes(p) || filters.platforms.some(fp => p.includes(fp)));
if (!hasPlatform) return false;
}
return true;
});
// Filter Events
const dayEvents = filters.showEvents ? events.filter(e => {
if (!isSameDay(new Date(e.startTime), date)) return false;
if (filters.search && !e.title.toLowerCase().includes(filters.search.toLowerCase())) return false;
return true;
}) : [];
return [...dayGames.map(g => ({ ...g, type: 'game' })), ...dayEvents.map(e => ({ ...e, type: 'event' }))];
};
return (
<Box w="full" bg="whiteAlpha.50" rounded="xl" p={6} backdropFilter="blur(10px)" border="1px solid" borderColor="whiteAlpha.100">
{/* Header */}
<Flex justify="space-between" align="center" mb={6}>
<Heading size="lg" color="white">
{format(currentDate, 'MMMM yyyy', { locale: dateLocale })}
</Heading>
<Flex gap={2}>
<IconButton aria-label="Previous month" onClick={prevMonth} variant="ghost" color="white">
<FaChevronLeft />
</IconButton>
<IconButton aria-label="Next month" onClick={nextMonth} variant="ghost" color="white">
<FaChevronRight />
</IconButton>
</Flex>
</Flex>
{/* Filters */}
<FilterBar filters={filters} onFilterChange={setFilters} />
{/* Days Header */}
<SimpleGrid columns={7} mb={2}>
{['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => (
<Text key={day} textAlign="center" color="gray.400" fontWeight="bold" fontSize="sm">
{day}
</Text>
))}
</SimpleGrid>
{/* Calendar Grid */}
<SimpleGrid columns={7} gap={1} minH="500px">
{paddingDays.map((_, i) => (
<Box key={`padding-${i}`} bg="transparent" />
))}
{daysInMonth.map((date) => {
const items = getItemsForDay(date);
const isToday = isSameDay(date, new Date());
return (
<Box
key={date.toString()}
bg={isToday ? 'primary.900' : 'whiteAlpha.50'}
border="1px solid"
borderColor={isToday ? 'primary.500' : 'whiteAlpha.100'}
rounded="md"
p={2}
minH="100px"
transition="all 0.2s"
_hover={{ bg: 'whiteAlpha.200', transform: 'scale(1.02)', zIndex: 1 }}
cursor="pointer"
position="relative"
overflow="hidden"
>
<Text fontSize="sm" color={isToday ? 'primary.300' : 'gray.400'} mb={1} fontWeight={isToday ? 'bold' : 'normal'}>
{format(date, 'd')}
</Text>
<VStack align="stretch" gap={1}>
{items.map((item: any, idx) => (
<Badge
key={`${item.id}-${idx}`}
size="sm"
variant="solid"
colorPalette={item.type === 'game' ? 'blue' : 'purple'}
truncate
fontSize="xs"
px={1}
>
{item.title}
</Badge>
))}
</VStack>
{/* Background image effect for heavy days? Optional polish */}
</Box>
);
})}
</SimpleGrid>
</Box >
);
}

View File

@@ -0,0 +1,38 @@
'use client';
import { HStack, Button, Text } from '@chakra-ui/react';
import { useDynamicTheme, DynamicThemeProvider } from '@/components/ui/dynamic-theme-provider';
import { ThemeConfig, defaultTheme } from '@/types/theme';
const eldenRingTheme: ThemeConfig = {
key: 'elden_ring',
isActive: true,
gameTitle: 'Elden Ring',
primaryColor: '#C4A484', // Goldish/Beige
secondaryColor: '#8B4513', // Brown
backgroundColor: '#1a1815', // Dark brown/black
backgroundImage: 'https://images.igdb.com/igdb/image/upload/t_1080p/co4jni.jpg', // Elden Ring Art
};
const cyberPunkTheme: ThemeConfig = {
key: 'cyberpunk',
isActive: true,
gameTitle: 'Cyberpunk 2077',
primaryColor: '#FCEE0A', // Yellow
secondaryColor: '#00F0FF', // Blue
backgroundColor: '#0a0a0a',
backgroundImage: 'https://images.igdb.com/igdb/image/upload/t_1080p/co2mjs.jpg'
}
export function ThemeSwitcher() {
const { setTheme } = useDynamicTheme();
return (
<HStack p={4} bg="blackAlpha.500" rounded="lg" position="fixed" bottom={4} right={4} zIndex={9999}>
<Text fontSize="xs" color="white" fontWeight="bold">Simulate GOTY:</Text>
<Button size="xs" onClick={() => setTheme(defaultTheme)}>Default</Button>
<Button size="xs" colorPalette="yellow" onClick={() => setTheme(eldenRingTheme)}>Elden Ring</Button>
<Button size="xs" colorPalette="cyan" onClick={() => setTheme(cyberPunkTheme)}>Cyberpunk</Button>
</HStack>
);
}

View File

@@ -0,0 +1,61 @@
'use client';
import { ThemeConfig, defaultTheme } from '@/types/theme';
import { createContext, useContext, useEffect, useState } from 'react';
interface DynamicThemeContextType {
theme: ThemeConfig;
setTheme: (theme: ThemeConfig) => void;
}
const DynamicThemeContext = createContext<DynamicThemeContextType>({
theme: defaultTheme,
setTheme: () => { },
});
export const useDynamicTheme = () => useContext(DynamicThemeContext);
interface DynamicThemeProviderProps {
children: React.ReactNode;
initialTheme?: ThemeConfig;
}
export function DynamicThemeProvider({ children, initialTheme }: DynamicThemeProviderProps) {
const [theme, setTheme] = useState<ThemeConfig>(initialTheme || defaultTheme);
// Apply theme to CSS variables
useEffect(() => {
if (!theme) return;
const root = document.documentElement;
// We update the CSS variables that map to our Chakra tokens
// Note: You might need to adjust the exact variable names based on your final theme.ts generation
// Chakra v3 usually uses var(--chakra-colors-primary-500) etc.
// For this simple implementation, we will assume we can override specific brand colors
// In a real generic system, we might need a more complex palette generator to generate 50-950 scales
root.style.setProperty('--chakra-colors-primary-500', theme.primaryColor);
root.style.setProperty('--chakra-colors-primary-600', theme.secondaryColor);
// Backgrounds for the app
if (theme.backgroundColor) {
// We can create a custom variable for app-bg
root.style.setProperty('--app-background', theme.backgroundColor);
}
if (theme.backgroundImage) {
root.style.setProperty('--app-background-image', `url(${theme.backgroundImage})`);
} else {
root.style.removeProperty('--app-background-image');
}
}, [theme]);
return (
<DynamicThemeContext.Provider value={{ theme, setTheme }}>
{children}
</DynamicThemeContext.Provider>
);
}

51
src/lib/api/mock-data.ts Normal file
View File

@@ -0,0 +1,51 @@
export const MOCK_GAMES = [
{
id: '1',
title: 'Elden Ring: Shadow of the Erdtree',
slug: 'elden-ring-shadow-of-the-erdtree',
releaseDate: new Date('2026-06-21'),
coverImage: 'https://images.igdb.com/igdb/image/upload/t_cover_big/co848y.jpg',
platforms: ['PS5', 'PC', 'Xbox Series X'],
},
{
id: '2',
title: 'Grand Theft Auto VI',
slug: 'gta-vi',
releaseDate: new Date('2026-09-15'), // Speculative
coverImage: 'https://images.igdb.com/igdb/image/upload/t_cover_big/co848z.jpg',
platforms: ['PS5', 'Xbox Series X'],
},
// Add some for current month to show in calendar
{
id: '3',
title: 'Hades II',
slug: 'hades-ii',
releaseDate: new Date(), // Today
coverImage: 'https://images.igdb.com/igdb/image/upload/t_cover_big/co8490.jpg',
platforms: ['PC'],
}
];
export const MOCK_EVENTS = [
{
id: '1',
title: 'PlayStation Showcase',
slug: 'playstation-showcase-2026',
startTime: new Date('2026-05-24T20:00:00Z'),
type: 'SHOWCASE',
},
{
id: '2',
title: 'Summer Game Fest',
slug: 'summer-game-fest-2026',
startTime: new Date('2026-06-07T18:00:00Z'),
type: 'SHOWCASE',
},
{
id: '3',
title: 'Indie World',
slug: 'indie-world-april',
startTime: new Date(), // Today
type: 'SHOWCASE',
}
];

9
src/middleware.ts Normal file
View File

@@ -0,0 +1,9 @@
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(tr|en)/:path*']
};

19
src/types/theme.ts Normal file
View File

@@ -0,0 +1,19 @@
export interface ThemeConfig {
key: string;
isActive: boolean;
gameTitle: string; // "Elden Ring"
primaryColor: string; // Hex code for primary brand color
secondaryColor: string; // Hex code for secondary
backgroundColor: string; // Hex code for background
backgroundImage?: string; // URL
logoImage?: string; // URL
}
export const defaultTheme: ThemeConfig = {
key: 'default',
isActive: true,
gameTitle: 'Game Calendar',
primaryColor: '#319795', // Chakra Teal 500
secondaryColor: '#2C7A7B',
backgroundColor: '#132E30', // Dark teal-ish black
};

View File

@@ -33,7 +33,9 @@
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts", ".next/types/**/*.ts",
".next/dev/types/**/*.ts" ".next/dev/types/**/*.ts",
".next\\dev/types/**/*.ts",
".next\\dev/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"