256 lines
7 KiB
TypeScript
256 lines
7 KiB
TypeScript
|
|
import { forwardRef, useState, useEffect } from 'react'
|
||
|
|
import { Dialog, Button, Input, FormItem } from '@/components/ui'
|
||
|
|
import { FaTimes } from 'react-icons/fa'
|
||
|
|
import type { FileItem } from '@/types/fileManagement'
|
||
|
|
|
||
|
|
// Create Folder Modal
|
||
|
|
export interface CreateFolderModalProps {
|
||
|
|
isOpen: boolean
|
||
|
|
onClose: () => void
|
||
|
|
onCreate: (name: string) => Promise<void>
|
||
|
|
loading?: boolean
|
||
|
|
currentFolderId?: string
|
||
|
|
}
|
||
|
|
|
||
|
|
export const CreateFolderModal = forwardRef<HTMLDivElement, CreateFolderModalProps>(
|
||
|
|
(props, ref) => {
|
||
|
|
const { isOpen, onClose, onCreate, loading = false } = props
|
||
|
|
const [folderName, setFolderName] = useState('')
|
||
|
|
const [error, setError] = useState('')
|
||
|
|
|
||
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault()
|
||
|
|
|
||
|
|
if (!folderName.trim()) {
|
||
|
|
setError('Folder name is required')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await onCreate(folderName.trim())
|
||
|
|
setFolderName('')
|
||
|
|
setError('')
|
||
|
|
onClose()
|
||
|
|
} catch (err) {
|
||
|
|
setError('Failed to create folder')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleClose = () => {
|
||
|
|
setFolderName('')
|
||
|
|
setError('')
|
||
|
|
onClose()
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog isOpen={isOpen} onClose={handleClose}>
|
||
|
|
<div ref={ref}>
|
||
|
|
<form onSubmit={handleSubmit} className="py-6">
|
||
|
|
<FormItem label="Folder Name" invalid={!!error} errorMessage={error}>
|
||
|
|
<Input
|
||
|
|
value={folderName}
|
||
|
|
onChange={(e) => setFolderName(e.target.value)}
|
||
|
|
placeholder="Enter folder name"
|
||
|
|
autoFocus
|
||
|
|
className="mt-2"
|
||
|
|
/>
|
||
|
|
</FormItem>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<div className="flex justify-end space-x-2 pt-4 border-t">
|
||
|
|
<Button variant="default" onClick={handleClose} disabled={loading}>
|
||
|
|
Cancel
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
variant="solid"
|
||
|
|
onClick={handleSubmit}
|
||
|
|
loading={loading}
|
||
|
|
disabled={!folderName.trim()}
|
||
|
|
>
|
||
|
|
Create Folder
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</Dialog>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
CreateFolderModal.displayName = 'CreateFolderModal'
|
||
|
|
|
||
|
|
// Rename Item Modal
|
||
|
|
export interface RenameItemModalProps {
|
||
|
|
isOpen: boolean
|
||
|
|
onClose: () => void
|
||
|
|
onRename: (newName: string) => Promise<void>
|
||
|
|
item?: FileItem
|
||
|
|
loading?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
export const RenameItemModal = forwardRef<HTMLDivElement, RenameItemModalProps>((props, ref) => {
|
||
|
|
const { isOpen, onClose, onRename, item, loading = false } = props
|
||
|
|
const [newName, setNewName] = useState('')
|
||
|
|
const [error, setError] = useState('')
|
||
|
|
|
||
|
|
// Update input when item changes
|
||
|
|
useEffect(() => {
|
||
|
|
if (item) {
|
||
|
|
setNewName(item.name)
|
||
|
|
}
|
||
|
|
}, [item])
|
||
|
|
|
||
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault()
|
||
|
|
|
||
|
|
if (!newName.trim()) {
|
||
|
|
setError(`${item?.type === 'folder' ? 'Folder' : 'File'} name is required`)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newName.trim() === item?.name) {
|
||
|
|
onClose()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await onRename(newName.trim())
|
||
|
|
setError('')
|
||
|
|
onClose()
|
||
|
|
} catch (err) {
|
||
|
|
setError(`Failed to rename ${item?.type}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleClose = () => {
|
||
|
|
setNewName('')
|
||
|
|
setError('')
|
||
|
|
onClose()
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!item) return null
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog isOpen={isOpen} onClose={handleClose}>
|
||
|
|
<div ref={ref} className="max-w-md">
|
||
|
|
<div className="flex items-center justify-between pb-4 border-b">
|
||
|
|
<h3 className="text-lg font-semibold">
|
||
|
|
Rename {item.type === 'folder' ? 'Folder' : 'File'}
|
||
|
|
</h3>
|
||
|
|
<Button variant="plain" size="sm" icon={<FaTimes />} onClick={handleClose} />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<form onSubmit={handleSubmit} className="py-6">
|
||
|
|
<FormItem
|
||
|
|
label={`${item.type === 'folder' ? 'Folder' : 'File'} Name`}
|
||
|
|
invalid={!!error}
|
||
|
|
errorMessage={error}
|
||
|
|
>
|
||
|
|
<Input
|
||
|
|
value={newName}
|
||
|
|
onChange={(e) => setNewName(e.target.value)}
|
||
|
|
placeholder={`Enter ${item.type} name`}
|
||
|
|
autoFocus
|
||
|
|
/>
|
||
|
|
</FormItem>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<div className="flex justify-end space-x-2 pt-4 border-t">
|
||
|
|
<Button variant="default" onClick={handleClose} disabled={loading}>
|
||
|
|
Cancel
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
variant="solid"
|
||
|
|
onClick={handleSubmit}
|
||
|
|
loading={loading}
|
||
|
|
disabled={!newName.trim()}
|
||
|
|
>
|
||
|
|
Rename
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</Dialog>
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
RenameItemModal.displayName = 'RenameItemModal'
|
||
|
|
|
||
|
|
// Delete Confirmation Modal
|
||
|
|
export interface DeleteConfirmModalProps {
|
||
|
|
isOpen: boolean
|
||
|
|
onClose: () => void
|
||
|
|
onDelete: () => Promise<void>
|
||
|
|
items: FileItem[]
|
||
|
|
loading?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
export const DeleteConfirmModal = forwardRef<HTMLDivElement, DeleteConfirmModalProps>(
|
||
|
|
(props, ref) => {
|
||
|
|
const { isOpen, onClose, onDelete, items, loading = false } = props
|
||
|
|
|
||
|
|
const handleDelete = async () => {
|
||
|
|
try {
|
||
|
|
await onDelete()
|
||
|
|
onClose()
|
||
|
|
} catch (err) {
|
||
|
|
// Error handling is done in parent component
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const folderCount = items.filter((item) => item.type === 'folder').length
|
||
|
|
const fileCount = items.filter((item) => item.type === 'file').length
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog isOpen={isOpen} onClose={onClose}>
|
||
|
|
<div ref={ref}>
|
||
|
|
<div className="flex items-center justify-between pb-4 border-b">
|
||
|
|
<h3 className="text-lg font-semibold text-red-600">Delete Items</h3>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="py-6">
|
||
|
|
<p className="text-gray-900 dark:text-gray-100 mb-4">
|
||
|
|
Are you sure you want to delete the following items? This action cannot be undone.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-2">
|
||
|
|
{folderCount > 0 && (
|
||
|
|
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||
|
|
{folderCount} folder{folderCount > 1 ? 's' : ''}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
{fileCount > 0 && (
|
||
|
|
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||
|
|
{fileCount} file{fileCount > 1 ? 's' : ''}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{items.length === 1 && (
|
||
|
|
<div className="mt-4">
|
||
|
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||
|
|
{items[0].name}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-end space-x-2 pt-4 border-t">
|
||
|
|
<Button variant="default" onClick={onClose} disabled={loading}>
|
||
|
|
Cancel
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
variant="solid"
|
||
|
|
className="bg-red-600 hover:bg-red-700 text-white"
|
||
|
|
onClick={handleDelete}
|
||
|
|
loading={loading}
|
||
|
|
>
|
||
|
|
Delete
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</Dialog>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
DeleteConfirmModal.displayName = 'DeleteConfirmModal'
|