Final working front
This commit is contained in:
@@ -65,6 +65,7 @@ export type DetectionData = {
|
|||||||
export default function DetectionPage() {
|
export default function DetectionPage() {
|
||||||
const [detectionData, setDetectionData] = useState<DetectionData | null>(null)
|
const [detectionData, setDetectionData] = useState<DetectionData | null>(null)
|
||||||
const [videoId, setVideoId] = useState<string | null>(null)
|
const [videoId, setVideoId] = useState<string | null>(null)
|
||||||
|
const [videoFile, setVideoFile] = useState<File | null>(null)
|
||||||
const [detectionType, setDetectionType] = useState<DetectionType>("pothole-detection")
|
const [detectionType, setDetectionType] = useState<DetectionType>("pothole-detection")
|
||||||
|
|
||||||
const getTitle = () => {
|
const getTitle = () => {
|
||||||
@@ -93,20 +94,22 @@ export default function DetectionPage() {
|
|||||||
{/* Upload Section */}
|
{/* Upload Section */}
|
||||||
<div className="animate-in fade-in slide-in-from-bottom duration-700 delay-100">
|
<div className="animate-in fade-in slide-in-from-bottom duration-700 delay-100">
|
||||||
<UploadSection
|
<UploadSection
|
||||||
onDetectionComplete={(data, vId) => {
|
onDetectionComplete={(data, vId, file) => {
|
||||||
setDetectionData(data)
|
setDetectionData(data)
|
||||||
setVideoId(vId)
|
setVideoId(vId)
|
||||||
|
setVideoFile(file)
|
||||||
}}
|
}}
|
||||||
onDetectionTypeChange={setDetectionType}
|
onDetectionTypeChange={setDetectionType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Video Player Section */}
|
{/* Video Player Section */}
|
||||||
{detectionData && videoId && (
|
{detectionData && videoId && videoFile && (
|
||||||
<div className="mt-6 animate-in fade-in slide-in-from-bottom duration-700 delay-200">
|
<div className="mt-6 animate-in fade-in slide-in-from-bottom duration-700 delay-200">
|
||||||
<VideoPlayerSection
|
<VideoPlayerSection
|
||||||
data={detectionData}
|
data={detectionData}
|
||||||
videoId={videoId}
|
videoId={videoId}
|
||||||
|
videoFile={videoFile}
|
||||||
detectionType={detectionType}
|
detectionType={detectionType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import type { DetectionData, DetectionType } from "@/app/page"
|
|||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1"
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1"
|
||||||
const WS_URL = process.env.NEXT_PUBLIC_WS_URL || "ws://127.0.0.1:8000/api/v1"
|
const WS_URL = process.env.NEXT_PUBLIC_WS_URL || "ws://127.0.0.1:8000/api/v1"
|
||||||
|
|
||||||
|
|
||||||
type UploadSectionProps = {
|
type UploadSectionProps = {
|
||||||
onDetectionComplete: (data: DetectionData, videoId: string) => void
|
onDetectionComplete: (data: DetectionData, videoId: string, file: File) => void
|
||||||
onDetectionTypeChange: (type: DetectionType) => void
|
onDetectionTypeChange: (type: DetectionType) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +111,10 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
|
|||||||
setStatusMessage("✓ Complete!")
|
setStatusMessage("✓ Complete!")
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
// Pass video_id instead of file
|
// Pass detection data, video_id, and the original file
|
||||||
onDetectionComplete(detectionData, videoId)
|
if (file) {
|
||||||
|
onDetectionComplete(detectionData, videoId, file)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Upload] Failed to load results:", error)
|
console.error("[Upload] Failed to load results:", error)
|
||||||
setError("Failed to load results. Please try again.")
|
setError("Failed to load results. Please try again.")
|
||||||
@@ -139,6 +142,10 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
|
|||||||
try {
|
try {
|
||||||
console.log("[Upload] Uploading to:", `${API_URL}/upload`)
|
console.log("[Upload] Uploading to:", `${API_URL}/upload`)
|
||||||
console.log("[Upload] Detection type:", detectionType)
|
console.log("[Upload] Detection type:", detectionType)
|
||||||
|
console.log("[Upload] FormData contents:")
|
||||||
|
console.log(" - file:", file.name)
|
||||||
|
console.log(" - detection_type:", detectionType)
|
||||||
|
console.log(" - speed_kmh:", speed)
|
||||||
|
|
||||||
const response = await fetch(`${API_URL}/upload`, {
|
const response = await fetch(`${API_URL}/upload`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -159,6 +166,7 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
|
|||||||
const videoId = result.video_id
|
const videoId = result.video_id
|
||||||
|
|
||||||
console.log("[Upload] Video uploaded successfully, ID:", videoId)
|
console.log("[Upload] Video uploaded successfully, ID:", videoId)
|
||||||
|
console.log("[Upload] Response:", result)
|
||||||
setStatusMessage("Uploaded! Starting processing...")
|
setStatusMessage("Uploaded! Starting processing...")
|
||||||
setProgress(10)
|
setProgress(10)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1
|
|||||||
type VideoPlayerSectionProps = {
|
type VideoPlayerSectionProps = {
|
||||||
data: DetectionData
|
data: DetectionData
|
||||||
videoId: string
|
videoId: string
|
||||||
|
videoFile: File
|
||||||
detectionType: DetectionType
|
detectionType: DetectionType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +110,10 @@ function SummarySection({ data, show, detectionType }: { data: DetectionData; sh
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function VideoPlayerSection({ data, videoId, detectionType }: VideoPlayerSectionProps) {
|
export default function VideoPlayerSection({ data, videoId, videoFile, detectionType }: VideoPlayerSectionProps) {
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const [currentFrame, setCurrentFrame] = useState(0)
|
const [currentFrame, setCurrentFrame] = useState(0)
|
||||||
const [detectionsCount, setDetectionsCount] = useState(0)
|
const [detectionsCount, setDetectionsCount] = useState(0)
|
||||||
const [logs, setLogs] = useState<DetectionLog[]>([])
|
const [logs, setLogs] = useState<DetectionLog[]>([])
|
||||||
@@ -147,23 +150,131 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
frameDetectionMap.current = map
|
frameDetectionMap.current = map
|
||||||
}, [data, isPothole])
|
}, [data, isPothole])
|
||||||
|
|
||||||
// Load processed video from backend
|
// Function to draw bounding boxes on canvas
|
||||||
|
const drawBoundingBoxes = useCallback((detections: any[]) => {
|
||||||
|
const canvas = canvasRef.current
|
||||||
|
const video = videoRef.current
|
||||||
|
|
||||||
|
if (!canvas || !video) return
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
if (!ctx) return
|
||||||
|
|
||||||
|
// Clear canvas
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
|
if (!detections || detections.length === 0) return
|
||||||
|
|
||||||
|
// Calculate scale between video natural size and displayed size
|
||||||
|
const scaleX = canvas.width / data.video_info.width
|
||||||
|
const scaleY = canvas.height / data.video_info.height
|
||||||
|
|
||||||
|
detections.forEach((detection) => {
|
||||||
|
const bbox = detection.bbox
|
||||||
|
if (!bbox) return
|
||||||
|
|
||||||
|
// Scale coordinates to match displayed video size
|
||||||
|
const x1 = bbox.x1 * scaleX
|
||||||
|
const y1 = bbox.y1 * scaleY
|
||||||
|
const x2 = bbox.x2 * scaleX
|
||||||
|
const y2 = bbox.y2 * scaleY
|
||||||
|
const width = x2 - x1
|
||||||
|
const height = y2 - y1
|
||||||
|
|
||||||
|
// Set colors based on detection type
|
||||||
|
const boxColor = isPothole ? '#ef4444' : '#3b82f6' // red for potholes, blue for signboards
|
||||||
|
const textBgColor = isPothole ? 'rgba(239, 68, 68, 0.9)' : 'rgba(59, 130, 246, 0.9)'
|
||||||
|
|
||||||
|
// Draw bounding box
|
||||||
|
ctx.strokeStyle = boxColor
|
||||||
|
ctx.lineWidth = 3
|
||||||
|
ctx.strokeRect(x1, y1, width, height)
|
||||||
|
|
||||||
|
// Draw semi-transparent fill
|
||||||
|
ctx.fillStyle = isPothole ? 'rgba(239, 68, 68, 0.15)' : 'rgba(59, 130, 246, 0.15)'
|
||||||
|
ctx.fillRect(x1, y1, width, height)
|
||||||
|
|
||||||
|
// Prepare label text
|
||||||
|
const id = isPothole ? detection.pothole_id : detection.signboard_id
|
||||||
|
const confidence = (detection.confidence * 100).toFixed(1)
|
||||||
|
let labelText = isPothole
|
||||||
|
? `Pothole #${id}`
|
||||||
|
: `${detection.type || 'Sign'} #${id}`
|
||||||
|
labelText += ` ${confidence}%`
|
||||||
|
|
||||||
|
// Draw label background
|
||||||
|
ctx.font = 'bold 14px system-ui'
|
||||||
|
const textMetrics = ctx.measureText(labelText)
|
||||||
|
const textWidth = textMetrics.width + 16
|
||||||
|
const textHeight = 26
|
||||||
|
|
||||||
|
ctx.fillStyle = textBgColor
|
||||||
|
ctx.fillRect(x1, y1 - textHeight - 2, textWidth, textHeight)
|
||||||
|
|
||||||
|
// Draw label text
|
||||||
|
ctx.fillStyle = '#ffffff'
|
||||||
|
ctx.fillText(labelText, x1 + 8, y1 - 8)
|
||||||
|
})
|
||||||
|
}, [data.video_info.width, data.video_info.height, isPothole])
|
||||||
|
|
||||||
|
// Resize canvas to match video display size
|
||||||
|
const resizeCanvas = useCallback(() => {
|
||||||
|
const video = videoRef.current
|
||||||
|
const canvas = canvasRef.current
|
||||||
|
const container = containerRef.current
|
||||||
|
|
||||||
|
if (!video || !canvas || !container) return
|
||||||
|
|
||||||
|
// Get the displayed size of the video
|
||||||
|
const rect = video.getBoundingClientRect()
|
||||||
|
canvas.width = rect.width
|
||||||
|
canvas.height = rect.height
|
||||||
|
|
||||||
|
// Redraw current frame's detections
|
||||||
|
const frame = Math.round(video.currentTime * data.video_info.fps)
|
||||||
|
const detections = frameDetectionMap.current.get(frame)
|
||||||
|
if (detections) {
|
||||||
|
drawBoundingBoxes(detections)
|
||||||
|
}
|
||||||
|
}, [data.video_info.fps, drawBoundingBoxes])
|
||||||
|
|
||||||
|
// Handle window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoRef.current && videoId) {
|
window.addEventListener('resize', resizeCanvas)
|
||||||
const videoUrl = `${API_URL}/video/${videoId}`
|
return () => window.removeEventListener('resize', resizeCanvas)
|
||||||
console.log(`[VideoPlayer] Loading processed video from: ${videoUrl}`)
|
}, [resizeCanvas])
|
||||||
|
|
||||||
|
// Initialize canvas size when video loads
|
||||||
|
useEffect(() => {
|
||||||
|
const video = videoRef.current
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
const handleLoadedMetadata = () => {
|
||||||
|
resizeCanvas()
|
||||||
|
}
|
||||||
|
|
||||||
|
video.addEventListener('loadedmetadata', handleLoadedMetadata)
|
||||||
|
return () => video.removeEventListener('loadedmetadata', handleLoadedMetadata)
|
||||||
|
}, [resizeCanvas])
|
||||||
|
|
||||||
|
// Load video from uploaded file
|
||||||
|
useEffect(() => {
|
||||||
|
if (videoRef.current && videoFile) {
|
||||||
|
const videoUrl = URL.createObjectURL(videoFile)
|
||||||
|
console.log(`[VideoPlayer] Loading video from uploaded file`)
|
||||||
|
|
||||||
videoRef.current.src = videoUrl
|
videoRef.current.src = videoUrl
|
||||||
|
|
||||||
// Handle video load errors
|
// Handle video load errors
|
||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
console.error("[VideoPlayer] Failed to load processed video")
|
console.error("[VideoPlayer] Failed to load video")
|
||||||
setVideoError("Failed to load processed video. Please try refreshing the page.")
|
setVideoError("Failed to load video. Please try refreshing the page.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLoaded = () => {
|
const handleLoaded = () => {
|
||||||
console.log("[VideoPlayer] Processed video loaded successfully")
|
console.log("[VideoPlayer] Video loaded successfully")
|
||||||
setVideoError(null)
|
setVideoError(null)
|
||||||
|
resizeCanvas()
|
||||||
}
|
}
|
||||||
|
|
||||||
videoRef.current.addEventListener("error", handleError)
|
videoRef.current.addEventListener("error", handleError)
|
||||||
@@ -174,9 +285,11 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
videoRef.current.removeEventListener("error", handleError)
|
videoRef.current.removeEventListener("error", handleError)
|
||||||
videoRef.current.removeEventListener("loadeddata", handleLoaded)
|
videoRef.current.removeEventListener("loadeddata", handleLoaded)
|
||||||
}
|
}
|
||||||
|
// Revoke object URL to free memory
|
||||||
|
URL.revokeObjectURL(videoUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [videoId])
|
}, [videoFile, resizeCanvas])
|
||||||
|
|
||||||
// Add detection log with deduplication and size limit
|
// Add detection log with deduplication and size limit
|
||||||
const addDetectionLog = useCallback((frame: number, detections: any[]) => {
|
const addDetectionLog = useCallback((frame: number, detections: any[]) => {
|
||||||
@@ -215,11 +328,14 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
const detections = frameDetectionMap.current.get(frame)
|
const detections = frameDetectionMap.current.get(frame)
|
||||||
setDetectionsCount(detections?.length || 0)
|
setDetectionsCount(detections?.length || 0)
|
||||||
|
|
||||||
|
// Draw bounding boxes for current frame
|
||||||
|
drawBoundingBoxes(detections || [])
|
||||||
|
|
||||||
if (detections && detections.length > 0) {
|
if (detections && detections.length > 0) {
|
||||||
addDetectionLog(frame, detections)
|
addDetectionLog(frame, detections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data.video_info.fps, addDetectionLog])
|
}, [data.video_info.fps, addDetectionLog, drawBoundingBoxes])
|
||||||
|
|
||||||
// Video playback monitoring
|
// Video playback monitoring
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -270,6 +386,9 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
|
|
||||||
const detections = frameDetectionMap.current.get(frame)
|
const detections = frameDetectionMap.current.get(frame)
|
||||||
setDetectionsCount(detections?.length || 0)
|
setDetectionsCount(detections?.length || 0)
|
||||||
|
|
||||||
|
// Draw bounding boxes for current frame
|
||||||
|
drawBoundingBoxes(detections || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSeeked = () => {
|
const handleSeeked = () => {
|
||||||
@@ -294,7 +413,7 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
video.removeEventListener("seeked", handleSeeked)
|
video.removeEventListener("seeked", handleSeeked)
|
||||||
video.removeEventListener("play", handlePlay)
|
video.removeEventListener("play", handlePlay)
|
||||||
}
|
}
|
||||||
}, [data.video_info.fps])
|
}, [data.video_info.fps, drawBoundingBoxes])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -302,12 +421,12 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Video Playback with Detection</CardTitle>
|
<CardTitle>Video Playback with Detection</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Watch the processed video with {isPothole ? "pothole" : "signboard"} detections already drawn
|
Watch the video with real-time {isPothole ? "pothole" : "signboard"} detection overlays
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
{/* Video Player */}
|
{/* Video Player with Canvas Overlay */}
|
||||||
<div className="lg:col-span-2 space-y-4">
|
<div className="lg:col-span-2 space-y-4">
|
||||||
{videoError && (
|
{videoError && (
|
||||||
<div className="bg-destructive/10 text-destructive p-4 rounded-lg mb-4">
|
<div className="bg-destructive/10 text-destructive p-4 rounded-lg mb-4">
|
||||||
@@ -315,17 +434,27 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="relative bg-black rounded-lg overflow-hidden">
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="relative bg-black rounded-lg overflow-hidden"
|
||||||
|
style={{
|
||||||
|
aspectRatio: `${data.video_info.width} / ${data.video_info.height}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
controls
|
controls
|
||||||
className="w-full h-auto"
|
className="w-full h-full"
|
||||||
style={{
|
|
||||||
aspectRatio: `${data.video_info.width} / ${data.video_info.height}`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
{/* Canvas overlay for bounding boxes */}
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className="absolute top-0 left-0 pointer-events-none"
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Video Info */}
|
{/* Video Info */}
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -2480,7 +2480,6 @@
|
|||||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -2491,7 +2490,6 @@
|
|||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
@@ -2593,7 +2591,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -2856,8 +2853,7 @@
|
|||||||
"version": "8.5.1",
|
"version": "8.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.1.tgz",
|
||||||
"integrity": "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A==",
|
"integrity": "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/embla-carousel-react": {
|
"node_modules/embla-carousel-react": {
|
||||||
"version": "8.5.1",
|
"version": "8.5.1",
|
||||||
@@ -3304,7 +3300,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.0.10.tgz",
|
||||||
"integrity": "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==",
|
"integrity": "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.0.10",
|
"@next/env": "16.0.10",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
@@ -3430,7 +3425,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -3468,7 +3462,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -3499,7 +3492,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -3512,7 +3504,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz",
|
||||||
"integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
|
"integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -3798,8 +3789,7 @@
|
|||||||
"version": "4.1.18",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss-animate": {
|
"node_modules/tailwindcss-animate": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
|
|||||||
Reference in New Issue
Block a user