diff --git a/package-lock.json b/package-lock.json index 3d27f6f..22a9ba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@hookform/resolvers": "^5.2.2", "@tanstack/react-query": "^5.90.16", "axios": "^1.13.1", + "date-fns": "^4.1.0", "i18next": "^25.6.0", "next": "16.0.0", "next-auth": "^4.24.13", @@ -5368,6 +5369,16 @@ "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": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", diff --git a/package.json b/package.json index fe20d7e..2da4f21 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@hookform/resolvers": "^5.2.2", "@tanstack/react-query": "^5.90.16", "axios": "^1.13.1", + "date-fns": "^4.1.0", "i18next": "^25.6.0", "next": "16.0.0", "next-auth": "^4.24.13", diff --git a/src/app/[locale]/(site)/events/[slug]/page.tsx b/src/app/[locale]/(site)/events/[slug]/page.tsx new file mode 100644 index 0000000..8100160 --- /dev/null +++ b/src/app/[locale]/(site)/events/[slug]/page.tsx @@ -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 ( + + + + {/* Event Type Badge */} + + {event.type} + + + {/* Title */} + + {event.title} + + + {/* Date & Time Box */} + + + + + {formattedDate} + + + + {formattedTime} + + + + + {/* Description (Placeholder) */} + + Join us for the {event.title}. Expect world premieres, developer interviews, and exclusive gameplay reveals. Don't miss out on the biggest gaming news! + + + {/* Action Buttons */} + + + + + + + + ); +} diff --git a/src/app/[locale]/(site)/games/[slug]/page.tsx b/src/app/[locale]/(site)/games/[slug]/page.tsx new file mode 100644 index 0000000..c795325 --- /dev/null +++ b/src/app/[locale]/(site)/games/[slug]/page.tsx @@ -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 ( + + {/* Hero / Cover Section */} + + {game.title} + + + {game.title} + + + {new Date() < game.releaseDate ? 'Upcoming' : 'Released'} + + {game.platforms.map(p => ( + + {p} + + ))} + + + + + + {/* Details Grid */} + + {/* Main Content */} + + About + + {/* 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. + + + + {/* Sidebar Info */} + + + Game Info + + + + + Release Date + + {formattedDate} + + + + + + Platforms + + + {game.platforms.map(p => ( + {p} + ))} + + + + + + + ); +} diff --git a/src/app/[locale]/(site)/home/page.tsx b/src/app/[locale]/(site)/home/page.tsx index df78358..6a9867a 100644 --- a/src/app/[locale]/(site)/home/page.tsx +++ b/src/app/[locale]/(site)/home/page.tsx @@ -1,14 +1,28 @@ -import { getTranslations } from "next-intl/server"; -import HomeCard from "@/components/site/home/home-card"; +import { Container, VStack, Heading, Text, Box } from '@chakra-ui/react'; +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() { - const t = await getTranslations(); +export default function HomePage() { + // Static for now, in future useTranslations + // const t = useTranslations('dashboard'); - return { - title: `${t("home")} | FCS`, - }; -} - -export default function Home() { - return ; + return ( + + + {/* Hero Section / GOTY Banner could go here */} + + Game Calendar + Track releases, events, and showcases in one place. + + + {/* Calendar */} + + + + {/* Debug Switcher */} + + + ); } diff --git a/src/app/[locale]/global.css b/src/app/[locale]/global.css index 356c295..c7cbb5b 100644 --- a/src/app/[locale]/global.css +++ b/src/app/[locale]/global.css @@ -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 { scroll-behavior: smooth; + height: 100%; } body { 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); } diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 85f0838..7afd225 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,4 +1,5 @@ import { Provider } from '@/components/ui/provider'; +import { DynamicThemeProvider } from '@/components/ui/dynamic-theme-provider'; import { Bricolage_Grotesque } from 'next/font/google'; import { hasLocale, NextIntlClientProvider } from 'next-intl'; import { notFound } from 'next/navigation'; @@ -33,7 +34,11 @@ export default async function RootLayout({ - {children} + + + {children} + + diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 90d254a..de70bff 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,5 +1,5 @@ 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 Credentials from "next-auth/providers/credentials"; diff --git a/src/components/features/calendar/filter-bar.tsx b/src/components/features/calendar/filter-bar.tsx new file mode 100644 index 0000000..c8b7a2e --- /dev/null +++ b/src/components/features/calendar/filter-bar.tsx @@ -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) => { + 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 ( + + }> + + + + + + + + + + Platforms + + {['PC', 'PS5', 'Xbox', 'Switch'].map(p => ( + togglePlatform(p)} + > + {p} + + ))} + + + + + toggleEvents(checked)} + > + Show Events + + + + + + ); +} diff --git a/src/components/features/calendar/game-calendar.tsx b/src/components/features/calendar/game-calendar.tsx new file mode 100644 index 0000000..90d93d6 --- /dev/null +++ b/src/components/features/calendar/game-calendar.tsx @@ -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({ + 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 ( + + {/* Header */} + + + {format(currentDate, 'MMMM yyyy', { locale: dateLocale })} + + + + + + + + + + + + {/* Filters */} + + + {/* Days Header */} + + {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => ( + + {day} + + ))} + + + {/* Calendar Grid */} + + {paddingDays.map((_, i) => ( + + ))} + + {daysInMonth.map((date) => { + const items = getItemsForDay(date); + const isToday = isSameDay(date, new Date()); + + return ( + + + {format(date, 'd')} + + + + {items.map((item: any, idx) => ( + + {item.title} + + ))} + + + {/* Background image effect for heavy days? Optional polish */} + + ); + })} + + + ); +} diff --git a/src/components/features/theme-switcher.tsx b/src/components/features/theme-switcher.tsx new file mode 100644 index 0000000..1e015c5 --- /dev/null +++ b/src/components/features/theme-switcher.tsx @@ -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 ( + + Simulate GOTY: + + + + + ); +} diff --git a/src/components/ui/dynamic-theme-provider.tsx b/src/components/ui/dynamic-theme-provider.tsx new file mode 100644 index 0000000..b2c8a80 --- /dev/null +++ b/src/components/ui/dynamic-theme-provider.tsx @@ -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({ + 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(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 ( + + {children} + + ); +} diff --git a/src/lib/api/mock-data.ts b/src/lib/api/mock-data.ts new file mode 100644 index 0000000..d9b4eb0 --- /dev/null +++ b/src/lib/api/mock-data.ts @@ -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', + } +]; diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..b4f3ad3 --- /dev/null +++ b/src/middleware.ts @@ -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*'] +}; diff --git a/src/types/theme.ts b/src/types/theme.ts new file mode 100644 index 0000000..41ede80 --- /dev/null +++ b/src/types/theme.ts @@ -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 +}; diff --git a/tsconfig.json b/tsconfig.json index b575f7d..c3ea842 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,9 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" + ".next/dev/types/**/*.ts", + ".next\\dev/types/**/*.ts", + ".next\\dev/types/**/*.ts" ], "exclude": [ "node_modules"