Skip to content

Commit

Permalink
feat: Implement a soft delete with a bin (#106)
Browse files Browse the repository at this point in the history
Co-authored-by: Reza Rahemtola <[email protected]>
  • Loading branch information
EdenComp and RezaRahemtola authored Nov 25, 2022
1 parent aa606cb commit 434c5eb
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 12 deletions.
1 change: 1 addition & 0 deletions pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const Login = (): JSX.Element => {
toast({ title: login.message, status: 'success' });
setUser(login.user);
router.push('/dashboard');
await login.user.drive.autoDelete()
} else {
toast({ title: login.message, status: 'error' });
}
Expand Down
20 changes: 19 additions & 1 deletion src/components/DisplayCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ProgramCards from 'components/ProgramCards';

import { useDriveContext } from 'contexts/drive';
import { useUserContext } from 'contexts/user';
import DeleteBin from "./file/DeleteBin";

type CardsProps = {
myPrograms: IPCProgram[];
Expand Down Expand Up @@ -60,7 +61,7 @@ const DisplayCards = ({
</Box>
</HStack>
<DriveCards
files={files.filter((elem) => elem.path === path)}
files={files.filter((elem) => elem.path === path && !elem.deletedAt)}
folders={folders.filter((elem) => elem.path === path)}
/>
</VStack>
Expand Down Expand Up @@ -107,6 +108,23 @@ const DisplayCards = ({
</VStack>
);
if (index === 4) return <ProfileCard profile={user.contact.contacts[0]} />;
if (index === 5) {
const deletedFiles = files.filter((elem) => elem.path === path && elem.deletedAt !== null)
const deletedFolders = folders.filter((elem) => elem.path === path)

return (
<VStack w="100%" id="test" spacing="16px" mt={{ base: '64px', lg: '0px' }}>
<Box w="100%">
<Text fontSize="35" textColor={colorText}>
Your bin
</Text>
</Box>
<DeleteBin files={deletedFiles} folders={deletedFolders} concernedFiles={sharedFiles} />
<DriveCards files={deletedFiles} folders={deletedFolders} />
</VStack>
);
}

return <ConfigPage />;
};

Expand Down
20 changes: 14 additions & 6 deletions src/components/DriveCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { IPCFile, IPCFolder } from 'types/types';
import { useConfigContext } from 'contexts/config';
import { useDriveContext } from 'contexts/drive';
import { FcFile, FcFolder } from 'react-icons/fc';
import RestoreFile from "./file/RestoreFile";

const PathCard = (): JSX.Element => {
const { path, setPath } = useDriveContext();
Expand Down Expand Up @@ -140,12 +141,19 @@ const DriveCards = ({ files, folders }: DriveCardsProps): JSX.Element => {
</PopoverTrigger>
<Portal>
<PopoverContent w="300px" backgroundColor={config?.theme ?? 'white'}>
<DownloadFile file={file} />
<ShareFile file={file} />
<MoveFile file={file} />
<RenameFile file={file} concernedFiles={files} />
<UpdateContentFile file={file} />
<DeleteFile file={file} concernedFiles={files} />
<>
{file.deletedAt === null ? (
<>
<DownloadFile file={file} />
<ShareFile file={file} />
<MoveFile file={file} />
<RenameFile file={file} concernedFiles={files} />
<UpdateContentFile file={file} />
</>
) : <RestoreFile file={file} concernedFiles={files} />
}
<DeleteFile file={file} concernedFiles={files} />
</>
</PopoverContent>
</Portal>
</Popover>
Expand Down
1 change: 1 addition & 0 deletions src/components/ResponsiveBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const LeftBar = ({
myFilesTab="My files"
myProgramsTab="My programs"
profileTab="My profile"
binTab="Bin"
configTab="Config"
sharedFilesTab="Shared with me"
setSelectedTab={setSelectedTab}
Expand Down
10 changes: 10 additions & 0 deletions src/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type SideBarPropsType = {
sharedFilesTab: string;
myProgramsTab: string;
profileTab: string;
binTab: string;
configTab: string;
deployButton: JSX.Element;
setSelectedTab: (tab: number) => void;
Expand All @@ -43,6 +44,7 @@ const SideBar = ({
myProgramsTab,
profileTab,
configTab,
binTab,
deployButton,
githubButton,
setSelectedTab,
Expand Down Expand Up @@ -139,6 +141,14 @@ const SideBar = ({
>
{profileTab}
</Tab>
<Tab
borderLeft={`5px solid ${colors.blue[700]}`}
_selected={{
borderLeft: `5px solid ${colors.red[700]}`,
}}
>
{binTab}
</Tab>
<Tab
borderLeft={`5px solid ${colors.blue[700]}`}
_selected={{
Expand Down
84 changes: 84 additions & 0 deletions src/components/file/DeleteBin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {Button, Text, useColorModeValue, useDisclosure, useToast} from "@chakra-ui/react";
import {useState} from "react";
import {IPCFile, IPCFolder} from "../../types/types";
import Modal from "../Modal";
import {useUserContext} from "../../contexts/user";
import {useDriveContext} from "../../contexts/drive";

type DeleteBinProps = {
files: IPCFile[];
folders: IPCFolder[];
concernedFiles: IPCFile[];
};

const DeleteBin = ({ files, folders, concernedFiles }: DeleteBinProps): JSX.Element => {
const colorText = useColorModeValue('gray.800', 'white');
const toast = useToast({ duration: 2000, isClosable: true });

const { user } = useUserContext();
const { setFiles } = useDriveContext()

const [isLoading, setIsLoading] = useState(false);
const { isOpen, onOpen, onClose } = useDisclosure();

const deleteAllFiles = async () => {
setIsLoading(true);
if (user.account) {
const deleted = await user.drive.delete(files.map((file) => file.hash));
if (deleted.success) {
const removed = await user.contact.deleteFiles(files.map((file) => file.id), concernedFiles);

if (!removed.success) {
toast({ title: removed.message, status: 'error' });
} else {
setFiles(user.drive.files);
toast( { title: "All the files in your bin have been deleted.", status: 'success' })
}
}
} else {
toast({ title: 'Failed to load account', status: 'error' });
}
setIsLoading(false);
onClose();
}

if (files.length === 0 && folders.length === 0) {
return (
<Text fontSize="24" textColor={colorText}>
Your bin is empty
</Text>
)
}

return (
<>
<Button
variant="inline"
onClick={() => onOpen()}
>
Delete all files
</Button>
<Modal
isOpen={isOpen}
onClose={onClose}
title="Delete the bin"
CTA={
<Button
variant="inline"
w="100%"
mb="16px"
onClick={async () => deleteAllFiles()}
isLoading={isLoading}
id="ipc-dashboard-delete-bin-button"
>
Delete
</Button>
}
>
<Text>Are you sure you want to delete all the files in your bin?</Text>
</Modal>
</>
)
}

export default DeleteBin
34 changes: 29 additions & 5 deletions src/components/file/DeleteFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type DeleteFileProps = {

const DeleteFile = ({ file, concernedFiles }: DeleteFileProps): JSX.Element => {
const { user } = useUserContext();
const { setFiles } = useDriveContext();
const { files, setFiles } = useDriveContext();
const toast = useToast({ duration: 2000, isClosable: true });
const { config } = useConfigContext();
const colorText = useColorModeValue('gray.800', 'white');
Expand All @@ -28,7 +28,6 @@ const DeleteFile = ({ file, concernedFiles }: DeleteFileProps): JSX.Element => {
setIsLoading(true);
if (user.account) {
const deleted = await user.drive.delete([file.hash]);

toast({ title: deleted.message, status: deleted.success ? 'success' : 'error' });
if (deleted.success) {
const removed = await user.contact.deleteFiles([file.id], concernedFiles);
Expand All @@ -46,6 +45,31 @@ const DeleteFile = ({ file, concernedFiles }: DeleteFileProps): JSX.Element => {
onClose();
};

const moveToBin = async (deletedAt: number) => {
setIsLoading(true);
if (user.account) {
const moved = await user.contact.moveFileToBin(file, deletedAt, concernedFiles)
toast({ title: moved.message, status: moved.success ? 'success' : 'error' });

const index = files.indexOf(file);
if (index !== -1) {
files[index].deletedAt = deletedAt;
setFiles([...files]);
}
} else {
toast({ title: 'Failed to load account', status: 'error' });
}
setIsLoading(false);
};

const onBinClicked = async () => {
if (!file.deletedAt) {
await moveToBin(Date.now());
} else {
onOpen();
}
}

if (!['owner', 'editor'].includes(file.permission)) return <></>;

return (
Expand All @@ -58,11 +82,11 @@ const DeleteFile = ({ file, concernedFiles }: DeleteFileProps): JSX.Element => {
w="100%"
p="0px"
mx="4px"
onClick={() => onOpen()}
isLoading={false}
onClick={onBinClicked}
isLoading={isLoading}
id="ipc-dashboard-delete-file-button"
>
Delete
{file.deletedAt === null ? 'Move to bin' : 'Delete'}
</Button>
<Modal
isOpen={isOpen}
Expand Down
65 changes: 65 additions & 0 deletions src/components/file/RestoreFile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Button, HStack, PopoverFooter, useColorModeValue, useToast } from '@chakra-ui/react';
import {FcRedo} from 'react-icons/fc';

import type { IPCFile } from 'types/types';

import { useConfigContext } from 'contexts/config';
import { useDriveContext } from 'contexts/drive';
import { useUserContext } from 'contexts/user';
import { useState } from 'react';

type DeleteFileProps = {
file: IPCFile;
concernedFiles: IPCFile[];
};

const DeleteFile = ({ file, concernedFiles }: DeleteFileProps): JSX.Element => {
const { user } = useUserContext();
const { files, setFiles } = useDriveContext();
const toast = useToast({ duration: 2000, isClosable: true });
const { config } = useConfigContext();
const colorText = useColorModeValue('gray.800', 'white');

const [isLoading, setIsLoading] = useState(false);

const restoreFile = async () => {
setIsLoading(true);
if (user.account) {
const moved = await user.contact.moveFileToBin(file, null, concernedFiles)
toast({ title: moved.message, status: moved.success ? 'success' : 'error' });

const index = files.indexOf(file);
if (index !== -1) {
files[index].deletedAt = null;
setFiles([...files]);
}
} else {
toast({ title: 'Failed to load account', status: 'error' });
}
setIsLoading(false);
};

if (!['owner', 'editor'].includes(file.permission)) return <></>;

return (
<PopoverFooter>
<HStack>
<FcRedo size="30"></FcRedo>
<Button
backgroundColor={config?.theme ?? 'white'}
textColor={colorText}
w="100%"
p="0px"
mx="4px"
onClick={async () => restoreFile()}
isLoading={isLoading}
id="ipc-dashboard-restore-file-button"
>
Restore
</Button>
</HStack>
</PopoverFooter>
);
};

export default DeleteFile;
1 change: 1 addition & 0 deletions src/components/file/UploadFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const UploadFile = (): JSX.Element => {
createdAt: Date.now(),
key: { iv: '', ephemPublicKey: '', ciphertext: '', mac: '' },
path,
deletedAt: null,
permission: 'owner',
};

Expand Down
36 changes: 36 additions & 0 deletions src/lib/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ class Contact {
} else if (type === 'delete') {
const owner = this.contacts.find((co) => co.address === c.address)!;
owner.files = owner.files.filter((f) => f.id !== fileId);
} else if (type === 'bin') {
foundFile.deletedAt = file.deletedAt;
}
}
});
Expand Down Expand Up @@ -279,6 +281,40 @@ class Contact {
}
}

public async moveFileToBin(concernedFile: IPCFile, deletedAt: number | null, sharedFiles: IPCFile[]): Promise<ResponseType> {
try {
let fileFound = false;
this.contacts.forEach(async (contact) => {
const file = contact.files.find((f) => f.id === concernedFile.id);

if (file) {
file.deletedAt = deletedAt;
fileFound = true;
await this.publishAggregate();
}
});
if (!fileFound) {
const file = sharedFiles.find((f) => f.id === concernedFile.id);
if (!file) {
return { success: false, message: 'File not found' };
}
await post.Publish({
account: this.account!,
postType: 'InterPlanetaryCloud',
content: { file: { ...concernedFile, deletedAt }, tags: ['bin', concernedFile.id] },
channel: 'TEST',
APIServer: DEFAULT_API_V2,
inlineRequested: true,
storageEngine: ItemType.ipfs,
});
}
return { success: true, message: `File ${deletedAt === null ? "removed from" : "moved to"} the bin` };
} catch (err) {
console.error(err);
return { success: false, message: `Failed to ${deletedAt === null ? "remove the file from" : "move the file to"} the bin` };
}
}

public async addFileToContact(contactAddress: string, mainFile: IPCFile): Promise<ResponseType> {
try {
if (this.account) {
Expand Down
Loading

0 comments on commit 434c5eb

Please sign in to comment.