bookwiz.io / app / dashboard / images / page.tsx
page.tsx
Raw
'use client'

import { useState, useEffect } from 'react'
import { 
  IoImageOutline, 
  IoDownloadOutline, 
  IoTrashOutline,
  IoCloseOutline,
  IoSparklesOutline,
  IoTimeOutline,
  IoSendOutline,
  IoGridOutline,
  IoListOutline,
  IoChevronDownOutline,
  IoChevronForwardOutline
} from 'react-icons/io5'
import { useImageGeneration, type GeneratedImage, type ImageGenerationSettings } from '@/lib/hooks/useImageGeneration'
import DashboardLayout from '@/components/dashboard/DashboardLayout'
import ImagesGridSkeleton from '@/components/dashboard/ImagesGridSkeleton'

function SettingsToggle({ 
  options, 
  value, 
  onChange 
}: { 
  options: Array<{ value: string; label: string; desc: string; icon?: string; cost?: string }>
  value: string
  onChange: (value: string) => void
}) {
  return (
    <div className={`flex gap-1.5 ${options.length === 3 ? '' : ''}`}>
      {options.map((option) => (
        <button
          key={option.value}
          type="button"
          onClick={() => onChange(option.value)}
          className={`px-2.5 py-1.5 text-xs font-medium rounded-lg border transition-all duration-200 ${
            value === option.value
              ? 'border-purple-500 bg-purple-500/10 text-purple-400'
              : 'border-slate-600 hover:border-slate-500 text-slate-400 hover:text-slate-300'
          }`}
          title={option.desc + (option.cost ? `${option.cost}` : '')}
        >
          {option.icon && <span className="mr-1">{option.icon}</span>}
          {option.label}
          {option.cost && <span className="ml-1 text-emerald-400"></span>}
        </button>
      ))}
    </div>
  )
}

