feat: AI commentary skeleton loading - separate async endpoint
Deploy Iddaai Frontend / build-and-deploy (push) Successful in 2m20s

This commit is contained in:
2026-05-17 16:46:53 +03:00
parent e744a62fc2
commit 71a6ed320c
8 changed files with 228 additions and 40 deletions
+15 -20
View File
@@ -1,29 +1,24 @@
# ========================================== # ==========================================
# IDDAAI - DEVELOPMENT ENVIRONMENT VARIABLES # IDDAAI-FE — DEVELOPMENT ENVIRONMENT VARIABLES
# ========================================== # ==========================================
# Bu dosya lokal geliştirme için kullanılır.
# Prod değerleri: .gitea/workflows/deploy.yml → Gitea Secrets
# --- FRONTEND (iddaai-fe) --- # --- Next.js App ---
# Next.js uygulaması için gerekli ayarlar
NEXT_PUBLIC_API_URL=http://localhost:3005/api NEXT_PUBLIC_API_URL=http://localhost:3005/api
NEXT_PUBLIC_APP_URL=http://localhost:3000
PORT=3000 PORT=3000
HOSTNAME="0.0.0.0" HOSTNAME="0.0.0.0"
# --- NextAuth ---
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET='fFw34R134jRof1H2jofh2!32hU3gfjA1'
# --- BACKEND (iddaai-be) --- # --- Auth Config ---
# NestJS uygulaması için gerekli ayarlar NEXT_PUBLIC_AUTH_REQUIRED=false
NODE_ENV=development NEXT_PUBLIC_ENABLE_MOCK_MODE=false
# Database (Localde PostgreSQL çalışıyorsa) # --- Paddle (Sandbox) ---
DATABASE_URL="postgresql://iddaai_user:IddaA1_S4crET!@localhost:5432/iddaai_db?schema=public" NEXT_PUBLIC_PADDLE_CLIENT_TOKEN='test_...'
NEXT_PUBLIC_PADDLE_ENVIRONMENT='sandbox'
# Redis NEXT_PUBLIC_PADDLE_SELLER_ID='...'
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD="IddaA1_Redis_Pass!"
# AI Engine
AI_ENGINE_URL="http://localhost:8000"
# JWT Config
JWT_SECRET="b7V8jM2wP1L5mQxs2RdfFkAsLpI2oG!w"
JWT_ACCESS_EXPIRATION="1d"
+138
View File
@@ -0,0 +1,138 @@
> iddaai-fe@0.0.1 lint
> eslint
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/admin/edit-user-modal.tsx
36:9 warning 't' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
37:9 warning 'tCommon' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
50:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/admin/edit-user-modal.tsx:50:7
48 | useEffect(() => {
49 | if (user) {
> 50 | setRole(user.role || "user");
| ^^^^^^^ Avoid calling setState() directly within an effect
51 | setPlan(user.subscriptionStatus || "free");
52 | setIsActive(user.isActive);
53 | if (user.subscriptionExpiresAt) { react-hooks/set-state-in-effect
57:17 warning 'e' is defined but never used @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/analysis/analysis-content.tsx
14:3 warning 'SimpleGrid' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
31:9 warning 'tCommon' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/coupons/coupon-builder-content.tsx
367:6 warning React Hook React.useEffect has a missing dependency: 'upcomingQuery'. Either include it or remove the dependency array react-hooks/exhaustive-deps
381:6 warning React Hook React.useEffect has a missing dependency: 'finishedQuery'. Either include it or remove the dependency array react-hooks/exhaustive-deps
403:9 warning The 'leagueGroups' logical expression could make the dependencies of useMemo Hook (at line 407) change on every render. To fix this, wrap the initialization of 'leagueGroups' in its own useMemo() Hook react-hooks/exhaustive-deps
404:9 warning The 'finishedLeagueGroups' logical expression could make the dependencies of useMemo Hook (at line 411) change on every render. To fix this, wrap the initialization of 'finishedLeagueGroups' in its own useMemo() Hook react-hooks/exhaustive-deps
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/dashboard/dashboard-content.tsx
21:3 warning 'ScrollSlideUp' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
127:3 warning 'confidence' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
188:39 warning 'statsLoading' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/h2h/h2h-content.tsx
21:24 warning 'HeadToHeadDto' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
24:20 warning 'useEffect' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
84:17 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/home/home-content.tsx
13:3 warning 'Icon' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
27:3 warning 'springs' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/layout/header/header.tsx
47:8 warning 'Image' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
293:13 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/leagues/league-detail-content.tsx
5:3 warning 'Flex' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
29:9 warning 't' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/leagues/leagues-content.tsx
239:23 error React Hook "useColorModeValue" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
345:39 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
414:27 error React Hook "useColorModeValue" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
715:23 error `"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;` react/no-unescaped-entities
715:44 error `"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;` react/no-unescaped-entities
762:31 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/matches/match-detail-content.tsx
15:3 warning 'Grid' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
33:3 warning 'LuShield' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
34:3 warning 'LuFlag' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/matches/match-list.tsx
9:3 warning 'ScrollSlideUp' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/matches/matches-content.tsx
38:41 warning 'matchesLoading' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/matches/odds-card.tsx
7:3 warning 'Badge' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/matches/v28-odds-band-panel.tsx
374:15 error React Hook "useColorModeValue" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
565:15 error React Hook "useColorModeValue" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/motion/index.tsx
417:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/motion/index.tsx:417:5
415 | delay: Math.random() * 3,
416 | }));
> 417 | setSparkles(newSparkles);
| ^^^^^^^^^^^ Avoid calling setState() directly within an effect
418 | }, [count]);
419 |
420 | if (sparkles.length === 0) { react-hooks/set-state-in-effect
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/spor-toto/spor-toto-content.tsx
18:37 warning 'StaggerItem' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
29:3 warning 'SporTotoPredictionResultDto' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
52:9 warning 'tCommon' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
88:11 warning 'result' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/teams/team-detail-content.tsx
26:3 warning 'LuChevronDown' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
81:10 warning 'getSeasonFromTimestamp' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
107:9 warning 't' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
144:9 warning The 'matches' conditional could make the dependencies of useMemo Hook (at line 157) change on every render. To fix this, wrap the initialization of 'matches' in its own useMemo() Hook react-hooks/exhaustive-deps
144:9 warning The 'matches' conditional could make the dependencies of useMemo Hook (at line 161) change on every render. To fix this, wrap the initialization of 'matches' in its own useMemo() Hook react-hooks/exhaustive-deps
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/ui/top-loader.tsx
12:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/components/ui/top-loader.tsx:12:5
10 |
11 | useEffect(() => {
> 12 | setMounted(true);
| ^^^^^^^^^^ Avoid calling setState() directly within an effect
13 | }, []);
14 |
15 | if (!mounted) return null; react-hooks/set-state-in-effect
/Users/piton/Documents/GitHub/iddaai/iddaai-fe/src/lib/api/leagues/service.ts
3:15 warning 'MatchResponseDto' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
✖ 48 problems (9 errors, 39 warnings)
@@ -20,7 +20,7 @@ import { useState } from "react";
import { useColorModeValue } from "@/components/ui/color-mode"; import { useColorModeValue } from "@/components/ui/color-mode";
import { SlideUp, FadeIn } from "@/components/motion"; import { SlideUp, FadeIn } from "@/components/motion";
import { useMatchDetails } from "@/lib/api/matches/use-hooks"; import { useMatchDetails } from "@/lib/api/matches/use-hooks";
import { usePrediction } from "@/lib/api/predictions/use-hooks"; import { usePrediction, useAiCommentary } from "@/lib/api/predictions/use-hooks";
import { useGetMe } from "@/lib/api/users/use-hooks"; import { useGetMe } from "@/lib/api/users/use-hooks";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { UsersQueryKeys } from "@/lib/api/users/use-hooks"; import { UsersQueryKeys } from "@/lib/api/users/use-hooks";
@@ -32,6 +32,7 @@ import {
LuRefreshCw, LuRefreshCw,
LuUser, LuUser,
LuSparkles, LuSparkles,
LuBrain,
LuInfo, LuInfo,
LuChevronDown, LuChevronDown,
LuChevronUp, LuChevronUp,
@@ -142,6 +143,11 @@ export default function MatchDetailContent() {
isFetching: isPredFetching, isFetching: isPredFetching,
} = usePrediction(matchId); } = usePrediction(matchId);
const { data: commentaryData, isLoading: commentaryLoading } = useAiCommentary(
matchId,
!!predictionData?.data, // prediction yüklendikten sonra başlat
);
const [officialsOpen, setOfficialsOpen] = useState(false); const [officialsOpen, setOfficialsOpen] = useState(false);
const [activeTab, setActiveTab] = useState<string>("all"); const [activeTab, setActiveTab] = useState<string>("all");
@@ -762,7 +768,33 @@ export default function MatchDetailContent() {
<Skeleton h="80px" borderRadius="xl" /> <Skeleton h="80px" borderRadius="xl" />
</VStack> </VStack>
) : prediction ? ( ) : prediction ? (
<PredictionCard prediction={prediction} /> <>
<PredictionCard prediction={prediction} />
{/* AI Uzman Yorumu — prediction yüklendikten sonra ayrıca çekiliyor */}
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="2xl">
<Card.Body gap={3}>
<Flex align="center" gap={2}>
<LuBrain size={16} />
<Text fontWeight="semibold" fontSize="sm">AI Uzman Yorumu</Text>
</Flex>
{commentaryLoading ? (
<VStack align="stretch" gap={2}>
<Skeleton h="16px" borderRadius="md" />
<Skeleton h="16px" borderRadius="md" w="90%" />
<Skeleton h="16px" borderRadius="md" w="75%" />
</VStack>
) : commentaryData?.data?.commentary ? (
<Text fontSize="sm" color="fg.muted" lineHeight="1.7">
{commentaryData.data.commentary}
</Text>
) : (
<Text fontSize="sm" color="fg.subtle">
Yorum üretilemedi.
</Text>
)}
</Card.Body>
</Card.Root>
</>
) : ( ) : (
<Card.Root borderColor={borderColor} borderRadius="xl"> <Card.Root borderColor={borderColor} borderRadius="xl">
<Card.Body> <Card.Body>
@@ -1627,23 +1627,6 @@ export default function PredictionCard({ prediction }: PredictionCardProps) {
)} )}
ui={ui} ui={ui}
/> />
{(prediction as unknown as Record<string, unknown>).ai_expert_commentary ? (
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="2xl">
<Card.Body gap={3}>
<SectionTitle
icon={LuBrain}
title={uiText("ai-expert-commentary-title", "AI Uzman Yorumu")}
info={uiText(
"ai-expert-commentary-info",
"Yapay zekanın maç verilerini okuyarak ürettiği uzman bahis analizi.",
)}
/>
<Text fontSize="sm" color="fg.muted" lineHeight="1.7">
{String((prediction as unknown as Record<string, unknown>).ai_expert_commentary)}
</Text>
</Card.Body>
</Card.Root>
) : null}
{prediction.match_commentary?.headline || {prediction.match_commentary?.headline ||
prediction.match_commentary?.summary ? ( prediction.match_commentary?.summary ? (
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="2xl"> <Card.Root bg={cardBg} borderColor={borderColor} borderRadius="2xl">
+9
View File
@@ -65,6 +65,14 @@ const checkHealth = () => {
}); });
}; };
const getCommentary = (matchId: string) => {
return apiRequest<ApiResponse<{ commentary: string | null }>>({
url: `/predictions/${matchId}/commentary`,
client: "core",
method: "get",
});
};
const generateSmartCoupon = (body: SmartCouponRequestDto) => { const generateSmartCoupon = (body: SmartCouponRequestDto) => {
return apiRequest<ApiResponse<SmartCouponResponseDto>>({ return apiRequest<ApiResponse<SmartCouponResponseDto>>({
url: "/predictions/smart-coupon", url: "/predictions/smart-coupon",
@@ -82,4 +90,5 @@ export const predictionsService = {
getHistory, getHistory,
checkHealth, checkHealth,
generateSmartCoupon, generateSmartCoupon,
getCommentary,
}; };
+12
View File
@@ -7,6 +7,8 @@ export const PredictionsQueryKeys = {
all: ["predictions"] as const, all: ["predictions"] as const,
detail: (matchId: string) => detail: (matchId: string) =>
[...PredictionsQueryKeys.all, "detail", matchId] as const, [...PredictionsQueryKeys.all, "detail", matchId] as const,
commentary: (matchId: string) =>
[...PredictionsQueryKeys.all, "commentary", matchId] as const,
upcoming: () => [...PredictionsQueryKeys.all, "upcoming"] as const, upcoming: () => [...PredictionsQueryKeys.all, "upcoming"] as const,
valueBets: () => [...PredictionsQueryKeys.all, "valueBets"] as const, valueBets: () => [...PredictionsQueryKeys.all, "valueBets"] as const,
history: () => [...PredictionsQueryKeys.all, "history"] as const, history: () => [...PredictionsQueryKeys.all, "history"] as const,
@@ -21,6 +23,16 @@ export const usePrediction = (matchId: string) => {
}); });
}; };
export const useAiCommentary = (matchId: string, enabled: boolean) => {
return useQuery({
queryKey: PredictionsQueryKeys.commentary(matchId),
queryFn: () => predictionsService.getCommentary(matchId),
enabled,
staleTime: 10 * 60 * 1000, // 10 dakika cache
retry: 1,
});
};
export const useGeneratePrediction = () => { export const useGeneratePrediction = () => {
return useMutation({ return useMutation({
mutationFn: (body: { matchId: string; sport?: SportType }) => mutationFn: (body: { matchId: string; sport?: SportType }) =>
+19
View File
@@ -0,0 +1,19 @@
import nextConfig from 'eslint-config-next';
import prettierConfig from 'eslint-config-prettier';
const eslintConfig = [
...nextConfig,
prettierConfig,
{
ignores: ['node_modules/**', '.next/**', 'out/**', 'build/**', 'next-env.d.ts'],
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
},
},
];
export default eslintConfig;
+1 -1
View File
File diff suppressed because one or more lines are too long