diff --git a/.github/assets/dashboard.png b/.github/assets/dashboard.png
deleted file mode 100644
index faf1bba9..00000000
Binary files a/.github/assets/dashboard.png and /dev/null differ
diff --git a/.github/assets/dashboardUpload.png b/.github/assets/dashboardUpload.png
deleted file mode 100644
index 7c6934bd..00000000
Binary files a/.github/assets/dashboardUpload.png and /dev/null differ
diff --git a/.github/assets/ipc-dashboard-add-a-contact.png b/.github/assets/ipc-dashboard-add-a-contact.png
new file mode 100644
index 00000000..e9313798
Binary files /dev/null and b/.github/assets/ipc-dashboard-add-a-contact.png differ
diff --git a/.github/assets/ipc-dashboard-contacts.png b/.github/assets/ipc-dashboard-contacts.png
new file mode 100644
index 00000000..2319b209
Binary files /dev/null and b/.github/assets/ipc-dashboard-contacts.png differ
diff --git a/.github/assets/ipc-dashboard-files-shared.png b/.github/assets/ipc-dashboard-files-shared.png
new file mode 100644
index 00000000..442fecd0
Binary files /dev/null and b/.github/assets/ipc-dashboard-files-shared.png differ
diff --git a/.github/assets/ipc-dashboard-my-profile.png b/.github/assets/ipc-dashboard-my-profile.png
new file mode 100644
index 00000000..771ca396
Binary files /dev/null and b/.github/assets/ipc-dashboard-my-profile.png differ
diff --git a/.github/assets/ipc-dashboard-share-a-file.png b/.github/assets/ipc-dashboard-share-a-file.png
new file mode 100644
index 00000000..89dce481
Binary files /dev/null and b/.github/assets/ipc-dashboard-share-a-file.png differ
diff --git a/.github/assets/ipc-dashboard-update-a-contact.png b/.github/assets/ipc-dashboard-update-a-contact.png
new file mode 100644
index 00000000..683ccf65
Binary files /dev/null and b/.github/assets/ipc-dashboard-update-a-contact.png differ
diff --git a/.github/assets/ipc-dashboard-upload-a-file.png b/.github/assets/ipc-dashboard-upload-a-file.png
new file mode 100644
index 00000000..d063194f
Binary files /dev/null and b/.github/assets/ipc-dashboard-upload-a-file.png differ
diff --git a/.github/assets/ipc-dashboard.png b/.github/assets/ipc-dashboard.png
new file mode 100644
index 00000000..543daeff
Binary files /dev/null and b/.github/assets/ipc-dashboard.png differ
diff --git a/.github/assets/ipc-download-a-file.png b/.github/assets/ipc-download-a-file.png
index 2bf343d7..827c28b1 100644
Binary files a/.github/assets/ipc-download-a-file.png and b/.github/assets/ipc-download-a-file.png differ
diff --git a/.github/assets/ipc-file-loading.png b/.github/assets/ipc-file-loading.png
index 5a98bfc9..6c0341c0 100644
Binary files a/.github/assets/ipc-file-loading.png and b/.github/assets/ipc-file-loading.png differ
diff --git a/.github/assets/ipc-graph.png b/.github/assets/ipc-graph.png
new file mode 100644
index 00000000..b27d0e4d
Binary files /dev/null and b/.github/assets/ipc-graph.png differ
diff --git a/.github/assets/ipc-post-message.png b/.github/assets/ipc-post-message.png
new file mode 100644
index 00000000..a5debaa1
Binary files /dev/null and b/.github/assets/ipc-post-message.png differ
diff --git a/.github/assets/ipc-share-a-file.png b/.github/assets/ipc-share-a-file.png
index 7a055f8e..52dfa9dd 100644
Binary files a/.github/assets/ipc-share-a-file.png and b/.github/assets/ipc-share-a-file.png differ
diff --git a/.github/assets/ipc-upload-a-file.png b/.github/assets/ipc-upload-a-file.png
index 9a647f21..b3cd817d 100644
Binary files a/.github/assets/ipc-upload-a-file.png and b/.github/assets/ipc-upload-a-file.png differ
diff --git a/.gitignore b/.gitignore
index 755dbaec..78e2f5a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,5 @@ cypress/screenshots
cypress/videos
.metamask
+
+/.vscode
diff --git a/README.md b/README.md
index fe0a53a7..0e94ce8f 100644
--- a/README.md
+++ b/README.md
@@ -63,17 +63,59 @@ You are now ready to access to your decentralized cloud :boom: !
Dashboard
- ![Dashboard](.github/assets/dashboard.png)
+ ![Dashboard](.github/assets/ipc-dashboard.png)
- Dashboard - Upload document
+ Dashboard - Upload a file
- ![Dashboard Upload](.github/assets/dashboardUpload.png)
+ ![Dashboard Upload](.github/assets/ipc-dashboard-upload-a-file.png)
+
+ Dashboard - Share a file
+
+![Dashboard Upload](.github/assets/ipc-dashboard-share-a-file.png)
+
+
+
+
+ Dashboard - Files shared
+
+![Dashboard Upload](.github/assets/ipc-dashboard-files-shared.png)
+
+
+
+
+ Dashboard - Contacts
+
+![Dashboard Upload](.github/assets/ipc-dashboard-contacts.png)
+
+
+
+
+ Dashboard - Add a contact
+
+![Dashboard Upload](.github/assets/ipc-dashboard-add-a-contact.png)
+
+
+
+
+ Dashboard - Update a contact
+
+![Dashboard Upload](.github/assets/ipc-dashboard-update-a-contact.png)
+
+
+
+
+ Dashboard - User's profile
+
+![Dashboard Upload](.github/assets/ipc-dashboard-my-profile.png)
+
+
+
## How ? :thinking:
**Technologies 🧑💻**
@@ -87,6 +129,78 @@ We use [Aleph SDK TS](https://github.com/aleph-im/aleph-sdk-ts#readme).
**Security 🛡️**
Every file that you upload will be encrypted thanks to [crypto-js](https://www.npmjs.com/package/crypto-js).
+**How it works?**
+
+
+ Full overview
+
+
+
+
+---
+
+- For each file, a random key is generated and the content of the file is encrypted with this key.
+- The content is pushed into a store message via the aleph network.
+- The hash of the store message and the key are added to the 'Contacts' post message.
+
+
+ Upload a file
+
+
+
+
+
+---
+
+- For each contacts into the 'Post Message - Contacts', the files and contacts are get.
+- An occurrence between the address of the user and the contacts is searched.
+- For each file found, metadata about the files are retrieved.
+
+
+ Load a file
+
+
+
+
+
+---
+
+- The content is retrieved from the aleph network from his hash.
+- The content is decrypt with the key, itself decrypt with the private key of the user.
+
+
+ Download a file
+
+
+
+
+
+---
+
+- The hash and the key are encrypted with the public key of the contact.
+- These infos are added to the list of shared files of the contact.
+
+
+ Share a file
+
+
+
+
+
+---
+
+- One post message, with the list of contacts and the list of shared files for each contacts
+- The post message contains the info about the contact, his name, address, public key and a list of shared files
+
+
+ Post messages
+
+
+
+
+
+
+
## Our PoC team :ok_hand:
### September 2021 - Today
diff --git a/cypress/integration/dashboard.spec.js b/cypress/integration/dashboard.spec.js
index 9c9ccefc..4e933fc0 100644
--- a/cypress/integration/dashboard.spec.js
+++ b/cypress/integration/dashboard.spec.js
@@ -14,55 +14,6 @@ describe('Create account for DashboardView tests', () => {
});
});
-describe('Good front for DashboardView', () => {
- it('Go to dashboard view', () => {
- cy.visit('http://localhost:3000/login');
- cy.wait(1000);
- cy.get('#ipc-loginView-text-area').click().type(dashboardSpecMnemonic);
- cy.get('#ipc-loginView-credentials-button').click();
- });
-
- it('Good title', () => {
- cy.get('#ipc-sideBar-title').should('contain', 'Inter Planetary Cloud');
- });
-
- it('Good name for upload button', () => {
- cy.get('#ipc-dashboardView-drawer-button').click({force: true});
- cy.get('#ipc-upload-button').should('contain', 'Upload a file');
- });
-});
-
-describe('Good Modal Front for DashboardView', () => {
- it('Go to upload modal into dashboard view', () => {
- cy.visit('http://localhost:3000/login');
- cy.wait(1000);
- cy.get('#ipc-loginView-text-area').click().type(dashboardSpecMnemonic);
- cy.get('#ipc-loginView-credentials-button').click().wait(3000);
- cy.get('#ipc-dashboardView-drawer-button').click({force: true});
- cy.get('#ipc-upload-button').click();
- });
-
- it('Good header', () => {
- cy.get('header').should('contain', 'Upload a file');
- });
-
- it('Good number of buttons', () => {
- cy.get('button').should('have.length', 3);
- });
-
- it('Good number of input', () => {
- cy.get('input[type=file]').should('have.length', 1);
- });
-
- it('Good name for upload a file button', () => {
- cy.get('#ipc-dashboardView-upload-file-modal-button').should('contain', 'Upload file');
- });
-
- it('Good name for close button', () => {
- cy.get('#ipc-modal-close-button').should('contain', 'Close');
- });
-});
-
describe('Upload a file modal for DashboardView', () => {
const fixtureFile = 'upload_test_file.txt';
@@ -71,20 +22,20 @@ describe('Upload a file modal for DashboardView', () => {
cy.wait(1000);
cy.get('#ipc-loginView-text-area').click().type(dashboardSpecMnemonic);
cy.get('#ipc-loginView-credentials-button').click().wait(3000);
- cy.get('#ipc-dashboardView-drawer-button').click({force: true});
- cy.get('#ipc-upload-button').click();
+ cy.get('#ipc-dashboardView-drawer-button').click({ force: true });
+ cy.get('#ipc-upload-button').click().wait(2500);
});
it('Good number of buttons after upload', () => {
cy.get('#ipc-dashboardView-upload-file').attachFile(fixtureFile);
cy.get('#ipc-dashboardView-upload-file-modal-button').click();
cy.wait(2000);
- cy.get('button').should('have.length', 2);
+ cy.get('button').should('have.length', 8);
});
it('Good number of buttons after closing modal', () => {
cy.get('#ipc-modal-close-button').click();
- cy.get('button').should('have.length', 2);
+ cy.get('button').should('have.length', 8);
});
});
@@ -94,8 +45,9 @@ describe('Download a file for DashboardView', () => {
cy.wait(1000);
cy.get('#ipc-loginView-text-area').click().type(dashboardSpecMnemonic);
cy.get('#ipc-loginView-credentials-button').click();
+ cy.wait(2500);
cy.get('#ipc-dashboardView-download-button').click();
- cy.wait(1000);
+ cy.wait(3000);
});
it('Good content for downloaded file', () => {
diff --git a/cypress/integration/dashboardFront.spec.js b/cypress/integration/dashboardFront.spec.js
new file mode 100644
index 00000000..c94cf049
--- /dev/null
+++ b/cypress/integration/dashboardFront.spec.js
@@ -0,0 +1,64 @@
+let dashboardSpecMnemonic = '';
+
+describe('Create account for DashboardView tests', () => {
+ it('Connect', () => {
+ cy.visit('http://localhost:3000/signup');
+ cy.wait(1000);
+ cy.get('#ipc-signupView-credentials-signup-button').click();
+ cy.get('#ipc-signupView-text-area')
+ .invoke('val')
+ .then((input) => {
+ dashboardSpecMnemonic = input;
+ });
+ cy.get('#ipc-modal-close-button').click();
+ });
+});
+
+describe('Good front for DashboardView', () => {
+ it('Go to dashboard view', () => {
+ cy.visit('http://localhost:3000/login');
+ cy.wait(1000);
+ cy.get('#ipc-loginView-text-area').click().type(dashboardSpecMnemonic);
+ cy.get('#ipc-loginView-credentials-button').click();
+ });
+
+ it('Good title', () => {
+ cy.get('#ipc-sideBar-title').should('contain', 'Inter Planetary Cloud');
+ });
+
+ it('Good name for upload button', () => {
+ cy.get('#ipc-dashboardView-drawer-button').click({ force: true });
+ cy.get('#ipc-upload-button').should('contain', 'Upload a file');
+ });
+});
+
+describe('Good Modal Front for DashboardView', () => {
+ it('Go to upload modal into dashboard view', () => {
+ cy.visit('http://localhost:3000/login');
+ cy.wait(1000);
+ cy.get('#ipc-loginView-text-area').click().type(dashboardSpecMnemonic);
+ cy.get('#ipc-loginView-credentials-button').click().wait(3000);
+ cy.get('#ipc-dashboardView-drawer-button').click({ force: true });
+ cy.get('#ipc-upload-button').click();
+ });
+
+ it('Good header', () => {
+ cy.get('header').should('contain', 'Upload a file');
+ });
+
+ it('Good number of buttons', () => {
+ cy.get('button').should('have.length', 8);
+ });
+
+ it('Good number of input', () => {
+ cy.get('input[type=file]').should('have.length', 1);
+ });
+
+ it('Good name for upload a file button', () => {
+ cy.get('#ipc-dashboardView-upload-file-modal-button').should('contain', 'Upload file');
+ });
+
+ it('Good name for close button', () => {
+ cy.get('#ipc-modal-close-button').should('contain', 'Close');
+ });
+});
diff --git a/package.json b/package.json
index 0bf19cf8..8cc37165 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"aleph-sdk-ts": "^2.2.1",
"crypto-js": "^4.0.0",
"env-var": "^7.1.1",
+ "eth-crypto": "^2.2.0",
"ethers": "^5.5.2",
"framer-motion": "^4.1.17",
"it-all": "^1.0.5",
@@ -23,6 +24,7 @@
"react": "^17.0.2",
"react-clipboard.js": "^2.0.16",
"react-dom": "^17.0.2",
+ "react-icons": "^4.3.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"typescript": "^4.3.4",
diff --git a/src/components/ContactCard.tsx b/src/components/ContactCard.tsx
new file mode 100644
index 00000000..0a93ab91
--- /dev/null
+++ b/src/components/ContactCard.tsx
@@ -0,0 +1,28 @@
+import { Box, Flex, Text, VStack } from '@chakra-ui/react';
+import { IPCContact } from '../types/types';
+
+type FileCardProps = {
+ contact: IPCContact;
+ children: JSX.Element;
+};
+
+export const ContactCard = ({ contact, children }: FileCardProps): JSX.Element => (
+
+
+ {contact.name}
+
+ {children}
+
+
+
+);
diff --git a/src/components/ContactCards.tsx b/src/components/ContactCards.tsx
new file mode 100644
index 00000000..39614ddb
--- /dev/null
+++ b/src/components/ContactCards.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import { Box, Button, Divider, Tooltip, VStack } from '@chakra-ui/react';
+import { CopyIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons';
+import { IPCContact } from '../types/types';
+import { ContactCard } from './ContactCard';
+
+type ContactCardsProps = {
+ contacts: IPCContact[];
+ setContactInfo: React.Dispatch>;
+ onOpenContactUpdate: () => void;
+ onOpenContactAdd: () => void;
+ deleteContact: (contactToDelete: IPCContact) => Promise;
+};
+
+export const ContactCards = ({
+ contacts,
+ setContactInfo,
+ onOpenContactUpdate,
+ onOpenContactAdd,
+ deleteContact,
+}: ContactCardsProps): JSX.Element => (
+ <>
+
+
+
+ Add a contact
+
+
+
+ {contacts.map((contact, index) => {
+ if (index !== 0)
+ return (
+
+ <>
+
+ {
+ await navigator.clipboard.writeText(contact.publicKey);
+ }}
+ >
+
+
+
+
+ {
+ setContactInfo(contact);
+ onOpenContactUpdate();
+ }}
+ >
+
+
+
+
+ deleteContact(contact)}>
+
+
+
+ >
+
+ );
+ return ;
+ })}
+ >
+);
diff --git a/src/components/CustomButtons.tsx b/src/components/CustomButtons.tsx
new file mode 100644
index 00000000..b6568b94
--- /dev/null
+++ b/src/components/CustomButtons.tsx
@@ -0,0 +1,19 @@
+import { Button } from '@chakra-ui/react';
+
+type UploadButtonProps = {
+ text: string;
+ onClick: () => void;
+ isLoading: boolean;
+};
+
+export const UploadButton = ({ text, onClick, isLoading }: UploadButtonProps): JSX.Element => (
+
+ {text}
+
+);
+
+export const ContactButton = ({ text, onClick, isLoading }: UploadButtonProps): JSX.Element => (
+
+ {text}
+
+);
diff --git a/src/components/DisplayFileCards.tsx b/src/components/DisplayFileCards.tsx
new file mode 100644
index 00000000..07d49863
--- /dev/null
+++ b/src/components/DisplayFileCards.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import { IPCContact, IPCFile } from '../types/types';
+import { FileCards } from './FileCards';
+import { ContactCards } from './ContactCards';
+import { ProfileCard } from './ProfileCard';
+
+type FileCardsProps = {
+ myFiles: IPCFile[];
+ sharedFiles: IPCFile[];
+ contacts: IPCContact[];
+ index: number;
+ downloadFile: (file: IPCFile) => Promise;
+ isDownloadLoading: boolean;
+ setSelectedFile: React.Dispatch>;
+ onOpenShare: () => void;
+ setContactInfo: React.Dispatch>;
+ onOpenContactUpdate: () => void;
+ onOpenContactAdd: () => void;
+ deleteContact: (contactToDelete: IPCContact) => Promise;
+};
+
+export const DisplayFileCards = ({
+ myFiles,
+ sharedFiles,
+ contacts,
+ index,
+ downloadFile,
+ isDownloadLoading,
+ setSelectedFile,
+ onOpenShare,
+ setContactInfo,
+ onOpenContactUpdate,
+ onOpenContactAdd,
+ deleteContact,
+}: FileCardsProps): JSX.Element => {
+ if (index === 0)
+ return (
+
+ );
+ if (index === 1)
+ return (
+
+ );
+ if (index === 2)
+ return (
+
+ );
+ return (
+
+ );
+};
diff --git a/src/components/FileCard.tsx b/src/components/FileCard.tsx
index 2b22fa5b..9855e463 100644
--- a/src/components/FileCard.tsx
+++ b/src/components/FileCard.tsx
@@ -1,6 +1,6 @@
-import { Box, Flex, Text } from '@chakra-ui/react';
+import { Box, Flex, Text, VStack } from '@chakra-ui/react';
-import { IPCFile } from 'lib/drive';
+import { IPCFile } from 'types/types';
type FileCardProps = {
file: IPCFile;
@@ -19,10 +19,14 @@ const FileCard = ({ file, children }: FileCardProps): JSX.Element => (
display="flex"
justifyContent="space-between"
>
-
- {file.name}
- {children}
-
+
+
+ {file.name}
+
+
+ {children}
+
+
);
diff --git a/src/components/FileCards.tsx b/src/components/FileCards.tsx
new file mode 100644
index 00000000..fe4a9703
--- /dev/null
+++ b/src/components/FileCards.tsx
@@ -0,0 +1,58 @@
+import { Button, Icon } from '@chakra-ui/react';
+import { DownloadIcon } from '@chakra-ui/icons';
+import { MdPeopleAlt } from 'react-icons/md';
+import React from 'react';
+import FileCard from './FileCard';
+import { IPCFile } from '../types/types';
+
+type FileCardsProps = {
+ files: IPCFile[];
+ downloadFile: (file: IPCFile) => Promise;
+ isDownloadLoading: boolean;
+ setSelectedFile: React.Dispatch>;
+ onOpenShare: () => void;
+};
+
+export const FileCards = ({
+ files,
+ downloadFile,
+ isDownloadLoading,
+ setSelectedFile,
+ onOpenShare,
+}: FileCardsProps): JSX.Element => (
+ <>
+ {files.map((file) => (
+
+ <>
+ downloadFile(file)}
+ isLoading={isDownloadLoading}
+ id="ipc-dashboardView-download-button"
+ >
+
+
+ {
+ setSelectedFile(file);
+ onOpenShare();
+ }}
+ isLoading={isDownloadLoading}
+ id="ipc-dashboardView-share-button"
+ >
+
+
+ >
+
+ ))}
+ >
+);
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
index deb3243e..f6f47052 100644
--- a/src/components/Modal.tsx
+++ b/src/components/Modal.tsx
@@ -9,7 +9,7 @@ type PopupProps = {
onClose: () => void;
title: string;
children: JSX.Element;
- CTA: JSX.Element;
+ CTA?: JSX.Element;
};
const Popup = ({ isOpen, onClose, title, children, CTA }: PopupProps): JSX.Element => (
diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx
new file mode 100644
index 00000000..2daac5a9
--- /dev/null
+++ b/src/components/ProfileCard.tsx
@@ -0,0 +1,60 @@
+import { Box, VStack, Text, Flex, Tooltip, Button } from '@chakra-ui/react';
+import { CopyIcon, EditIcon } from '@chakra-ui/icons';
+import React from 'react';
+import { IPCContact } from '../types/types';
+
+type ProfileCardProps = {
+ profile: IPCContact;
+ setContactInfo: React.Dispatch>;
+ onOpenContactUpdate: () => void;
+};
+
+export const ProfileCard = ({ profile, setContactInfo, onOpenContactUpdate }: ProfileCardProps): JSX.Element => (
+
+
+ {profile.name}
+
+ {profile.address}
+
+
+
+ {
+ await navigator.clipboard.writeText(profile.publicKey);
+ }}
+ >
+
+
+
+
+ {
+ setContactInfo(profile);
+ onOpenContactUpdate();
+ }}
+ >
+
+
+
+
+
+
+);
diff --git a/src/components/ResponsiveBar.tsx b/src/components/ResponsiveBar.tsx
new file mode 100644
index 00000000..2e8a87c8
--- /dev/null
+++ b/src/components/ResponsiveBar.tsx
@@ -0,0 +1,103 @@
+import {
+ Box,
+ Button,
+ Divider,
+ Drawer,
+ DrawerContent,
+ DrawerOverlay,
+ HStack,
+ Icon,
+ SlideDirection,
+ Text,
+ useBreakpointValue,
+ useDisclosure,
+ VStack,
+} from '@chakra-ui/react';
+import { HamburgerIcon } from '@chakra-ui/icons';
+
+import React from 'react';
+import colors from '../theme/foundations/colors';
+import Sidebar from './SideBar';
+import { UploadButton } from './CustomButtons';
+
+type BarProps = {
+ onOpen: () => void;
+ isUploadLoading: boolean;
+ setSelectedTab: React.Dispatch>;
+ selectedTab: number;
+};
+
+export const LeftBar = ({ onOpen, isUploadLoading, setSelectedTab, selectedTab }: BarProps): JSX.Element => (
+ onOpen()} isLoading={isUploadLoading} />}
+ contactTab="Contacts"
+ myFilesTab="My files"
+ profileTab="My profile"
+ sharedFilesTab="Shared with me"
+ setSelectedTab={setSelectedTab}
+ currentTabIndex={selectedTab}
+ />
+);
+
+export const BarWithDrawer = ({ onOpen, setSelectedTab, isUploadLoading, selectedTab }: BarProps): JSX.Element => {
+ const { isOpen: isOpenDrawer, onOpen: onOpenDrawer, onClose: onCloseDrawer } = useDisclosure();
+ const placement: SlideDirection = 'left';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const ResponsiveBar = ({ onOpen, setSelectedTab, isUploadLoading, selectedTab }: BarProps): JSX.Element => {
+ const isDrawerNeeded: boolean = useBreakpointValue({ base: true, xs: true, lg: false }) || false;
+
+ if (!isDrawerNeeded)
+ return (
+
+ );
+ return (
+
+ );
+};
diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx
index 647a34a6..6b8fa2a8 100644
--- a/src/components/SideBar.tsx
+++ b/src/components/SideBar.tsx
@@ -1,12 +1,27 @@
-import { Text, VStack } from '@chakra-ui/react';
+import { Tab, TabList, Tabs, Text, VStack } from '@chakra-ui/react';
import colors from 'theme/foundations/colors';
+import React from 'react';
type SideBarPropsType = {
+ contactTab: string;
+ myFilesTab: string;
+ sharedFilesTab: string;
+ profileTab: string;
uploadButton: JSX.Element;
+ setSelectedTab: React.Dispatch>;
+ currentTabIndex: number;
};
-const SideBar = ({ uploadButton }: SideBarPropsType): JSX.Element => (
+const SideBar = ({
+ contactTab,
+ myFilesTab,
+ sharedFilesTab,
+ profileTab,
+ uploadButton,
+ setSelectedTab,
+ currentTabIndex,
+}: SideBarPropsType): JSX.Element => (
(
bgGradient={`linear-gradient(90deg, ${colors.blue[700]} 0%, ${colors.red[700]} 100%)`}
bgClip="text"
id="ipc-sideBar-title"
+ pb="64px"
>
Inter Planetary Cloud
+ setSelectedTab(index)}>
+
+
+ {myFilesTab}
+
+
+ {sharedFilesTab}
+
+
+ {contactTab}
+
+
+ {profileTab}
+
+
+
+ {uploadButton}
- {uploadButton}
);
diff --git a/src/components/UploadButton.tsx b/src/components/UploadButton.tsx
deleted file mode 100644
index 34fc2305..00000000
--- a/src/components/UploadButton.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Button } from '@chakra-ui/react';
-
-type UploadButtonProps = {
- text: string;
- onClick: () => void;
- isLoading: boolean;
-};
-
-const UploadButton = ({ text, onClick, isLoading }: UploadButtonProps): JSX.Element => (
-
- {text}
-
-);
-
-export default UploadButton;
diff --git a/src/lib/contact.ts b/src/lib/contact.ts
new file mode 100644
index 00000000..8827246f
--- /dev/null
+++ b/src/lib/contact.ts
@@ -0,0 +1,237 @@
+import { accounts, post } from 'aleph-sdk-ts';
+
+import { DEFAULT_API_V2 } from 'aleph-sdk-ts/global';
+import { ItemType } from 'aleph-sdk-ts/messages/message';
+import { ALEPH_CHANNEL } from 'config/constants';
+
+import { IPCContact, IPCFile, ResponseType } from 'types/types';
+import EthCrypto from 'eth-crypto';
+
+class Contact {
+ public contacts: IPCContact[];
+
+ public contactsPostHash: string;
+
+ private readonly account: accounts.base.Account | undefined;
+
+ private private_key: string;
+
+ constructor(importedAccount: accounts.base.Account, private_key: string) {
+ this.contacts = [];
+ this.contactsPostHash = '';
+ this.account = importedAccount;
+ this.private_key = private_key;
+ }
+
+ public async load(): Promise {
+ try {
+ if (this.account) {
+ const userData = await post.Get({
+ APIServer: DEFAULT_API_V2,
+ types: '',
+ pagination: 200,
+ page: 1,
+ refs: [],
+ addresses: [this.account.address],
+ tags: [],
+ hashes: [],
+ });
+
+ userData.posts.map((postContent) => {
+ const itemContent = JSON.parse(postContent.item_content);
+ if (itemContent.content.header === 'InterPlanetaryCloud2.0 - Contacts') {
+ this.contactsPostHash = postContent.hash;
+ if (itemContent.content.contacts.length > 0) {
+ itemContent.content.contacts.map((contact: IPCContact) => {
+ this.contacts.push(contact);
+ return true;
+ });
+ }
+ return true;
+ }
+ return false;
+ });
+
+ if (this.contactsPostHash === '') {
+ console.log('Create Post Message for Contacts');
+ this.contacts.push({
+ name: 'Owner (Me)',
+ address: this.account.address,
+ publicKey: this.account.publicKey,
+ files: [],
+ });
+ const newPostPublishResponse = await post.Publish({
+ APIServer: DEFAULT_API_V2,
+ channel: ALEPH_CHANNEL,
+ inlineRequested: true,
+ storageEngine: ItemType.ipfs,
+ account: this.account,
+ postType: '',
+ content: {
+ header: 'InterPlanetaryCloud2.0 - Contacts',
+ contacts: this.contacts,
+ },
+ });
+ this.contactsPostHash = newPostPublishResponse.item_hash;
+ }
+ return { success: true, message: 'Contacts loaded' };
+ }
+ return { success: false, message: 'Failed to load account' };
+ } catch (err) {
+ console.log(err);
+ return { success: false, message: 'Failed to load contacts' };
+ }
+ }
+
+ public async add(contactToAdd: IPCContact): Promise {
+ try {
+ if (this.account) {
+ if (this.contacts.find((contact) => contact.address === contactToAdd.address)) {
+ return { success: false, message: 'Contact already exist' };
+ }
+ this.contacts.push(contactToAdd);
+
+ await post.Publish({
+ APIServer: DEFAULT_API_V2,
+ channel: ALEPH_CHANNEL,
+ inlineRequested: true,
+ storageEngine: ItemType.ipfs,
+ account: this.account,
+ postType: 'amend',
+ content: {
+ header: 'InterPlanetaryCloud2.0 - Contacts',
+ contacts: this.contacts,
+ },
+ ref: this.contactsPostHash,
+ });
+ return { success: true, message: 'Contact added' };
+ }
+ return { success: false, message: 'Failed to load account' };
+ } catch (err) {
+ console.log(err);
+ return { success: false, message: 'Failed to add this contact' };
+ }
+ }
+
+ public async remove(contactAddress: string): Promise {
+ try {
+ if (this.account) {
+ if (contactAddress !== this.account.address) {
+ this.contacts.map((contact, index) => {
+ if (contact.address === contactAddress) {
+ this.contacts.splice(index, 1);
+ return true;
+ }
+ return false;
+ });
+
+ await post.Publish({
+ APIServer: DEFAULT_API_V2,
+ channel: ALEPH_CHANNEL,
+ inlineRequested: true,
+ storageEngine: ItemType.ipfs,
+ account: this.account,
+ postType: 'amend',
+ content: {
+ header: 'InterPlanetaryCloud2.0 - Contacts',
+ contacts: this.contacts,
+ },
+ ref: this.contactsPostHash,
+ });
+ return { success: true, message: 'Contact deleted' };
+ }
+ return { success: false, message: "You can't delete your account" };
+ }
+ return { success: false, message: 'Failed to load account' };
+ } catch (err) {
+ console.log(err);
+ return { success: false, message: 'Failed to delete this contact' };
+ }
+ }
+
+ public async update(contactAddress: string, newName: string): Promise {
+ try {
+ if (this.account) {
+ if (
+ this.contacts.find((contact, index) => {
+ if (contact.address === contactAddress) {
+ this.contacts[index].name = newName;
+ return true;
+ }
+ return false;
+ })
+ ) {
+ await post.Publish({
+ APIServer: DEFAULT_API_V2,
+ channel: ALEPH_CHANNEL,
+ inlineRequested: true,
+ storageEngine: ItemType.ipfs,
+ account: this.account,
+ postType: 'amend',
+ content: {
+ header: 'InterPlanetaryCloud2.0 - Contacts',
+ contacts: this.contacts,
+ },
+ ref: this.contactsPostHash,
+ });
+ return { success: true, message: 'Contact updated' };
+ }
+ return { success: false, message: 'Contact does not exist' };
+ }
+ return { success: false, message: 'Failed to load account' };
+ } catch (err) {
+ console.log(err);
+ return { success: false, message: 'Failed to update this contact' };
+ }
+ }
+
+ public async addFileToContact(contactAddress: string, mainFile: IPCFile): Promise {
+ try {
+ if (this.account) {
+ if (
+ await Promise.all(
+ this.contacts.map(async (contact, contactIndex) => {
+ if (contact.address === contactAddress) {
+ if (this.contacts[contactIndex].files.find((file) => file.hash === mainFile.hash)) {
+ return { success: false, message: 'The file is already shared' };
+ }
+ this.contacts[contactIndex].files.push({
+ hash: mainFile.hash,
+ key: await EthCrypto.encryptWithPublicKey(
+ contact.publicKey.slice(2),
+ await EthCrypto.decryptWithPrivateKey(this.private_key, mainFile.key),
+ ),
+ created_at: mainFile.created_at,
+ name: mainFile.name,
+ });
+ await post.Publish({
+ APIServer: DEFAULT_API_V2,
+ channel: ALEPH_CHANNEL,
+ inlineRequested: true,
+ storageEngine: ItemType.ipfs,
+ account: this.account!,
+ postType: 'amend',
+ content: {
+ header: 'InterPlanetaryCloud2.0 - Contacts',
+ contacts: this.contacts,
+ },
+ ref: this.contactsPostHash,
+ });
+ return true;
+ }
+ return false;
+ }),
+ )
+ )
+ return { success: true, message: 'File shared with the contact' };
+ return { success: false, message: 'Contact does not exist' };
+ }
+ return { success: false, message: 'Failed to load account' };
+ } catch (err) {
+ console.log(err);
+ return { success: false, message: 'Failed to share the file with the contact' };
+ }
+ }
+}
+
+export default Contact;
diff --git a/src/lib/drive.ts b/src/lib/drive.ts
index 5075ac15..1c0557cc 100644
--- a/src/lib/drive.ts
+++ b/src/lib/drive.ts
@@ -10,21 +10,15 @@ import CryptoJS from 'crypto-js';
import { ArraybufferToString } from 'utils/arraytbufferToString';
-type IPCFile = {
- name: string;
- content: string;
- created_at: number;
-};
-
-type ResponseType = {
- success: boolean;
- message: string;
-};
+import { IPCContact, IPCFile, ResponseType } from 'types/types';
+import EthCrypto from 'eth-crypto';
class Drive {
public files: IPCFile[];
- public postsHash: string;
+ public sharedFiles: IPCFile[];
+
+ public filesPostHash: string;
private readonly account: accounts.base.Account | undefined;
@@ -32,71 +26,65 @@ class Drive {
constructor(importedAccount: accounts.base.Account, private_key: string) {
this.files = [];
+ this.sharedFiles = [];
this.account = importedAccount;
- this.postsHash = '';
+ this.filesPostHash = '';
this.private_key = private_key;
}
- public async load(): Promise {
+ public async loadShared(contacts: IPCContact[]): Promise {
try {
if (this.account) {
- const userData = await post.Get({
- APIServer: DEFAULT_API_V2,
- types: '',
- pagination: 200,
- page: 1,
- refs: [],
- addresses: [this.account.address],
- tags: [],
- hashes: [],
- });
-
- const postMessage = userData.posts.map((postContent) => {
- const itemContent = JSON.parse(postContent.item_content);
- if (itemContent.content.header === 'InterPlanetaryCloud2.0 Header') {
- this.postsHash = postContent.hash;
- if (itemContent.content.files.length > 0) {
- itemContent.content.files[0].map((file: IPCFile) => {
- this.files.push(file);
- return true;
- });
- }
- return true;
- }
- return false;
- });
- if (postMessage.length !== 1) {
- if (postMessage.length > 1) {
- return { success: false, message: 'Too many post messages' };
- }
- console.log('Create Post Message');
- const newPostPublishResponse = await post.Publish({
- APIServer: DEFAULT_API_V2,
- channel: ALEPH_CHANNEL,
- inlineRequested: true,
- storageEngine: ItemType.ipfs,
- account: this.account,
- postType: '',
- content: {
- header: 'InterPlanetaryCloud2.0 Header',
- files: this.files,
- },
- });
- this.postsHash = newPostPublishResponse.item_hash;
- }
- return { success: true, message: 'Drive loaded' };
+ await Promise.all(
+ contacts.map(async (contact) => {
+ const userData = await post.Get({
+ APIServer: DEFAULT_API_V2,
+ types: '',
+ pagination: 200,
+ page: 1,
+ refs: [],
+ addresses: [contact.address],
+ tags: [],
+ hashes: [],
+ });
+
+ await Promise.all(
+ userData.posts.map(async (postContent) => {
+ const itemContent = JSON.parse(postContent.item_content);
+
+ if (itemContent.content.header === 'InterPlanetaryCloud2.0 - Contacts') {
+ console.log('Post contacts founded');
+ await Promise.all(
+ itemContent.content.contacts.map(async (contactToFind: IPCContact) => {
+ if (contactToFind.address === this.account!.address) {
+ if (contact.address === this.account!.address)
+ this.files = this.files.concat(contactToFind.files);
+ else this.sharedFiles = this.sharedFiles.concat(contactToFind.files);
+ return true;
+ }
+ return false;
+ }),
+ );
+ return true;
+ }
+ return false;
+ }),
+ );
+ }),
+ );
+ return { success: true, message: 'Shared drive loaded' };
}
return { success: false, message: 'Failed to load account' };
} catch (err) {
- console.log(err);
- return { success: false, message: 'Failed to load drive' };
+ console.error(err);
+ return { success: false, message: 'Failed to load shared drive' };
}
}
- public async upload(file: IPCFile): Promise {
+ public async upload(file: IPCFile, key: string): Promise {
try {
if (this.account) {
- const encryptedContentFile = CryptoJS.AES.encrypt(file.content, this.private_key).toString();
+ const encryptedContentFile = CryptoJS.AES.encrypt(file.hash, key).toString();
const newStoreFile = new File([encryptedContentFile], file.name, {
type: 'text/plain',
@@ -112,24 +100,12 @@ class Drive {
const newFile: IPCFile = {
name: file.name,
- content: fileHashPublishStore.content.item_hash,
+ hash: fileHashPublishStore.content.item_hash,
created_at: file.created_at,
+ key: await EthCrypto.encryptWithPublicKey(this.account.publicKey.slice(2), key),
};
this.files.push(newFile);
- await post.Publish({
- APIServer: DEFAULT_API_V2,
- channel: ALEPH_CHANNEL,
- inlineRequested: true,
- storageEngine: ItemType.ipfs,
- account: this.account,
- postType: 'amend',
- content: {
- header: 'InterPlanetaryCloud2.0 Header',
- files: [this.files],
- },
- ref: this.postsHash,
- });
return { success: true, message: 'File uploaded' };
}
@@ -145,15 +121,18 @@ class Drive {
if (this.account) {
const storeFile = await store.Get({
APIServer: DEFAULT_API_V2,
- fileHash: file.content,
+ fileHash: file.hash,
});
- const decryptedContentFile = CryptoJS.AES.decrypt(ArraybufferToString(storeFile), this.private_key).toString(
+ const keyFile = await EthCrypto.decryptWithPrivateKey(this.private_key.slice(2), file.key);
+ const decryptedContentFile = CryptoJS.AES.decrypt(ArraybufferToString(storeFile), keyFile).toString(
CryptoJS.enc.Utf8,
);
- const blob = new Blob([decryptedContentFile]);
- fileDownload(blob, file.name);
+ const newFile = new File([decryptedContentFile], file.name, {
+ type: 'plain/text',
+ });
+ fileDownload(newFile, file.name);
return { success: true, message: 'File downloaded' };
}
return { success: false, message: 'Failed to load account' };
@@ -164,6 +143,4 @@ class Drive {
}
}
-export type { IPCFile };
-
export default Drive;
diff --git a/src/lib/user.ts b/src/lib/user.ts
index 22da0a2d..59c55fbb 100644
--- a/src/lib/user.ts
+++ b/src/lib/user.ts
@@ -4,14 +4,19 @@ import { mnemonicToPrivateKey } from 'utils/mnemonicToPrivateKey';
import Drive from './drive';
+import Contact from './contact';
+
class User {
public account: accounts.base.Account | undefined;
public drive: Drive;
+ public contact: Contact;
+
constructor(importedAccount: accounts.base.Account, mnemonic: string) {
this.account = importedAccount;
this.drive = new Drive(this.account, mnemonicToPrivateKey(mnemonic));
+ this.contact = new Contact(this.account, mnemonicToPrivateKey(mnemonic));
}
}
diff --git a/src/theme/components/button.ts b/src/theme/components/button.ts
index ed04250a..57ec4ca6 100644
--- a/src/theme/components/button.ts
+++ b/src/theme/components/button.ts
@@ -32,6 +32,10 @@ const Button = {
bgGradient: `linear-gradient(90deg, ${colors.blue[700]} 0%, ${colors.red[700]} 100%)`,
color: 'white',
},
+ reverseInline: {
+ bgGradient: `linear-gradient(90deg, ${colors.red[700]} 0%, ${colors.blue[700]} 100%)`,
+ color: 'white',
+ },
},
defaultProps: {
size: 'md',
diff --git a/src/theme/index.ts b/src/theme/index.ts
index cf882b58..ccb266d3 100644
--- a/src/theme/index.ts
+++ b/src/theme/index.ts
@@ -1,7 +1,7 @@
import { extendTheme } from '@chakra-ui/react';
import { createBreakpoints } from '@chakra-ui/theme-tools';
-// Foundations overrides
+// Foundation overrides
import fonts from './foundations/fonts';
import colors from './foundations/colors';
import radius from './foundations/borderRadius';
diff --git a/src/types/types.ts b/src/types/types.ts
new file mode 100644
index 00000000..01b6cfc5
--- /dev/null
+++ b/src/types/types.ts
@@ -0,0 +1,20 @@
+import { Encrypted } from 'eth-crypto';
+
+export type IPCFile = {
+ hash: string;
+ key: Encrypted;
+ name: string;
+ created_at: number;
+};
+
+export type IPCContact = {
+ name: string;
+ address: string;
+ publicKey: string;
+ files: IPCFile[];
+};
+
+export type ResponseType = {
+ success: boolean;
+ message: string;
+};
diff --git a/src/utils/fileManipulation.ts b/src/utils/fileManipulation.ts
new file mode 100644
index 00000000..177a04d2
--- /dev/null
+++ b/src/utils/fileManipulation.ts
@@ -0,0 +1,6 @@
+export const extractFilename = (filepath: string): string => {
+ const result = /[^\\]*$/.exec(filepath);
+ return result && result.length ? result[0] : '';
+};
+
+export const getFileContent = async (file: unknown): Promise => (file as Blob).text();
diff --git a/src/utils/generateFileKey.ts b/src/utils/generateFileKey.ts
new file mode 100644
index 00000000..48ef1d4d
--- /dev/null
+++ b/src/utils/generateFileKey.ts
@@ -0,0 +1,3 @@
+import CryptoJS from 'crypto-js';
+
+export const generateFileKey = (): string => CryptoJS.lib.WordArray.random(256 / 8).toString();
diff --git a/src/views/DashboardView.tsx b/src/views/DashboardView.tsx
index 98c9e7f8..783e0d67 100644
--- a/src/views/DashboardView.tsx
+++ b/src/views/DashboardView.tsx
@@ -8,74 +8,81 @@ import {
useDisclosure,
useToast,
Input,
- useBreakpointValue,
- Drawer,
- DrawerOverlay,
- DrawerContent,
- SlideDirection,
- Icon,
Text,
+ Flex,
+ Spacer,
Divider,
} from '@chakra-ui/react';
-import { DownloadIcon, HamburgerIcon } from '@chakra-ui/icons';
+import { CheckIcon } from '@chakra-ui/icons';
+
+import EthCrypto from 'eth-crypto';
import { useUserContext } from 'contexts/user';
-import { IPCFile } from 'lib/drive';
+import { IPCFile, IPCContact } from 'types/types';
import Modal from 'components/Modal';
-import Sidebar from 'components/SideBar';
-import FileCard from 'components/FileCard';
-import UploadButton from 'components/UploadButton';
-import colors from 'theme/foundations/colors';
-
-const extractFilename = (filepath: string) => {
- const result = /[^\\]*$/.exec(filepath);
- return result && result.length ? result[0] : '';
-};
-const getFileContent = (file: unknown): Promise =>
- new Promise((resolve, reject) => {
- const reader = new window.FileReader();
- reader.onload = (event: unknown) => {
- // eslint-disable-next-line
- resolve((event as any).target.result);
- };
- reader.onerror = (event) => {
- reject(event);
- };
- reader.readAsText(file as Blob);
- });
+import { generateFileKey } from 'utils/generateFileKey';
+
+import { getFileContent, extractFilename } from '../utils/fileManipulation';
+
+import { ResponsiveBar } from '../components/ResponsiveBar';
+import { DisplayFileCards } from '../components/DisplayFileCards';
const Dashboard = (): JSX.Element => {
const toast = useToast();
const { user } = useUserContext();
const { isOpen, onOpen, onClose } = useDisclosure();
+ const { isOpen: isOpenContactAdd, onOpen: onOpenContactAdd, onClose: onCloseContactAdd } = useDisclosure();
+ const { isOpen: isOpenContactUpdate, onOpen: onOpenContactUpdate, onClose: onCloseContactUpdate } = useDisclosure();
+ const { isOpen: isOpenShare, onOpen: onOpenShare, onClose: onCloseShare } = useDisclosure();
const [files, setFiles] = useState([]);
+ const [sharedFiles, setSharedFiles] = useState([]);
+ const [contacts, setContacts] = useState([]);
+ const [contactInfos, setContactInfo] = useState({
+ name: '',
+ address: '',
+ publicKey: '',
+ files: [],
+ });
+ const [selectedTab, setSelectedTab] = useState(0);
const [isUploadLoading, setIsUploadLoading] = useState(false);
const [isDownloadLoading, setIsDownloadLoading] = useState(false);
const [fileEvent, setFileEvent] = useState | undefined>(undefined);
+ const [contactsNameEvent, setContactNameEvent] = useState | undefined>(undefined);
+ const [contactsPublicKeyEvent, setContactPublicKeyEvent] = useState | undefined>(
+ undefined,
+ );
+ const [selectedFile, setSelectedFile] = useState({
+ name: '',
+ hash: '',
+ created_at: 0,
+ key: { iv: '', ephemPublicKey: '', ciphertext: '', mac: '' },
+ });
useEffect(() => {
(async () => {
- await loadDrive();
+ await loadContact();
+ await loadSharedDrive();
})();
}, []);
- const loadDrive = async () => {
+ const loadSharedDrive = async () => {
try {
- const load = await user.drive.load();
+ const loadShared = await user.drive.loadShared(user.contact.contacts);
toast({
- title: load.message,
- status: load.success ? 'success' : 'error',
+ title: loadShared.message,
+ status: loadShared.success ? 'success' : 'error',
duration: 2000,
isClosable: true,
});
setFiles(user.drive.files);
+ setSharedFiles(user.drive.sharedFiles);
} catch (error) {
console.error(error);
toast({
- title: 'Unable to load drive',
+ title: 'Unable to load shared drive',
status: 'error',
duration: 2000,
isClosable: true,
@@ -87,21 +94,49 @@ const Dashboard = (): JSX.Element => {
if (!fileEvent) return;
const filename = extractFilename(fileEvent.target.value);
const fileContent = await getFileContent(fileEvent.target.files ? fileEvent.target.files[0] : []);
+ const key = generateFileKey();
+
if (!filename || !fileContent) return;
setIsUploadLoading(true);
try {
- const upload = await user.drive.upload({
- name: filename,
- content: fileContent,
- created_at: Date.now(),
- });
- toast({
- title: upload.message,
- status: upload.success ? 'success' : 'error',
- duration: 2000,
- isClosable: true,
- });
+ if (user.account) {
+ const upload = await user.drive.upload(
+ {
+ name: filename,
+ hash: fileContent,
+ created_at: Date.now(),
+ key: { iv: '', ephemPublicKey: '', ciphertext: '', mac: '' },
+ },
+ key,
+ );
+ if (!upload.success) {
+ toast({
+ title: upload.message,
+ status: upload.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ } else {
+ const shared = await user.contact.addFileToContact(
+ user.account.address,
+ user.drive.files[user.drive.files.length - 1],
+ );
+ toast({
+ title: shared.success ? upload.message : 'Failed to upload the file',
+ status: shared.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ } else {
+ toast({
+ title: 'Failed to load account',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
onClose();
} catch (error) {
console.error(error);
@@ -137,74 +172,180 @@ const Dashboard = (): JSX.Element => {
setIsDownloadLoading(false);
};
- const LeftBar = (): JSX.Element => (
- onOpen()} isLoading={isUploadLoading} />}
- />
- );
+ const shareFile = async (contact: IPCContact) => {
+ setIsDownloadLoading(true);
+ try {
+ console.log(selectedFile.key);
+ const share = await user.contact.addFileToContact(contact.address, selectedFile);
+ onCloseShare();
+ toast({
+ title: share.message,
+ status: share.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ } catch (error) {
+ console.log(error);
+ toast({
+ title: 'Unable to share the file',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ setIsDownloadLoading(false);
+ };
- const BarWithDrawer = () => {
- // eslint-disable-next-line @typescript-eslint/no-shadow
- const { isOpen, onOpen, onClose } = useDisclosure();
- const placement: SlideDirection = 'left';
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
+ const loadContact = async () => {
+ try {
+ const load = await user.contact.load();
+ toast({
+ title: load.message,
+ status: load.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+
+ setContacts(user.contact.contacts);
+ } catch (error) {
+ console.log(error);
+ toast({
+ title: 'Unable to load contacts',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ };
+
+ const addContact = async () => {
+ try {
+ if (contactsNameEvent && contactsPublicKeyEvent) {
+ const add = await user.contact.add({
+ name: contactsNameEvent.target.value,
+ address: EthCrypto.publicKey.toAddress(contactsPublicKeyEvent.target.value.slice(2)),
+ publicKey: contactsPublicKeyEvent.target.value,
+ files: [],
+ });
+
+ toast({
+ title: add.message,
+ status: add.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ setContacts(user.contact.contacts);
+ } else {
+ toast({
+ title: 'Bad contact infos',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ onCloseContactAdd();
+ } catch (error) {
+ console.log(error);
+ toast({
+ title: 'Unable to add this contact',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
};
- const ResponsiveBar = () => {
- const isDrawerNeeded: boolean = useBreakpointValue({ base: true, xs: true, lg: false }) || false;
+ const updateContact = async () => {
+ try {
+ if (contactsPublicKeyEvent) {
+ const update = await user.contact.update(
+ contactInfos.address,
+ contactsNameEvent ? contactsNameEvent.target.value : contactInfos.name,
+ );
+ toast({
+ title: update.message,
+ status: update.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ setContacts(user.contact.contacts);
+ } else {
+ toast({
+ title: 'Invalid address',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ onCloseContactUpdate();
+ } catch (error) {
+ console.log(error);
+ toast({
+ title: 'Unable to update this contact',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ };
+
+ const deleteContact = async (contactToDelete: IPCContact) => {
+ try {
+ const deletedContact = contacts.find((contact) => contact === contactToDelete);
+
+ if (deletedContact) {
+ const deleteResponse = await user.contact.remove(contactToDelete.address);
- if (!isDrawerNeeded) return ;
- return ;
+ toast({
+ title: deleteResponse.message,
+ status: deleteResponse.success ? 'success' : 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ setContacts(user.contact.contacts);
+ } else {
+ toast({
+ title: 'Unable to find this contact',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ toast({
+ title: 'Unable to delete this contact',
+ status: 'error',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
};
return (
-
+
-
- {files.map((file) => (
-
- downloadFile(file)}
- isLoading={isDownloadLoading}
- id="ipc-dashboardView-download-button"
- >
-
-
-
- ))}
+
+
{
id="ipc-dashboardView-upload-file"
/>
+
+ Add the contact
+
+ }
+ >
+ <>
+ ) => setContactNameEvent(e)}
+ id="ipc-dashboardView-input-contact-name"
+ />
+ ) => setContactPublicKeyEvent(e)}
+ id="ipc-dashboardView-input-contact-public-key"
+ />
+ >
+
+
+ Update the contact
+
+ }
+ >
+ <>
+ New name *
+ ) => setContactNameEvent(e)}
+ id="ipc-dashboardView-input-contact-name"
+ />
+ * Fill, to update the info
+ >
+
+
+
+ {contacts.map((contact) => {
+ if (user.account && contact.address !== user.account.address)
+ return (
+
+
+ {contact.name}
+ {contact.address}
+
+
+ {
+ await shareFile(contact);
+ }}
+ >
+
+
+
+ );
+ return ;
+ })}
+
+
);
};
diff --git a/yarn.lock b/yarn.lock
index b88b41ab..769f0f17 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1032,6 +1032,13 @@
core-js-pure "^3.19.0"
regenerator-runtime "^0.13.4"
+"@babel/runtime@7.16.7", "@babel/runtime@^7.11.2":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
+ integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.16.3"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz"
@@ -1039,13 +1046,6 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.11.2":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
- integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
- dependencies:
- regenerator-runtime "^0.13.4"
-
"@babel/runtime@^7.15.4":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.0.tgz#b8d142fc0f7664fb3d9b5833fd40dcbab89276c0"
@@ -1851,7 +1851,7 @@
crc-32 "^1.2.0"
ethereumjs-util "^7.1.3"
-"@ethereumjs/tx@^3.3.2":
+"@ethereumjs/tx@3.4.0", "@ethereumjs/tx@^3.3.2":
version "3.4.0"
resolved "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.4.0.tgz"
integrity sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw==
@@ -3029,6 +3029,13 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/bn.js@5.1.0", "@types/bn.js@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz"
+ integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==
+ dependencies:
+ "@types/node" "*"
+
"@types/bn.js@^4.11.5", "@types/bn.js@^4.11.6":
version "4.11.6"
resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz"
@@ -3036,13 +3043,6 @@
dependencies:
"@types/node" "*"
-"@types/bn.js@^5.1.0":
- version "5.1.0"
- resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz"
- integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==
- dependencies:
- "@types/node" "*"
-
"@types/bs58@^4.0.1":
version "4.0.1"
resolved "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz"
@@ -3722,6 +3722,11 @@ acorn-walk@^8.1.1:
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+acorn@7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
+ integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==
+
acorn@^6.4.1:
version "6.4.2"
resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz"
@@ -4462,6 +4467,13 @@ bip39@^3.0.4:
pbkdf2 "^3.0.9"
randombytes "^2.0.1"
+bip66@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
+ integrity sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=
+ dependencies:
+ safe-buffer "^5.0.1"
+
bl@^4.0.0:
version "4.1.0"
resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz"
@@ -4585,7 +4597,7 @@ browser-process-hrtime@^1.0.0:
resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz"
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
-browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.2.0:
+browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6, browserify-aes@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz"
integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
@@ -6327,6 +6339,15 @@ dotenv@8.2.0:
resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
+drbg.js@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b"
+ integrity sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=
+ dependencies:
+ browserify-aes "^1.0.6"
+ create-hash "^1.1.2"
+ create-hmac "^1.1.4"
+
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz"
@@ -6355,6 +6376,18 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
+eccrypto@1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/eccrypto/-/eccrypto-1.1.6.tgz#846bd1222323036f7a3515613704386399702bd3"
+ integrity sha512-d78ivVEzu7Tn0ZphUUaL43+jVPKTMPFGtmgtz1D0LrFn7cY3K8CdrvibuLz2AAkHBLKZtR8DMbB2ukRYFk987A==
+ dependencies:
+ acorn "7.1.1"
+ elliptic "6.5.4"
+ es6-promise "4.2.8"
+ nan "2.14.0"
+ optionalDependencies:
+ secp256k1 "3.7.1"
+
eciesjs@^0.3.12:
version "0.3.13"
resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.3.13.tgz#456a89b9aa68e71d6019b7fd903640c388154b72"
@@ -6386,7 +6419,7 @@ electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.17:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.23.tgz"
integrity sha512-q3tB59Api3+DMbLnDPkW/UBHBO7KTGcF+rDCeb0GAGyqFj562s6y+c/2tDKTS/y5lbC+JOvT4MSUALJLPqlcSA==
-elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4:
+elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.4.1, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4:
version "6.5.4"
resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
@@ -6541,7 +6574,7 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
-es6-promise@^4.0.3:
+es6-promise@4.2.8, es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
@@ -6901,6 +6934,19 @@ etag@~1.8.1:
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+eth-crypto@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/eth-crypto/-/eth-crypto-2.2.0.tgz#8fa9bd7b04ee256d0e755d73e9a0a6c7e977c5b9"
+ integrity sha512-g4YKmHcPNFkIGylWVMYwhoxtBphXX9xc0rttoYQGqH1Mg0YuQk2rYa9jcnTzSyduG0GDL60Cib8Uhhrx13CS4w==
+ dependencies:
+ "@babel/runtime" "7.16.7"
+ "@ethereumjs/tx" "3.4.0"
+ "@types/bn.js" "5.1.0"
+ eccrypto "1.1.6"
+ ethereumjs-util "7.1.3"
+ ethers "5.5.4"
+ secp256k1 "4.0.3"
+
eth-ens-namehash@2.0.8:
version "2.0.8"
resolved "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz"
@@ -6958,7 +7004,7 @@ ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1"
setimmediate "^1.0.5"
-ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3:
+ethereumjs-util@7.1.3, ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3:
version "7.1.3"
resolved "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.3.tgz"
integrity sha512-y+82tEbyASO0K0X1/SRhbJJoAlfcvq8JbrG4a5cjrOks7HS/36efU/0j2flxCPOUM++HFahk33kr/ZxyC4vNuw==
@@ -6969,7 +7015,7 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3:
ethereum-cryptography "^0.1.3"
rlp "^2.2.4"
-ethers@^5.4.6:
+ethers@5.5.4, ethers@^5.4.6:
version "5.5.4"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.4.tgz#e1155b73376a2f5da448e4a33351b57a885f4352"
integrity sha512-N9IAXsF8iKhgHIC6pquzRgPBJEzc9auw3JoRkaKe+y4Wl/LFBtDDunNe7YmdomontECAcC5APaAgWZBiu1kirw==
@@ -10376,7 +10422,12 @@ multihashes@^0.4.15, multihashes@~0.4.15:
multibase "^0.7.0"
varint "^5.0.0"
-nan@^2.12.1, nan@^2.13.2:
+nan@2.14.0:
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
+ integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
+
+nan@^2.12.1, nan@^2.13.2, nan@^2.14.0:
version "2.15.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
@@ -12230,6 +12281,11 @@ react-focus-lock@2.5.2:
use-callback-ref "^1.2.5"
use-sidecar "^1.0.5"
+react-icons@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
+ integrity sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==
+
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
@@ -12962,16 +13018,21 @@ scryptsy@^2.1.0:
resolved "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz"
integrity sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==
-secp256k1@^4.0.1:
- version "4.0.2"
- resolved "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz"
- integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==
+secp256k1@3.7.1:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.7.1.tgz#12e473e0e9a7c2f2d4d4818e722ad0e14cc1e2f1"
+ integrity sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==
dependencies:
- elliptic "^6.5.2"
- node-addon-api "^2.0.0"
- node-gyp-build "^4.2.0"
+ bindings "^1.5.0"
+ bip66 "^1.1.5"
+ bn.js "^4.11.8"
+ create-hash "^1.2.0"
+ drbg.js "^1.0.1"
+ elliptic "^6.4.1"
+ nan "^2.14.0"
+ safe-buffer "^5.1.2"
-secp256k1@^4.0.2, secp256k1@^4.0.3:
+secp256k1@4.0.3, secp256k1@^4.0.2, secp256k1@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303"
integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==
@@ -12980,6 +13041,15 @@ secp256k1@^4.0.2, secp256k1@^4.0.3:
node-addon-api "^2.0.0"
node-gyp-build "^4.2.0"
+secp256k1@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz"
+ integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==
+ dependencies:
+ elliptic "^6.5.2"
+ node-addon-api "^2.0.0"
+ node-gyp-build "^4.2.0"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz"