feat: Implement Google Geocoding API integration#32
Conversation
This commit introduces a new feature allowing you to search for addresses using Google's Geocoding API.
Key changes include:
- **API Key Management**:
- A new modal (`ApiKeyModal`) allows you to enter your Google Geocoding API key and a password.
- The API key is encrypted using AES with the user-provided password and stored in `localStorage`. The password itself is also stored in `localStorage` to facilitate decryption for API calls.
- An icon in the `TopMenu` triggers this modal.
- **Geocoding Search**:
- A new search bar (`GeocodingSearch`) is added to the `TopMenu`.
- It retrieves the encrypted API key and password, decrypts the key, and calls the Google Geocoding API.
- Handles various API responses (success, no results, errors) and displays appropriate messages/Snackbar notifications.
- **Map Integration**:
- Successful geocoding results (latitude/longitude of the first result) are displayed as a marker on the map.
- The map view automatically pans and zooms (`flyTo`) to the new marker.
- The marker is temporary and not saved with other features.
- **Dependencies**:
- Added `crypto-js` for encryption/decryption.
- Added Vitest and React Testing Library for testing.
- **Testing**:
- Unit and component tests were added for `ApiKeyModal` (encryption, storage, UI interaction) and `GeocodingSearch` (decryption, API mocking, result handling).
- Test setup and configuration for Vitest were included.
- **Documentation**:
- The `WelcomeModal` (Next Steps tab) was updated to explain the new feature, the need for a user-provided API key, and how to configure it.
This addresses Issue #6 by providing a client-side solution for geocoding, respecting the $0 budget by requiring you to use your own API keys.
There was a problem hiding this comment.
Hello @jricardo27, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
Summary of Changes
This pull request introduces a new feature to integrate with the Google Geocoding API. The primary goal is to allow users to search for addresses directly within the application and visualize the results on the map. Key aspects include a new modal for securely managing the user's Google API key (encrypted and stored locally), a search bar component to perform the geocoding requests, and updates to the map components to display the search result location. Additionally, this PR sets up a new testing framework using Vitest and React Testing Library and includes tests for the new components. The Welcome Modal has also been updated to inform users about this new functionality and the requirement for their own API key.
Highlights
- Google Geocoding API Integration: Adds functionality to search for geographical coordinates based on a provided address using the Google Geocoding API.
- API Key Management: Introduces a new modal (
ApiKeyModal) for users to input their Google Geocoding API key and a password. The API key is encrypted using AES and stored locally inlocalStoragealong with the password for decryption. - Address Search Component: Adds a new search bar component (
GeocodingSearch) to the top menu, which handles retrieving the stored API key, decrypting it, making the API call, and processing the response. - Map Integration: Successful geocoding results are displayed as a temporary marker on the map, and the map view automatically pans and zooms to the result location.
- Testing Framework Setup: Integrates Vitest and React Testing Library into the project, adding new test scripts (
test,test:ui,coverage) and configuration. - Component Testing: Includes unit and component tests for the new
ApiKeyModalandGeocodingSearchcomponents, covering encryption/decryption, storage, API calls (mocked), and UI interactions. - User Documentation: Updates the 'Next Steps' tab in the
WelcomeModalto explain the new geocoding feature, the need for a user-provided API key, and how to configure it.
Changelog
Click here to see the changelog
- package-lock.json
- Added
crypto-jsfor encryption. - Added
@testing-library/jest-dom,@testing-library/react,@testing-library/user-eventfor component testing. - Added
vitest,@vitest/ui,jsdomfor the testing environment and framework. - Added various transitive dependencies required by the new packages.
- Added
- package.json
- Added
crypto-jsdependency (line 25). - Added testing-related dev dependencies (
@testing-library/*,@types/crypto-js,@types/testing-library__jest-dom,jsdom,vitest,@vitest/ui) (lines 45-58). - Added new npm scripts for
test,test:ui, andcoverageusingvitest(lines 11-13).
- Added
- src/App.tsx
- Imported
TCoordinate(line 7). - Added
currentSearchResultstate to manage the geocoded location (line 44). - Passed
setCurrentSearchResultprop toTopMenu(line 73). - Passed
currentSearchResultprop to all regional map components (AustralianCapitalTerritory,NewSouthWales, etc.) (lines 77-85).
- Imported
- src/components/ApiKeyModal/ApiKeyModal.test.tsx
- New file containing tests for
ApiKeyModal. - Mocks
crypto-jsandlocalStorage. - Tests saving encrypted API key and password.
- Tests validation for empty inputs.
- Tests cancel button functionality.
- Tests modal visibility based on
openprop.
- New file containing tests for
- src/components/ApiKeyModal/ApiKeyModal.tsx
- New file implementing the
ApiKeyModalcomponent. - Uses
useStatefor API key, password, and error state. - Implements
handleSaveto encrypt the API key usingCryptoJS.AES.encryptand store it along with the password inlocalStorage. - Includes input validation and displays errors using
Snackbar. - Implements
handleCancelto close the modal and clear fields. - Uses Material UI components (
Dialog,TextField,Button,Snackbar).
- New file implementing the
- src/components/ApiKeyModal/index.ts
- New file exporting the
ApiKeyModalcomponent.
- New file exporting the
- src/components/GeocodingSearch/GeocodingSearch.test.tsx
- New file containing tests for
GeocodingSearch. - Mocks
crypto-js,axios, andlocalStorage. - Tests decryption of the stored API key.
- Tests handling cases where the API key is not configured or decryption fails.
- Tests successful API calls and updating
setCurrentSearchResult. - Tests handling various API status responses (
ZERO_RESULTS,REQUEST_DENIED, etc.). - Tests handling network errors.
- New file containing tests for
- src/components/GeocodingSearch/GeocodingSearch.tsx
- New file implementing the
GeocodingSearchcomponent. - Uses
useStatefor address input, search results, and error state. - Retrieves and decrypts the API key and password from
localStorageusingCryptoJS.AES.decrypt. - Makes an asynchronous GET request to the Google Geocoding API using
axios. - Parses the API response and calls
setCurrentSearchResultwith the coordinates of the first result if successful. - Handles various API status codes and displays appropriate error messages using
Snackbar. - Displays search results (formatted address and coordinates) below the search bar.
- Includes basic input validation for the address field.
- New file implementing the
- src/components/GeocodingSearch/index.ts
- New file exporting the
GeocodingSearchcomponent.
- New file exporting the
- src/components/MapComponent/FeatureMap.tsx
- Imported
TCoordinate(line 19). - Added
currentSearchResultprop (line 25). - Passed
currentSearchResultprop toMapComponent(line 134).
- Imported
- src/components/MapComponent/MapComponent.tsx
- Imported
Icon,Point,Marker,Popupfromreact-leaflet(line 1). - Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 20). - Defined a custom
searchResultIconfor the geocoding marker (lines 24-32). - Rendered a
Markerwith aPopupat thecurrentSearchResultlocation if it exists (lines 141-145). - Passed
currentSearchResultprop toMapViewUpdater(line 107).
- Imported
- src/components/MapComponent/MapViewUpdater.tsx
- Imported
TCoordinate(line 4). - Added
currentSearchResultprop (line 9). - Modified
useEffectto usemap.flyToto thecurrentSearchResultif available, otherwise usesmap.setView(lines 15-23).
- Imported
- src/components/TopMenu/TopMenu.tsx
- Imported
VpnKeyIcon(line 1). - Imported
TCoordinate,ApiKeyModal,GeocodingSearch(lines 9-11). - Added
setCurrentSearchResultprop (line 21). - Added
apiKeyModalOpenstate and handlers (handleOpenApiKeyModal,handleCloseApiKeyModal) (lines 51, 93-94). - Added the
GeocodingSearchcomponent to the toolbar, passingsetCurrentSearchResult(lines 116-118). - Added a button with
VpnKeyIconto open theApiKeyModal(lines 219-223). - Adjusted
Grid2spacing and sizing for layout changes (lines 106, 116, 119).
- Imported
- src/components/WelcomeModal/NextSteps.tsx
- Imported additional icons (
MdSearch,MdVpnKey,MdStorage,MdInfoOutline) (line 3). - Added a new section titled 'New: Address Search (Geocoding)!' (lines 8-47).
- Updated the 'Future Enhancements' section heading and content slightly (lines 49-72).
- Ensured the modal content is scrollable (line 7).
- Imported additional icons (
- src/pages/Australia/AustralianCapitalTerritory.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 30).
- Imported
- src/pages/Australia/NewSouthWales.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 36).
- Imported
- src/pages/Australia/NorthernTerritory.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 36).
- Imported
- src/pages/Australia/Queensland.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 36).
- Imported
- src/pages/Australia/SouthAustralia.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 36).
- Imported
- src/pages/Australia/Tasmania.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 36).
- Imported
- src/pages/Australia/Victoria.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 36).
- Imported
- src/pages/Australia/WesternAustralia.tsx
- Imported
TCoordinate(line 8). - Added
currentSearchResultprop (line 13). - Passed
currentSearchResultprop toFeatureMap(line 63).
- Imported
- src/pages/NewZealand/NewZealand.tsx
- Imported
TCoordinate(line 6). - Added
currentSearchResultprop (line 11). - Passed
currentSearchResultprop toFeatureMap(line 23).
- Imported
- src/setupTests.ts
- New file for Vitest test setup.
- Imports
@testing-library/jest-dom/vitestfor Jest DOM matchers. - Sets
global.IS_REACT_ACT_ENVIRONMENTfor React 18+ testing.
- vite.config.ts
- Added
<reference types="vitest" />(line 1). - Added
testconfiguration block for Vitest, specifyingglobals,environment,setupFiles, andcss(lines 16-21).
- Added
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
An address you seek,
API key, encryption,
Map shows where you are.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
This commit addresses critical feedback on the Google Geocoding API integration:
**Security Enhancements:**
- **Removed Password Storage (Critical Fix)**: The decryption password for the Google API key is no longer stored in localStorage.
- `ApiKeyModal.tsx` now only stores the encrypted API key. It also cleans up previously stored passwords or unencrypted keys.
- `GeocodingSearch.tsx` now prompts you to enter your password each session (or when required) to decrypt the API key. This password is held in component state and not persisted.
- Documentation in `WelcomeModal.tsx` has been updated to reflect this new, more secure behavior.
**UX and Code Quality Improvements:**
- **Enhanced Popup Messages**: The map marker for a geocoded search result now displays the full formatted address in its popup instead of a generic "Searched Location".
- This involved updating the `currentSearchResult` state to include the address and propagating this change through relevant components.
- **Fixed Duplicate Imports**: Removed redundant import statements in `src/components/GeocodingSearch/GeocodingSearch.tsx`.
**Testing:**
- All relevant unit and component tests for `ApiKeyModal.tsx` and `GeocodingSearch.tsx` have been updated to reflect these changes, including the new password input workflow and data structures.
These changes significantly improve the security of the geocoding feature by ensuring the decryption password is not persisted, while also enhancing your experience with more informative map popups and maintaining code quality.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable geocoding feature with a good approach to client-side API key security. The new components are well-tested and error handling is robust. The primary suggestion focuses on a minor refactor for improved clarity in the GeocodingSearch component. Please also consider updating the PR description to accurately reflect the secure password handling (i.e., password is not stored in localStorage).
Summary of Findings
- Code Clarity in API Key Decryption Logic: The
GeocodingSearch.tsxcomponent has a slightly complex flow for obtaining the decrypted API key if the session state isn't immediately updated. RefactoringattemptDecryptionAndSetKeyto return the key could simplify this. - PR Description Accuracy: The PR description mentions storing the password in localStorage, but the code correctly avoids this, prompting per session. The description should be updated to reflect the more secure actual implementation.
- Security Implementation: The actual implementation of API key encryption and session-based password prompting is well-handled and secure for a client-side solution.
Merge Readiness
The pull request is in good shape and introduces a valuable feature with strong considerations for security and usability. The suggested changes are primarily for enhancing code clarity and maintainability. Once these minor points are considered and the PR description is updated for accuracy, this PR should be ready for merging. As an AI, I am not authorized to approve pull requests; please ensure it is reviewed and approved by a team member.
| if (!currentApiKey) { | ||
| if (!sessionPassword) { | ||
| setShowPasswordPrompt(true); | ||
| // Optionally, set a specific error message like "Password needed for this session." | ||
| setError('Please enter your password to unlock the Geocoding feature for this session.'); | ||
| return; | ||
| } | ||
| // If sessionPassword is set, but decryptedApiKeyForSession is not, try to decrypt again. | ||
| // This handles the case where user enters password then immediately searches. | ||
| if (!attemptDecryptionAndSetKey()) { | ||
| // attemptDecryptionAndSetKey will set appropriate errors and showPasswordPrompt | ||
| return; | ||
| } | ||
| // After successful decryption, decryptedApiKeyForSession will be set. | ||
| // We need to use it in this current execution of handleSearch. | ||
| // Re-reading from state here won't work immediately due to async nature of setState. | ||
| // So, let's retrieve it directly after attemptDecryptionAndSetKey potentially sets it. | ||
| // This is a bit of a workaround for the immediate need of the key. | ||
| // A better approach might be to make attemptDecryptionAndSetKey return the key or make handleSearch more state-driven. | ||
| // For now, let's assume attemptDecryptionAndSetKey updates a variable that can be read here. | ||
| // The state `decryptedApiKeyForSession` will be set for subsequent calls. | ||
| // A more direct way: | ||
| try { | ||
| const decryptedBytes = CryptoJS.AES.decrypt(encryptedApiKeyString, sessionPassword); | ||
| currentApiKey = decryptedBytes.toString(CryptoJS.enc.Utf8); | ||
| if (!currentApiKey) { | ||
| setError('Invalid password. Please try again.'); | ||
| setSessionPassword(''); // Clear session password | ||
| setShowPasswordPrompt(true); | ||
| return; | ||
| } | ||
| setDecryptedApiKeyForSession(currentApiKey); // Save for next time | ||
| } catch (e) { | ||
| setError('Decryption failed. Please check your password or re-set API Key.'); | ||
| setSessionPassword(''); | ||
| setShowPasswordPrompt(true); | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic within this block for obtaining the currentApiKey when decryptedApiKeyForSession (state) is not yet set involves a nested call to attemptDecryptionAndSetKey or an inline re-decryption. This can happen if handleSearch is called immediately after attemptDecryptionAndSetKey (e.g., from an Enter key press in the password field) before the state update for decryptedApiKeyForSession is processed.
While this works, it makes the control flow a bit complex and involves potentially redundant decryption operations.
Consider refactoring attemptDecryptionAndSetKey to return the decrypted key (or null/throw on failure). handleSearch could then call this function once if decryptedApiKeyForSession is not available and use the returned value directly. This would streamline the logic and make it easier to follow.
For example:
// In attemptDecryptionAndSetKey
// ... on successful decryption ...
setDecryptedApiKeyForSession(decryptedKey);
setShowPasswordPrompt(false);
setError(null);
return decryptedKey; // Return the key
// ... on failure ...
return null;
// In handleSearch
// ...
let currentApiKey = decryptedApiKeyForSession;
if (!currentApiKey) {
if (!sessionPassword) {
setShowPasswordPrompt(true);
setError('Please enter your password...');
return;
}
currentApiKey = attemptDecryptionAndSetKey(); // Call it once
if (!currentApiKey) {
// attemptDecryptionAndSetKey would have set errors/prompt
return;
}
}
// Proceed with currentApiKey...This would remove the nested try-catch for decryption within handleSearch and make the sequence of operations clearer.
| // Remove old insecurely stored items if they exist | ||
| localStorage.removeItem('googleApiKeyPassword'); // Remove insecure password | ||
| localStorage.removeItem('googleApiKey'); // Remove old key if name changed |
There was a problem hiding this comment.
It's excellent that you're explicitly removing potentially insecurely stored items (googleApiKeyPassword and googleApiKey) here. This proactive cleanup enhances security and ensures that older, less secure storage patterns are deprecated.
This also aligns with the modal's help text (lines 62-66) which correctly states the password is not stored and is required per session. This is a much more secure approach than what was initially implied in the PR description. Great job on this implementation detail!
| if (!decryptedKey) { | ||
| setError('Invalid password or corrupted API key. Please re-enter password or re-set API Key.'); | ||
| setSessionPassword(''); // Clear session password on failure | ||
| setDecryptedApiKeyForSession(null); | ||
| setShowPasswordPrompt(true); | ||
| return false; |
There was a problem hiding this comment.
The error message "Invalid password or corrupted API key. Please re-enter password or re-set API Key." is generally good.
However, if decryptedBytes.toString(CryptoJS.enc.Utf8) results in an empty string, it's overwhelmingly likely due to an incorrect password rather than a 'corrupted API key' (assuming the encrypted key was stored correctly by ApiKeyModal). While corruption of localStorage data is theoretically possible, it's less common.
Would it be clearer to the user if the message was more focused on the password, e.g., "Invalid password. Please try again. If the issue persists, you may need to re-set the API Key."? This is a minor point, but could slightly improve the user experience by guiding them to the most probable solution first.
This commit introduces a new feature allowing you to search for addresses using Google's Geocoding API.
Key changes include:
API Key Management:
ApiKeyModal) allows you to enter your Google Geocoding API key and a password.localStorage. The password itself is also stored inlocalStorageto facilitate decryption for API calls.TopMenutriggers this modal.Geocoding Search:
GeocodingSearch) is added to theTopMenu.Map Integration:
flyTo) to the new marker.Dependencies:
crypto-jsfor encryption/decryption.Testing:
ApiKeyModal(encryption, storage, UI interaction) andGeocodingSearch(decryption, API mocking, result handling).Documentation:
WelcomeModal(Next Steps tab) was updated to explain the new feature, the need for a user-provided API key, and how to configure it.This addresses Issue #6 by providing a client-side solution for geocoding, respecting the $0 budget by requiring you to use your own API keys.