From 5185135a9d7c08acc44ff356e042f8f4fe15a677 Mon Sep 17 00:00:00 2001 From: Sujatx Date: Tue, 30 Dec 2025 18:42:03 +0530 Subject: [PATCH 1/3] Improve results page UX and layout Align results page with overall app theme. Main results area scrolls while header stays fixed. Analyze another article without scrolling back. AI Discussion panel collapsible into a bottom-right chat button. --- frontend/app/analyze/loading/page.tsx | 279 ++++++------ frontend/app/analyze/page.tsx | 229 +++++----- frontend/app/analyze/results/page.tsx | 621 +++++++++++++++++--------- frontend/package-lock.json | 505 +++++++++++---------- frontend/package.json | 2 +- frontend/tsconfig.json | 24 +- 6 files changed, 944 insertions(+), 716 deletions(-) diff --git a/frontend/app/analyze/loading/page.tsx b/frontend/app/analyze/loading/page.tsx index 05067e9c..932723da 100644 --- a/frontend/app/analyze/loading/page.tsx +++ b/frontend/app/analyze/loading/page.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Globe, @@ -11,22 +10,11 @@ import { CheckCircle, Database, Sparkles, - Zap, + Loader2, } from "lucide-react"; import ThemeToggle from "@/components/theme-toggle"; import axios from "axios"; -// const backend_url = process.env.NEXT_PUBLIC_API_URL; - - - -/** - * Displays a multi-step animated loading and progress interface for the article analysis workflow. - * - * Guides the user through sequential analysis steps—fetching the article, AI analysis, bias detection, fact checking, and generating perspectives—while visually indicating progress and status. Retrieves the article URL from session storage, automatically advances through each step, and redirects to the results page upon completion. If no article URL is found, redirects to the analysis input page. - * - * @remark This component manages its own navigation and redirects based on session state. - */ export default function LoadingPage() { const [currentStep, setCurrentStep] = useState(0); const [progress, setProgress] = useState(0); @@ -37,31 +25,26 @@ export default function LoadingPage() { { icon: Globe, title: "Fetching Article", - description: "Retrieving content from the provided URL", color: "from-blue-500 to-cyan-500", }, { icon: Brain, title: "AI Analysis", - description: "Processing content with advanced NLP algorithms", color: "from-purple-500 to-indigo-500", }, { icon: Shield, title: "Bias Detection", - description: "Identifying potential biases and one-sided perspectives", color: "from-emerald-500 to-teal-500", }, { icon: CheckCircle, title: "Fact Checking", - description: "Cross-referencing claims with reliable sources", color: "from-orange-500 to-red-500", }, { icon: Database, title: "Generating Perspectives", - description: "Creating balanced alternative viewpoints", color: "from-pink-500 to-rose-500", }, ]; @@ -83,11 +66,9 @@ export default function LoadingPage() { ]); sessionStorage.setItem("BiasScore", JSON.stringify(biasRes.data)); - console.log("Bias score saved"); console.log(biasRes); - // Save response to sessionStorage sessionStorage.setItem( "analysisResult", JSON.stringify(processRes.data) @@ -95,15 +76,12 @@ export default function LoadingPage() { console.log("Analysis result saved"); console.log(processRes); - - // optional logging } catch (err) { console.error("Failed to process article:", err); - router.push("/analyze"); // fallback in case of error + router.push("/analyze"); return; } - // Progress and step simulation const stepInterval = setInterval(() => { setCurrentStep((prev) => { if (prev < steps.length - 1) { @@ -140,158 +118,159 @@ export default function LoadingPage() { }, [router]); return ( -
- {/* Animated background elements */} +
+ {/* Animated background */}
-
-
-
+
+
{/* Header */} -
-
+
+
router.push("/")} > -
- +
+
- + Perspective
-
- -
+
- {/* Main Content */} -
-
- {/* Status Badge */} - - - AI Processing in Progress - + {/* Main Content - Centered with flex */} +
+
+
+ + {/* Left: Title & Progress */} +
+ + + AI Processing + - {/* Main Title */} -

- Analyzing Your Article -

+

+ Analyzing Your Article +

- {/* Article URL Display */} -
-

- Processing: -

-

- {articleUrl} -

-
+ {/* Article URL */} +
+ +
+

Processing

