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 <[email protected]>
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>