Skip to content

Commit

Permalink
feat: added restore file functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Bhagya-Vishwakarma-20 committed Jan 31, 2025
1 parent de07e3d commit 79de675
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 17 deletions.
10 changes: 6 additions & 4 deletions src/app/actions/ftp/deleteFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ export async function deleteFile({
}) {
const client = await connectToFTP(loc);
try {
await client.remove(filename);
console.log(`File ${filename} deleted successfully.`);
await client.ensureDir('/trash');
await client.rename(`/${filename}`, `/trash/${filename}`);

console.log(`File ${filename} moved to /trash successfully.`);
} catch (e) {
const errorMsg = (e as { message: string })?.message || '';
console.error('Error deleting file:', e);
throw new Error(`Failed to delete file: ${errorMsg}`);
console.error('Error moving file to trash:', e);
throw new Error(`Failed to move file to trash: ${errorMsg}`);
} finally {
client.close();
}
Expand Down
23 changes: 23 additions & 0 deletions src/app/actions/ftp/getRecentlyDeleted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use server';

import { connectToFTP } from './connect';

export async function getRecentlyDeletedFiles(loc: string) {
const client = await connectToFTP(loc);
try {
await client.cd('trash');
const files = await client.list();
const filesData =
files?.map((file) => ({
name: file.name,
size: file.size,
modifiedAt: file.modifiedAt,
})) ?? [];
return filesData;
} catch (e) {
console.error(e);
throw e;
} finally {
client.close();
}
}
23 changes: 23 additions & 0 deletions src/app/actions/ftp/restoreFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use server';

import { connectToFTP } from './connect';

export async function restoreFile({
filename,
loc,
}: {
filename: string;
loc: string;
}) {
const client = await connectToFTP(loc);
try {
await client.rename(`/trash/${filename}`, `/${filename}`);
console.log(`File ${filename} restored successfully.`);
} catch (e) {
const errorMsg = (e as { message: string })?.message || '';
console.error('Error restoring file:', e);
throw new Error(`Failed to restore file: ${errorMsg}`);
} finally {
client.close();
}
}
119 changes: 108 additions & 11 deletions src/components/FTPComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
'use client';

import { useCallback, useEffect, useMemo, useState } from 'react';
import { Download, Edit3Icon, Trash2 } from 'lucide-react';
import { Download, Edit3Icon, Trash2, ArchiveRestore } from 'lucide-react';

import { deleteFile } from '@/app/actions/ftp/deleteFile';
import { downloadFile } from '@/app/actions/ftp/downloadFile';
import { GetFiles } from '@/app/actions/ftp/getFiles';
import { renameFile } from '@/app/actions/ftp/renameFile';
import { UploadFile } from '@/app/actions/ftp/uploadFile';
import { getRecentlyDeletedFiles } from '@/app/actions/ftp/getRecentlyDeleted';
import { ReactTable } from '@/components/ReactTable';
import { Card, Spinner, useToast } from '@sanity/ui';
import { Button } from '@/components/ui/button';
Expand All @@ -21,6 +22,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { restoreFile } from '@/app/actions/ftp/restoreFile';

export default function FTPComponent() {
const [data, setData] = useState<unknown[]>([]);
Expand All @@ -31,14 +33,18 @@ export default function FTPComponent() {
const [updatedFilename, setUpdatedFilename] = useState('');
const [loc, setLoc] = useState<'images' | 'docs'>('images');
const [editingFilename, setEditingFilename] = useState<string | null>(null);
const [isViewingDeleted, setIsViewingDeleted] = useState(false);

const toast = useToast();

const fetchData = useCallback(async () => {
try {
setIsLoading(true);
const result = await GetFiles(loc);
setData(result);
const filteredResult = result.filter(
(file: any) => !file.name.includes('trash')
);
setData(filteredResult);
setAllFiles(result);
} catch (err) {
console.log(`FTP Error: ${err}`);
Expand Down Expand Up @@ -72,10 +78,10 @@ export default function FTPComponent() {

const handleUpload = useCallback(
async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent the default form submission
event.preventDefault();
setIsUploading(true);

const formData = new FormData(event.currentTarget); // Get form data
const formData = new FormData(event.currentTarget);
const file = formData.get('file') as File;

if (!file?.name) {
Expand All @@ -87,14 +93,28 @@ export default function FTPComponent() {
setIsUploading(false);
return;
}
const fileExists = allFiles.some(
(existingFile: any) =>
existingFile.name.toLowerCase() === file.name.toLowerCase()
);

if (fileExists) {
toast.push({
status: 'error',
title: 'File already exists',
description:
'A file with this name already exists. Please rename the file and try again.',
});
setIsUploading(false);
return;
}

try {
await UploadFile({ formData, loc });
toast.push({
status: 'success',
title: 'File uploaded successfully.',
});
// Refresh the file list after upload
await fetchData();
} catch (err: unknown) {
toast.push({
Expand Down Expand Up @@ -147,6 +167,7 @@ export default function FTPComponent() {

const handleDelete = useCallback(
async (filename: string) => {
setIsLoading(true);
try {
await deleteFile({ filename, loc });
toast.push({
Expand All @@ -161,6 +182,32 @@ export default function FTPComponent() {
title: 'Failed to delete file',
description: errorMsg,
});
} finally {
setIsLoading(false);
}
},
[loc, toast, fetchData]
);

const handleRestore = useCallback(
async (filename: string) => {
setIsLoading(true);
try {
await restoreFile({ filename, loc });
toast.push({
status: 'success',
title: `File ${filename} restored successfully.`,
});
await handleRecentlyDeleted();
} catch (e) {
const errorMsg = (e as { message: string })?.message || '';
toast.push({
status: 'error',
title: 'Failed to restore file',
description: errorMsg,
});
} finally {
setIsLoading(false);
}
},
[loc, toast, fetchData]
Expand Down Expand Up @@ -206,6 +253,32 @@ export default function FTPComponent() {
[loc, toast, updatedFilename, fetchData]
);

const handleRecentlyDeleted = useCallback(async () => {
try {
setIsLoading(true);
if (!isViewingDeleted) {
const deletedFiles = await getRecentlyDeletedFiles(loc);
setData(deletedFiles);
setIsViewingDeleted(true);
toast.push({
status: 'success',
title: 'Showing recently deleted files',
});
} else {
await fetchData(); // Return to normal view
setIsViewingDeleted(false);
}
} catch (err) {
toast.push({
status: 'error',
title: 'Failed to fetch deleted files',
description: (err as Error).message,
});
} finally {
setIsLoading(false);
}
}, [loc, toast, isViewingDeleted, fetchData, handleRestore]);

const columns = useMemo(
() => [
{
Expand Down Expand Up @@ -261,12 +334,21 @@ export default function FTPComponent() {
>
<Edit3Icon className="size-5" />
</button>
<button
onClick={() => handleDelete(row.original.name)}
className="text-zinc-400 hover:text-zinc-600 transition-colors"
>
<Trash2 className="size-5" />
</button>
{isViewingDeleted ? (
<button
onClick={() => handleRestore(row.original.name)}
className="text-zinc-400 hover:text-zinc-600 transition-colors"
>
<ArchiveRestore className="size-5" />
</button>
) : (
<button
onClick={() => handleDelete(row.original.name)}
className="text-zinc-400 hover:text-zinc-600 transition-colors"
>
<Trash2 className="size-5" />
</button>
)}
{editingFilename == row.original.name && (
<Button
onClick={() => handleRename(row.original.name)}
Expand All @@ -292,8 +374,10 @@ export default function FTPComponent() {
handleDownload,
handleDelete,
handleRename,
handleRestore,
editingFilename,
updatedFilename,
isViewingDeleted,
]
);

Expand Down Expand Up @@ -338,6 +422,19 @@ export default function FTPComponent() {
<span>Upload</span>
)}
</Button>
<Button
onClick={handleRecentlyDeleted}
disabled={isUploading || isLoading}
className="flex items-center gap-2 min-w-20"
>
{isUploading ? (
<Spinner muted className="!size-5" />
) : (
<span>
{isViewingDeleted ? 'Show All Files' : 'Recently Deleted'}
</span>
)}
</Button>
</form>
</CardHeader>
{isLoading ? (
Expand Down
4 changes: 2 additions & 2 deletions src/sanity/components/CustomNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ function FTPDialogTrigger() {
<Dialog open={isDialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild>
<button className="flex items-center justify-center px-4 py-1 text-white bg-slate-800 rounded-md transition duration-200 hover:bg-slate-700">
Upload Files
File management
</button>
</DialogTrigger>
<DialogContent className="w-full max-w-none sm:max-w-full md:max-w-[90vw] z-[1000] border-2 border-zinc-500 max-h-[95vh] overflow-auto">
<DialogHeader>
<DialogTitle>Upload Files via FTP</DialogTitle>
<DialogTitle>File management via FTP</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<FTPComponent />
Expand Down

0 comments on commit 79de675

Please sign in to comment.