diff --git a/messages/en.json b/messages/en.json
index 390ae21..3974242 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -28,5 +28,9 @@
"name": "Name",
"low": "Low",
"medium": "Medium",
- "high": "High"
+ "high": "High",
+ "predictions": "Predictions",
+ "games": "Games",
+ "events": "Events",
+ "calendar": "Calendar"
}
\ No newline at end of file
diff --git a/messages/tr.json b/messages/tr.json
index e8433d5..beda1f6 100644
--- a/messages/tr.json
+++ b/messages/tr.json
@@ -28,5 +28,9 @@
"name": "İsim",
"low": "Düşük",
"medium": "Orta",
- "high": "Yüksek"
+ "high": "Yüksek",
+ "predictions": "Tahminler",
+ "games": "Oyunlar",
+ "events": "Etkinlikler",
+ "calendar": "Takvim"
}
\ No newline at end of file
diff --git a/next-env.d.ts b/next-env.d.ts
index 9edff1c..c4b7818 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/next.config.ts b/next.config.ts
index 11a5810..db56568 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -10,7 +10,7 @@ const nextConfig: NextConfig = {
return [
{
source: "/api/backend/:path*",
- destination: "http://localhost:3000/api/:path*",
+ destination: "http://localhost:4000/api/:path*",
},
];
},
diff --git a/src/app/[locale]/(site)/home/page.tsx b/src/app/[locale]/(site)/home/page.tsx
index 6a9867a..7eadb0e 100644
--- a/src/app/[locale]/(site)/home/page.tsx
+++ b/src/app/[locale]/(site)/home/page.tsx
@@ -13,7 +13,7 @@ export default function HomePage() {
{/* Hero Section / GOTY Banner could go here */}
- Game Calendar
+ Calendar
Track releases, events, and showcases in one place.
diff --git a/src/app/[locale]/games/[slug]/page.tsx b/src/app/[locale]/games/[slug]/page.tsx
new file mode 100644
index 0000000..b8456f8
--- /dev/null
+++ b/src/app/[locale]/games/[slug]/page.tsx
@@ -0,0 +1,12 @@
+
+import { GameDetail } from "@/components/features/games/GameDetail";
+
+type Props = {
+ params: Promise<{ slug: string }>;
+};
+
+export default async function GamePage({ params }: Props) {
+ const { slug } = await params;
+ console.log('Server Page received slug:', slug);
+ return ;
+}
diff --git a/src/app/[locale]/global.css b/src/app/[locale]/global.css
index c7cbb5b..dbe9be4 100644
--- a/src/app/[locale]/global.css
+++ b/src/app/[locale]/global.css
@@ -39,3 +39,17 @@ body {
::-webkit-scrollbar-thumb:hover {
background: var(--chakra-colors-primary-500);
}
+
+@keyframes text-gradient {
+ 0% {
+ background-position: 0% 50%;
+ }
+
+ 50% {
+ background-position: 100% 50%;
+ }
+
+ 100% {
+ background-position: 0% 50%;
+ }
+}
\ No newline at end of file
diff --git a/src/components/features/calendar/game-calendar.tsx b/src/components/features/calendar/game-calendar.tsx
index 90d93d6..e42e6f4 100644
--- a/src/components/features/calendar/game-calendar.tsx
+++ b/src/components/features/calendar/game-calendar.tsx
@@ -1,4 +1,5 @@
'use client';
+import { Link } from '@/i18n/navigation';
import { Box, Grid, Heading, Text, VStack, Badge, Flex, Image as ChakraImage, SimpleGrid } from '@chakra-ui/react';
import { useState } from 'react';
@@ -66,7 +67,7 @@ export function GameCalendar({ games = [], events = [] }: CalendarProps) {
};
return (
-
+
{/* Header */}
@@ -107,7 +108,7 @@ export function GameCalendar({ games = [], events = [] }: CalendarProps) {
return (
- {items.map((item: any, idx) => (
-
- {item.title}
-
- ))}
+ {items.map((item: any, idx) => {
+ const badge = (
+
+ {item.title}
+
+ );
+
+ if (item.type === 'game') {
+ return (
+
+ {badge}
+
+ );
+ }
+
+ return badge;
+ })}
{/* Background image effect for heavy days? Optional polish */}
diff --git a/src/components/features/games/GameDetail.tsx b/src/components/features/games/GameDetail.tsx
new file mode 100644
index 0000000..5484d52
--- /dev/null
+++ b/src/components/features/games/GameDetail.tsx
@@ -0,0 +1,203 @@
+'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 Loading...;
+ if (error || !response?.data?.data) return (
+
+ Game not found
+
+
+ );
+
+ const game = response.data.data;
+
+ return (
+
+ {/* Hero Section */}
+
+ {/* Background Blur */}
+
+
+
+
+
+
+
+
+
+ {game.title}
+
+ {game.rating && (
+
+ {Math.round(game.rating).toFixed(1)}
+
+ )}
+ {game.releaseDate && (
+
+
+ {new Date(game.releaseDate).toLocaleDateString()}
+
+ )}
+
+
+
+ {game.platforms?.map(p => (
+
+ {p.platform.name}
+
+ ))}
+
+
+
+ {game.genres?.map(g => (
+
+ {g.genre.name}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Content Section */}
+
+
+
+ About
+
+ {game.description || "No description available."}
+
+
+ {game.screenshots && game.screenshots.length > 0 && (
+
+ Gallery
+
+ {game.screenshots.map((shot, idx) => (
+
+
+
+
+
+ ))}
+
+
+ )}
+
+
+
+
+ Information
+
+
+ Developer
+ {game.developer || "Unknown"}
+
+
+ Publisher
+ {game.publisher || "Unknown"}
+
+
+ Release Date
+ {game.releaseDate ? new Date(game.releaseDate).toLocaleDateString(undefined, { dateStyle: 'long' }) : 'TBD'}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/features/theme-switcher.tsx b/src/components/features/theme-switcher.tsx
index 1e015c5..585afcc 100644
--- a/src/components/features/theme-switcher.tsx
+++ b/src/components/features/theme-switcher.tsx
@@ -1,38 +1,45 @@
-'use client';
+"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'
-}
+import { HStack, Box, Text } from '@chakra-ui/react';
+import { useDynamicTheme } from '@/components/ui/dynamic-theme-provider';
+import { defaultTheme } from '@/types/theme';
+import { additionalThemes } from '@/theme/palettes';
+import { Button } from '@/components/ui/buttons/button';
+import {
+ MenuContent,
+ MenuItem,
+ MenuRoot,
+ MenuTrigger,
+} from "@/components/ui/overlays/menu";
export function ThemeSwitcher() {
const { setTheme } = useDynamicTheme();
return (
- Simulate GOTY:
-
-
-
+
+
+
+
+
+
+ {additionalThemes.map((theme) => (
+
+ ))}
+
+
);
}
diff --git a/src/components/layout/footer/footer.tsx b/src/components/layout/footer/footer.tsx
index ad35a98..c22ed9a 100644
--- a/src/components/layout/footer/footer.tsx
+++ b/src/components/layout/footer/footer.tsx
@@ -23,12 +23,12 @@ export default function Footer() {
- {"FCS"}
+ {"Game Calendar"}
. {t("all-right-reserved")}
diff --git a/src/components/layout/header/header.tsx b/src/components/layout/header/header.tsx
index 6e1041d..e000103 100644
--- a/src/components/layout/header/header.tsx
+++ b/src/components/layout/header/header.tsx
@@ -165,17 +165,23 @@ export default function Header() {
- {"FCS "}
+ {"Game Calendar"}
diff --git a/src/components/site/home/home-card.tsx b/src/components/site/home/home-card.tsx
index 71880c7..454cd3b 100644
--- a/src/components/site/home/home-card.tsx
+++ b/src/components/site/home/home-card.tsx
@@ -1004,15 +1004,15 @@ function HomeCard() {
css={
index === 0
? {
- fontStyle: "inherit",
- color: "primary.500",
- position: "relative",
- }
+ fontStyle: "inherit",
+ color: "primary.500",
+ position: "relative",
+ }
: {
- fontStyle: "italic",
- color: "red.500",
- position: "relative",
- }
+ fontStyle: "italic",
+ color: "red.500",
+ position: "relative",
+ }
}
>
{chunk.text}
@@ -2325,10 +2325,10 @@ function HomeCard() {
)}
- {(variant) => }
+ {(variant) => }
-
-
+
+
(initialTheme || defaultTheme);
+ // Fetch theme on mount
+ useEffect(() => {
+ const fetchTheme = async () => {
+ try {
+ const response = await themeApi.getTheme();
+ // @ts-ignore - The API response wrapper might be generic, assuming response.data is the payload if wrapped, or response if not.
+ // Based on standard axios + nestjs wrapper: response.data.data or response is the data.
+ // Let's assume our client unwraps it or we check.
+ // If createApiClient returns axios instance, .get returns AxiosResponse.
+ // api-service.ts unwraps response.data.
+ // BUT theme.ts calls client.get directly.
+ // Let's fix theme.ts to use apiRequest or handle .data
+
+ // Correction: theme.ts uses client.get. client is axios instance.
+ // So response is AxiosResponse. response.data is the body ({ success, data: theme }).
+ if (response.data && response.data.success && response.data.data) {
+ setTheme(response.data.data);
+ }
+ } catch (error) {
+ console.error('Failed to fetch theme:', error);
+ }
+ };
+ fetchTheme();
+ }, []);
+
// 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);
+ // ... rest of logic
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);
}
diff --git a/src/components/ui/locale-switcher.tsx b/src/components/ui/locale-switcher.tsx
index a647ca7..9f8451f 100644
--- a/src/components/ui/locale-switcher.tsx
+++ b/src/components/ui/locale-switcher.tsx
@@ -18,7 +18,14 @@ const LocaleSwitcher = () => {
const [isPending, startTransition] = useTransition();
const router = useRouter();
const pathname = usePathname();
+ /* eslint-disable react-hooks/exhaustive-deps */
const params = useParams();
+ const [mounted, setMounted] = React.useState(false);
+
+ // Effect to handle hydration match
+ React.useEffect(() => {
+ setMounted(true);
+ }, []);
const collections = createListCollection({
items: [
@@ -39,6 +46,11 @@ const LocaleSwitcher = () => {
);
});
}
+
+ if (!mounted) {
+ return null;
+ }
+
return (
client.get<{ items: Game[]; meta: any }>('/', { params }),
+ getBySlug: (slug: string) => client.get<{ data: Game }>(`/${slug}`),
+ sync: (slug: string) => client.post(`/${slug}/sync`),
+};
diff --git a/src/lib/api/notifications.ts b/src/lib/api/notifications.ts
new file mode 100644
index 0000000..6936c94
--- /dev/null
+++ b/src/lib/api/notifications.ts
@@ -0,0 +1,8 @@
+import { createApiClient } from './create-api-client';
+
+const client = createApiClient('/api/backend/notifications');
+
+export const notificationsApi = {
+ subscribe: (gameId: string) => client.post('/subscribe', { gameId }),
+ unsubscribe: (gameId: string) => client.post('/unsubscribe', { gameId }),
+};
diff --git a/src/lib/api/theme.ts b/src/lib/api/theme.ts
new file mode 100644
index 0000000..49789ff
--- /dev/null
+++ b/src/lib/api/theme.ts
@@ -0,0 +1,9 @@
+import { createApiClient } from './create-api-client';
+import { ThemeConfig } from '@/types/theme';
+
+const client = createApiClient('/api/backend/theme');
+
+export const themeApi = {
+ getTheme: () => client.get<{ data: ThemeConfig; success: boolean }>('/'),
+ updateTheme: (data: Partial) => client.patch<{ data: ThemeConfig; success: boolean }>('/', data),
+};
diff --git a/src/middleware.ts b/src/middleware.ts
index b4f3ad3..3a4b4e5 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -4,6 +4,8 @@ import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
- // Match only internationalized pathnames
- matcher: ['/', '/(tr|en)/:path*']
+ // Match all pathnames except for
+ // - … if they start with `/api`, `/_next` or `/_vercel`
+ // - … the ones containing a dot (e.g. `favicon.ico`)
+ matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
diff --git a/src/theme/palettes.ts b/src/theme/palettes.ts
new file mode 100644
index 0000000..cfaa1c4
--- /dev/null
+++ b/src/theme/palettes.ts
@@ -0,0 +1,74 @@
+import { ThemeConfig } from '@/types/theme';
+
+export 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
+};
+
+export 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'
+};
+
+// Palette 1: https://colorhunt.co/palette/280905740a03c3110ce6501b
+// Colors: #280905, #740a03, #c3110c, #e6501b
+export const fireTheme: ThemeConfig = {
+ key: 'fire',
+ isActive: true,
+ gameTitle: 'Blazing Fast',
+ primaryColor: '#e6501b', // Bright orange
+ secondaryColor: '#c3110c', // Darker red/orange
+ backgroundColor: '#280905', // Deep dark brown/red
+};
+
+// Palette 2: https://colorhunt.co/palette/3b060a8a0000c83f12fff287
+// Colors: #3b060a, #8a0000, #c83f12, #fff287
+export const crimsonTheme: ThemeConfig = {
+ key: 'crimson',
+ isActive: true,
+ gameTitle: 'Crimson Tide',
+ primaryColor: '#fff287', // Light yellow accent
+ secondaryColor: '#c83f12', // Red/Orange
+ backgroundColor: '#3b060a', // Deep dark red
+};
+
+// Palette 3: https://colorhunt.co/palette/0054610c7779249e943bc1a8
+// Colors: #005461, #0c7779, #249e94, #3bc1a8
+export const oceanTheme: ThemeConfig = {
+ key: 'ocean',
+ isActive: true,
+ gameTitle: 'Oceanic Depths',
+ primaryColor: '#3bc1a8', // Light teal
+ secondaryColor: '#249e94', // Teal
+ backgroundColor: '#005461', // Dark blue/teal
+};
+
+// Palette 4: https://colorhunt.co/palette/1a3263547792fab95be8e2db
+// Colors: #1a3263, #547792, #fab95b, #e8e2db
+export const nightTheme: ThemeConfig = {
+ key: 'night',
+ isActive: true,
+ gameTitle: 'Starry Night',
+ primaryColor: '#fab95b', // Golden yellow
+ secondaryColor: '#547792', // Muted blue
+ backgroundColor: '#1a3263', // Deep blue
+};
+
+export const additionalThemes = [
+ eldenRingTheme,
+ cyberPunkTheme,
+ fireTheme,
+ crimsonTheme,
+ oceanTheme,
+ nightTheme,
+];