← back to Handbag Auth Nextjs

src/app/camera/page.tsx

442 lines

'use client'

import { useState, useRef } from 'react'
import Link from 'next/link'
import Image from 'next/image'

export default function CameraPage() {
  const [selectedFiles, setSelectedFiles] = useState<File[]>([])
  const [previews, setPreviews] = useState<string[]>([])
  const [analyzing, setAnalyzing] = useState(false)
  const [result, setResult] = useState<any>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const videoRef = useRef<HTMLVideoElement>(null)
  const [cameraActive, setCameraActive] = useState(false)
  const [captureCount, setCaptureCount] = useState(0)
  const [showHints, setShowHints] = useState(true)

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files || [])
    setSelectedFiles(files)

    // Create previews
    const newPreviews = files.map(file => URL.createObjectURL(file))
    setPreviews(newPreviews)
    setResult(null)
  }

  const removePhoto = (index: number) => {
    setSelectedFiles(selectedFiles.filter((_, i) => i !== index))
    setPreviews(previews.filter((_, i) => i !== index))
  }

  const startCamera = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: 'environment' }
      })
      if (videoRef.current) {
        videoRef.current.srcObject = stream
        setCameraActive(true)
      }
    } catch (error) {
      console.error('Error accessing camera:', error)
      alert('Could not access camera. Please use file upload instead.')
    }
  }

  const capturePhoto = () => {
    if (!videoRef.current) return

    const canvas = document.createElement('canvas')
    canvas.width = videoRef.current.videoWidth
    canvas.height = videoRef.current.videoHeight
    const ctx = canvas.getContext('2d')
    if (!ctx) return

    ctx.drawImage(videoRef.current, 0, 0)
    canvas.toBlob((blob) => {
      if (!blob) return
      const count = captureCount + 1
      const file = new File([blob], `handbag-photo-${count}.jpg`, { type: 'image/jpeg' })

      // Add to existing files instead of replacing
      const newFiles = [...selectedFiles, file]
      setSelectedFiles(newFiles)
      setPreviews([...previews, URL.createObjectURL(file)])
      setCaptureCount(count)

      // Auto-stop camera after 4 photos
      if (count >= 4) {
        stopCamera()
      }
    }, 'image/jpeg', 0.95)
  }

  const stopCamera = () => {
    if (videoRef.current?.srcObject) {
      const stream = videoRef.current.srcObject as MediaStream
      stream.getTracks().forEach(track => track.stop())
      setCameraActive(false)
    }
  }

  const analyzeImages = async () => {
    if (selectedFiles.length === 0) {
      alert('Please select or capture at least one image')
      return
    }

    setAnalyzing(true)
    setResult(null)

    try {
      // Convert files to base64
      const filePromises = selectedFiles.map(file => {
        return new Promise<{data: string, type: string}>((resolve) => {
          const reader = new FileReader()
          reader.onloadend = () => {
            resolve({
              data: reader.result as string,
              type: file.type
            })
          }
          reader.readAsDataURL(file)
        })
      })

      const files = await Promise.all(filePromises)

      const response = await fetch('/api/analyze/image', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ files })
      })

      const data = await response.json()

      if (data.success) {
        setResult(data.analysis)
      } else {
        alert('Analysis failed: ' + data.error)
      }
    } catch (error) {
      console.error('Analysis error:', error)
      alert('Analysis failed. Please try again.')
    } finally {
      setAnalyzing(false)
    }
  }

  return (
    <div className="min-h-screen bg-gray-50">
      {/* Header */}
      <header className="bg-white shadow-sm sticky top-0 z-50">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex justify-between items-center py-4">
            <Link href="/" className="flex items-center space-x-2">
              <div className="w-10 h-10 bg-gradient-to-br from-purple-600 to-blue-500 rounded-lg flex items-center justify-center">
                <span className="text-white font-bold text-xl">HB</span>
              </div>
              <span className="text-xl font-bold text-gray-900">Handbag Auth</span>
            </Link>
            <Link
              href="/"
              className="text-gray-700 hover:text-gray-900 font-medium"
            >
              Back to Deals
            </Link>
          </div>
        </div>
      </header>

      {/* Main Content */}
      <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
        <div className="bg-white rounded-lg shadow-lg p-6 md:p-8">
          <h1 className="text-3xl font-bold text-gray-900 mb-2">
            AI Handbag Authentication
          </h1>
          <p className="text-gray-600 mb-8">
            Upload or capture photos of your handbag for instant AI-powered authentication and price analysis
          </p>

          {/* Camera/Upload Section */}
          <div className="space-y-4 mb-8">
            {!cameraActive ? (
              <div className="space-y-4">
                <button
                  onClick={startCamera}
                  className="w-full bg-gradient-to-r from-purple-600 to-blue-500 text-white py-4 rounded-lg font-medium hover:shadow-lg transition-all flex items-center justify-center space-x-2"
                >
                  <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
                  </svg>
                  <span>Use Camera</span>
                </button>

                <div className="relative">
                  <input
                    ref={fileInputRef}
                    type="file"
                    accept="image/*,video/*"
                    multiple
                    onChange={handleFileChange}
                    className="hidden"
                  />
                  <button
                    onClick={() => fileInputRef.current?.click()}
                    className="w-full bg-white border-2 border-dashed border-gray-300 text-gray-700 py-4 rounded-lg font-medium hover:border-purple-500 hover:bg-purple-50 transition-all flex items-center justify-center space-x-2"
                  >
                    <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
                    </svg>
                    <span>Upload Photos/Videos</span>
                  </button>
                </div>
              </div>
            ) : (
              <div className="space-y-4">
                {/* Camera Hints */}
                {showHints && captureCount === 0 && (
                  <div className="bg-gradient-to-r from-purple-100 to-blue-100 rounded-lg p-4 mb-4">
                    <div className="flex items-start space-x-3">
                      <svg className="w-6 h-6 text-purple-600 flex-shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                      </svg>
                      <div className="flex-1">
                        <p className="text-sm font-medium text-gray-900 mb-2">Capture Tips for Best Results:</p>
                        <ul className="text-xs text-gray-700 space-y-1">
                          <li>• Take 3-4 photos from different angles</li>
                          <li>• Include close-ups of logos, stitching, and hardware</li>
                          <li>• Ensure good lighting (natural light works best)</li>
                          <li>• Hold camera steady for clear photos</li>
                        </ul>
                        <button
                          onClick={() => setShowHints(false)}
                          className="text-xs text-purple-600 mt-2 hover:underline"
                        >
                          Got it, dismiss
                        </button>
                      </div>
                    </div>
                  </div>
                )}

                {/* Camera Preview */}
                <div className="relative">
                  <video
                    ref={videoRef}
                    autoPlay
                    playsInline
                    className="w-full rounded-lg shadow-xl"
                  />

                  {/* Capture Counter */}
                  {captureCount > 0 && (
                    <div className="absolute top-4 right-4 bg-purple-600 text-white px-3 py-1 rounded-full text-sm font-bold shadow-lg">
                      {captureCount}/4 photos
                    </div>
                  )}

                  {/* Grid Overlay for Composition */}
                  <div className="absolute inset-0 pointer-events-none">
                    <div className="w-full h-full grid grid-cols-3 grid-rows-3">
                      {[...Array(9)].map((_, i) => (
                        <div key={i} className="border border-white/20" />
                      ))}
                    </div>
                  </div>
                </div>

                {/* Capture Buttons */}
                <div className="flex space-x-3">
                  <button
                    onClick={capturePhoto}
                    className="flex-1 bg-gradient-to-r from-purple-600 to-blue-500 text-white py-4 rounded-lg font-medium hover:shadow-xl transition-all flex items-center justify-center space-x-2"
                  >
                    <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
                    </svg>
                    <span>Capture Photo</span>
                  </button>
                  <button
                    onClick={stopCamera}
                    className="px-6 bg-gray-200 text-gray-700 py-4 rounded-lg font-medium hover:bg-gray-300 transition-all"
                  >
                    Done
                  </button>
                </div>
              </div>
            )}
          </div>

          {/* Previews */}
          {previews.length > 0 && (
            <div className="mb-8">
              <div className="flex items-center justify-between mb-4">
                <h3 className="text-lg font-semibold">Selected Images ({previews.length})</h3>
                <button
                  onClick={() => {
                    setSelectedFiles([])
                    setPreviews([])
                    setCaptureCount(0)
                  }}
                  className="text-sm text-red-600 hover:text-red-700 font-medium"
                >
                  Clear All
                </button>
              </div>
              <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
                {previews.map((preview, index) => (
                  <div key={index} className="relative aspect-square group">
                    <Image
                      src={preview}
                      alt={`Preview ${index + 1}`}
                      fill
                      className="object-cover rounded-lg"
                    />
                    <button
                      onClick={() => removePhoto(index)}
                      className="absolute top-2 right-2 bg-red-600 text-white p-1.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-red-700"
                    >
                      <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                      </svg>
                    </button>
                    <div className="absolute bottom-2 left-2 bg-black/60 text-white text-xs px-2 py-1 rounded">
                      Photo {index + 1}
                    </div>
                  </div>
                ))}
              </div>
              <button
                onClick={analyzeImages}
                disabled={analyzing}
                className="mt-4 w-full bg-gradient-to-r from-green-600 to-teal-500 text-white py-4 rounded-lg font-medium hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
              >
                {analyzing ? (
                  <>
                    <svg className="animate-spin h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                    </svg>
                    <span>Analyzing with AI...</span>
                  </>
                ) : (
                  <>
                    <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                    </svg>
                    <span>Analyze Handbag</span>
                  </>
                )}
              </button>
            </div>
          )}

          {/* Results */}
          {result && (
            <div className="border-t pt-8">
              <h2 className="text-2xl font-bold text-gray-900 mb-6">Analysis Results</h2>

              <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                {/* Basic Info */}
                <div className="space-y-4">
                  <div>
                    <label className="text-sm font-medium text-gray-500">Brand</label>
                    <p className="text-lg font-semibold">{result.brand || 'N/A'}</p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Model</label>
                    <p className="text-lg font-semibold">{result.model || 'N/A'}</p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Material</label>
                    <p className="text-lg">{result.material || 'N/A'}</p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Color</label>
                    <p className="text-lg">{result.color || 'N/A'}</p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Hardware</label>
                    <p className="text-lg">{result.hardware || 'N/A'}</p>
                  </div>
                </div>

                {/* Condition & Value */}
                <div className="space-y-4">
                  <div>
                    <label className="text-sm font-medium text-gray-500">Condition</label>
                    <p className="text-lg font-semibold">{result.condition || 'N/A'}</p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Estimated Value</label>
                    <p className="text-2xl font-bold text-green-600">
                      ${result.estimated_value_low?.toLocaleString() || '0'} - ${result.estimated_value_high?.toLocaleString() || '0'}
                    </p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Authenticity Confidence</label>
                    <p className={`text-lg font-semibold ${
                      result.authenticity_confidence === 'High' ? 'text-green-600' :
                      result.authenticity_confidence === 'Medium' ? 'text-yellow-600' :
                      'text-red-600'
                    }`}>
                      {result.authenticity_confidence || 'N/A'}
                    </p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Investment Rating</label>
                    <p className="text-lg font-semibold">{result.investment_rating || 'N/A'}</p>
                  </div>
                  <div>
                    <label className="text-sm font-medium text-gray-500">Market Demand</label>
                    <p className="text-lg font-semibold">{result.demand_level || 'N/A'}</p>
                  </div>
                </div>
              </div>

              {/* Detailed Analysis */}
              <div className="mt-6 space-y-4">
                {result.authenticity_markers && (
                  <div>
                    <label className="text-sm font-medium text-gray-500">Authenticity Markers</label>
                    <p className="text-gray-700 mt-1">{result.authenticity_markers}</p>
                  </div>
                )}
                {result.red_flags && (
                  <div>
                    <label className="text-sm font-medium text-red-600">Red Flags</label>
                    <p className="text-gray-700 mt-1">{result.red_flags}</p>
                  </div>
                )}
                {result.issues && (
                  <div>
                    <label className="text-sm font-medium text-gray-500">Visible Issues</label>
                    <p className="text-gray-700 mt-1">{result.issues}</p>
                  </div>
                )}
                {result.best_platforms && (
                  <div>
                    <label className="text-sm font-medium text-gray-500">Best Selling Platforms</label>
                    <p className="text-gray-700 mt-1">{result.best_platforms}</p>
                  </div>
                )}
                {result.market_notes && (
                  <div>
                    <label className="text-sm font-medium text-gray-500">Market Notes</label>
                    <p className="text-gray-700 mt-1">{result.market_notes}</p>
                  </div>
                )}
              </div>
            </div>
          )}
        </div>
      </main>
    </div>
  )
}