256 lines
65beb53 samthecodingguy Jan 29, 2026
'use client';
import React, { useRef, useState } from 'react';
import { Upload, Image as ImageIcon, Link, Trash2 } from 'lucide-react';
import { useEditorStore } from '@/lib/store/editor-store';
import type { ImageLayerData, ScreenshotLayerData } from '@/types';
export function ImagesPanel() {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [uploadedImages, setUploadedImages] = useState<string[]>([]);
  const [imageUrl, setImageUrl] = useState('');
  const [dragActive, setDragActive] = useState(false);
  const { addLayer, canvas, selectedLayerIds, layers, updateLayer } = useEditorStore();
  const handleFileUpload = (files: FileList | null) => {
    if (!files) return;
    Array.from(files).forEach((file) => {
      if (!file.type.startsWith('image/')) return;
      const reader = new FileReader();
      reader.onload = (e) => {
        const src = e.target?.result as string;
        setUploadedImages((prev) => [...prev, src]);
        // Auto-add to canvas if no device is selected
        const selectedDevice = selectedLayerIds.find(
          (id) => layers.find((l) => l.id === id)?.type === 'device'
        );
        if (!selectedDevice) {
          addImageToCanvas(src);
        } else {
          // Add screenshot to device
          addScreenshotToDevice(src, selectedDevice);
        }
      };
      reader.readAsDataURL(file);
    });
  };
  const addImageToCanvas = (src: string) => {
    const img = new window.Image();
    img.onload = () => {
      // Calculate size to fit in canvas
      const maxWidth = canvas.width * 0.6;
      const maxHeight = canvas.height * 0.6;
      const scale = Math.min(maxWidth / img.width, maxHeight / img.height);
      const width = img.width * scale;
      const height = img.height * scale;
      const data: ImageLayerData = {
        type: 'image',
        src,
      };
      addLayer({
        type: 'image',
        name: 'Image',
        visible: true,
        locked: false,
        opacity: 1,
        position: {
          x: (canvas.width - width) / 2,
          y: (canvas.height - height) / 2,
        },
        size: { width, height },
        rotation: 0,
        data,
      });
    };
    img.src = src;
  };
  const addScreenshotToDevice = (src: string, deviceLayerId: string) => {
    const device = layers.find((l) => l.id === deviceLayerId);
    if (!device || device.type !== 'device') return;
    const data: ScreenshotLayerData = {
      type: 'screenshot',
      src,
      fit: 'cover',
    };
    addLayer({
      type: 'screenshot',
      name: 'Screenshot',
      visible: true,
      locked: false,
      opacity: 1,
      position: device.position,
      size: device.size,
      rotation: device.rotation,
      data,
    });
  };
  const handleDrag = (e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    if (e.type === 'dragenter' || e.type === 'dragover') {
      setDragActive(true);
    } else if (e.type === 'dragleave') {
      setDragActive(false);
    }
  };
  const handleDrop = (e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setDragActive(false);
    handleFileUpload(e.dataTransfer.files);
  };
  const handleUrlImport = () => {
    if (!imageUrl) return;
    setUploadedImages((prev) => [...prev, imageUrl]);
    addImageToCanvas(imageUrl);
    setImageUrl('');
  };
  const handleRemoveImage = (index: number) => {
    setUploadedImages((prev) => prev.filter((_, i) => i !== index));
  };
  return (
    <div className="p-4">
      <h2 className="text-lg font-semibold text-white mb-4">Images & Screenshots</h2>
      {/* Upload area */}
      <div
        onDragEnter={handleDrag}
        onDragLeave={handleDrag}
        onDragOver={handleDrag}
        onDrop={handleDrop}
        className={`
          relative p-6 rounded-xl border-2 border-dashed
          transition-all cursor-pointer
          ${
            dragActive
              ? 'border-indigo-500 bg-indigo-500/10'
              : 'border-neutral-700 hover:border-neutral-600 bg-neutral-800/30'
          }
        `}
        onClick={() => fileInputRef.current?.click()}
      >
        <input
          ref={fileInputRef}
          type="file"
          accept="image/*"
          multiple
          className="hidden"
          onChange={(e) => handleFileUpload(e.target.files)}
        />
        <div className="flex flex-col items-center gap-2 text-center">
          <div
            className={`
            w-12 h-12 rounded-xl flex items-center justify-center
            ${dragActive ? 'bg-indigo-500/20' : 'bg-neutral-700/50'}
          `}
          >
            <Upload
              className={`w-6 h-6 ${dragActive ? 'text-indigo-400' : 'text-neutral-400'}`}
            />
          </div>
          <div>
            <p className="text-sm text-white">Drop images here</p>
            <p className="text-xs text-neutral-500">or click to browse</p>
          </div>
        </div>
      </div>
      {/* URL import */}
      <div className="mt-4">
        <label className="text-sm text-neutral-400 block mb-2">Import from URL</label>
        <div className="flex gap-2">
          <input
            type="url"
            value={imageUrl}
            onChange={(e) => setImageUrl(e.target.value)}
            placeholder="https://..."
            className="
              flex-1 px-3 py-2 rounded-lg
              bg-neutral-800 border border-neutral-700
              text-white text-sm placeholder-neutral-500
              focus:outline-none focus:border-indigo-500
            "
          />
          <button
            onClick={handleUrlImport}
            disabled={!imageUrl}
            className="
              px-3 py-2 rounded-lg
              bg-indigo-500 hover:bg-indigo-600 disabled:bg-neutral-700
              text-white disabled:text-neutral-500
              transition-colors
            "
          >
            <Link className="w-4 h-4" />
          </button>
        </div>
      </div>
      {/* Uploaded images gallery */}
      {uploadedImages.length > 0 && (
        <div className="mt-6">
          <label className="text-sm text-neutral-400 block mb-2">Recent Uploads</label>
          <div className="grid grid-cols-2 gap-2">
            {uploadedImages.map((src, index) => (
              <div
                key={index}
                className="relative group aspect-video rounded-lg overflow-hidden bg-neutral-800"
              >
                <img
                  src={src}
                  alt={`Upload ${index + 1}`}
                  className="w-full h-full object-cover"
                />
                {/* Hover overlay */}
                <div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
                  <button
                    onClick={() => addImageToCanvas(src)}
                    className="p-2 rounded-lg bg-indigo-500 hover:bg-indigo-600 transition-colors"
                  >
                    <ImageIcon className="w-4 h-4 text-white" />
                  </button>
                  <button
                    onClick={() => handleRemoveImage(index)}
                    className="p-2 rounded-lg bg-red-500 hover:bg-red-600 transition-colors"
                  >
                    <Trash2 className="w-4 h-4 text-white" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
      {/* Tips */}
      <div className="mt-6 p-3 rounded-lg bg-neutral-800/50 border border-neutral-700/50">
        <h3 className="text-sm font-medium text-white mb-2">Tips</h3>
        <ul className="text-xs text-neutral-400 space-y-1">
          <li>• Drop screenshots directly onto device mockups</li>
          <li>• Use CMD/Ctrl + V to paste from clipboard</li>
          <li>• Supports PNG, JPG, GIF, and WebP</li>
        </ul>
      </div>
    </div>
  );
}