diff options
| author | diogo464 <[email protected]> | 2025-08-13 10:58:20 +0100 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2025-08-13 10:58:20 +0100 |
| commit | daca3c076675b43fcae8c362ddd9e922bb0f5e9d (patch) | |
| tree | 8acfdf0b791727bb6699f2b7c238628a0f3f766e | |
| parent | 5c48d5cc58ce5d296d770c0e16cca13204b8200f (diff) | |
Add Create Folder functionality with dialog interface
- New /api/mkdir endpoint for authenticated folder creation
- Create Folder button positioned left of Upload Files button
- Modal dialog with input validation and keyboard shortcuts
- Proper error handling and success notifications
- Uses existing CLI Drive_mkdir function for backend operations
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>
| -rw-r--r-- | frontend/app/api/mkdir/route.ts | 54 | ||||
| -rw-r--r-- | frontend/components/drive/DriveDirectoryClient.tsx | 83 |
2 files changed, 137 insertions, 0 deletions
diff --git a/frontend/app/api/mkdir/route.ts b/frontend/app/api/mkdir/route.ts new file mode 100644 index 0000000..18e1bbc --- /dev/null +++ b/frontend/app/api/mkdir/route.ts | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | import { NextRequest, NextResponse } from 'next/server' | ||
| 2 | import { Auth_get_user } from '@/lib/auth' | ||
| 3 | import { Drive_mkdir } from '@/lib/drive_server' | ||
| 4 | |||
| 5 | // POST /api/mkdir - Create directory | ||
| 6 | export async function POST(request: NextRequest) { | ||
| 7 | try { | ||
| 8 | // Check user authentication | ||
| 9 | const user = await Auth_get_user() | ||
| 10 | if (!user.isLoggedIn) { | ||
| 11 | return NextResponse.json({ error: 'User not authenticated' }, { status: 401 }) | ||
| 12 | } | ||
| 13 | |||
| 14 | // Parse request body to get directory info | ||
| 15 | const body = await request.json() | ||
| 16 | const { path, name } = body | ||
| 17 | |||
| 18 | if (!path || typeof path !== 'string') { | ||
| 19 | return NextResponse.json({ error: 'Path is required' }, { status: 400 }) | ||
| 20 | } | ||
| 21 | |||
| 22 | if (!name || typeof name !== 'string' || name.trim() === '') { | ||
| 23 | return NextResponse.json({ error: 'Directory name is required' }, { status: 400 }) | ||
| 24 | } | ||
| 25 | |||
| 26 | // Construct full directory path | ||
| 27 | const dirName = name.trim() | ||
| 28 | const fullPath = path === '/' ? `/${dirName}` : `${path}/${dirName}` | ||
| 29 | |||
| 30 | // Create directory using Drive_mkdir | ||
| 31 | try { | ||
| 32 | await Drive_mkdir(fullPath, user.email) | ||
| 33 | |||
| 34 | return NextResponse.json({ | ||
| 35 | success: true, | ||
| 36 | message: `Directory "${dirName}" created successfully`, | ||
| 37 | path: fullPath | ||
| 38 | }) | ||
| 39 | |||
| 40 | } catch (error) { | ||
| 41 | console.error(`Failed to create directory ${fullPath}:`, error) | ||
| 42 | return NextResponse.json({ | ||
| 43 | error: error instanceof Error ? error.message : 'Failed to create directory' | ||
| 44 | }, { status: 500 }) | ||
| 45 | } | ||
| 46 | |||
| 47 | } catch (error) { | ||
| 48 | console.error('Mkdir API error:', error) | ||
| 49 | return NextResponse.json( | ||
| 50 | { error: error instanceof Error ? error.message : 'Internal server error' }, | ||
| 51 | { status: 500 } | ||
| 52 | ) | ||
| 53 | } | ||
| 54 | } \ No newline at end of file | ||
diff --git a/frontend/components/drive/DriveDirectoryClient.tsx b/frontend/components/drive/DriveDirectoryClient.tsx index 4657141..eee83c3 100644 --- a/frontend/components/drive/DriveDirectoryClient.tsx +++ b/frontend/components/drive/DriveDirectoryClient.tsx | |||
| @@ -17,6 +17,7 @@ import { | |||
| 17 | Info, | 17 | Info, |
| 18 | LogIn, | 18 | LogIn, |
| 19 | LogOut, | 19 | LogOut, |
| 20 | FolderPlus, | ||
| 20 | } from "lucide-react" | 21 | } from "lucide-react" |
| 21 | import { Button } from "@/components/ui/button" | 22 | import { Button } from "@/components/ui/button" |
| 22 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" | 23 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" |
| @@ -77,8 +78,10 @@ export function DriveDirectoryClient({ path, files, breadcrumbs }: DriveDirector | |||
| 77 | const [renameDialogOpen, setRenameDialogOpen] = useState(false) | 78 | const [renameDialogOpen, setRenameDialogOpen] = useState(false) |
| 78 | const [infoDialogOpen, setInfoDialogOpen] = useState(false) | 79 | const [infoDialogOpen, setInfoDialogOpen] = useState(false) |
| 79 | const [moveDialogOpen, setMoveDialogOpen] = useState(false) | 80 | const [moveDialogOpen, setMoveDialogOpen] = useState(false) |
| 81 | const [createFolderDialogOpen, setCreateFolderDialogOpen] = useState(false) | ||
| 80 | const [currentItem, setCurrentItem] = useState<DriveLsEntry | null>(null) | 82 | const [currentItem, setCurrentItem] = useState<DriveLsEntry | null>(null) |
| 81 | const [newName, setNewName] = useState("") | 83 | const [newName, setNewName] = useState("") |
| 84 | const [newFolderName, setNewFolderName] = useState("") | ||
| 82 | const fileInputRef = useRef<HTMLInputElement>(null) | 85 | const fileInputRef = useRef<HTMLInputElement>(null) |
| 83 | const [uploading, setUploading] = useState(false) | 86 | const [uploading, setUploading] = useState(false) |
| 84 | 87 | ||
| @@ -294,6 +297,46 @@ export function DriveDirectoryClient({ path, files, breadcrumbs }: DriveDirector | |||
| 294 | window.location.reload() | 297 | window.location.reload() |
| 295 | } | 298 | } |
| 296 | 299 | ||
| 300 | const handleCreateFolder = async () => { | ||
| 301 | if (!newFolderName.trim()) return | ||
| 302 | |||
| 303 | try { | ||
| 304 | const response = await fetch('/api/mkdir', { | ||
| 305 | method: 'POST', | ||
| 306 | headers: { | ||
| 307 | 'Content-Type': 'application/json', | ||
| 308 | }, | ||
| 309 | body: JSON.stringify({ | ||
| 310 | path: path, | ||
| 311 | name: newFolderName.trim() | ||
| 312 | }) | ||
| 313 | }) | ||
| 314 | |||
| 315 | const result = await response.json() | ||
| 316 | |||
| 317 | if (response.ok) { | ||
| 318 | setCreateFolderDialogOpen(false) | ||
| 319 | setNewFolderName("") | ||
| 320 | toast({ | ||
| 321 | title: "Folder created", | ||
| 322 | description: result.message, | ||
| 323 | }) | ||
| 324 | |||
| 325 | // Refresh page to show changes | ||
| 326 | window.location.reload() | ||
| 327 | } else { | ||
| 328 | throw new Error(result.error || `Failed to create folder`) | ||
| 329 | } | ||
| 330 | } catch (error) { | ||
| 331 | console.error('Create folder error:', error) | ||
| 332 | toast({ | ||
| 333 | title: "Failed to create folder", | ||
| 334 | description: error instanceof Error ? error.message : 'Unknown error occurred', | ||
| 335 | variant: "destructive" | ||
| 336 | }) | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 297 | return ( | 340 | return ( |
| 298 | <div className="container mx-auto p-6 space-y-6"> | 341 | <div className="container mx-auto p-6 space-y-6"> |
| 299 | {/* Header with Breadcrumbs */} | 342 | {/* Header with Breadcrumbs */} |
| @@ -326,6 +369,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs }: DriveDirector | |||
| 326 | 369 | ||
| 327 | <div className="flex items-center gap-2"> | 370 | <div className="flex items-center gap-2"> |
| 328 | <Button | 371 | <Button |
| 372 | variant="secondary" | ||
| 373 | onClick={() => setCreateFolderDialogOpen(true)} | ||
| 374 | > | ||
| 375 | <FolderPlus className="mr-2 h-4 w-4" /> | ||
| 376 | Create Folder | ||
| 377 | </Button> | ||
| 378 | <Button | ||
| 329 | onClick={() => fileInputRef.current?.click()} | 379 | onClick={() => fileInputRef.current?.click()} |
| 330 | disabled={uploading} | 380 | disabled={uploading} |
| 331 | > | 381 | > |
| @@ -593,6 +643,39 @@ export function DriveDirectoryClient({ path, files, breadcrumbs }: DriveDirector | |||
| 593 | onMove={handleMove} | 643 | onMove={handleMove} |
| 594 | /> | 644 | /> |
| 595 | 645 | ||
| 646 | {/* Create Folder Dialog */} | ||
| 647 | <Dialog open={createFolderDialogOpen} onOpenChange={setCreateFolderDialogOpen}> | ||
| 648 | <DialogContent> | ||
| 649 | <DialogHeader> | ||
| 650 | <DialogTitle>Create New Folder</DialogTitle> | ||
| 651 | </DialogHeader> | ||
| 652 | <div className="space-y-4 pt-2"> | ||
| 653 | <div className="space-y-2"> | ||
| 654 | <Label htmlFor="folderName">Folder Name</Label> | ||
| 655 | <Input | ||
| 656 | id="folderName" | ||
| 657 | value={newFolderName} | ||
| 658 | onChange={(e) => setNewFolderName(e.target.value)} | ||
| 659 | onKeyDown={(e) => { | ||
| 660 | if (e.key === "Enter") { | ||
| 661 | handleCreateFolder() | ||
| 662 | } | ||
| 663 | }} | ||
| 664 | placeholder="Enter folder name" | ||
| 665 | /> | ||
| 666 | </div> | ||
| 667 | <div className="flex justify-end gap-2"> | ||
| 668 | <Button variant="outline" onClick={() => setCreateFolderDialogOpen(false)}> | ||
| 669 | Cancel | ||
| 670 | </Button> | ||
| 671 | <Button onClick={handleCreateFolder} disabled={!newFolderName.trim()}> | ||
| 672 | Create Folder | ||
| 673 | </Button> | ||
| 674 | </div> | ||
| 675 | </div> | ||
| 676 | </DialogContent> | ||
| 677 | </Dialog> | ||
| 678 | |||
| 596 | <input | 679 | <input |
| 597 | ref={fileInputRef} | 680 | ref={fileInputRef} |
| 598 | type="file" | 681 | type="file" |