function ImageCard({ image, onDelete, viewMode }: { 
  image: GeneratedImage; 
  onDelete: (id: string) => void;
  viewMode: 'grid' | 'list'
}) {
  const [isImageLoading, setIsImageLoading] = useState(true)
  const [imageError, setImageError] = useState(false)
  
  const handleDownload = async (e: React.MouseEvent) => {
    e.stopPropagation()
    try {
      const response = await fetch(image.image_url)
      const blob = await response.blob()
      const url = URL.createObjectURL(blob)
      
      const link = document.createElement('a')
      link.href = url
      link.download = `bookwiz-generated-${image.id}.png`
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(url)
    } catch (error) {
      // Silently fail for download errors
    }
  }

  const formatDate = (dateString: string) => {
    const date = new Date(dateString)
    const now = new Date()
    const diffInHours = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60))
    
    if (diffInHours < 1) return 'Just now'
    if (diffInHours < 24) return `${diffInHours}h ago`
    if (diffInHours < 48) return 'Yesterday'
    return date.toLocaleDateString()
  }

  const handleImageError = () => {
    setIsImageLoading(false)
    setImageError(true)
  }

  if (viewMode === 'list') {
    return (
      <div className="group flex items-start gap-4 p-4 bg-slate-800/10 backdrop-blur-sm rounded-xl border border-slate-700/20 hover:border-slate-600/40 hover:bg-slate-800/20 transition-all duration-200">
        <div className="w-16 h-16 rounded-lg overflow-hidden flex-shrink-0 border border-slate-600/20 relative">
          {isImageLoading && (
            <div className="absolute inset-0 bg-slate-700/50 animate-pulse flex items-center justify-center">
              <div className="w-6 h-6 bg-white/20 rounded animate-pulse"></div>
            </div>
          )}
          {imageError && (
            <div className="absolute inset-0 bg-slate-700/50 flex items-center justify-center">
              <div className="w-6 h-6 bg-white/10 rounded flex items-center justify-center">
                <IoImageOutline className="w-3 h-3 text-slate-500" />
              </div>
            </div>
          )}
          {!imageError && (
            <img
              src={image.image_url}
              alt={image.prompt}
              className="w-full h-full object-cover"
              onLoad={() => setIsImageLoading(false)}
              onError={handleImageError}
            />
          )}
        </div>
        
        <div className="flex-1 min-w-0 space-y-2">
          <div>
            <p className="text-white font-medium text-sm line-clamp-2 leading-tight">{image.prompt}</p>
          </div>
          
          <div className="space-y-1">
            <div className="flex items-center gap-1.5 text-xs text-slate-400">
              <IoTimeOutline className="w-3 h-3" />
              <span>{formatDate(image.created_at)}</span>
            </div>
            <div className="flex items-center gap-1.5 text-xs text-slate-400">
              <span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
              <span>GPT Image 1</span>
            </div>
          </div>
        </div>
        
        <div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
          <button
            onClick={handleDownload}
            className="p-2 bg-slate-700/50 hover:bg-slate-600/50 rounded-lg transition-colors"
            title="Download image"
          >
            <IoDownloadOutline className="w-3.5 h-3.5 text-slate-300" />
          </button>
          <button
            onClick={(e) => {
              e.stopPropagation()
              onDelete(image.id)
            }}
            className="p-2 bg-red-500/10 hover:bg-red-500/20 rounded-lg transition-colors"
            title="Delete image"
          >
            <IoTrashOutline className="w-3.5 h-3.5 text-red-400" />
          </button>
        </div>
      </div>
    )
  }

  return (
    <div className="group relative bg-slate-800/10 backdrop-blur-sm rounded-xl border border-slate-700/20 overflow-hidden hover:border-slate-600/40 hover:bg-slate-800/20 transition-all duration-200">
      <div className="aspect-square relative">
        {isImageLoading && (
          <div className="absolute inset-0 bg-slate-700/50 animate-pulse flex items-center justify-center">
            <div className="w-8 h-8 bg-white/20 rounded animate-pulse"></div>
          </div>
        )}
        {imageError && (
          <div className="absolute inset-0 bg-slate-700/50 flex items-center justify-center">
            <div className="text-center">
              <IoImageOutline className="w-8 h-8 text-slate-500 mx-auto mb-1" />
              <p className="text-xs text-slate-500">Image unavailable</p>
            </div>
          </div>
        )}
        {!imageError && (
          <img
            src={image.image_url}
            alt={image.prompt}
            className="w-full h-full object-cover"
            onLoad={() => setIsImageLoading(false)}
            onError={handleImageError}
          />
        )}
        
        {/* Overlay with actions */}
        <div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center gap-3">
          <button
            onClick={handleDownload}
            className="p-2.5 bg-white/20 backdrop-blur-sm rounded-lg hover:bg-white/30 transition-colors"
            title="Download image"
          >
            <IoDownloadOutline className="w-4 h-4 text-white" />
          </button>
          <button
            onClick={(e) => {
              e.stopPropagation()
              onDelete(image.id)
            }}
            className="p-2.5 bg-red-500/20 backdrop-blur-sm rounded-lg hover:bg-red-500/30 transition-colors"
            title="Delete image"
          >
            <IoTrashOutline className="w-4 h-4 text-red-400" />
          </button>
        </div>
      </div>
      
      {/* Image info */}
      <div className="p-3 space-y-2">
        <p className="text-xs text-slate-200 line-clamp-2 leading-tight font-medium">{image.prompt}</p>
        <div className="space-y-1">
          <div className="flex items-center gap-1.5 text-xs text-slate-400">
            <IoTimeOutline className="w-3 h-3" />
            <span>{formatDate(image.created_at)}</span>
          </div>
          <div className="flex items-center gap-1.5 text-xs text-slate-400">
            <span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
            <span>GPT Image 1</span>
          </div>
        </div>
      </div>
    </div>
  )
}

