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 2ab75da  Added key upload functionality
2ab75da is described below

commit 2ab75da76e3b0df3a72e3aa443614c1fa3dd2ad0
Author: Apratim Shukla <[email protected]>
AuthorDate: Sat Oct 12 12:45:00 2024 -0700

    Added key upload functionality
    
    - Exported key pairs can now be imported into your ResVault profile.
    - Added verification checks for uploaded key pairs.
    - Added redundancy checks for uploaded key pairs.
    - UI modifications corresponding to these changes.
---
 src/context/GlobalContext.js | 141 ++++++++++++++++++++++++++++++++--
 src/css/App.css              |  74 ++++++++++++++++--
 src/pages/Dashboard.jsx      | 177 +++++++++++++++++++++++++++++++++----------
 3 files changed, 337 insertions(+), 55 deletions(-)

diff --git a/src/context/GlobalContext.js b/src/context/GlobalContext.js
index 5615c56..98512cd 100644
--- a/src/context/GlobalContext.js
+++ b/src/context/GlobalContext.js
@@ -16,6 +16,9 @@ export const GlobalProvider = ({ children }) => {
   const [selectedKeyPairIndex, setSelectedKeyPairIndex] = useState(0);
   const [isAuthenticated, setIsAuthenticated] = useState(false);
   const [storedPassword, setStoredPassword] = useState('');
+  
+  // Alert state for modals
+  const [alert, setAlert] = useState({ isOpen: false, message: '' });
 
   // Function to encrypt and store key pairs
   const saveKeyPairsToStorage = (keyPairs, password) => {
@@ -39,6 +42,7 @@ export const GlobalProvider = ({ children }) => {
           callback(decryptedKeyPairs);
         } catch (err) {
           console.error('Error decrypting key pairs:', err);
+          setAlert({ isOpen: true, message: 'Failed to decrypt key pairs. 
Please check your password.' });
           callback([]);
         }
       } else {
@@ -47,6 +51,92 @@ export const GlobalProvider = ({ children }) => {
     });
   };
 
+  // Function to append new key pairs while preventing duplicates and 
validating them
+  const appendKeyPairs = (newKeyPairs) => {
+    const password = storedPassword;
+    if (!password) {
+        console.error('Password is not available');
+        setAlert({ isOpen: true, message: 'Password is not available. Please 
log in again.' });
+        return;
+    }
+
+    // Validate each key pair
+    for (let i = 0; i < newKeyPairs.length; i++) {
+        const keyPair = newKeyPairs[i];
+        if (!keyPair.publicKey || !keyPair.privateKey) {
+            setAlert({ isOpen: true, message: `Key pair at index ${i} is 
missing publicKey or privateKey.` });
+            console.error(`Key pair at index ${i} is missing publicKey or 
privateKey.`);
+            return;
+        }
+
+        try {
+            // Decode private key from Base58
+            const decodedPrivateKey = Base58.decode(keyPair.privateKey);
+            if (decodedPrivateKey.length !== 32) {
+                setAlert({ isOpen: true, message: `Private key at index ${i} 
is not 32 bytes.` });
+                console.error(`Private key at index ${i} is not 32 bytes.`);
+                return;
+            }
+
+            // Derive public key from private key using 
nacl.sign.keyPair.fromSeed
+            const derivedKeyPair = 
nacl.sign.keyPair.fromSeed(decodedPrivateKey);
+            const derivedPublicKey = Base58.encode(derivedKeyPair.publicKey);
+
+            // Compare derived public key with provided public key
+            if (derivedPublicKey !== keyPair.publicKey) {
+                setAlert({ isOpen: true, message: `Public key does not match 
private key at index ${i}.` });
+                console.error(`Public key does not match private key at index 
${i}.`);
+                return;
+            }
+        } catch (err) {
+            console.error('Error validating key pair:', err);
+            setAlert({ isOpen: true, message: `Error validating key pair at 
index ${i}.` });
+            return;
+        }
+    }
+
+    // Load existing key pairs
+    loadKeyPairsFromStorage(password, (existingKeyPairs) => {
+        // Filter out duplicates
+        const uniqueNewKeyPairs = newKeyPairs.filter(newKey => {
+            return !existingKeyPairs.some(existingKey =>
+                existingKey.publicKey === newKey.publicKey &&
+                existingKey.privateKey === newKey.privateKey
+            );
+        });
+
+        if (uniqueNewKeyPairs.length === 0) {
+            console.log('No new unique key pairs to add.');
+            setAlert({ isOpen: true, message: 'No new unique key pairs to 
add.' });
+            return;
+        }
+
+        const updatedKeyPairs = [...existingKeyPairs, ...uniqueNewKeyPairs];
+        saveKeyPairsToStorage(updatedKeyPairs, password);
+        setKeyPairs(updatedKeyPairs);
+
+        // Update selected key pair to the last one added
+        const newIndex = updatedKeyPairs.length - 1;
+        setSelectedKeyPairIndex(newIndex);
+        setPublicKey(updatedKeyPairs[newIndex].publicKey);
+        setPrivateKey(updatedKeyPairs[newIndex].privateKey);
+        saveSelectedKeyPairIndex(newIndex);
+
+        // Update 'store' with the new key pair
+        const encryptedPrivateKey = 
CryptoJS.AES.encrypt(updatedKeyPairs[newIndex].privateKey, password).toString();
+        const hash = CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex);
+        const store = {
+            hash,
+            publicKey: updatedKeyPairs[newIndex].publicKey,
+            encryptedPrivateKey: encryptedPrivateKey,
+            history: [],
+        };
+        chrome.storage.sync.set({ store }, () => {
+            console.log('Store updated with new key pair');
+        });
+    });
+  };
+
   // Function to save selected key pair index
   const saveSelectedKeyPairIndex = (index) => {
     chrome.storage.local.set({ selectedKeyPairIndex: index }, () => {});
@@ -65,16 +155,29 @@ export const GlobalProvider = ({ children }) => {
     const password = storedPassword;
     if (!password) {
       console.error('Password is not available');
+      setAlert({ isOpen: true, message: 'Password is not available. Please log 
in again.' });
       return;
     }
 
     const keyPair = nacl.sign.keyPair();
     const newPublicKey = Base58.encode(keyPair.publicKey);
-    const newPrivateKey = Base58.encode(keyPair.secretKey.slice(0, 32));
+    const newPrivateKey = Base58.encode(keyPair.secretKey.slice(0, 32)); // 
Using the first 32 bytes as seed
     const newKeyPair = { publicKey: newPublicKey, privateKey: newPrivateKey };
 
     // Load existing key pairs
     loadKeyPairsFromStorage(password, (existingKeyPairs) => {
+      // Check for duplicates before adding
+      const isDuplicate = existingKeyPairs.some(existingKey =>
+          existingKey.publicKey === newKeyPair.publicKey &&
+          existingKey.privateKey === newKeyPair.privateKey
+      );
+
+      if (isDuplicate) {
+          console.log('Generated key pair is a duplicate. Skipping.');
+          setAlert({ isOpen: true, message: 'Generated key pair is a 
duplicate. Skipping.' });
+          return;
+      }
+
       const updatedKeyPairs = [...existingKeyPairs, newKeyPair];
       // Save updated key pairs
       saveKeyPairsToStorage(updatedKeyPairs, password);
@@ -109,12 +212,14 @@ export const GlobalProvider = ({ children }) => {
     const password = storedPassword;
     if (!password) {
         console.error('Password is not available');
+        setAlert({ isOpen: true, message: 'Password is not available. Please 
log in again.' });
         return;
     }
 
     loadKeyPairsFromStorage(password, (existingKeyPairs) => {
         if (existingKeyPairs.length <= 1) {
             console.error('Cannot delete the last remaining key pair.');
+            setAlert({ isOpen: true, message: 'Cannot delete the last 
remaining key pair.' });
             return;
         }
 
@@ -122,16 +227,32 @@ export const GlobalProvider = ({ children }) => {
         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);
+        setKeyPairs(updatedKeyPairs);
 
         // Reset to the first key pair after deletion
         setSelectedKeyPairIndex(0);
-        setPublicKey(updatedKeyPairs.length > 0 ? updatedKeyPairs[0].publicKey 
: '');
-        setPrivateKey(updatedKeyPairs.length > 0 ? 
updatedKeyPairs[0].privateKey : '');
+        if (updatedKeyPairs.length > 0) {
+            setPublicKey(updatedKeyPairs[0].publicKey);
+            setPrivateKey(updatedKeyPairs[0].privateKey);
+            saveSelectedKeyPairIndex(0);
+        }
+
+        // Update 'store' with the new selected key pair
+        if (updatedKeyPairs.length > 0) {
+            const encryptedPrivateKey = 
CryptoJS.AES.encrypt(updatedKeyPairs[0].privateKey, password).toString();
+            const hash = CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex);
+            const store = {
+                hash,
+                publicKey: updatedKeyPairs[0].publicKey,
+                encryptedPrivateKey: encryptedPrivateKey,
+                history: [],
+            };
+            chrome.storage.sync.set({ store }, () => {
+                console.log('Store updated with selected key pair after 
deletion');
+            });
+        }
 
         // Optionally call the callback
         if (callback) {
@@ -172,7 +293,7 @@ export const GlobalProvider = ({ children }) => {
   }, []);
 
   // Function to set selected key pair
-  const setSelectedKeyPair = (index) => {
+  const setSelectedKeyPairFn = (index) => {
     if (keyPairs[index]) {
       setPublicKey(keyPairs[index].publicKey);
       setPrivateKey(keyPairs[index].privateKey);
@@ -182,6 +303,7 @@ export const GlobalProvider = ({ children }) => {
       const password = storedPassword;
       if (!password) {
         console.error('Password is not available');
+        setAlert({ isOpen: true, message: 'Password is not available. Please 
log in again.' });
         return;
       }
       const encryptedPrivateKey = 
CryptoJS.AES.encrypt(keyPairs[index].privateKey, password).toString();
@@ -216,12 +338,15 @@ export const GlobalProvider = ({ children }) => {
         generateKeyPair,
         selectedKeyPairIndex,
         setSelectedKeyPairIndex,
-        setSelectedKeyPair,
+        setSelectedKeyPair: setSelectedKeyPairFn,
         isAuthenticated,
         setIsAuthenticated,
         storedPassword,
         setStoredPassword,
         deleteKeyPair,
+        appendKeyPairs,
+        alert, // For alert modal
+        setAlert, // For alert modal
       }}
     >
       {children}
diff --git a/src/css/App.css b/src/css/App.css
index d95dd29..fd9fcd1 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -3706,33 +3706,95 @@ tr:hover {
   opacity: 0.7;
 }
 
+/* Container for keypair actions */
 .keypair-actions {
   display: flex;
-  justify-content: space-between;
+  gap: 10px;
+  justify-content: center;
   margin-top: 10px;
 }
 
+/* Badge Button Styles */
 .badge-button {
   background-color: #3c4e63; /* Matches your theme's primary color */
   color: white;
   border: none;
-  border-radius: 20px;
-  padding: 10px 20px;
+  border-radius: 50%; /* Makes the button circular */
+  padding: 10px; /* Uniform padding */
   font-size: 14px;
   cursor: pointer;
-  width: 48%; /* Ensures equal width for both buttons */
-  text-align: center;
+  width: 40px; /* Fixed width suitable for icons */
+  height: 40px; /* Fixed height suitable for icons */
   display: flex;
   align-items: center;
   justify-content: center;
+  transition: background-color 0.3s;
 }
 
 .badge-button:hover {
   background-color: #50647e; /* A slightly lighter shade for hover effect */
 }
 
+/* Remove margin-left from SVGs since there's no text */
 .badge-button svg {
-  margin-left: 8px; /* Adds space between the text and icon */
+  margin: 0; /* Eliminates any unintended spacing */
+  width: 24px; /* Adjust icon size as needed */
+  height: 24px; /* Adjust icon size as needed */
 }
 
+/* Tooltip container */
+.button-with-tooltip {
+  position: relative;
+  display: inline-block;
+}
+
+/* Tooltip text */
+.tooltip-text {
+  visibility: hidden;
+  width: max-content;
+  background-color: black;
+  color: #fff;
+  text-align: center;
+  border-radius: 6px;
+  padding: 5px 8px;
+  position: absolute;
+  z-index: 1;
+  bottom: 125%; /* Position above the button */
+  left: 50%;
+  transform: translateX(-50%);
+  opacity: 0;
+  transition: opacity 0.3s;
+}
 
+/* Tooltip arrow */
+.tooltip-text::after {
+  content: "";
+  position: absolute;
+  top: 100%; /* At the bottom of the tooltip */
+  left: 50%;
+  margin-left: -5px;
+  border-width: 5px;
+  border-style: solid;
+  border-color: black transparent transparent transparent;
+}
+
+/* Show the tooltip text when hovering over the container */
+.button-with-tooltip:hover .tooltip-text {
+  visibility: visible;
+  opacity: 1;
+}
+
+/* Additional styling for .keypair-actions to align buttons */
+.keypair-actions {
+    display: flex;
+    gap: 10px;
+    justify-content: center;
+    margin-top: 10px;
+}
+
+/* Centered icon class (optional if not needed) */
+.centered-icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
\ No newline at end of file
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index df05436..bb28209 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -25,6 +25,7 @@ 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 UploadIcon from '@mui/icons-material/Upload';
 import React, { useRef, useState, useEffect, useContext } from 'react';
 import Lottie from 'react-lottie';
 import versionData from '../data/version.json';
@@ -47,6 +48,9 @@ function Dashboard() {
         setSelectedKeyPair,
         setIsAuthenticated,
         deleteKeyPair,
+        appendKeyPairs, 
+        alert, // For alert modal
+        setAlert, // For alert modal
     } = useContext(GlobalContext);
 
     const [tabId, setTabId] = useState(null);
@@ -64,16 +68,17 @@ function Dashboard() {
     const [error, setError] = useState('');
     const [jsonFileName, setJsonFileName] = useState('');
     const fileInputRef = useRef(null);
+    const keyPairFileInputRef = useRef(null);
     const navigate = useNavigate();
 
-    // New state variables for transaction data and handling
+    // State variables for transaction data and handling
     const [transactionData, setTransactionData] = useState(null);
     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
+    // State for copying transaction ID
     const [isIdCopied, setIsIdCopied] = useState(false);
 
     const defaultOptions = {
@@ -225,6 +230,7 @@ function Dashboard() {
     const addNet = () => {
         if (!newNetName.trim() || !customUrl.trim()) {
         setError('Both fields are required.');
+        setAlert({ isOpen: true, message: 'Both Net Name and GraphQL URL are 
required.' });
         return;
         }
         setError('');
@@ -245,6 +251,7 @@ function Dashboard() {
 
     const toggleConnection = () => {
         if (!publicKey || !privateKey) {
+        setAlert({ isOpen: true, message: 'Public or Private key is missing.' 
});
         console.error('Public or Private key is missing');
         return;
         }
@@ -363,7 +370,7 @@ function Dashboard() {
                 setSelectedKeyPairIndex(0);
                 setSelectedKeyPair(0);
             });
-    
+
             // Close the delete confirmation modal
             setShowDeleteModal(false);
         }
@@ -383,6 +390,7 @@ function Dashboard() {
             setIsCopied(false);
         }, 1500);
         } catch (err) {
+        setAlert({ isOpen: true, message: 'Unable to copy text.' });
         console.error('Unable to copy text: ', err);
         }
     };
@@ -433,11 +441,13 @@ function Dashboard() {
             } else {
                 setTransactionData(null);
                 setTransactionError('Invalid JSON format: Missing required 
fields.');
+                setAlert({ isOpen: true, message: 'Invalid JSON format: 
Missing required fields.' });
             }
             } catch (err) {
             console.error('Error parsing JSON:', err);
             setTransactionData(null);
             setTransactionError('Invalid JSON format.');
+            setAlert({ isOpen: true, message: 'Invalid JSON format.' });
             }
         };
         reader.readAsText(file);
@@ -445,6 +455,39 @@ function Dashboard() {
         setJsonFileName(''); // Clear if the file is not JSON
         setTransactionData(null);
         setTransactionError('Please upload a JSON file.');
+        setAlert({ isOpen: true, message: 'Please upload a valid JSON file.' 
});
+        }
+    };
+
+    // Function to handle file upload for key pairs
+    const handleKeyPairFileUpload = (e) => {
+        const file = e.target.files[0];
+        if (file && file.type === 'application/json') {
+            const reader = new FileReader();
+            reader.onload = (event) => {
+                try {
+                    const uploadedKeyPairs = JSON.parse(event.target.result);
+
+                    // Ensure the uploaded data is either an array or an object
+                    if (Array.isArray(uploadedKeyPairs)) {
+                        appendKeyPairs(uploadedKeyPairs);
+                        // After appending, the context sets the 
selectedKeyPairIndex to the last one
+                    } else if (uploadedKeyPairs.publicKey && 
uploadedKeyPairs.privateKey) {
+                        appendKeyPairs([uploadedKeyPairs]); // Wrap single key 
pair into an array
+                        // After appending, the context sets the 
selectedKeyPairIndex to the last one
+                    } else {
+                        console.error('Invalid JSON format for key pairs.');
+                        setAlert({ isOpen: true, message: 'Invalid JSON format 
for key pairs.' });
+                    }
+                } catch (err) {
+                    console.error('Error parsing JSON:', err);
+                    setAlert({ isOpen: true, message: 'Error parsing JSON 
file.' });
+                }
+            };
+            reader.readAsText(file);
+        } else {
+            console.error('Please upload a valid JSON file.');
+            setAlert({ isOpen: true, message: 'Please upload a valid JSON 
file.' });
         }
     };
 
@@ -476,11 +519,13 @@ function Dashboard() {
             } else {
                 setTransactionData(null);
                 setTransactionError('Invalid JSON format: Missing required 
fields.');
+                setAlert({ isOpen: true, message: 'Invalid JSON format: 
Missing required fields.' });
             }
             } catch (err) {
             console.error('Error parsing JSON:', err);
             setTransactionData(null);
             setTransactionError('Invalid JSON format.');
+            setAlert({ isOpen: true, message: 'Invalid JSON format.' });
             }
         };
         reader.readAsText(file);
@@ -488,20 +533,27 @@ function Dashboard() {
         setJsonFileName('');
         setTransactionData(null);
         setTransactionError('Please upload a JSON file.');
+        setAlert({ isOpen: true, message: 'Please upload a valid JSON file.' 
});
         }
     };
 
     const handleFileClick = () => {
-        fileInputRef.current.click(); // Open file explorer when clicking on 
the field
+        fileInputRef.current.click();
+    };
+
+    const handleKeyPairFileClick = () => {
+        keyPairFileInputRef.current.click();
     };
 
     const handleSubmit = () => {
         if (!transactionData) {
         setTransactionError('No valid transaction data found.');
+        setAlert({ isOpen: true, message: 'No valid transaction data found.' 
});
         return;
         }
         if (!isConnected) {
         setTransactionError('Please connect to a net before submitting a 
transaction.');
+        setAlert({ isOpen: true, message: 'Please connect to a net before 
submitting a transaction.' });
         return;
         }
 
@@ -520,11 +572,12 @@ function Dashboard() {
             setTransactionData(null);
         } else {
             setTransactionError(response.error || 'Transaction submission 
failed.');
+            setAlert({ isOpen: true, message: response.error || 'Transaction 
submission failed.' });
         }
         });
     };
 
-    // New function to handle transaction ID click
+    // Function to handle transaction ID click
     const handleIdClick = () => {
         try {
         const transactionId = (successResponse && 
successResponse.postTransaction && successResponse.postTransaction.id) || '';
@@ -539,11 +592,12 @@ function Dashboard() {
             setIsIdCopied(false);
         }, 1500);
         } catch (err) {
+        setAlert({ isOpen: true, message: 'Unable to copy transaction ID.' });
         console.error('Unable to copy text: ', err);
         }
     };
 
-    // **New function to handle favicon load error**
+    // Function to handle favicon load error
     const handleFaviconError = () => {
         setFaviconUrl(''); // This will trigger the globe icon to display
     };
@@ -570,11 +624,15 @@ function Dashboard() {
 
     const handleGenerateKeyPair = () => {
         generateKeyPair(() => {
-          setSelectedKeyPairIndex(keyPairs.length); // Select the newly 
generated key pair
+          setSelectedKeyPairIndex(keyPairs.length - 1); // Select the newly 
generated key pair
           disconnectDueToKeysChange();
         });
     };
-      
+
+    // Function to close the alert modal
+    const closeAlertModal = () => {
+        setAlert({ isOpen: false, message: '' });
+    };
 
     return (
         <>
@@ -594,6 +652,7 @@ function Dashboard() {
                 <button
                     style={{ background: 'none', color: 'white', fontWeight: 
'bolder', outline: 'none', borderStyle: 'none', cursor: 'pointer' }}
                     onClick={back}
+                    title="Logout"
                 >
                     <ExitToAppIcon />
                 </button>
@@ -623,7 +682,7 @@ function Dashboard() {
                             <tr key={net.name}>
                                 <td>{net.name}</td>
                                 <td>
-                                <button className="icon-button" onClick={() => 
removeNet(net.name)}>
+                                <button className="icon-button" onClick={() => 
removeNet(net.name)} title="Delete Net">
                                     <i className="fas fa-trash"></i>
                                 </button>
                                 </td>
@@ -644,10 +703,10 @@ function Dashboard() {
                     </div>
                     </div>
                     <div className="save-container">
-                        <button onClick={addNet} className="button-save">
+                        <button onClick={addNet} className="button-save" 
title="Save Net">
                             Save
                         </button>
-                        <button onClick={() => setShowModal(false)} 
className="button-close">
+                        <button onClick={() => setShowModal(false)} 
className="button-close" title="Close Modal">
                             Close
                         </button>
                     </div>
@@ -665,25 +724,25 @@ function Dashboard() {
                     {/* Extract transaction ID */}
                     {successResponse && successResponse.postTransaction && 
successResponse.postTransaction.id ? (
                     <div className="fieldset">
-                    <div className="radio-option radio-option--full">
-                        <input
-                        type="radio"
-                        name="transactionId"
-                        id="txId"
-                        value={successResponse.postTransaction.id}
-                        checked
-                        readOnly
-                        onClick={handleIdClick}
-                        />
-                        <label htmlFor="txId">
-                        <span>{isIdCopied ? 'Copied' : 
`${successResponse.postTransaction.id.slice(0, 
5)}...${successResponse.postTransaction.id.slice(-5)}`}</span>
-                        </label>
-                    </div>
+                        <div className="radio-option radio-option--full">
+                            <input
+                            type="radio"
+                            name="transactionId"
+                            id="txId"
+                            value={successResponse.postTransaction.id}
+                            checked
+                            readOnly
+                            onClick={handleIdClick}
+                            />
+                            <label htmlFor="txId">
+                            <span>{isIdCopied ? 'Copied' : 
`${successResponse.postTransaction.id.slice(0, 
5)}...${successResponse.postTransaction.id.slice(-5)}`}</span>
+                            </label>
+                        </div>
                     </div>
                     ) : (
                     <p>No transaction ID found.</p>
                     )}
-                    <button onClick={() => setShowSuccessModal(false)} 
className="button-close">
+                    <button onClick={() => setShowSuccessModal(false)} 
className="button-close" title="Close Modal">
                     Close
                     </button>
                 </div>
@@ -691,6 +750,23 @@ function Dashboard() {
             </div>
             )}
 
+            {/* Alert Modal */}
+            {alert.isOpen && (
+                <div className="overlay">
+                    <div className="modal">
+                        <div className="modal-content">
+                            <h2>Alert</h2>
+                            <p>{alert.message}</p>
+                            <div className="save-container">
+                                <button onClick={closeAlertModal} 
className="button-save" title="Okay">
+                                    Okay
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            )}
+
             <div className="page__content page__content--with-header 
page__content--with-bottom-nav">
             <h2 className="page__title">Dashboard</h2>
 
@@ -722,7 +798,7 @@ function Dashboard() {
                     <span className="status-dot"></span>
                     <span className="tooltip">{isConnected ? 'Connected' : 
'Disconnected'}</span>
                 </div>
-                {selectedNet === 'Custom Net' && (
+                {selectedNet === 'Custom URL' && (
                     <input
                     type="text"
                     value={customUrl}
@@ -758,7 +834,6 @@ function Dashboard() {
                 </div>
             </div>
 
-            
 
             <h2 className="page__title">Select Account</h2>
             <div className="net">
@@ -767,7 +842,7 @@ function Dashboard() {
                         <div className="select-wrapper">
                             <select
                             value={selectedKeyPairIndex}
-                            onChange={(e) => switchKeyPair(e.target.value)}
+                            onChange={(e) => 
switchKeyPair(Number(e.target.value))}
                             className="select"
                             >
                             {keyPairs.map((keyPair, index) => (
@@ -780,25 +855,44 @@ function Dashboard() {
                         </div>
                         <div className="keypair-icons">
                             {keyPairs.length > 1 && (
-                            <button onClick={() => setShowDeleteModal(true)} 
className="icon-button">
+                            <button onClick={() => setShowDeleteModal(true)} 
className="icon-button" title="Delete Key Pair">
                                 <DeleteIcon style={{ color: 'white' }} />
                             </button>
                             )}
-                            <button onClick={handleCopyPublicKey} 
className="icon-button">
+                            <button onClick={handleCopyPublicKey} 
className="icon-button" title="Copy Public Key">
                                 <ContentCopyIcon style={{ color: isCopied ? 
'grey' : 'white' }} />
                             </button>
-                            <button onClick={handleDownloadKeyPair} 
className="icon-button">
+                            <button onClick={handleDownloadKeyPair} 
className="icon-button" title="Download Key Pair">
                                 <DownloadIcon style={{ color: 'white' }} />
                             </button>
                         </div>
                     </div>
                     <div className="keypair-actions">
-                        <button onClick={handleGenerateKeyPair} 
className="badge-button">
-                            Generate <AddCircleOutlineIcon style={{ color: 
'white' }} />
-                        </button>
-                        <button onClick={handleDownloadAllKeyPairs} 
className="badge-button">
-                            Export <SaveAltIcon style={{ color: 'white' }} />
-                        </button>
+                        <div className="button-with-tooltip">
+                            <button onClick={handleGenerateKeyPair} 
className="badge-button centered-icon" title="Generate Keys">
+                                <AddCircleOutlineIcon style={{ color: 'white' 
}} />
+                            </button>
+                            <span className="tooltip-text">Generate Keys</span>
+                        </div>
+                        <div className="button-with-tooltip">
+                            <button onClick={handleDownloadAllKeyPairs} 
className="badge-button centered-icon" title="Export All Keys">
+                                <SaveAltIcon style={{ color: 'white' }} />
+                            </button>
+                            <span className="tooltip-text">Export All 
Keys</span>
+                        </div>
+                        <div className="button-with-tooltip">
+                            <button onClick={handleKeyPairFileClick} 
className="badge-button centered-icon" title="Upload Keys">
+                                <UploadIcon style={{ color: 'white' }} />
+                            </button>
+                            <span className="tooltip-text">Upload Keys</span>
+                        </div>
+                        <input
+                            type="file"
+                            ref={keyPairFileInputRef}
+                            style={{ display: 'none' }}
+                            accept="application/json"
+                            onChange={handleKeyPairFileUpload}
+                        />
                     </div>
                 </div>
             </div>
@@ -810,10 +904,10 @@ function Dashboard() {
                             <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">
+                                <button onClick={handleDeleteKeyPair} 
className="button-save" title="Delete Key Pair">
                                     Delete
                                 </button>
-                                <button onClick={() => 
setShowDeleteModal(false)} className="button-close">
+                                <button onClick={() => 
setShowDeleteModal(false)} className="button-close" title="Cancel">
                                     Cancel
                                 </button>
                             </div>
@@ -822,7 +916,7 @@ function Dashboard() {
                 </div>
             )}
 
-            <button className="button button--full button--main open-popup" 
onClick={handleSubmit}>
+            <button className="button button--full button--main open-popup" 
onClick={handleSubmit} title="Submit Transaction">
                 Submit
             </button>
             <p className="bottom-navigation" style={{ backgroundColor: 
'transparent', display: 'flex', justifyContent: 'center', textShadow: '1px 1px 
1px rgba(0, 0, 0, 0.3)', color: 'rgb(255, 255, 255, 0.5)', fontSize: '9px' }}>
@@ -832,6 +926,7 @@ function Dashboard() {
         </div>
         </>
     );
+
 }
 
 export default Dashboard;
\ No newline at end of file

Reply via email to