From 3f09accc10d168a9abb993067a89dead9ac84987 Mon Sep 17 00:00:00 2001 From: DavidN016 Date: Fri, 19 Sep 2025 22:28:48 -0700 Subject: [PATCH 01/11] Add edit alias functionality for OfficeAccessCard (#1900) - Implemented a new endpoint to edit card aliases, including validation for input. - Updated the OfficeAccessCard utility to support alias editing. - Enhanced the CardReader component to allow users to edit card aliases directly in the UI. - Added corresponding tests to ensure proper functionality and error handling for the new feature. - Introduced a new audit log action for alias edits. --- api/main_endpoints/routes/OfficeAccessCard.js | 53 +++++++++ api/main_endpoints/util/OfficeAccessCard.js | 28 ++++- api/main_endpoints/util/auditLogActions.js | 1 + package-lock.json | 13 -- src/APIFunctions/CardReader.js | 25 ++++ src/Pages/CardReader/CardReader.js | 112 ++++++++++++++++-- src/Pages/Overview/SVG.js | 9 ++ src/config/config.example.json | 1 + test/api/OfficeAccessCard.js | 108 ++++++++++++++++- 9 files changed, 324 insertions(+), 26 deletions(-) diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index 53dabd032..aeee24b91 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -20,6 +20,7 @@ const { checkIfCardExists, generateAlias, deleteCard, + editAlias, } = require('../util/OfficeAccessCard.js'); const AuditLogActions = require('../util/auditLogActions.js'); const AuditLog = require('../models/AuditLog.js'); @@ -209,6 +210,58 @@ router.post('/getAllCards', async (req, res) => { } }); +router.post('/edit', async (req, res) => { + const decoded = decodeToken(req); + if (!decoded) { + return res.sendStatus(UNAUTHORIZED); + } + + const { _id, alias } = req.body; + + if (!_id || !alias) { + return res.status(BAD_REQUEST).send('_id and alias are required in request body'); + } + + // Validate alias is not empty or whitespace only + if (!alias.trim()) { + return res.status(BAD_REQUEST).send('alias cannot be empty or whitespace only'); + } + + // Validate _id is a valid ObjectId format + if (!/^[0-9a-fA-F]{24}$/.test(_id)) { + return res.status(BAD_REQUEST).send('_id must be a valid ObjectId'); + } + + try { + const updatedCard = await editAlias(_id, alias); + + if (!updatedCard) { + return res.status(NOT_FOUND).send('Card not found'); + } + + // Log the edit action + AuditLog.create({ + userId: decoded._id, + action: AuditLogActions.EDIT_CARD, + details: { + cardId: _id, + newAlias: alias, + oldAlias: updatedCard.alias !== alias ? 'unknown' : alias + } + }); + + logger.info(`Card alias updated successfully for card ID: ${_id}`); + return res.status(OK).json({ + message: 'Card alias updated successfully', + card: updatedCard + }); + + } catch (error) { + logger.error('Error updating card alias: ', error); + return res.status(SERVER_ERROR).send('Error updating card alias'); + } +}); + router.get('/listen', async (req, res) => { const decoded = await decodeToken(req, membershipState.OFFICER); if (decoded.status !== OK) { diff --git a/api/main_endpoints/util/OfficeAccessCard.js b/api/main_endpoints/util/OfficeAccessCard.js index c9b31cc2b..2ec5bb85f 100644 --- a/api/main_endpoints/util/OfficeAccessCard.js +++ b/api/main_endpoints/util/OfficeAccessCard.js @@ -87,4 +87,30 @@ function deleteCard(alias) { }); } -module.exports = { checkIfCardExists, generateAlias, deleteCard }; +function editAlias(_id, newAlias) { + return new Promise((resolve) => { + try { + OfficeAccessCard.findByIdAndUpdate( + _id, + { $set: { alias: newAlias } }, + { new: true, useFindAndModify: false }, + (error, result) => { + if (error) { + logger.error('editAlias got an error querying mongodb: ', error); + return resolve(false); + } + if (!result) { + logger.info(`Card with id ${_id} not found in the database`); + return resolve(false); + } + return resolve(result); + } + ); + } catch (error) { + logger.error('editAlias caught an error: ', error); + return resolve(false); + } + }); +} + +module.exports = { checkIfCardExists, generateAlias, deleteCard, editAlias }; diff --git a/api/main_endpoints/util/auditLogActions.js b/api/main_endpoints/util/auditLogActions.js index b1da29785..13e7a3d5b 100644 --- a/api/main_endpoints/util/auditLogActions.js +++ b/api/main_endpoints/util/auditLogActions.js @@ -14,6 +14,7 @@ const AuditLogActions = { VERIFY_CARD: 'VERIFY_CARD', ADD_CARD: 'ADD_CARD', DELETE_CARD: 'DELETE_CARD', + EDIT_CARD: 'EDIT_CARD', }; module.exports = AuditLogActions; diff --git a/package-lock.json b/package-lock.json index af1711335..b47f66712 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19920,19 +19920,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", - "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", - "optional": true, - "peer": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/src/APIFunctions/CardReader.js b/src/APIFunctions/CardReader.js index 25c1e4a4b..1f7fdfcee 100644 --- a/src/APIFunctions/CardReader.js +++ b/src/APIFunctions/CardReader.js @@ -55,3 +55,28 @@ export async function deleteCardFromDb(token, alias) { } return status; } + +export async function editCardAlias(token, _id, alias) { + let status = new ApiResponse(); + try { + const url = new URL('/api/OfficeAccessCard/edit', BASE_API_URL); + const res = await fetch(url.href, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ _id, alias }), + }); + if (res.ok) { + const result = await res.json(); + status.responseData = result; + } else { + status.error = true; + } + } catch (err) { + status.error = true; + status.responseData = err; + } + return status; +} diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index d068ffc72..7906ab73d 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -1,9 +1,9 @@ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect } from 'react'; import { BASE_API_URL } from '../../Enums'; import { useSCE } from '../../Components/context/SceContext'; -import { getAllCardsFromDb, deleteCardFromDb } from '../../APIFunctions/CardReader'; +import { getAllCardsFromDb, deleteCardFromDb, editCardAlias } from '../../APIFunctions/CardReader'; import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal'; -import { trashcanSymbol } from '../Overview/SVG'; +import { trashcanSymbol, pencilSymbol } from '../Overview/SVG'; const header = [ 'Time'.padEnd(29), @@ -21,6 +21,8 @@ export default function CardReader() { const [cards, setCards] = useState([]); const [toggleDelete, setToggleDelete] = useState(false); const [cardToDelete, setCardToDelete] = useState({}); + const [editingCardId, setEditingCardId] = useState(null); + const [editedAlias, setEditedAlias] = useState(''); const [tab, setTab] = useState(() => { const params = new URLSearchParams(window.location.search); return params.get('tab') || 'registry'; @@ -44,7 +46,7 @@ export default function CardReader() { const getColumnClassName = (columnName) => { let className = 'px-6 py-3 whitespace-nowrap '; - if(columnName === 'lastVerifiedAt' | columnName === 'registrationDate'){ + if(columnName === 'lastVerifiedAt' || columnName === 'registrationDate'){ className += 'hidden md:table-cell '; } else if (columnName === 'verifiedCount'){ className += 'hidden lg:table-cell'; @@ -89,12 +91,66 @@ export default function CardReader() { setCardToDelete(card); } + function handleEditClick(card) { + setEditingCardId(card._id); + setEditedAlias(card.alias); + } + + function handleCancelEdit() { + setEditingCardId(null); + setEditedAlias(''); + } + + async function handleSaveEdit(cardId) { + if (!editedAlias.trim()) { + return; // Don't save empty alias + } + + try { + const response = await editCardAlias(token, cardId, editedAlias.trim()); + if (!response.error) { + setCards(prevCards => + prevCards.map(card => + card._id === cardId + ? { ...card, alias: editedAlias.trim() } + : card + ) + ); + setEditingCardId(null); + setEditedAlias(''); + } else { + console.error('Failed to update card alias'); + } + } catch (error) { + console.error('Error updating card alias:', error); + } + } + function CardEntry({ card }) { + const isEditing = editingCardId === card._id; + return (
- {card.alias} + {isEditing ? ( + setEditedAlias(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleSaveEdit(card._id); + } else if (e.key === 'Escape') { + handleCancelEdit(); + } + }} + className='bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded px-2 py-1 text-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500' + autoFocus + /> + ) : ( + card.alias + )}
@@ -113,12 +169,46 @@ export default function CardReader() { - +
+ + {isEditing && ( + + )} + +
); diff --git a/src/Pages/Overview/SVG.js b/src/Pages/Overview/SVG.js index 9584f5007..ca290a6c9 100644 --- a/src/Pages/Overview/SVG.js +++ b/src/Pages/Overview/SVG.js @@ -51,6 +51,15 @@ export function trashcanSymbol(color = 'black') { ); } +export function pencilSymbol(color = '#6b7280') { + return ( + + + + + ); +} + export function copyIcon(className, onClick) { return ( diff --git a/src/config/config.example.json b/src/config/config.example.json index 78eecdc0b..9f72cfd5c 100644 --- a/src/config/config.example.json +++ b/src/config/config.example.json @@ -3,3 +3,4 @@ "TINYMCE_API_KEY": "XXXXXXXXXXXXXXXXXXXXX", "GOOGLE_API_CLIENT_ID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com" } + diff --git a/test/api/OfficeAccessCard.js b/test/api/OfficeAccessCard.js index 74145b3d7..ea5583f28 100644 --- a/test/api/OfficeAccessCard.js +++ b/test/api/OfficeAccessCard.js @@ -45,6 +45,8 @@ const token = ''; describe('OfficeAccessCard', () => { let deleteCardStub = null; let getAllCardsStub = null; + let editAliasStub = null; + let testCardId = null; const VALID_CARD_BYTES = 'wesleys card'; const NEW_CARD_BYTES = 'dials card'; @@ -52,10 +54,14 @@ describe('OfficeAccessCard', () => { const VALID_ALIAS = 'gauravs card'; const INVALID_ALIAS = 'bobs card'; + const NEW_ALIAS = 'updated test card'; + const EMPTY_ALIAS = ''; + const WHITESPACE_ALIAS = ' '; const VERIFY_API_PATH = '/api/OfficeAccessCard/verify'; const DELETE_API_PATH = '/api/OfficeAccessCard/delete'; const GET_ALL_CARDS_API_PATH = '/api/OfficeAccessCard/getAllCards'; + const EDIT_API_PATH = '/api/OfficeAccessCard/edit'; const INCREMENT_VERIFY_COUNT = 0; before(() => { @@ -76,7 +82,10 @@ describe('OfficeAccessCard', () => { }); return new Promise((resolve, reject) => { testOfficeAccessCard.save() - .then(resolve) + .then(savedCard => { + testCardId = savedCard._id.toString(); + resolve(savedCard); + }) .catch(reject); }); }); @@ -241,4 +250,101 @@ describe('OfficeAccessCard', () => { }); }); + describe('POST edit', () => { + it('Should return 401 when token is not sent', async () => { + const result = await test.sendPostRequest(EDIT_API_PATH); + expect(result).to.have.status(UNAUTHORIZED); + }); + + it('Should return 401 when invalid token is sent', async () => { + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH); + expect(result).to.have.status(UNAUTHORIZED); + }); + + it('Should return 400 when _id is missing from request body', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { alias: NEW_ALIAS }); + expect(result).to.have.status(BAD_REQUEST); + }); + + it('Should return 400 when alias is missing from request body', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: testCardId }); + expect(result).to.have.status(BAD_REQUEST); + }); + + it('Should return 400 when both _id and alias are missing', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, {}); + expect(result).to.have.status(BAD_REQUEST); + }); + + it('Should return 404 when trying to edit a non-existent card', async () => { + setTokenStatus(true); + const nonExistentId = new mongoose.Types.ObjectId().toString(); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: nonExistentId, alias: NEW_ALIAS }); + expect(result).to.have.status(NOT_FOUND); + }); + + it('Should return 400 when _id is not a valid ObjectId', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: 'invalid-id', alias: NEW_ALIAS }); + expect(result).to.have.status(BAD_REQUEST); + }); + + it('Should return 200 and successfully update alias for valid request', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); + expect(result).to.have.status(OK); + expect(result.body).to.have.property('message', 'Card alias updated successfully'); + expect(result.body).to.have.property('card'); + expect(result.body.card).to.have.property('alias', NEW_ALIAS); + }); + + it('Should actually update the alias in the database', async () => { + setTokenStatus(true); + await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); + + const updatedCard = await OfficeAccessCard.findById(testCardId); + expect(updatedCard.alias).to.equal(NEW_ALIAS); + }); + + it('Should handle empty alias by returning 400', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: testCardId, alias: EMPTY_ALIAS }); + expect(result).to.have.status(BAD_REQUEST); + }); + + it('Should handle whitespace-only alias by returning 400', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: testCardId, alias: WHITESPACE_ALIAS }); + expect(result).to.have.status(BAD_REQUEST); + }); + + it('Should preserve other card properties when updating alias', async () => { + setTokenStatus(true); + const originalCard = await OfficeAccessCard.findById(testCardId); + const originalCardBytes = originalCard.cardBytes; + const originalVerifiedCount = originalCard.verifiedCount; + + await test.sendPostRequestWithToken(token, + EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); + + const updatedCard = await OfficeAccessCard.findById(testCardId); + expect(updatedCard.cardBytes).to.equal(originalCardBytes); + expect(updatedCard.verifiedCount).to.equal(originalVerifiedCount); + expect(updatedCard.alias).to.equal(NEW_ALIAS); + }); + }); + }); From 15c1254f3cbbad6d25896c2aecd8ac5bfb207799 Mon Sep 17 00:00:00 2001 From: DavidN016 Date: Fri, 19 Sep 2025 22:57:49 -0700 Subject: [PATCH 02/11] fixed lint issues --- api/main_endpoints/routes/OfficeAccessCard.js | 7 +++---- src/Pages/CardReader/CardReader.js | 18 ++++++++++-------- test/api/OfficeAccessCard.js | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index aeee24b91..95c503495 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -217,7 +217,7 @@ router.post('/edit', async (req, res) => { } const { _id, alias } = req.body; - + if (!_id || !alias) { return res.status(BAD_REQUEST).send('_id and alias are required in request body'); } @@ -234,7 +234,7 @@ router.post('/edit', async (req, res) => { try { const updatedCard = await editAlias(_id, alias); - + if (!updatedCard) { return res.status(NOT_FOUND).send('Card not found'); } @@ -243,7 +243,7 @@ router.post('/edit', async (req, res) => { AuditLog.create({ userId: decoded._id, action: AuditLogActions.EDIT_CARD, - details: { + details: { cardId: _id, newAlias: alias, oldAlias: updatedCard.alias !== alias ? 'unknown' : alias @@ -255,7 +255,6 @@ router.post('/edit', async (req, res) => { message: 'Card alias updated successfully', card: updatedCard }); - } catch (error) { logger.error('Error updating card alias: ', error); return res.status(SERVER_ERROR).send('Error updating card alias'); diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index 7906ab73d..39e2f1697 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -105,30 +105,32 @@ export default function CardReader() { if (!editedAlias.trim()) { return; // Don't save empty alias } - + try { const response = await editCardAlias(token, cardId, editedAlias.trim()); if (!response.error) { - setCards(prevCards => - prevCards.map(card => - card._id === cardId + setCards(prevCards => + prevCards.map(card => + card._id === cardId ? { ...card, alias: editedAlias.trim() } : card ) ); setEditingCardId(null); setEditedAlias(''); - } else { - console.error('Failed to update card alias'); } } catch (error) { - console.error('Error updating card alias:', error); + setLogs( + (currLogs) => [ + '[error] unable to update card alias, check browser logs: \n' + error, + ...currLogs, + ] + ); } } function CardEntry({ card }) { const isEditing = editingCardId === card._id; - return (
diff --git a/test/api/OfficeAccessCard.js b/test/api/OfficeAccessCard.js index ea5583f28..9a0d6895c 100644 --- a/test/api/OfficeAccessCard.js +++ b/test/api/OfficeAccessCard.js @@ -312,7 +312,7 @@ describe('OfficeAccessCard', () => { setTokenStatus(true); await test.sendPostRequestWithToken(token, EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); - + const updatedCard = await OfficeAccessCard.findById(testCardId); expect(updatedCard.alias).to.equal(NEW_ALIAS); }); @@ -336,10 +336,10 @@ describe('OfficeAccessCard', () => { const originalCard = await OfficeAccessCard.findById(testCardId); const originalCardBytes = originalCard.cardBytes; const originalVerifiedCount = originalCard.verifiedCount; - + await test.sendPostRequestWithToken(token, EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); - + const updatedCard = await OfficeAccessCard.findById(testCardId); expect(updatedCard.cardBytes).to.equal(originalCardBytes); expect(updatedCard.verifiedCount).to.equal(originalVerifiedCount); From 27b2f4698d9cc447286e1994ebaf9dc3efdd55ff Mon Sep 17 00:00:00 2001 From: DavidN016 Date: Sat, 20 Sep 2025 23:36:34 -0700 Subject: [PATCH 03/11] fixed issues with edit alias --- src/Pages/CardReader/CardReader.js | 21 +++++++++++++-------- src/Pages/Overview/SVG.js | 8 -------- test/api/OfficeAccessCard.js | 7 ------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index 39e2f1697..f4b82b862 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -3,7 +3,7 @@ import { BASE_API_URL } from '../../Enums'; import { useSCE } from '../../Components/context/SceContext'; import { getAllCardsFromDb, deleteCardFromDb, editCardAlias } from '../../APIFunctions/CardReader'; import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal'; -import { trashcanSymbol, pencilSymbol } from '../Overview/SVG'; +import { trashcanSymbol } from '../Overview/SVG'; const header = [ 'Time'.padEnd(29), @@ -17,6 +17,16 @@ const header = [ export default function CardReader() { const { user } = useSCE(); const token = user.token; + + // Local pencil icon for edit functionality + const pencilSymbol = (color = '#6b7280') => { + return ( + + + + + ); + }; const [logs, setLogs] = useState([]); const [cards, setCards] = useState([]); const [toggleDelete, setToggleDelete] = useState(false); @@ -109,13 +119,8 @@ export default function CardReader() { try { const response = await editCardAlias(token, cardId, editedAlias.trim()); if (!response.error) { - setCards(prevCards => - prevCards.map(card => - card._id === cardId - ? { ...card, alias: editedAlias.trim() } - : card - ) - ); + // Refetch all cards from database to ensure UI matches server reality + await getAllCards(); setEditingCardId(null); setEditedAlias(''); } diff --git a/src/Pages/Overview/SVG.js b/src/Pages/Overview/SVG.js index ca290a6c9..470120415 100644 --- a/src/Pages/Overview/SVG.js +++ b/src/Pages/Overview/SVG.js @@ -51,14 +51,6 @@ export function trashcanSymbol(color = 'black') { ); } -export function pencilSymbol(color = '#6b7280') { - return ( - - - - - ); -} export function copyIcon(className, onClick) { return ( diff --git a/test/api/OfficeAccessCard.js b/test/api/OfficeAccessCard.js index 9a0d6895c..f50c97aa3 100644 --- a/test/api/OfficeAccessCard.js +++ b/test/api/OfficeAccessCard.js @@ -276,13 +276,6 @@ describe('OfficeAccessCard', () => { expect(result).to.have.status(BAD_REQUEST); }); - it('Should return 400 when both _id and alias are missing', async () => { - setTokenStatus(true); - const result = await test.sendPostRequestWithToken(token, - EDIT_API_PATH, {}); - expect(result).to.have.status(BAD_REQUEST); - }); - it('Should return 404 when trying to edit a non-existent card', async () => { setTokenStatus(true); const nonExistentId = new mongoose.Types.ObjectId().toString(); From 9d31a3347c37d10ff0014ddfcfab8dec46ff2739 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Fri, 3 Oct 2025 17:16:43 -0700 Subject: [PATCH 04/11] fixed input re-sizing --- src/Pages/CardReader/CardReader.js | 41 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index f4b82b862..f4fab1857 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -152,11 +152,16 @@ export default function CardReader() { handleCancelEdit(); } }} - className='bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded px-2 py-1 text-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500' + className='bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded text-gray-700 dark:text-white font-medium m-0 px-1 py-0 focus:outline-none focus:ring-1 focus:ring-blue-500' + style={{ width: '16ch' }} autoFocus /> ) : ( - card.alias +
+ + {card.alias} + +
)} @@ -176,38 +181,36 @@ export default function CardReader() { -
+
+ - {isEditing && ( - - )}
- +
+ + {isEditing && ( + + )} + +
); diff --git a/src/Pages/Overview/SVG.js b/src/Pages/Overview/SVG.js index 9584f5007..ca290a6c9 100644 --- a/src/Pages/Overview/SVG.js +++ b/src/Pages/Overview/SVG.js @@ -51,6 +51,15 @@ export function trashcanSymbol(color = 'black') { ); } +export function pencilSymbol(color = '#6b7280') { + return ( + + + + + ); +} + export function copyIcon(className, onClick) { return ( diff --git a/src/config/config.example.json b/src/config/config.example.json index 78eecdc0b..9f72cfd5c 100644 --- a/src/config/config.example.json +++ b/src/config/config.example.json @@ -3,3 +3,4 @@ "TINYMCE_API_KEY": "XXXXXXXXXXXXXXXXXXXXX", "GOOGLE_API_CLIENT_ID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com" } + diff --git a/test/api/OfficeAccessCard.js b/test/api/OfficeAccessCard.js index c199b9c91..97b1b0660 100644 --- a/test/api/OfficeAccessCard.js +++ b/test/api/OfficeAccessCard.js @@ -276,6 +276,13 @@ describe('OfficeAccessCard', () => { expect(result).to.have.status(BAD_REQUEST); }); + it('Should return 400 when both _id and alias are missing', async () => { + setTokenStatus(true); + const result = await test.sendPostRequestWithToken(token, + EDIT_API_PATH, {}); + expect(result).to.have.status(BAD_REQUEST); + }); + it('Should return 404 when trying to edit a non-existent card', async () => { setTokenStatus(true); const nonExistentId = new mongoose.Types.ObjectId().toString(); @@ -305,7 +312,7 @@ describe('OfficeAccessCard', () => { setTokenStatus(true); await test.sendPostRequestWithToken(token, EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); - + const updatedCard = await OfficeAccessCard.findById(testCardId); expect(updatedCard.alias).to.equal(NEW_ALIAS); }); @@ -329,10 +336,10 @@ describe('OfficeAccessCard', () => { const originalCard = await OfficeAccessCard.findById(testCardId); const originalCardBytes = originalCard.cardBytes; const originalVerifiedCount = originalCard.verifiedCount; - + await test.sendPostRequestWithToken(token, EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); - + const updatedCard = await OfficeAccessCard.findById(testCardId); expect(updatedCard.cardBytes).to.equal(originalCardBytes); expect(updatedCard.verifiedCount).to.equal(originalVerifiedCount); From fa3e1994e4ceed6560262544ce7edc1895dc63fa Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sat, 18 Oct 2025 11:45:31 -0700 Subject: [PATCH 09/11] undo diff --- api/main_endpoints/routes/OfficeAccessCard.js | 1 + package-lock.json | 13 +++++++++++++ src/Pages/Overview/SVG.js | 9 --------- src/config/config.example.json | 1 - test/api/OfficeAccessCard.js | 13 +++---------- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index 2ed5380ac..efc88a1f5 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -242,6 +242,7 @@ router.post('/edit', async (req, res) => { try { const updatedCard = await editAlias(_id, alias); + if (!updatedCard) { return res.status(NOT_FOUND).send('Card not found'); } diff --git a/package-lock.json b/package-lock.json index b47f66712..af1711335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19920,6 +19920,19 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", + "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", + "optional": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/src/Pages/Overview/SVG.js b/src/Pages/Overview/SVG.js index ca290a6c9..9584f5007 100644 --- a/src/Pages/Overview/SVG.js +++ b/src/Pages/Overview/SVG.js @@ -51,15 +51,6 @@ export function trashcanSymbol(color = 'black') { ); } -export function pencilSymbol(color = '#6b7280') { - return ( - - - - - ); -} - export function copyIcon(className, onClick) { return ( diff --git a/src/config/config.example.json b/src/config/config.example.json index 9f72cfd5c..78eecdc0b 100644 --- a/src/config/config.example.json +++ b/src/config/config.example.json @@ -3,4 +3,3 @@ "TINYMCE_API_KEY": "XXXXXXXXXXXXXXXXXXXXX", "GOOGLE_API_CLIENT_ID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com" } - diff --git a/test/api/OfficeAccessCard.js b/test/api/OfficeAccessCard.js index 97b1b0660..c199b9c91 100644 --- a/test/api/OfficeAccessCard.js +++ b/test/api/OfficeAccessCard.js @@ -276,13 +276,6 @@ describe('OfficeAccessCard', () => { expect(result).to.have.status(BAD_REQUEST); }); - it('Should return 400 when both _id and alias are missing', async () => { - setTokenStatus(true); - const result = await test.sendPostRequestWithToken(token, - EDIT_API_PATH, {}); - expect(result).to.have.status(BAD_REQUEST); - }); - it('Should return 404 when trying to edit a non-existent card', async () => { setTokenStatus(true); const nonExistentId = new mongoose.Types.ObjectId().toString(); @@ -312,7 +305,7 @@ describe('OfficeAccessCard', () => { setTokenStatus(true); await test.sendPostRequestWithToken(token, EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); - + const updatedCard = await OfficeAccessCard.findById(testCardId); expect(updatedCard.alias).to.equal(NEW_ALIAS); }); @@ -336,10 +329,10 @@ describe('OfficeAccessCard', () => { const originalCard = await OfficeAccessCard.findById(testCardId); const originalCardBytes = originalCard.cardBytes; const originalVerifiedCount = originalCard.verifiedCount; - + await test.sendPostRequestWithToken(token, EDIT_API_PATH, { _id: testCardId, alias: NEW_ALIAS }); - + const updatedCard = await OfficeAccessCard.findById(testCardId); expect(updatedCard.cardBytes).to.equal(originalCardBytes); expect(updatedCard.verifiedCount).to.equal(originalVerifiedCount); From 95a8ce15cad133dd61a09e236c690074387e4a41 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sun, 19 Oct 2025 20:22:20 -0700 Subject: [PATCH 10/11] fixed requested changes --- src/Pages/CardReader/CardReader.js | 59 +++++++++++------------------- src/Pages/Overview/SVG.js | 26 +++++++++++++ 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index a3b6aeb03..7bee47d50 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -3,7 +3,7 @@ import { BASE_API_URL } from '../../Enums'; import { useSCE } from '../../Components/context/SceContext'; import { getAllCardsFromDb, deleteCardFromDb, editCardAlias } from '../../APIFunctions/CardReader'; import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal'; -import { trashcanSymbol, pencilSymbol } from '../Overview/SVG'; +import { trashcanSymbol, pencilSymbol, checkSymbol, cancelSymbol } from '../Overview/SVG'; const header = [ 'Time'.padEnd(29), @@ -18,15 +18,6 @@ export default function CardReader() { const { user } = useSCE(); const token = user.token; - // Local pencil icon for edit functionality - const pencilSymbol = (color = '#6b7280') => { - return ( - - - - - ); - }; const [logs, setLogs] = useState([]); const [cards, setCards] = useState([]); const [toggleDelete, setToggleDelete] = useState(false); @@ -102,13 +93,13 @@ export default function CardReader() { } function handleEditClick(card) { - setEditingCardId(card._id); - setEditedAlias(card.alias); - } - - function handleCancelEdit() { - setEditingCardId(null); - setEditedAlias(''); + if (editingCardId === card._id) { + setEditingCardId(null); + setEditedAlias(''); + } else { + setEditingCardId(card._id); + setEditedAlias(card.alias); + } } async function handleSaveEdit(cardId) { @@ -134,6 +125,15 @@ export default function CardReader() { } } + function handleEditKeyDown(key) { + if (key === 'Enter') { + handleSaveEdit(editingCardId); + } else if (key === 'Escape') { + setEditingCardId(null); + setEditedAlias(''); + } + } + function CardEntry({ card }) { const isEditing = editingCardId === card._id; return ( @@ -145,13 +145,7 @@ export default function CardReader() { type='text' value={editedAlias} onChange={(e) => setEditedAlias(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSaveEdit(card._id); - } else if (e.key === 'Escape') { - handleCancelEdit(); - } - }} + onKeyDown={(e) => handleEditKeyDown(e.key)} className='bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded text-gray-700 dark:text-white font-medium m-0 px-1 py-0 focus:outline-none focus:ring-1 focus:ring-blue-500' style={{ width: '16ch' }} autoFocus @@ -187,26 +181,15 @@ export default function CardReader() { onClick={() => handleSaveEdit(card._id)} title='Save changes' > - - - + {checkSymbol('#22c55e')}
+ + {card.alias} + +
+ ); + } + function CardEntry({ card }) { const isEditing = editingCardId === card._id; return (
- {isEditing ? ( - setEditedAlias(e.target.value)} - onKeyDown={(e) => handleEditKeyDown(e.key)} - className='bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded text-gray-700 dark:text-white font-medium m-0 px-1 py-0 focus:outline-none focus:ring-1 focus:ring-blue-500' - style={{ width: '16ch' }} - autoFocus - /> - ) : ( -
- - {card.alias} - -
- )} + {renderInputOrAlias(card)}