function ImageModal({ image, onClose }: { image: GeneratedImage | null, onClose: () => void }) {
  useEffect(() => {
    if (!image) return;
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    };
    window.addEventListener('keydown', handleKey);
    return () => window.removeEventListener('keydown', handleKey);
  }, [image, onClose]);

  if (!image) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4 sm:p-4">
      <div className="absolute inset-0" onClick={onClose} />
      <div className="relative z-10 w-full h-full sm:max-w-4xl sm:max-h-[90vh] sm:h-auto bg-slate-900 sm:rounded-2xl shadow-2xl flex flex-col overflow-hidden">
        {/* Header */}
        <div className="flex items-center justify-between p-3 sm:p-4 border-b border-slate-700/50 flex-shrink-0">
          <div className="flex items-center gap-2">
            <div className="w-6 h-6 sm:w-8 sm:h-8 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center">
              <IoImageOutline className="w-3 h-3 sm:w-4 sm:h-4 text-white" />
            </div>
            <div>
              <h3 className="text-sm font-semibold text-white">Generated Image</h3>
              <p className="text-xs text-slate-400 hidden sm:block">View and download your AI creation</p>
            </div>
          </div>
          <button
            onClick={onClose}
            className="p-1.5 sm:p-2 rounded-full bg-black/50 hover:bg-black/80 text-white transition-colors"
            title="Close"
          >
            <IoCloseOutline className="w-4 h-4 sm:w-5 sm:h-5" />
          </button>
        </div>

        {/* Scrollable Content */}
        <div className="flex-1 overflow-y-auto">
          <div className="p-4 sm:p-6">
            {/* Image Container */}
            <div className="flex justify-center mb-4">
              <div className="relative max-w-full">
                <img
                  src={image.image_url}
                  alt={image.prompt}
                  className="max-w-full max-h-[50vh] sm:max-h-[70vh] w-auto rounded-xl shadow-lg border border-slate-700 object-contain"
                />
              </div>
            </div>

            {/* Download Button */}
            <div className="flex justify-center mb-6">
              <button
                onClick={async () => {
                  try {
                    const response = await fetch(image.image_url)
                    const blob = await response.blob()
                    const url = URL.createObjectURL(blob)
                    
                    const link = document.createElement('a')
                    link.href = url
                    link.download = `bookwiz-generated-${image.id}.png`
                    document.body.appendChild(link)
                    link.click()
                    document.body.removeChild(link)
                    URL.revokeObjectURL(url)
                  } catch (error) {
                    // Silently fail for download errors
                  }
                }}
                className="flex items-center gap-1.5 px-3 py-1.5 bg-slate-700/50 hover:bg-slate-600/50 text-slate-300 hover:text-white rounded-md transition-colors text-xs"
              >
                <IoDownloadOutline className="w-3.5 h-3.5" />
                <span>Download</span>
              </button>
            </div>

            {/* Image Details */}
            <div className="space-y-3">
              {/* Prompt */}
              <div className="flex items-start gap-2 p-3 bg-slate-800/30 rounded-lg border border-slate-700/30">
                <div className="flex-1">
                  <p className="text-xs text-slate-400 mb-1">Prompt</p>
                  <p className="text-sm sm:text-base text-white leading-relaxed">
                    {image.prompt}
                  </p>
                </div>
                <button
                  onClick={async () => {
                    try {
                      await navigator.clipboard.writeText(image.prompt)
                    } catch (error) {
                      // Fallback for older browsers
                      const textArea = document.createElement('textarea')
                      textArea.value = image.prompt
                      document.body.appendChild(textArea)
                      textArea.select()
                      document.execCommand('copy')
                      document.body.removeChild(textArea)
                    }
                  }}
                  className="flex-shrink-0 p-1.5 bg-slate-700/50 hover:bg-slate-600/50 rounded-md transition-colors"
                  title="Copy prompt"
                >
                  <svg className="w-3.5 h-3.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
                  </svg>
                </button>
              </div>

              {/* Metadata */}
              <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
                <div className="flex items-center gap-2 p-3 bg-slate-800/30 rounded-lg border border-slate-700/30">
                  <IoTimeOutline className="w-4 h-4 text-slate-400 flex-shrink-0" />
                  <div>
                    <p className="text-xs text-slate-400">Created</p>
                    <p className="text-sm text-white">{new Date(image.created_at).toLocaleString()}</p>
                  </div>
                </div>
                <div className="flex items-center gap-2 p-3 bg-slate-800/30 rounded-lg border border-slate-700/30">
                  <IoImageOutline className="w-4 h-4 text-slate-400 flex-shrink-0" />
                  <div>
                    <p className="text-xs text-slate-400">Dimensions</p>
                    <p className="text-sm text-white">{image.size}</p>
                  </div>
                </div>
                <div className="flex items-center gap-2 p-3 bg-slate-800/30 rounded-lg border border-slate-700/30">
                  <span className="w-2 h-2 rounded-full bg-emerald-400 flex-shrink-0"></span>
                  <div>
                    <p className="text-xs text-slate-400">Quality</p>
                    <p className="text-sm text-white capitalize">{image.quality}</p>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>


      </div>
    </div>
  );
}

// Add new component for expandable tips
function ImageTips({ onPromptSelect }: { onPromptSelect: (prompt: string) => void }) {
  const [expandedTip, setExpandedTip] = useState<string | null>(null)

  const tips = [
    {
      id: 'covers',
      title: 'Book Covers',
      icon: '📚',
      description: 'Professional covers that capture your story',
      examples: [
        'Fantasy book cover, dramatic lighting, golden hour, dragons silhouetted against misty mountains, cinematic composition, professional typography, high contrast, detailed textures',
        'Sci-fi thriller cover, neon-lit cyberpunk cityscape, dramatic shadows, mysterious figure in trench coat, atmospheric fog, cinematic wide shot, high saturation',
        'Romance novel cover, soft bokeh background, elegant typography, warm golden lighting, shallow depth of field, intimate close-up composition, dreamy atmosphere',
        'Mystery book cover, film noir style, high contrast black and white, dramatic shadows, vintage detective elements, moody atmosphere, dramatic lighting'
      ]
    },
    {
      id: 'characters',
      title: 'Character Portraits',
      icon: '👤',
      description: 'Bring your characters to life',
      examples: [
        'Portrait photography, brave warrior, dramatic side lighting, battle scars, determined expression, shallow depth of field, professional studio lighting, high resolution',
        'Character portrait, mysterious wizard, flowing robes, magical aura, dramatic backlighting, ethereal glow, cinematic composition, detailed textures',
        'Close-up portrait, young heroine, natural lighting, flowing hair, adventurous spirit, soft bokeh background, intimate framing, warm color palette',
        'Character study, villain, dramatic low-key lighting, dark clothing, menacing presence, high contrast, moody atmosphere, professional photography'
      ]
    },
    {
      id: 'scenes',
      title: 'Story Scenes',
      icon: '🎭',
      description: 'Key moments and settings from your narrative',
      examples: [
        'Epic battle scene, wide cinematic shot, dramatic lighting, armies clashing, atmospheric perspective, golden hour, high dynamic range, detailed composition',
        'Peaceful village scene, soft natural lighting, thatched roofs, smoke rising, warm color palette, shallow depth of field, intimate storytelling, atmospheric',
        'Ancient castle landscape, dramatic cliff setting, stormy seas, moody atmosphere, wide-angle composition, dramatic clouds, cinematic lighting',
        'Futuristic cityscape, neon lighting, flying cars, cyberpunk aesthetic, dramatic shadows, high saturation, wide panoramic shot, atmospheric fog'
      ]
    },
    {
      id: 'maps',
      title: 'World Maps',
      icon: '🗺️',
      description: 'Maps of your fictional worlds and locations',
      examples: [
        'Fantasy world map, hand-drawn style, detailed topography, kingdoms and borders, mountain ranges, magical forests, vintage parchment texture, professional cartography',
        'Sci-fi galaxy map, space theme, planets and space stations, star fields, nebula backgrounds, futuristic design, high contrast, detailed space elements',
        'Medieval kingdom map, antique style, castles and villages, trade routes, detailed terrain, aged parchment look, professional cartographic design',
        'Underwater city map, aquatic theme, coral reefs, ancient ruins, bioluminescent elements, detailed underwater landscape, ethereal lighting'
      ]
    },
    {
      id: 'objects',
      title: 'Magical Objects',
      icon: '⚡',
      description: 'Artifacts, weapons, and special items',
      examples: [
        'Ancient sword photography, studio lighting, glowing runes, mystical energy, dramatic shadows, high contrast, detailed textures, professional product photography',
        'Crystal orb close-up, macro photography, swirling magical essence, ethereal glow, soft bokeh background, dramatic lighting, high resolution, detailed textures',
        'Golden crown detail shot, professional lighting, precious gems, intricate details, high contrast, dramatic shadows, luxury product photography',
        'Mysterious book macro shot, leather binding, magical symbols, atmospheric lighting, detailed textures, vintage aesthetic, professional photography'
      ]
    }
  ]

  return (
    <div className="space-y-3">
      <h4 className="text-sm font-semibold text-white flex items-center gap-2">
        <IoSparklesOutline className="w-4 h-4 text-purple-400" />
        Image Inspiration & Tips
      </h4>
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
        {tips.map((tip) => (
          <div key={tip.id} className="bg-slate-800/30 border border-slate-700/50 rounded-lg overflow-hidden">
            <button
              onClick={() => setExpandedTip(expandedTip === tip.id ? null : tip.id)}
              className="w-full p-3 text-left hover:bg-slate-700/30 transition-colors"
            >
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-2">
                  <span className="text-lg">{tip.icon}</span>
                  <div>
                    <h5 className="text-sm font-medium text-white">{tip.title}</h5>
                    <p className="text-xs text-slate-400">{tip.description}</p>
                  </div>
                </div>
                {expandedTip === tip.id ? (
                  <IoChevronDownOutline className="w-4 h-4 text-slate-400" />
                ) : (
                  <IoChevronForwardOutline className="w-4 h-4 text-slate-400" />
                )}
              </div>
            </button>
            
            {expandedTip === tip.id && (
              <div className="px-3 pb-3 border-t border-slate-700/50">
                <div className="pt-3 space-y-2">
                  <p className="text-xs text-slate-300 font-medium mb-2">Example prompts:</p>
                  {tip.examples.map((example, index) => (
                    <button
                      key={index}
                                           onClick={() => {
                       onPromptSelect(example)
                       setExpandedTip(null) // Close the tip after selection
                     }}
                      className="block w-full text-left p-2 text-xs text-slate-400 hover:text-purple-400 hover:bg-slate-700/30 rounded transition-colors"
                    >
                      "{example}"
                    </button>
                  ))}
                </div>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  )
}

export default function ImagesPage() {
  const [prompt, setPrompt] = useState('')
  const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
  const [modalImage, setModalImage] = useState<GeneratedImage | null>(null)
  
  const {
    images,
    isGenerating,
    isLoading,
    error,
    settings,
    generateImage,
    deleteImage,
    updateSettings,
    clearError
  } = useImageGeneration()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    if (!prompt.trim() || isGenerating) return

    const generatedImage = await generateImage(prompt)
    if (generatedImage) {
      setPrompt('') // Clear the prompt on successful generation
    }
  }

  return (
    <DashboardLayout
      title="AI Image Gallery"
      subtitle="Create covers, characters, scenes, maps & more with AI"
    >
      {/* Generation Form */}
      <div className="space-y-6">
        <form onSubmit={handleSubmit} className="space-y-6">
          <div className="space-y-4">
            {/* Input wrapper with border - similar to chat input */}
            <div className="relative bg-slate-800/50 backdrop-blur-sm border border-slate-700/50 rounded-xl focus-within:ring-1 focus-within:ring-purple-500/50 focus-within:border-transparent transition-all">
              <textarea
                value={prompt}
                onChange={(e) => setPrompt(e.target.value)}
                placeholder="Describe the image you want to generate... (e.g., 'Fantasy book cover, dramatic lighting, dragons silhouetted against misty mountains')"
                className="w-full p-4 bg-transparent border-none focus:outline-none focus:ring-0 focus:border-none resize-none text-white placeholder-slate-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 text-base"
                rows={3}
                disabled={isGenerating}
              />
              
                                {/* Model and Settings Info inside input */}
                  <div className="flex flex-row items-center justify-between gap-2 sm:gap-3 px-4 pb-3">
                    {/* Model Info and Settings */}
                    <div className="flex flex-row items-center gap-1.5 sm:gap-4 min-w-0 flex-1">
                      {/* Model Info - hidden on mobile */}
                      <div className="hidden sm:flex items-center gap-2 text-xs text-slate-400">
                        <span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
                        <span>GPT Image 1</span>
                      </div>
                      
                      <div className="hidden sm:block w-px h-3 bg-slate-600"></div>
                      
                      {/* Dimensions Select */}
                      <select
                        value={settings.size}
                        onChange={(e) => updateSettings({ size: e.target.value as any })}
                        className="text-xs bg-transparent text-slate-300 hover:text-white focus:outline-none focus:ring-0 border-0 cursor-pointer transition-colors min-w-0"
                      >
                        <option value="1024x1024">1:1</option>
                        <option value="1536x1024">3:2</option>
                        <option value="1024x1536">2:3</option>
                      </select>
                      
                      <div className="hidden sm:block w-px h-3 bg-slate-600"></div>
                      
                      {/* Quality Select */}
                      <select
                        value={settings.quality}
                        onChange={(e) => updateSettings({ quality: e.target.value as any })}
                        className="text-xs bg-transparent text-slate-300 hover:text-white focus:outline-none focus:ring-0 border-0 cursor-pointer transition-colors min-w-0"
                      >
                        <option value="auto">Auto</option>
                        <option value="low">Fast</option>
                        <option value="medium">Balanced</option>
                        <option value="high">Best</option>
                      </select>
                    </div>
                
                {/* Submit button */}
                <button
                  type="submit"
                  disabled={!prompt.trim() || isGenerating}
                  className="flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-4 py-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white font-medium rounded-lg hover:from-purple-600 hover:to-pink-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-purple-500/25 text-xs sm:text-sm flex-shrink-0"
                >
                  {isGenerating ? (
                    <>
                      <IoSparklesOutline className="w-3.5 h-3.5 sm:w-4 sm:h-4 animate-spin" />
                      <span className="hidden sm:inline">Generating...</span>
                      <span className="sm:hidden">...</span>
                    </>
                  ) : (
                    <>
                      <IoSendOutline className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
                      <span className="hidden sm:inline">Generate</span>
                      <span className="sm:hidden">Go</span>
                    </>
                  )}
                </button>
              </div>
            </div>
          </div>
        </form>

        {/* Image Tips */}
        <ImageTips onPromptSelect={(prompt) => setPrompt(prompt)} />

        {/* Error Display */}
        {error && (
          <div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl">
            <div className="flex items-center justify-between">
              <p className="text-red-400">{error}</p>
              <button
                onClick={clearError}
                className="text-red-400 hover:text-red-300 transition-colors"
              >
                <IoCloseOutline className="w-5 h-5" />
              </button>
            </div>
          </div>
        )}
      </div>

      {/* View Controls */}
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-4">
          <h2 className="text-2xl font-bold text-white">
            Images
            {images.length > 0 && (
              <span className="ml-3 px-3 py-1 text-sm font-medium text-purple-400 bg-purple-500/10 rounded-full border border-purple-500/20">
                {images.length}
              </span>
            )}
          </h2>
        </div>
        
        <div className="flex items-center gap-2 p-1 bg-slate-800/30 rounded-xl border border-slate-700/30">
          <button
            onClick={() => setViewMode('grid')}
            className={`p-2.5 rounded-lg transition-colors ${
              viewMode === 'grid' 
                ? 'bg-purple-500/20 text-purple-400' 
                : 'text-slate-400 hover:text-slate-200'
            }`}
            title="Grid view"
          >
            <IoGridOutline className="w-4 h-4" />
          </button>
          <button
            onClick={() => setViewMode('list')}
            className={`p-2.5 rounded-lg transition-colors ${
              viewMode === 'list' 
                ? 'bg-purple-500/20 text-purple-400' 
                : 'text-slate-400 hover:text-slate-200'
            }`}
            title="List view"
          >
            <IoListOutline className="w-4 h-4" />
          </button>
        </div>
      </div>

      {/* Images Display */}
      <div>
        {isLoading ? (
          <ImagesGridSkeleton viewMode={viewMode} />
        ) : images.length === 0 ? (
          <div className="flex flex-col items-center justify-center h-64 text-center">
            <div className="w-24 h-24 rounded-2xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 flex items-center justify-center mb-6 border border-purple-500/20">
              <IoImageOutline className="w-12 h-12 text-slate-600" />
            </div>
            <h3 className="text-2xl font-bold text-white mb-3">No images yet</h3>
            <p className="text-slate-400 mb-6 max-w-md text-lg leading-relaxed">Start by describing the image you want to generate above. Our AI will create stunning visuals for your books, characters, scenes, and more.</p>
            <div className="p-4 bg-slate-800/20 rounded-xl border border-slate-700/30">
              <div className="text-sm text-slate-500">
                💡 Try prompts like <span className="text-purple-400 font-medium">"Fantasy book cover, dramatic lighting, dragons silhouetted against misty mountains"</span> or <span className="text-purple-400 font-medium">"Portrait photography, brave warrior, dramatic side lighting"</span>
              </div>
            </div>
          </div>
        ) : (
          <div className={
            viewMode === 'grid' 
              ? 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3 sm:gap-4'
              : 'space-y-3'
          }>
            {images.map((image) => (
              <div key={image.id} onClick={() => setModalImage(image)} className="cursor-zoom-in">
                <ImageCard
                  image={image}
                  onDelete={deleteImage}
                  viewMode={viewMode}
                />
              </div>
            ))}
          </div>
        )}
      </div>
      
      <ImageModal image={modalImage} onClose={() => setModalImage(null)} />
    </DashboardLayout>
  )
}