This is an automated email from the ASF dual-hosted git repository.

apratim pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-resilientdb-resvault.git


The following commit(s) were added to refs/heads/main by this push:
     new e73bfad  Added delete keys functionality, and export all keys
e73bfad is described below

commit e73bfad69477f22df6a71e58d9fa3d95dbdf4422
Author: Apratim Shukla <apratimshuk...@gmail.com>
AuthorDate: Thu Oct 10 00:41:23 2024 -0700

    Added delete keys functionality, and export all keys
    
    - Specific key pairs can now be deleted for a ResVault account.
    - All key pairs can now be exported with a button.
    - UI updates corresponding to these changes.
---
 src/context/GlobalContext.js |  40 +++++++++++++--
 src/css/App.css              |  31 +++++++++++-
 src/pages/Dashboard.jsx      | 117 ++++++++++++++++++++++++++++++++-----------
 3 files changed, 156 insertions(+), 32 deletions(-)

diff --git a/src/context/GlobalContext.js b/src/context/GlobalContext.js
index 7a88a7a..5615c56 100644
--- a/src/context/GlobalContext.js
+++ b/src/context/GlobalContext.js
@@ -104,9 +104,44 @@ export const GlobalProvider = ({ children }) => {
     });
   };
 
+  // Function to delete a key pair
+  const deleteKeyPair = (index, callback) => {
+    const password = storedPassword;
+    if (!password) {
+        console.error('Password is not available');
+        return;
+    }
+
+    loadKeyPairsFromStorage(password, (existingKeyPairs) => {
+        if (existingKeyPairs.length <= 1) {
+            console.error('Cannot delete the last remaining key pair.');
+            return;
+        }
+
+        // Remove the key pair at the specified index
+        const updatedKeyPairs = [...existingKeyPairs];
+        updatedKeyPairs.splice(index, 1);
+
+        // Immediately update the key pairs state
+        setKeyPairs(updatedKeyPairs);
+
+        // Save the updated keyPairs back to storage
+        saveKeyPairsToStorage(updatedKeyPairs, password);
+
+        // Reset to the first key pair after deletion
+        setSelectedKeyPairIndex(0);
+        setPublicKey(updatedKeyPairs.length > 0 ? updatedKeyPairs[0].publicKey 
: '');
+        setPrivateKey(updatedKeyPairs.length > 0 ? 
updatedKeyPairs[0].privateKey : '');
+
+        // Optionally call the callback
+        if (callback) {
+            callback();
+        }
+    });
+  };
+
   // Load key pairs from storage when context is initialized
   useEffect(() => {
-    // Retrieve password from storage
     chrome.storage.local.get(['password'], (result) => {
       const password = result.password;
       if (password) {
@@ -114,7 +149,6 @@ export const GlobalProvider = ({ children }) => {
         loadKeyPairsFromStorage(password, (loadedKeyPairs) => {
           if (loadedKeyPairs.length > 0) {
             setKeyPairs(loadedKeyPairs);
-            // Load selected key pair index
             loadSelectedKeyPairIndex((index) => {
               if (loadedKeyPairs[index]) {
                 setPublicKey(loadedKeyPairs[index].publicKey);
@@ -150,7 +184,6 @@ export const GlobalProvider = ({ children }) => {
         console.error('Password is not available');
         return;
       }
-      // Encrypt the private key and save in 'store'
       const encryptedPrivateKey = 
CryptoJS.AES.encrypt(keyPairs[index].privateKey, password).toString();
       const hash = CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex);
       const store = {
@@ -188,6 +221,7 @@ export const GlobalProvider = ({ children }) => {
         setIsAuthenticated,
         storedPassword,
         setStoredPassword,
+        deleteKeyPair,
       }}
     >
       {children}
diff --git a/src/css/App.css b/src/css/App.css
index 1ab679e..d95dd29 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -346,7 +346,7 @@ h2 {
       position: absolute;
       left: 0px;
       bottom: 0;
-      z-index: 444; }
+      z-index: 0; }
 
 h3 {
   font-size: 1.6rem; }
@@ -3706,4 +3706,33 @@ tr:hover {
   opacity: 0.7;
 }
 
+.keypair-actions {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 10px;
+}
+
+.badge-button {
+  background-color: #3c4e63; /* Matches your theme's primary color */
+  color: white;
+  border: none;
+  border-radius: 20px;
+  padding: 10px 20px;
+  font-size: 14px;
+  cursor: pointer;
+  width: 48%; /* Ensures equal width for both buttons */
+  text-align: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.badge-button:hover {
+  background-color: #50647e; /* A slightly lighter shade for hover effect */
+}
+
+.badge-button svg {
+  margin-left: 8px; /* Adds space between the text and icon */
+}
+
 
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index 10cafc2..df05436 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -23,6 +23,8 @@ import ExitToAppIcon from '@mui/icons-material/ExitToApp';
 import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
 import ContentCopyIcon from '@mui/icons-material/ContentCopy';
 import DownloadIcon from '@mui/icons-material/Download';
+import DeleteIcon from '@mui/icons-material/Delete';
+import SaveAltIcon from '@mui/icons-material/SaveAlt';
 import React, { useRef, useState, useEffect, useContext } from 'react';
 import Lottie from 'react-lottie';
 import versionData from '../data/version.json';
@@ -44,6 +46,7 @@ function Dashboard() {
         setSelectedKeyPairIndex,
         setSelectedKeyPair,
         setIsAuthenticated,
+        deleteKeyPair,
     } = useContext(GlobalContext);
 
     const [tabId, setTabId] = useState(null);
@@ -68,6 +71,7 @@ function Dashboard() {
     const [transactionError, setTransactionError] = useState('');
     const [showSuccessModal, setShowSuccessModal] = useState(false);
     const [successResponse, setSuccessResponse] = useState(null);
+    const [showDeleteModal, setShowDeleteModal] = useState(false);
 
     // New state for copying transaction ID
     const [isIdCopied, setIsIdCopied] = useState(false);
@@ -352,6 +356,19 @@ function Dashboard() {
         });
     };
 
+    const handleDeleteKeyPair = () => {
+        if (keyPairs.length > 1) {
+            deleteKeyPair(selectedKeyPairIndex, () => {
+                // Reset to the first key pair after deletion
+                setSelectedKeyPairIndex(0);
+                setSelectedKeyPair(0);
+            });
+    
+            // Close the delete confirmation modal
+            setShowDeleteModal(false);
+        }
+    };       
+
     // Function to copy public key
     const handleCopyPublicKey = () => {
         try {
@@ -385,6 +402,21 @@ function Dashboard() {
         downloadAnchorNode.remove();
     };
 
+    // Function to download all key pairs as JSON
+    const handleDownloadAllKeyPairs = () => {
+        const allKeyPairs = keyPairs.map(({ publicKey, privateKey }) => ({
+        publicKey,
+        privateKey,
+        }));
+        const dataStr = "data:text/json;charset=utf-8," + 
encodeURIComponent(JSON.stringify(allKeyPairs));
+        const downloadAnchorNode = document.createElement('a');
+        downloadAnchorNode.setAttribute("href", dataStr);
+        downloadAnchorNode.setAttribute("download", "all-keypairs.json");
+        document.body.appendChild(downloadAnchorNode); // required for firefox
+        downloadAnchorNode.click();
+        downloadAnchorNode.remove();
+    };
+
     const handleFileUpload = (e) => {
         const file = e.target.files[0];
         if (file && file.type === 'application/json') {
@@ -612,12 +644,12 @@ function Dashboard() {
                     </div>
                     </div>
                     <div className="save-container">
-                    <button onClick={addNet} className="button-save">
-                        Save
-                    </button>
-                    <button onClick={() => setShowModal(false)} 
className="button-close">
-                        Close
-                    </button>
+                        <button onClick={addNet} className="button-save">
+                            Save
+                        </button>
+                        <button onClick={() => setShowModal(false)} 
className="button-close">
+                            Close
+                        </button>
                     </div>
                     {error && <p className="error-message">{error}</p>}
                 </div>
@@ -732,35 +764,64 @@ function Dashboard() {
             <div className="net">
                 <div className="keypair">
                     <div className="keypair-header">
-                    <div className="select-wrapper">
-                        <select
-                        value={selectedKeyPairIndex}
-                        onChange={(e) => switchKeyPair(e.target.value)}
-                        className="select"
-                        >
-                        {keyPairs.map((keyPair, index) => (
-                            <option key={index} value={index}>
-                            {`${keyPair.publicKey.slice(0, 
4)}...${keyPair.publicKey.slice(-4)}`}
-                            </option>
-                        ))}
-                        </select>
-                        <i className="fas fa-chevron-down"></i>
+                        <div className="select-wrapper">
+                            <select
+                            value={selectedKeyPairIndex}
+                            onChange={(e) => switchKeyPair(e.target.value)}
+                            className="select"
+                            >
+                            {keyPairs.map((keyPair, index) => (
+                                <option key={index} value={index}>
+                                {`${keyPair.publicKey.slice(0, 
4)}...${keyPair.publicKey.slice(-4)}`}
+                                </option>
+                            ))}
+                            </select>
+                            <i className="fas fa-chevron-down"></i>
+                        </div>
+                        <div className="keypair-icons">
+                            {keyPairs.length > 1 && (
+                            <button onClick={() => setShowDeleteModal(true)} 
className="icon-button">
+                                <DeleteIcon style={{ color: 'white' }} />
+                            </button>
+                            )}
+                            <button onClick={handleCopyPublicKey} 
className="icon-button">
+                                <ContentCopyIcon style={{ color: isCopied ? 
'grey' : 'white' }} />
+                            </button>
+                            <button onClick={handleDownloadKeyPair} 
className="icon-button">
+                                <DownloadIcon style={{ color: 'white' }} />
+                            </button>
+                        </div>
                     </div>
-                    <div className="keypair-icons">
-                        <button onClick={handleGenerateKeyPair} 
className="icon-button">
-                            <AddCircleOutlineIcon style={{ color: 'white' }} />
-                        </button>
-                        <button onClick={handleCopyPublicKey} 
className="icon-button">
-                            <ContentCopyIcon style={{ color: isCopied ? 
'green' : 'white' }} />
+                    <div className="keypair-actions">
+                        <button onClick={handleGenerateKeyPair} 
className="badge-button">
+                            Generate <AddCircleOutlineIcon style={{ color: 
'white' }} />
                         </button>
-                        <button onClick={handleDownloadKeyPair} 
className="icon-button">
-                            <DownloadIcon style={{ color: 'white' }} />
+                        <button onClick={handleDownloadAllKeyPairs} 
className="badge-button">
+                            Export <SaveAltIcon style={{ color: 'white' }} />
                         </button>
                     </div>
-                    </div>
                 </div>
             </div>
 
+            {showDeleteModal && (
+                <div className="modal-overlay">
+                    <div className="modal">
+                        <div className="modal-content">
+                            <h2>Are you sure?</h2>
+                            <p>This action is irreversible and will delete the 
selected key pair forever.</p>
+                            <div className="save-container">
+                                <button onClick={handleDeleteKeyPair} 
className="button-save">
+                                    Delete
+                                </button>
+                                <button onClick={() => 
setShowDeleteModal(false)} className="button-close">
+                                    Cancel
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            )}
+
             <button className="button button--full button--main open-popup" 
onClick={handleSubmit}>
                 Submit
             </button>

Reply via email to