bookwiz.io / components / FileDialogs.tsx
FileDialogs.tsx
Raw
import { useState, useEffect, useRef } from 'react'
import { IoCloseOutline, IoDocumentTextOutline, IoFolderOutline, IoTrashOutline } from 'react-icons/io5'

interface CreateFileDialogProps {
  isOpen: boolean
  onClose: () => void
  onSubmit: (name: string, content?: string) => Promise<void>
  type: 'file' | 'folder'
  parentName?: string
  loading?: boolean
}

interface RenameDialogProps {
  isOpen: boolean
  onClose: () => void
  onSubmit: (newName: string) => Promise<void>
  currentName: string
  type: 'file' | 'folder'
  loading?: boolean
}

interface DeleteConfirmDialogProps {
  isOpen: boolean
  onClose: () => void
  onConfirm: () => Promise<void>
  itemName: string
  itemType: 'file' | 'folder'
  loading?: boolean
}

export function CreateFileDialog({ 
  isOpen, 
  onClose, 
  onSubmit, 
  type, 
  parentName, 
  loading = false 
}: CreateFileDialogProps) {
  const [name, setName] = useState('')
  const [content, setContent] = useState('')
  const [error, setError] = useState('')
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    if (isOpen) {
      setName('')
      setContent('')
      setError('')
      setTimeout(() => inputRef.current?.focus(), 100)
    }
  }, [isOpen])

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    if (!name.trim()) {
      setError('Name is required')
      return
    }

    try {
      await onSubmit(name.trim(), type === 'file' ? content : undefined)
      onClose()
    } catch (error) {
      setError(error instanceof Error ? error.message : 'Failed to create item')
    }
  }

  if (!isOpen) return null

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
      <div className="bg-slate-800 rounded-lg border border-slate-700 w-full max-w-md">
        <div className="flex items-center justify-between p-4 border-b border-slate-700">
          <div className="flex items-center gap-2">
            {type === 'file' ? (
              <IoDocumentTextOutline className="w-5 h-5 text-blue-400" />
            ) : (
              <IoFolderOutline className="w-5 h-5 text-yellow-400" />
            )}
            <h2 className="text-lg font-semibold text-white">
              Create New {type === 'file' ? 'File' : 'Folder'}
            </h2>
          </div>
          <button
            onClick={onClose}
            className="text-slate-400 hover:text-slate-200 transition-colors"
            disabled={loading}
          >
            <IoCloseOutline className="w-5 h-5" />
          </button>
        </div>
        
        <form onSubmit={handleSubmit} className="p-4 space-y-4">
          {parentName && (
            <p className="text-sm text-slate-400">
              Creating in: <span className="text-slate-300">{parentName}</span>
            </p>
          )}
          
          <div>
            <label htmlFor="name" className="block text-sm font-medium text-slate-300 mb-1">
              {type === 'file' ? 'File name' : 'Folder name'}
            </label>
            <input
              ref={inputRef}
              type="text"
              id="name"
              value={name}
              onChange={(e) => {
                setName(e.target.value)
                setError('')
              }}
              placeholder={type === 'file' ? 'chapter-1' : 'characters'}
              className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-white placeholder-slate-400"
              disabled={loading}
            />
          </div>

          {type === 'file' && (
            <div>
              <label htmlFor="content" className="block text-sm font-medium text-slate-300 mb-1">
                Initial content (optional)
              </label>
              <textarea
                id="content"
                value={content}
                onChange={(e) => setContent(e.target.value)}
                placeholder="# Chapter 1&#10;&#10;Write your content here..."
                rows={4}
                className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-white placeholder-slate-400 resize-none"
                disabled={loading}
              />
            </div>
          )}

          {error && (
            <div className="text-red-400 text-sm">{error}</div>
          )}

          <div className="flex gap-3 pt-2">
            <button
              type="button"
              onClick={onClose}
              className="flex-1 px-4 py-2 text-sm font-medium text-slate-300 bg-slate-700 border border-slate-600 rounded-md hover:bg-slate-600 transition-colors"
              disabled={loading}
            >
              Cancel
            </button>
            <button
              type="submit"
              disabled={loading || !name.trim()}
              className="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
            >
              {loading ? 'Creating...' : `Create ${type === 'file' ? 'File' : 'Folder'}`}
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}