+

+ {articleUrl} +

+
+
- {/* Progress Bar */} -
-
-
-
+ {/* Progress Bar */} +
+
+ + Progress + + + {Math.min(progress, (currentStep + 1) * 20)}% + +
+ +
+
+
+
+
+ +

+ Our AI is analyzing content and generating balanced perspectives +

-

- {Math.min(progress, (currentStep + 1) * 20)}% Complete -

-
- {/* Processing Steps */} -
- {steps.map((step, index) => ( - -
+ {/* Right: Processing Steps */} +
+ {steps.map((step, index) => { + const isActive = index === currentStep; + const isComplete = index < currentStep; + + return (
- {index < currentStep ? ( - - ) : index === currentStep ? ( - - ) : ( - - )} -
-
-

- {step.title} -

-

- {step.description} -

-
- {index === currentStep && ( -
-
-
-
-
- )} -
- - ))} -
+
+ {/* Icon */} +
+ {isComplete ? ( + + ) : isActive ? ( + <> + +
+ + ) : ( + + )} +
- {/* AI Processing Animation */} -
-
-
-
-
-
- -
+ {/* Text */} +
+

+ {step.title} +

+
+ + {/* Status Indicator */} + {isActive && ( +
+
+
+
+
+ )} + + {isComplete && ( + + )} +
+
+ ); + })}
- -

- Our AI is working hard to provide you with comprehensive analysis... -

