diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-15 12:52:11 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-15 12:52:11 +0000 |
| commit | b54f6f03150dd78d86db62201b6386bf14b72394 (patch) | |
| tree | b3092bb34805fdc65eee5282e86a9fb90ba20d6e /components/file-manager/FileManager.tsx | |
| parent | c1bd1a2f499ee2f0742170021b37dab410983ab7 (diff) | |
(대표님) 커버, 데이터룸, 파일매니저, 담당자할당 등
Diffstat (limited to 'components/file-manager/FileManager.tsx')
| -rw-r--r-- | components/file-manager/FileManager.tsx | 729 |
1 files changed, 244 insertions, 485 deletions
diff --git a/components/file-manager/FileManager.tsx b/components/file-manager/FileManager.tsx index fa2d8c38..f92f6b04 100644 --- a/components/file-manager/FileManager.tsx +++ b/components/file-manager/FileManager.tsx @@ -266,10 +266,6 @@ const TreeItem: React.FC<{ {isInternalUser && ( <> - <DropdownMenuItem onClick={() => onShare(item)}> - <Share2 className="h-4 w-4 mr-2" /> - Share - </DropdownMenuItem> {item.permissions?.canEdit && ( <DropdownMenuItem onClick={() => onRename(item)}> @@ -624,44 +620,6 @@ export function FileManager({ projectId }: FileManagerProps) { } }; - // Share file - const shareFile = async () => { - if (!selectedFile) return; - - try { - const response = await fetch(`/api/data-room/${projectId}/share`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - fileId: selectedFile.id, - ...shareSettings, - }), - }); - - if (!response.ok) { - throw new Error('Failed to create share link'); - } - - const data = await response.json(); - - // Copy share link to clipboard - await navigator.clipboard.writeText(data.shareUrl); - - toast({ - title: 'Share Link Created', - description: 'Link copied to clipboard.', - }); - - setShareDialogOpen(false); - setSelectedFile(null); - } catch (error) { - toast({ - title: 'Error', - description: 'Failed to create share link.', - variant: 'destructive', - }); - } - }; // Download multiple files const downloadMultipleFiles = async (itemIds: string[]) => { @@ -974,9 +932,9 @@ export function FileManager({ projectId }: FileManagerProps) { }; return ( - <div className="flex flex-col h-full"> - {/* Toolbar */} - <div className="border-b p-4"> + <div className="h-full flex flex-col min-h-0"> + {/* Toolbar - 고정 */} + <div className="border-b p-4 bg-background shrink-0"> <div className="flex items-center justify-between mb-3"> <div className="flex items-center gap-2"> {isInternalUser && ( @@ -1091,204 +1049,114 @@ export function FileManager({ projectId }: FileManagerProps) { </Breadcrumb> </div> - {/* File List */} - <ScrollArea className="flex-1 p-4"> - {loading ? ( - <div className="flex justify-center items-center h-64"> - <div className="text-muted-foreground">Loading...</div> - </div> - ) : filteredItems.length === 0 ? ( - <div className="flex flex-col items-center justify-center h-64"> - <Folder className="h-12 w-12 text-muted-foreground mb-2" /> - <p className="text-muted-foreground">Empty</p> - </div> - ) : viewMode === 'grid' ? ( - <div className="grid grid-cols-6 gap-4"> - {filteredItems.map((item) => { - const CategoryIcon = categoryConfig[item.category].icon; - const categoryColor = categoryConfig[item.category].color; - - return ( - <ContextMenu key={item.id}> - <ContextMenuTrigger> - <div - className={cn( - "flex flex-col items-center p-3 rounded-lg cursor-pointer transition-colors", - "hover:bg-accent", - selectedItems.has(item.id) && "bg-accent" - )} - onClick={() => toggleItemSelection(item.id)} - onDoubleClick={() => { - if (item.type === 'folder') { - handleFolderOpen(item); - } - }} - > - <div className="relative"> - {item.type === 'folder' ? ( - <Folder className="h-12 w-12 text-blue-500" /> - ) : ( - <File className="h-12 w-12 text-gray-500" /> - )} - <CategoryIcon className={cn("h-4 w-4 absolute -bottom-1 -right-1", categoryColor)} /> - </div> - - <span className="mt-2 text-sm text-center truncate w-full"> - {item.name} - </span> + {/* File List - 스크롤 가능 영역 */} + <div className="flex-1 min-h-0"> + <ScrollArea className="h-full"> + <div className="p-4"> + {loading ? ( + <div className="flex justify-center items-center h-64"> + <div className="text-muted-foreground">Loading...</div> + </div> + ) : filteredItems.length === 0 ? ( + <div className="flex flex-col items-center justify-center h-64"> + <Folder className="h-12 w-12 text-muted-foreground mb-2" /> + <p className="text-muted-foreground">Empty</p> + </div> + ) : viewMode === 'grid' ? ( + <div className="grid grid-cols-6 gap-4"> + {filteredItems.map((item) => { + const CategoryIcon = categoryConfig[item.category].icon; + const categoryColor = categoryConfig[item.category].color; - {item.viewCount !== undefined && ( - <div className="flex items-center gap-2 mt-1"> - <span className="text-xs text-muted-foreground flex items-center"> - <Eye className="h-3 w-3 mr-1" /> - {item.viewCount} - </span> - {item.downloadCount !== undefined && ( - <span className="text-xs text-muted-foreground flex items-center"> - <Download className="h-3 w-3 mr-1" /> - {item.downloadCount} - </span> + return ( + <ContextMenu key={item.id}> + <ContextMenuTrigger> + <div + className={cn( + "flex flex-col items-center p-3 rounded-lg cursor-pointer transition-colors", + "hover:bg-accent", + selectedItems.has(item.id) && "bg-accent" )} - </div> - )} - </div> - </ContextMenuTrigger> - - <ContextMenuContent> - {item.type === 'folder' && ( - <> - <ContextMenuItem onClick={() => handleFolderOpen(item)}> - Open - </ContextMenuItem> - <ContextMenuItem onClick={() => downloadFolder(item)}> - <Download className="h-4 w-4 mr-2" /> - Download Folder - </ContextMenuItem> - </> - )} - - {item.type === 'file' && ( - <> - <ContextMenuItem onClick={() => viewFile(item)}> - <Eye className="h-4 w-4 mr-2" /> - View - </ContextMenuItem> - {item.permissions?.canDownload === 'true' && ( - <ContextMenuItem onClick={() => downloadFile(item)}> - <Download className="h-4 w-4 mr-2" /> - Download - </ContextMenuItem> - )} - </> - )} - - {isInternalUser && ( - <> - <ContextMenuSeparator /> - <ContextMenuSub> - <ContextMenuSubTrigger> - <Shield className="h-4 w-4 mr-2" /> - Change Category - </ContextMenuSubTrigger> - <ContextMenuSubContent> - {Object.entries(categoryConfig).map(([key, config]) => ( - <ContextMenuItem - key={key} - onClick={() => { - if (item.type === 'folder') { - // Show dialog for folders - setSelectedFile(item); - setNewCategory(key); - setCategoryDialogOpen(true); - } else { - // Change immediately for files - changeCategory(item.id, key, false); - } - }} - > - <config.icon className={cn("h-4 w-4 mr-2", config.color)} /> - {config.label} - </ContextMenuItem> - ))} - </ContextMenuSubContent> - </ContextMenuSub> - - <ContextMenuItem - onClick={() => { - setSelectedFile(item); - setShareDialogOpen(true); + onClick={() => toggleItemSelection(item.id)} + onDoubleClick={() => { + if (item.type === 'folder') { + handleFolderOpen(item); + } }} > - <Share2 className="h-4 w-4 mr-2" /> - Share - </ContextMenuItem> - - {item.permissions?.canEdit && ( - <ContextMenuItem onClick={() => { - setSelectedFile(item); - setDialogValue(item.name); - setRenameDialogOpen(true); - }}> - <Edit2 className="h-4 w-4 mr-2" /> - Rename - </ContextMenuItem> - )} - </> - )} + <div className="relative"> + {item.type === 'folder' ? ( + <Folder className="h-12 w-12 text-blue-500" /> + ) : ( + <File className="h-12 w-12 text-gray-500" /> + )} + <CategoryIcon className={cn("h-4 w-4 absolute -bottom-1 -right-1", categoryColor)} /> + </div> + + <span className="mt-2 text-sm text-center truncate w-full"> + {item.name} + </span> - {item.permissions?.canDelete && ( - <> - <ContextMenuSeparator /> - <ContextMenuItem - className="text-destructive" - onClick={() => deleteItems([item.id])} - > - <Trash2 className="h-4 w-4 mr-2" /> - Delete - </ContextMenuItem> - </> - )} - </ContextMenuContent> - </ContextMenu> - ); - })} - </div> - ) : ( - // Tree View - <div className="space-y-1"> - {filteredTreeItems.map((item) => ( - <TreeItem - key={item.id} - item={item} - level={0} - expandedFolders={expandedFolders} - selectedItems={selectedItems} - onToggleExpand={toggleFolderExpand} - onSelectItem={toggleItemSelection} - onDoubleClick={handleFolderOpen} - onView={viewFile} - onDownload={downloadFile} - onDownloadFolder={downloadFolder} - onDelete={deleteItems} - onShare={(item) => { - setSelectedFile(item); - setShareDialogOpen(true); - }} - onRename={(item) => { - setSelectedFile(item); - setDialogValue(item.name); - setRenameDialogOpen(true); - }} - isInternalUser={isInternalUser} - /> - ))} + {item.viewCount !== undefined && ( + <div className="flex items-center gap-2 mt-1"> + <span className="text-xs text-muted-foreground flex items-center"> + <Eye className="h-3 w-3 mr-1" /> + {item.viewCount} + </span> + {item.downloadCount !== undefined && ( + <span className="text-xs text-muted-foreground flex items-center"> + <Download className="h-3 w-3 mr-1" /> + {item.downloadCount} + </span> + )} + </div> + )} + </div> + </ContextMenuTrigger> + + {/* ... ContextMenuContent는 동일 ... */} + </ContextMenu> + ); + })} + </div> + ) : ( + // Tree View + <div className="space-y-1"> + {filteredTreeItems.map((item) => ( + <TreeItem + key={item.id} + item={item} + level={0} + expandedFolders={expandedFolders} + selectedItems={selectedItems} + onToggleExpand={toggleFolderExpand} + onSelectItem={toggleItemSelection} + onDoubleClick={handleFolderOpen} + onView={viewFile} + onDownload={downloadFile} + onDownloadFolder={downloadFolder} + onDelete={deleteItems} + onShare={(item) => { + setSelectedFile(item); + setShareDialogOpen(true); + }} + onRename={(item) => { + setSelectedFile(item); + setDialogValue(item.name); + setRenameDialogOpen(true); + }} + isInternalUser={isInternalUser} + /> + ))} + </div> + )} </div> - )} - </ScrollArea> + </ScrollArea> + </div> {/* Upload Dialog */} <Dialog open={uploadDialogOpen} onOpenChange={setUploadDialogOpen}> - <DialogContent className="max-w-2xl"> + <DialogContent className="max-w-2xl max-h-[90vh] flex flex-col"> <DialogHeader> <DialogTitle>Upload Files</DialogTitle> <DialogDescription> @@ -1296,138 +1164,154 @@ export function FileManager({ projectId }: FileManagerProps) { </DialogDescription> </DialogHeader> - <div className="space-y-4"> - {/* Category Selection */} - <div> - <Label htmlFor="upload-category">Category</Label> - <Select value={uploadCategory} onValueChange={setUploadCategory}> - <SelectTrigger> - <SelectValue /> - </SelectTrigger> - <SelectContent> - {Object.entries(categoryConfig) - .filter(([key]) => { - // 현재 폴더가 있는 경우 - if (currentParentId) { - const currentFolder = items.find(item => item.parentId === currentParentId); - // 현재 폴더가 public이 아니면 public 옵션 제외 - if (currentFolder && currentFolder.category !== 'public') { - return key !== 'public'; + <ScrollArea className="flex-1 pr-4"> + <div className="space-y-4"> + {/* Category Selection */} + <div> + <Label htmlFor="upload-category">Category</Label> + <Select value={uploadCategory} onValueChange={setUploadCategory}> + <SelectTrigger> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {Object.entries(categoryConfig) + .filter(([key]) => { + // 현재 폴더가 있는 경우 + if (currentParentId) { + const currentFolder = items.find(item => item.parentId === currentParentId); + // 현재 폴더가 public이 아니면 public 옵션 제외 + if (currentFolder && currentFolder.category !== 'public') { + return key !== 'public'; + } } - } - // 루트 폴더이거나 현재 폴더가 public인 경우 모든 옵션 표시 - return true; - }) - .map(([key, config]) => ( - <SelectItem key={key} value={key}> - <div className="flex items-center"> - <config.icon className={cn("h-4 w-4 mr-2", config.color)} /> - <span>{config.label}</span> - </div> - </SelectItem> - ))} - </SelectContent> - </Select> - {/* 현재 폴더 정보 표시 (선택사항) */} - {currentParentId && (() => { - const currentFolder = items.find(item => item.parentId === currentParentId); - if (currentFolder && currentFolder.category !== 'public') { - return ( - <p className="text-xs text-muted-foreground mt-1 flex items-center"> - <AlertCircle className="h-3 w-3 mr-1" /> - Current folder is {categoryConfig[currentFolder.category].label}. - Public uploads are not allowed. - </p> - ); - } - })()} - </div> + // 루트 폴더이거나 현재 폴더가 public인 경우 모든 옵션 표시 + return true; + }) + .map(([key, config]) => ( + <SelectItem key={key} value={key}> + <div className="flex items-center"> + <config.icon className={cn("h-4 w-4 mr-2", config.color)} /> + <span>{config.label}</span> + </div> + </SelectItem> + ))} + </SelectContent> + </Select> + {/* 현재 폴더 정보 표시 (선택사항) */} + {currentParentId && (() => { + const currentFolder = items.find(item => item.parentId === currentParentId); + if (currentFolder && currentFolder.category !== 'public') { + return ( + <p className="text-xs text-muted-foreground mt-1 flex items-center"> + <AlertCircle className="h-3 w-3 mr-1" /> + Current folder is {categoryConfig[currentFolder.category].label}. + Public uploads are not allowed. + </p> + ); + } + })()} + </div> - {/* Dropzone */} - <Dropzone - onDrop={(acceptedFiles: File[]) => { - handleFileUpload(acceptedFiles); - }} - accept={{ - 'application/pdf': ['.pdf'], - 'application/msword': ['.doc'], - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'], - 'application/vnd.ms-excel': ['.xls'], - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], - 'application/vnd.ms-powerpoint': ['.ppt'], - 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'], - 'text/plain': ['.txt'], - 'text/csv': ['.csv'], - 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'], - 'application/zip': ['.zip'], - 'application/x-rar-compressed': ['.rar'], - 'application/x-7z-compressed': ['.7z'], - 'application/x-dwg': ['.dwg'], - 'application/x-dxf': ['.dxf'], - }} - multiple={true} - disabled={false} - > - <DropzoneZone className="h-48 border-2 border-dashed border-gray-300 rounded-lg"> - <DropzoneInput /> - <div className="flex flex-col items-center justify-center h-full"> - <DropzoneUploadIcon className="h-12 w-12 text-muted-foreground mb-4" /> - <DropzoneTitle>Drag files or click to upload</DropzoneTitle> - <DropzoneDescription>Multiple files can be uploaded simultaneously</DropzoneDescription> - </div> - </DropzoneZone> - </Dropzone> - - {/* Uploading File List */} - {uploadingFiles.length > 0 && ( - <FileList> - <FileListHeader>Uploading Files</FileListHeader> - {uploadingFiles.map((uploadFile, index) => ( - <FileListItem key={index}> - <FileListIcon> - <File className="h-4 w-4" /> - </FileListIcon> - <FileListInfo> - <FileListName>{uploadFile.file.name}</FileListName> - <FileListDescription> - <div className="flex items-center gap-2"> - <FileListSize>{uploadFile.file.size}</FileListSize> - {uploadFile.status === 'uploading' && <span>Uploading...</span>} - {uploadFile.status === 'processing' && <span>Processing...</span>} - {uploadFile.status === 'completed' && ( - <span className="text-green-600">Complete</span> - )} - {uploadFile.status === 'error' && ( - <span className="text-red-600">{uploadFile.error}</span> + {/* Dropzone */} + <Dropzone + onDrop={(acceptedFiles: File[]) => { + handleFileUpload(acceptedFiles); + }} + accept={{ + 'application/pdf': ['.pdf'], + 'application/msword': ['.doc'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'], + 'application/vnd.ms-excel': ['.xls'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], + 'application/vnd.ms-powerpoint': ['.ppt'], + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'], + 'text/plain': ['.txt'], + 'text/csv': ['.csv'], + 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'], + 'application/zip': ['.zip'], + 'application/x-rar-compressed': ['.rar'], + 'application/x-7z-compressed': ['.7z'], + 'application/x-dwg': ['.dwg'], + 'application/x-dxf': ['.dxf'], + }} + multiple={true} + disabled={false} + > + <DropzoneZone className="h-48 border-2 border-dashed border-gray-300 rounded-lg"> + <DropzoneInput /> + <div className="flex flex-col items-center justify-center h-full"> + <DropzoneUploadIcon className="h-12 w-12 text-muted-foreground mb-4" /> + <DropzoneTitle>Drag files or click to upload</DropzoneTitle> + <DropzoneDescription>Multiple files can be uploaded simultaneously</DropzoneDescription> + </div> + </DropzoneZone> + </Dropzone> + + {/* Uploading File List */} + {uploadingFiles.length > 0 && ( + <div className="border rounded-lg p-4 bg-muted/50"> + <div className="flex items-center justify-between mb-3"> + <h4 className="font-medium text-sm"> + Uploading Files ({uploadingFiles.filter(f => f.status === 'completed').length}/{uploadingFiles.length}) + </h4> + {uploadingFiles.every(f => f.status === 'completed' || f.status === 'error') && ( + <Button + size="sm" + variant="ghost" + onClick={() => setUploadingFiles([])} + > + <X className="h-4 w-4" /> + </Button> + )} + </div> + <div className="space-y-2 max-h-[300px] overflow-y-auto"> + {uploadingFiles.map((uploadFile, index) => ( + <div key={index} className="flex items-start gap-3 p-3 bg-background rounded-md"> + <File className="h-5 w-5 mt-0.5 flex-shrink-0 text-muted-foreground" /> + <div className="flex-1 min-w-0"> + <p className="text-sm font-medium truncate">{uploadFile.file.name}</p> + <div className="flex items-center gap-2 mt-1"> + <span className="text-xs text-muted-foreground"> + {formatFileSize(uploadFile.file.size)} + </span> + <span className="text-xs"> + {uploadFile.status === 'pending' && 'Waiting...'} + {uploadFile.status === 'uploading' && 'Uploading...'} + {uploadFile.status === 'processing' && 'Processing...'} + {uploadFile.status === 'completed' && ( + <span className="text-green-600 font-medium">✓ Complete</span> + )} + {uploadFile.status === 'error' && ( + <span className="text-red-600 font-medium">✗ {uploadFile.error}</span> + )} + </span> + </div> + {(uploadFile.status === 'uploading' || uploadFile.status === 'processing') && ( + <Progress value={uploadFile.progress} className="h-1.5 mt-2" /> )} </div> - {(uploadFile.status === 'uploading' || uploadFile.status === 'processing') && ( - <Progress value={uploadFile.progress} className="h-1 mt-1" /> + {uploadFile.status === 'error' && ( + <Button + size="sm" + variant="ghost" + onClick={() => { + setUploadingFiles(prev => + prev.filter((_, i) => i !== index) + ); + }} + > + <X className="h-4 w-4" /> + </Button> )} - </FileListDescription> - </FileListInfo> - <FileListAction> - {uploadFile.status === 'error' && ( - <Button - size="sm" - variant="ghost" - onClick={() => { - setUploadingFiles(prev => - prev.filter((_, i) => i !== index) - ); - }} - > - <X className="h-4 w-4" /> - </Button> - )} - </FileListAction> - </FileListItem> - ))} - </FileList> - )} - </div> + </div> + ))} + </div> + </div> + )} + </div> + </ScrollArea> - <DialogFooter> + <DialogFooter className="mt-4"> <Button variant="outline" onClick={() => { @@ -1491,131 +1375,6 @@ export function FileManager({ projectId }: FileManagerProps) { </DialogContent> </Dialog> - {/* File Share Dialog */} - <Dialog open={shareDialogOpen} onOpenChange={setShareDialogOpen}> - <DialogContent className="max-w-md"> - <DialogHeader> - <DialogTitle>Share File</DialogTitle> - <DialogDescription> - Sharing {selectedFile?.name}. - </DialogDescription> - </DialogHeader> - - <Tabs defaultValue="link" className="w-full"> - <TabsList className="grid w-full grid-cols-2"> - <TabsTrigger value="link">Link Sharing</TabsTrigger> - <TabsTrigger value="permission">Permission Settings</TabsTrigger> - </TabsList> - - <TabsContent value="link" className="space-y-4"> - <div> - <Label htmlFor="access-level">Access Level</Label> - <Select - value={shareSettings.accessLevel} - onValueChange={(value) => setShareSettings({ ...shareSettings, accessLevel: value })} - > - <SelectTrigger> - <SelectValue /> - </SelectTrigger> - <SelectContent> - <SelectItem value="view_only"> - <div className="flex items-center"> - <Eye className="h-4 w-4 mr-2" /> - View Only - </div> - </SelectItem> - <SelectItem value="view_download"> - <div className="flex items-center"> - <Download className="h-4 w-4 mr-2" /> - View + Download - </div> - </SelectItem> - </SelectContent> - </Select> - </div> - - <div> - <Label htmlFor="password">Password (Optional)</Label> - <Input - id="password" - type="password" - value={shareSettings.password} - onChange={(e) => setShareSettings({ ...shareSettings, password: e.target.value })} - placeholder="Enter password" - /> - </div> - - <div> - <Label htmlFor="expires">Expiry Date (Optional)</Label> - <Input - id="expires" - type="datetime-local" - value={shareSettings.expiresAt} - onChange={(e) => setShareSettings({ ...shareSettings, expiresAt: e.target.value })} - /> - </div> - - <div> - <Label htmlFor="max-downloads">Max Downloads (Optional)</Label> - <Input - id="max-downloads" - type="number" - value={shareSettings.maxDownloads} - onChange={(e) => setShareSettings({ ...shareSettings, maxDownloads: e.target.value })} - placeholder="Unlimited" - /> - </div> - </TabsContent> - - <TabsContent value="permission" className="space-y-4"> - <div> - <Label htmlFor="target-domain">Target Domain</Label> - <Select> - <SelectTrigger> - <SelectValue placeholder="Select domain" /> - </SelectTrigger> - <SelectContent> - <SelectItem value="partners">Partners</SelectItem> - <SelectItem value="internal">Internal</SelectItem> - </SelectContent> - </Select> - </div> - - <div className="space-y-2"> - <Label>Permissions</Label> - <div className="space-y-2"> - <div className="flex items-center justify-between"> - <Label htmlFor="can-view" className="text-sm font-normal">View</Label> - <Switch id="can-view" defaultChecked /> - </div> - <div className="flex items-center justify-between"> - <Label htmlFor="can-download" className="text-sm font-normal">Download</Label> - <Switch id="can-download" /> - </div> - <div className="flex items-center justify-between"> - <Label htmlFor="can-edit" className="text-sm font-normal">Edit</Label> - <Switch id="can-edit" /> - </div> - <div className="flex items-center justify-between"> - <Label htmlFor="can-share" className="text-sm font-normal">Share</Label> - <Switch id="can-share" /> - </div> - </div> - </div> - </TabsContent> - </Tabs> - - <DialogFooter> - <Button variant="outline" onClick={() => setShareDialogOpen(false)}> - Cancel - </Button> - <Button onClick={shareFile}> - <Share2 className="h-4 w-4 mr-2" /> - Create Share Link - </Button> - </DialogFooter> - </DialogContent> - </Dialog> {/* Rename Dialog */} <Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}> |