export function RenameDialog({ 
  isOpen, 
  onClose, 
  onSubmit, 
  currentName, 
  type, 
  loading = false 
}: RenameDialogProps) {
  const [name, setName] = useState('')
  const [error, setError] = useState('')
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    if (isOpen) {
      setName(currentName)
      setError('')
      setTimeout(() => {
        inputRef.current?.focus()
        inputRef.current?.select()
      }, 100)
    }
  }, [isOpen, currentName])

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    if (!name.trim()) {
      setError('Name is required')
      return
    }

    if (name.trim() === currentName) {
      onClose()
      return
    }

    try {
      await onSubmit(name.trim())
      onClose()
    } catch (error) {
      setError(error instanceof Error ? error.message : 'Failed to rename item')
    }
  }

  if (!isOpen) return null

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
      <div className="bg-slate-800 rounded-lg border border-slate-700 w-full max-w-md">
        <div className="flex items-center justify-between p-4 border-b border-slate-700">
          <div className="flex items-center gap-2">
            {type === 'file' ? (
              <IoDocumentTextOutline className="w-5 h-5 text-blue-400" />
            ) : (
              <IoFolderOutline className="w-5 h-5 text-yellow-400" />
            )}
            <h2 className="text-lg font-semibold text-white">
              Rename {type === 'file' ? 'File' : 'Folder'}
            </h2>
          </div>
          <button
            onClick={onClose}
            className="text-slate-400 hover:text-slate-200 transition-colors"
            disabled={loading}
          >
            <IoCloseOutline className="w-5 h-5" />
          </button>
        </div>
        
        <form onSubmit={handleSubmit} className="p-4 space-y-4">
          <div>
            <label htmlFor="name" className="block text-sm font-medium text-slate-300 mb-1">
              New name
            </label>
            <input
              ref={inputRef}
              type="text"
              id="name"
              value={name}
              onChange={(e) => {
                setName(e.target.value)
                setError('')
              }}
              className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-white"
              disabled={loading}
            />
          </div>

          {error && (
            <div className="text-red-400 text-sm">{error}</div>
          )}

          <div className="flex gap-3 pt-2">
            <button
              type="button"
              onClick={onClose}
              className="flex-1 px-4 py-2 text-sm font-medium text-slate-300 bg-slate-700 border border-slate-600 rounded-md hover:bg-slate-600 transition-colors"
              disabled={loading}
            >
              Cancel
            </button>
            <button
              type="submit"
              disabled={loading || !name.trim() || name.trim() === currentName}
              className="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
            >
              {loading ? 'Renaming...' : 'Rename'}
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}

export function DeleteConfirmDialog({
  isOpen,
  onClose,
  onConfirm,
  itemName,
  itemType,
  loading = false
}: DeleteConfirmDialogProps) {
  const handleConfirm = async () => {
    try {
      await onConfirm()
      onClose()
    } catch (error) {
      // Error handling is done in the parent component
    }
  }

  if (!isOpen) return null

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
      <div className="bg-slate-800 rounded-lg border border-slate-700 w-full max-w-md">
        <div className="flex items-center justify-between p-4 border-b border-slate-700">
          <div className="flex items-center gap-2">
            <IoTrashOutline className="w-5 h-5 text-red-400" />
            <h2 className="text-lg font-semibold text-white">
              Delete {itemType === 'file' ? 'File' : 'Folder'}
            </h2>
          </div>
          <button
            onClick={onClose}
            className="text-slate-400 hover:text-slate-200 transition-colors"
            disabled={loading}
          >
            <IoCloseOutline className="w-5 h-5" />
          </button>
        </div>
        
        <div className="p-4 space-y-4">
          <div className="flex items-center gap-3">
            {itemType === 'file' ? (
              <IoDocumentTextOutline className="w-8 h-8 text-blue-400 flex-shrink-0" />
            ) : (
              <IoFolderOutline className="w-8 h-8 text-yellow-400 flex-shrink-0" />
            )}
            <div>
              <p className="text-white font-medium">{itemName}</p>
              <p className="text-sm text-slate-400">
                {itemType === 'file' 
                  ? 'Are you sure you want to delete this file? This action cannot be undone.'
                  : 'Are you sure you want to delete this folder and all its contents? This action cannot be undone.'
                }
              </p>
            </div>
          </div>

          <div className="flex gap-3 pt-2">
            <button
              type="button"
              onClick={onClose}
              className="flex-1 px-4 py-2 text-sm font-medium text-slate-300 bg-slate-700 border border-slate-600 rounded-md hover:bg-slate-600 transition-colors"
              disabled={loading}
            >
              Cancel
            </button>
            <button
              onClick={handleConfirm}
              disabled={loading}
              className="flex-1 px-4 py-2 text-sm font-medium text-white bg-red-600 border border-red-600 rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
            >
              {loading ? 'Deleting...' : 'Delete'}
            </button>
          </div>
        </div>
      </div>
    </div>
  )
}