diff --git a/frontend/app/analyze/page.tsx b/frontend/app/analyze/page.tsx index c86c6c9e..b9a6dcd4 100644 --- a/frontend/app/analyze/page.tsx +++ b/frontend/app/analyze/page.tsx @@ -1,7 +1,6 @@ "use client"; import type React from "react"; - import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -25,11 +24,6 @@ import { import { useRouter } from "next/navigation"; import ThemeToggle from "@/components/theme-toggle"; -/** - * Renders the main page for submitting an article URL to initiate AI-powered analysis. - * - * Provides a user interface for entering and validating an article URL, displays real-time feedback on URL validity, and enables users to trigger analysis. Features include a branded header, a hero section, a URL input card with validation, a grid highlighting analysis capabilities, and example article URLs for quick testing. On valid submission, the URL is stored in sessionStorage and the user is navigated to a loading page for further processing. - */ export default function AnalyzePage() { const [url, setUrl] = useState(""); const [isValidUrl, setIsValidUrl] = useState(false); @@ -56,7 +50,6 @@ export default function AnalyzePage() { const handleAnalyze = () => { if (isValidUrl && url) { - // Store the URL in sessionStorage to pass to loading page sessionStorage.setItem("articleUrl", url); router.push("/analyze/loading"); } @@ -84,154 +77,156 @@ export default function AnalyzePage() {
{/* Animated background elements */}
-
-
+
+
{/* Header */}
-
+
router.push("/")} > -
- +
+
Perspective
-
- -
+
{/* Main Content */} -
+
- {/* Hero Section */} -
- - + + {/* Hero Section - Compact */} +
+ + AI-Powered Analysis -

+

Analyze Any Article

-

- Paste the URL of any online article and get AI-powered bias - detection, fact-checking, and alternative perspectives in seconds. +

+ Paste the URL of any online article and get AI-powered bias detection, fact-checking, and alternative perspectives in seconds.

- {/* URL Input Section */} - - - - Enter Article URL - - - Provide the link to the article you want to analyze for bias and - alternative perspectives - - - -
-
- - - {url && ( -
- {isValidUrl ? ( - - ) : ( -
-
-
- )} -
- )} + {/* URL Input Section - Elevated & Less Boxy */} +
+ +
+
+

+ Enter Article URL +

+

+ Provide the link to the article you want to analyze +

- + +
+
+ + + {url && ( +
+ {isValidUrl ? ( + + ) : ( +
+
+
+ )} +
+ )} +
+ + +
+ + {url && !isValidUrl && ( +

+ + Please enter a valid URL +

+ )}
- {url && !isValidUrl && ( -

- Please enter a valid URL -

- )} - -
+ +
- {/* Features Grid */} -
+ {/* Features Grid - Softer Design */} +
{features.map((feature, index) => ( - - -
- +
+
+
- +

{feature.title} - - - - +

+

{feature.description} - - - +

+
+
))}
- {/* Example URLs */} - - - - Try These Example Articles - - - -
- {[ - "https://www.bbc.com/news/technology", - "https://www.reuters.com/business/", - "https://www.theguardian.com/world", - ].map((exampleUrl, index) => ( - - ))} -
-
-
+ {/* Example URLs - Streamlined */} +
+

+ Try Example Articles +

+

+ Click any URL to test the analyzer +

+ +
+ {[ + "https://www.bbc.com/news/technology", + "https://www.reuters.com/business/", + "https://www.theguardian.com/world", + ].map((exampleUrl, index) => ( + + ))} +
+
diff --git a/frontend/app/analyze/results/page.tsx b/frontend/app/analyze/results/page.tsx index bd484492..5cce83d7 100644 --- a/frontend/app/analyze/results/page.tsx +++ b/frontend/app/analyze/results/page.tsx @@ -1,270 +1,459 @@ "use client"; -import type React from "react"; -import { useState, useEffect, useRef } from "react"; +import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import Link from "next/link"; -import { Send, Link as LinkIcon } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; import { Input } from "@/components/ui/input"; -import { Badge } from "@/components/ui/badge"; -import BiasMeter from "@/components/bias-meter"; -import axios from "axios"; - -// const backend_url = process.env.NEXT_PUBLIC_API_URL; +import { + Globe, + ArrowRight, + CheckCircle, + AlertTriangle, + XCircle, + Link, + Send, + Sparkles, + AlertCircle, + MessageCircle, + X, +} from "lucide-react"; +import ThemeToggle from "@/components/theme-toggle"; -/** - * Renders the article analysis page with summary, perspectives, fact checks, bias meter, AI chat, and sources. - */ -export default function AnalyzePage() { +export default function ResultsPage() { + const [activeTab, setActiveTab] = useState("article"); const [analysisData, setAnalysisData] = useState(null); const [biasScore, setBiasScore] = useState(null); + const [newUrl, setNewUrl] = useState(""); + const [isValidUrl, setIsValidUrl] = useState(false); + const [chatMessages, setChatMessages] = useState>([]); + const [chatInput, setChatInput] = useState(""); + const [hasError, setHasError] = useState(false); + const [isChatCollapsed, setIsChatCollapsed] = useState(false); const router = useRouter(); - const isRedirecting = useRef(false); - const [activeTab, setActiveTab] = useState("summary"); - const [message, setMessage] = useState(""); - const [isLoading, setIsLoading] = useState(true); - const [messages, setMessages] = useState<{ role: string; content: string }[]>( - [ - { - role: "system", - content: - "Welcome to the Perspective chat. You can ask me questions about this article or request more information about specific claims.", - }, - ] - ); useEffect(() => { - const storedBiasScore = sessionStorage.getItem("BiasScore"); - const storedData = sessionStorage.getItem("analysisResult"); - if (storedBiasScore && storedData) { - setIsLoading(false); - } + const storedAnalysis = sessionStorage.getItem("analysisResult"); + const storedBias = sessionStorage.getItem("BiasScore"); - if (storedBiasScore) setBiasScore(JSON.parse(storedBiasScore).bias_score); - else console.warn("No bias score found."); + if (storedAnalysis) { + const parsed = JSON.parse(storedAnalysis); + setAnalysisData(parsed); + + if ((parsed.status === "stopped_due_to_error" || parsed.status === "error") && !parsed.cleaned_text) { + setHasError(true); + } + } + if (storedBias) { + setBiasScore(JSON.parse(storedBias)); + } - if (storedData) setAnalysisData(JSON.parse(storedData)); - else console.warn("No analysis result found"); + setChatMessages([ + { + role: "assistant", + content: "Welcome to the Perspective chat. You can ask me questions about this article or request more information about specific claims.", + }, + ]); }, []); - useEffect(() => { - if (isRedirecting.current) { - return; + const validateUrl = (inputUrl: string) => { + try { + new URL(inputUrl); + setIsValidUrl(true); + } catch { + setIsValidUrl(false); } + }; - const storedData = sessionStorage.getItem("analysisResult"); - const storedBiasScore = sessionStorage.getItem("BiasScore"); - - if (storedBiasScore && storedData) { - // inside here TS knows storedBiasScore and storedData are strings - setBiasScore(JSON.parse(storedBiasScore).bias_score); - setAnalysisData(JSON.parse(storedData)); - setIsLoading(false); + const handleUrlChange = (e: React.ChangeEvent) => { + const inputUrl = e.target.value; + setNewUrl(inputUrl); + if (inputUrl.length > 0) { + validateUrl(inputUrl); } else { - console.warn("No bias or data found. Redirecting..."); - if (!isRedirecting.current) { - isRedirecting.current = true; - router.push("/analyze"); // 🔹 You can also add a toast here - } + setIsValidUrl(false); } - }, [router]); + }; - async function handleSendMessage(e: React.FormEvent) { - e.preventDefault(); - if (!message.trim()) return; - const newMessages = [...messages, { role: "user", content: message }]; - setMessages(newMessages); - setMessage(""); + const handleAnalyzeNew = () => { + if (isValidUrl && newUrl) { + sessionStorage.setItem("articleUrl", newUrl); + router.push("/analyze/loading"); + } + }; - const res = await axios.post("https://thunder1245-perspective-backend.hf.space/api/chat", { - message: message, - }); - const data = res.data; + const handleSendMessage = () => { + if (chatInput.trim()) { + setChatMessages([...chatMessages, { role: "user", content: chatInput }]); + setTimeout(() => { + setChatMessages((prev) => [ + ...prev, + { + role: "assistant", + content: "This is a simulated response. In production, this would connect to your AI backend.", + }, + ]); + }, 1000); + setChatInput(""); + } + }; + + const getBiasLevel = (score: number) => { + if (score < 30) return { label: "Low Bias", color: "text-emerald-500", bgColor: "bg-emerald-500" }; + if (score < 70) return { label: "Moderate Bias", color: "text-yellow-500", bgColor: "bg-yellow-500" }; + return { label: "High Bias", color: "text-red-500", bgColor: "bg-red-500" }; + }; - console.log(data); + const biasLevel = biasScore?.bias_score !== undefined ? getBiasLevel(biasScore.bias_score) : null; - // 🔹 Step 2: Append LLM’s response - setMessages([...newMessages, { role: "assistant", content: data.answer }]); - } - if (isLoading) { - return ( -
-
Analyzing content...
+ return ( +
+ {/* Animated background */} +
+
+
- ); - } + {/* Fixed Header */} +
+
+
router.push("/")} + > +
+ +
+ + Perspective + +
+ +
+
+ + {/* Main Content Area - Flex container */} +
+
+ + {/* Left Column - Results (constrained max-width) */} +
+ + {/* Scrollable Results Area with fade effect */} +
+
+ {/* Error Message */} + {hasError && ( +
+
+ +
+

+ Analysis Failed +

+

+ We couldn't process this article. This could be due to: +

+
    +
  • The URL may be inaccessible or behind a paywall
  • +
  • The content couldn't be extracted properly
  • +
  • The article format isn't supported
  • +
  • Backend service timeout or error
  • +
+ +
+
+
+ )} - const { - cleaned_text, - facts = [], - sentiment, - perspective, - score, - } = analysisData; + {/* Title, Sentiment & Bias Score */} + {!hasError && ( +
+
+
+ + + Analysis Complete + +

+ Analysis Results +

+ + {/* Sentiment */} +
+

+ Sentiment: +

+

+ {analysisData?.sentiment || "Analyzing..."} +

+
+
- + {/* Compact Bias Score */} + {biasLevel && biasScore?.bias_score !== undefined && ( +
+

+ Bias Score +

+
+ + + + +
+ + {biasScore.bias_score} + +
+
+

+ {biasScore.bias_score}/100 +

+

+ {biasLevel.label} +

+
+ )} +
- + {/* Bias Score Label */} +
+

+ Bias Score: +

+
+
+ )} - return ( -
- {/* Header omitted for brevity */} -
-
-

Analysis Results

- - Sentiment: {sentiment} - -
-
- -

Bias Score: {biasScore}

-
+ {/* Content Tabs */} + {!hasError && ( +
+ {/* Tabs */} +
+ {["article", "perspective", "factcheck"].map((tab) => ( + + ))} +
-
-
- - - Article - Perspective - Fact Check - + {/* Tab Content */} +
+ {activeTab === "article" && ( +
+ {analysisData?.cleaned_text ? ( +

+ {analysisData.cleaned_text} +

+ ) : ( +

+ No article content available +

+ )} +
+ )} - -
- {cleaned_text - .split("\n\n") - .map((para: string, idx: number) => ( -

{para}

- ))} -
-
+ {activeTab === "perspective" && ( +
+
+

+ Alternative Perspective +

+ {analysisData?.alternative_perspective ? ( +

+ {analysisData.alternative_perspective} +

+ ) : ( +

+ Alternative perspectives will appear here once analysis is complete. +

+ )} +
+
+ )} - - {perspective ? ( -
-

- Counter-Perspective -

-

"{perspective.perspective}"

-

Reasoning:

-

{perspective.reasoning}

-
- ) : ( -
- No counter-perspective was generated for this content. + {activeTab === "factcheck" && ( +
+
+

+ + Fact Check Results +

+ {analysisData?.fact_check ? ( +

+ {analysisData.fact_check} +

+ ) : ( +

+ Fact-checking results will appear here once analysis is complete. +

+ )} +
+
+ )} +
)} - +
+ + {/* Bottom fade effect */} +
+
- -
- {facts.length > 0 ? ( - facts.map((fact: any, idx: number) => ( - - -
- {fact.original_claim} - - {fact.verdict} - -
-
- -

{fact.explanation}

- - Source - -
-
- )) - ) : ( -
- No specific claims were identified for fact-checking in - this content. + {/* Fixed Analyze Another Article Box */} +
+

+ Analyze Another Article +

+
+
+ + + {newUrl && ( +
+ {isValidUrl ? ( + + ) : ( +
+
+
+ )}
)}
- - + +
+
-
- - - AI Discussion - + {/* Right Column - Collapsible AI Discussion */} +
+
+ {/* Collapse Button */} + + + {/* Chat Header */} +
+

+ + AI Discussion +

+

Ask questions about this article - - - -

- {messages.map((msg, i) => ( +

+
+ + {/* Chat Messages - Scrollable */} +
+ {chatMessages.map((msg, idx) => ( +
-
- {msg.content} -
+ {msg.content}
- ))} -
-
+
+ ))} +
+ + {/* Chat Input - Fixed at bottom */} +
+
setMessage(e.target.value)} + value={chatInput} + onChange={(e) => setChatInput(e.target.value)} + onKeyPress={(e) => e.key === "Enter" && handleSendMessage()} + className="flex-1 h-9 text-xs border border-slate-300 dark:border-slate-600 focus-visible:border-slate-400 dark:focus-visible:border-slate-500 focus-visible:ring-0 focus-visible:ring-offset-0" + disabled={hasError} /> - - - - +
+
+
- {/* Footer omitted */} + + {/* Floating Chat Button (when collapsed) */} + {isChatCollapsed && ( + + )}
); } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9f3e2323..1792c68f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,7 +45,7 @@ "embla-carousel-react": "8.5.1", "input-otp": "1.4.1", "lucide-react": "^0.454.0", - "next": "15.2.4", + "next": "^16.1.1", "next-themes": "^0.4.6", "react": "^19", "react-day-picker": "8.10.1", @@ -91,9 +91,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "license": "MIT", "optional": true, "dependencies": { @@ -147,10 +147,20 @@ "react-hook-form": "^7.0.0" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], @@ -166,13 +176,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], @@ -188,13 +198,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], @@ -208,9 +218,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], @@ -224,9 +234,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], @@ -240,9 +250,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], @@ -255,10 +265,42 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], @@ -272,9 +314,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], @@ -288,9 +330,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], @@ -304,9 +346,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], @@ -320,9 +362,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], @@ -338,13 +380,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" + "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], @@ -360,13 +402,57 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], @@ -382,13 +468,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], @@ -404,13 +490,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" + "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], @@ -426,13 +512,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], @@ -448,21 +534,40 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.2.0" + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -471,9 +576,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], @@ -490,9 +595,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -580,15 +685,15 @@ } }, "node_modules/@next/env": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", - "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz", + "integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", - "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz", + "integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==", "cpu": [ "arm64" ], @@ -602,9 +707,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", - "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz", + "integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==", "cpu": [ "x64" ], @@ -618,9 +723,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", - "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz", + "integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==", "cpu": [ "arm64" ], @@ -634,9 +739,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", - "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz", + "integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==", "cpu": [ "arm64" ], @@ -650,9 +755,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", - "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz", + "integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==", "cpu": [ "x64" ], @@ -666,9 +771,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", - "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz", + "integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==", "cpu": [ "x64" ], @@ -682,9 +787,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", - "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz", + "integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==", "cpu": [ "arm64" ], @@ -698,9 +803,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", - "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz", + "integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==", "cpu": [ "x64" ], @@ -2078,12 +2183,6 @@ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", "license": "MIT" }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2296,13 +2395,13 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -2313,6 +2412,15 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2327,9 +2435,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2381,17 +2489,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2516,25 +2613,11 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2547,20 +2630,9 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, + "dev": true, "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2764,9 +2836,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, "engines": { @@ -3025,9 +3097,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -3124,9 +3196,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -3227,13 +3299,6 @@ "node": ">=12" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3510,15 +3575,14 @@ } }, "node_modules/next": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", - "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz", + "integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==", "license": "MIT", "dependencies": { - "@next/env": "15.2.4", - "@swc/counter": "0.1.3", + "@next/env": "16.1.1", "@swc/helpers": "0.5.15", - "busboy": "1.6.0", + "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -3527,22 +3591,22 @@ "next": "dist/bin/next" }, "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.2.4", - "@next/swc-darwin-x64": "15.2.4", - "@next/swc-linux-arm64-gnu": "15.2.4", - "@next/swc-linux-arm64-musl": "15.2.4", - "@next/swc-linux-x64-gnu": "15.2.4", - "@next/swc-linux-x64-musl": "15.2.4", - "@next/swc-win32-arm64-msvc": "15.2.4", - "@next/swc-win32-x64-msvc": "15.2.4", - "sharp": "^0.33.5" + "@next/swc-darwin-arm64": "16.1.1", + "@next/swc-darwin-x64": "16.1.1", + "@next/swc-linux-arm64-gnu": "16.1.1", + "@next/swc-linux-arm64-musl": "16.1.1", + "@next/swc-linux-x64-gnu": "16.1.1", + "@next/swc-linux-x64-musl": "16.1.1", + "@next/swc-win32-arm64-msvc": "16.1.1", + "@next/swc-win32-x64-msvc": "16.1.1", + "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", + "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", @@ -4203,9 +4267,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "optional": true, "bin": { @@ -4216,16 +4280,16 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -4234,25 +4298,30 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/shebang-command": { @@ -4291,16 +4360,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/sonner": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", @@ -4320,14 +4379,6 @@ "node": ">=0.10.0" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 91ac71ed..25833964 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,7 +46,7 @@ "embla-carousel-react": "8.5.1", "input-otp": "1.4.1", "lucide-react": "^0.454.0", - "next": "15.2.4", + "next": "^16.1.1", "next-themes": "^0.4.6", "react": "^19", "react-day-picker": "8.10.1", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 4b2dc7ba..48d6d820 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "target": "ES6", "skipLibCheck": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -19,9 +23,19 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": [ + "./*" + ] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } From 043d9d9421da738f33044f777aadc38a62225a29 Mon Sep 17 00:00:00 2001 From: Sujatx Date: Sat, 3 Jan 2026 22:14:20 +0530 Subject: [PATCH 2/3] fix: security hardening and code quality improvements - Add URL protocol whitelist to prevent XSS (http/https only) - Implement proper JSON.parse error handling with try/catch - Fix memory leaks from setTimeout/setInterval in loading page - Add useEffect cleanup for intervals and mounted state tracking - Replace console.log with conditional debug logger - Extract backend URL to NEXT_PUBLIC_BACKEND_URL env variable - Add configurable API timeout (60s default) - Guard async operations with isMounted check --- frontend/app/analyze/loading/page.tsx | 91 ++++++++++++++++++++------- frontend/app/analyze/page.tsx | 15 ++++- frontend/app/analyze/results/page.tsx | 76 +++++++++++++++++----- 3 files changed, 142 insertions(+), 40 deletions(-) diff --git a/frontend/app/analyze/loading/page.tsx b/frontend/app/analyze/loading/page.tsx index 932723da..4b64c058 100644 --- a/frontend/app/analyze/loading/page.tsx +++ b/frontend/app/analyze/loading/page.tsx @@ -15,6 +15,24 @@ import { import ThemeToggle from "@/components/theme-toggle"; import axios from "axios"; +// Backend URL from environment variable with fallback +const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "https://thunder1245-perspective-backend.hf.space"; + +// API timeout in milliseconds (60 seconds) +const API_TIMEOUT = parseInt(process.env.NEXT_PUBLIC_API_TIMEOUT || "60000", 10); + +// Simple logger utility that respects NODE_ENV +const logger = { + debug: (...args: any[]) => { + if (process.env.NODE_ENV === 'development') { + console.log('[DEBUG]', ...args); + } + }, + error: (...args: any[]) => { + console.error('[ERROR]', ...args); + } +}; + export default function LoadingPage() { const [currentStep, setCurrentStep] = useState(0); const [progress, setProgress] = useState(0); @@ -50,6 +68,10 @@ export default function LoadingPage() { ]; useEffect(() => { + let stepInterval: NodeJS.Timeout | null = null; + let progressInterval: NodeJS.Timeout | null = null; + let isMounted = true; + const runAnalysis = async () => { const storedUrl = sessionStorage.getItem("articleUrl"); if (storedUrl) { @@ -57,46 +79,69 @@ export default function LoadingPage() { try { const [processRes, biasRes] = await Promise.all([ - axios.post("https://thunder1245-perspective-backend.hf.space/api/process", { - url: storedUrl, - }), - axios.post("https://thunder1245-perspective-backend.hf.space/api/bias", { - url: storedUrl, - }), + axios.post( + `${BACKEND_URL}/api/process`, + { url: storedUrl }, + { timeout: API_TIMEOUT } + ), + axios.post( + `${BACKEND_URL}/api/bias`, + { url: storedUrl }, + { timeout: API_TIMEOUT } + ), ]); sessionStorage.setItem("BiasScore", JSON.stringify(biasRes.data)); - console.log("Bias score saved"); - console.log(biasRes); + logger.debug("Bias score saved", biasRes.data); sessionStorage.setItem( "analysisResult", JSON.stringify(processRes.data) ); - console.log("Analysis result saved"); - console.log(processRes); + logger.debug("Analysis result saved", processRes.data); } catch (err) { - console.error("Failed to process article:", err); - router.push("/analyze"); + // Enhanced error logging with timeout detection + if (axios.isAxiosError(err)) { + if (err.code === 'ECONNABORTED') { + logger.error("Request timeout - backend took too long to respond:", err); + } else if (err.response) { + logger.error("Backend error response:", err.response.status, err.response.data); + } else if (err.request) { + logger.error("No response received from backend:", err.message); + } else { + logger.error("Request setup error:", err.message); + } + } else { + logger.error("Failed to process article:", err); + } + + if (isMounted) { + router.push("/analyze"); + } return; } - const stepInterval = setInterval(() => { + // Don't start intervals if component already unmounted + if (!isMounted) return; + + stepInterval = setInterval(() => { setCurrentStep((prev) => { if (prev < steps.length - 1) { return prev + 1; } else { - clearInterval(stepInterval); + if (stepInterval) clearInterval(stepInterval); setTimeout(() => { - router.push("/analyze/results"); + if (isMounted) { + router.push("/analyze/results"); + } }, 2000); return prev; } }); }, 2000); - const progressInterval = setInterval(() => { + progressInterval = setInterval(() => { setProgress((prev) => { if (prev < 100) { return prev + 1; @@ -104,18 +149,20 @@ export default function LoadingPage() { return prev; }); }, 100); - - return () => { - clearInterval(stepInterval); - clearInterval(progressInterval); - }; } else { router.push("/analyze"); } }; runAnalysis(); - }, [router]); + + // Cleanup function returned directly from useEffect + return () => { + isMounted = false; + if (stepInterval) clearInterval(stepInterval); + if (progressInterval) clearInterval(progressInterval); + }; + }, [router, steps.length]); return (
diff --git a/frontend/app/analyze/page.tsx b/frontend/app/analyze/page.tsx index b9a6dcd4..ea57a801 100644 --- a/frontend/app/analyze/page.tsx +++ b/frontend/app/analyze/page.tsx @@ -31,8 +31,17 @@ export default function AnalyzePage() { const validateUrl = (inputUrl: string) => { try { - new URL(inputUrl); - setIsValidUrl(true); + const parsedUrl = new URL(inputUrl); + + // Whitelist only safe protocols to prevent XSS attacks + // Reject javascript:, data:, file:, and other dangerous protocols + const safeProtocols = ['http:', 'https:']; + + if (safeProtocols.includes(parsedUrl.protocol)) { + setIsValidUrl(true); + } else { + setIsValidUrl(false); + } } catch { setIsValidUrl(false); } @@ -168,7 +177,7 @@ export default function AnalyzePage() { {url && !isValidUrl && (

- Please enter a valid URL + Please enter a valid HTTP or HTTPS URL

)}
diff --git a/frontend/app/analyze/results/page.tsx b/frontend/app/analyze/results/page.tsx index 5cce83d7..0b743979 100644 --- a/frontend/app/analyze/results/page.tsx +++ b/frontend/app/analyze/results/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { useRouter } from "next/navigation"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -31,30 +31,66 @@ export default function ResultsPage() { const [hasError, setHasError] = useState(false); const [isChatCollapsed, setIsChatCollapsed] = useState(false); const router = useRouter(); + + // Track if component is mounted to prevent state updates after unmount + const isMounted = useRef(true); useEffect(() => { const storedAnalysis = sessionStorage.getItem("analysisResult"); const storedBias = sessionStorage.getItem("BiasScore"); + let parsedAnalysis = null; + let parsedBias = null; + + // Try to parse analysis data if (storedAnalysis) { - const parsed = JSON.parse(storedAnalysis); - setAnalysisData(parsed); - - if ((parsed.status === "stopped_due_to_error" || parsed.status === "error") && !parsed.cleaned_text) { + try { + parsedAnalysis = JSON.parse(storedAnalysis); + setAnalysisData(parsedAnalysis); + + // Check if analysis failed + if ((parsedAnalysis.status === "stopped_due_to_error" || parsedAnalysis.status === "error") && !parsedAnalysis.cleaned_text) { + setHasError(true); + } + } catch (error) { + console.error("Failed to parse analysis data:", error); + sessionStorage.removeItem("analysisResult"); setHasError(true); } } + + // Try to parse bias score data if (storedBias) { - setBiasScore(JSON.parse(storedBias)); + try { + parsedBias = JSON.parse(storedBias); + setBiasScore(parsedBias); + } catch (error) { + console.error("Failed to parse bias score:", error); + sessionStorage.removeItem("BiasScore"); + // Bias is optional, don't set error flag + } + } + + // Redirect if no valid analysis data + if (!storedAnalysis || !parsedAnalysis) { + console.warn("No valid analysis data found, redirecting to analyze page"); + router.push("/analyze"); + return; } + // Initialize chat messages setChatMessages([ { role: "assistant", content: "Welcome to the Perspective chat. You can ask me questions about this article or request more information about specific claims.", }, ]); - }, []); + + // Cleanup function to mark component as unmounted + return () => { + isMounted.current = false; + }; + }, [router]); const validateUrl = (inputUrl: string) => { try { @@ -85,16 +121,25 @@ export default function ResultsPage() { const handleSendMessage = () => { if (chatInput.trim()) { setChatMessages([...chatMessages, { role: "user", content: chatInput }]); - setTimeout(() => { - setChatMessages((prev) => [ - ...prev, - { - role: "assistant", - content: "This is a simulated response. In production, this would connect to your AI backend.", - }, - ]); + + // Store timeout ID for potential cleanup + const timeoutId = setTimeout(() => { + // Check if component is still mounted before updating state + if (isMounted.current) { + setChatMessages((prev) => [ + ...prev, + { + role: "assistant", + content: "This is a simulated response. In production, this would connect to your AI backend.", + }, + ]); + } }, 1000); + setChatInput(""); + + // Note: If you need to clear this specific timeout on unmount, + // you'd need to store it in a ref and clear it in useEffect cleanup } }; @@ -161,6 +206,7 @@ export default function ResultsPage() {
  • The content couldn't be extracted properly
  • The article format isn't supported
  • Backend service timeout or error
  • +
  • Corrupted or invalid session data