Skip to content

feat: Implement Google Geocoding API integration#32

Open
jricardo27 wants to merge 2 commits into
mainfrom
feature/google-geocoding
Open

feat: Implement Google Geocoding API integration#32
jricardo27 wants to merge 2 commits into
mainfrom
feature/google-geocoding

Conversation

@jricardo27

Copy link
Copy Markdown
Owner

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.

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in localStorage along 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 ApiKeyModal and GeocodingSearch components, covering encryption/decryption, storage, API calls (mocked), and UI interactions.
  • User Documentation: Updates the 'Next Steps' tab in the WelcomeModal to 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-js for encryption.
    • Added @testing-library/jest-dom, @testing-library/react, @testing-library/user-event for component testing.
    • Added vitest, @vitest/ui, jsdom for the testing environment and framework.
    • Added various transitive dependencies required by the new packages.
  • package.json
    • Added crypto-js dependency (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, and coverage using vitest (lines 11-13).
  • src/App.tsx
    • Imported TCoordinate (line 7).
    • Added currentSearchResult state to manage the geocoded location (line 44).
    • Passed setCurrentSearchResult prop to TopMenu (line 73).
    • Passed currentSearchResult prop to all regional map components (AustralianCapitalTerritory, NewSouthWales, etc.) (lines 77-85).
  • src/components/ApiKeyModal/ApiKeyModal.test.tsx
    • New file containing tests for ApiKeyModal.
    • Mocks crypto-js and localStorage.
    • Tests saving encrypted API key and password.
    • Tests validation for empty inputs.
    • Tests cancel button functionality.
    • Tests modal visibility based on open prop.
  • src/components/ApiKeyModal/ApiKeyModal.tsx
    • New file implementing the ApiKeyModal component.
    • Uses useState for API key, password, and error state.
    • Implements handleSave to encrypt the API key using CryptoJS.AES.encrypt and store it along with the password in localStorage.
    • Includes input validation and displays errors using Snackbar.
    • Implements handleCancel to close the modal and clear fields.
    • Uses Material UI components (Dialog, TextField, Button, Snackbar).
  • src/components/ApiKeyModal/index.ts
    • New file exporting the ApiKeyModal component.
  • src/components/GeocodingSearch/GeocodingSearch.test.tsx
    • New file containing tests for GeocodingSearch.
    • Mocks crypto-js, axios, and localStorage.
    • 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.
  • src/components/GeocodingSearch/GeocodingSearch.tsx
    • New file implementing the GeocodingSearch component.
    • Uses useState for address input, search results, and error state.
    • Retrieves and decrypts the API key and password from localStorage using CryptoJS.AES.decrypt.
    • Makes an asynchronous GET request to the Google Geocoding API using axios.
    • Parses the API response and calls setCurrentSearchResult with 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.
  • src/components/GeocodingSearch/index.ts
    • New file exporting the GeocodingSearch component.
  • src/components/MapComponent/FeatureMap.tsx
    • Imported TCoordinate (line 19).
    • Added currentSearchResult prop (line 25).
    • Passed currentSearchResult prop to MapComponent (line 134).
  • src/components/MapComponent/MapComponent.tsx
    • Imported Icon, Point, Marker, Popup from react-leaflet (line 1).
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 20).
    • Defined a custom searchResultIcon for the geocoding marker (lines 24-32).
    • Rendered a Marker with a Popup at the currentSearchResult location if it exists (lines 141-145).
    • Passed currentSearchResult prop to MapViewUpdater (line 107).
  • src/components/MapComponent/MapViewUpdater.tsx
    • Imported TCoordinate (line 4).
    • Added currentSearchResult prop (line 9).
    • Modified useEffect to use map.flyTo to the currentSearchResult if available, otherwise uses map.setView (lines 15-23).
  • src/components/TopMenu/TopMenu.tsx
    • Imported VpnKeyIcon (line 1).
    • Imported TCoordinate, ApiKeyModal, GeocodingSearch (lines 9-11).
    • Added setCurrentSearchResult prop (line 21).
    • Added apiKeyModalOpen state and handlers (handleOpenApiKeyModal, handleCloseApiKeyModal) (lines 51, 93-94).
    • Added the GeocodingSearch component to the toolbar, passing setCurrentSearchResult (lines 116-118).
    • Added a button with VpnKeyIcon to open the ApiKeyModal (lines 219-223).
    • Adjusted Grid2 spacing and sizing for layout changes (lines 106, 116, 119).
  • 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).
  • src/pages/Australia/AustralianCapitalTerritory.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 30).
  • src/pages/Australia/NewSouthWales.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 36).
  • src/pages/Australia/NorthernTerritory.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 36).
  • src/pages/Australia/Queensland.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 36).
  • src/pages/Australia/SouthAustralia.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 36).
  • src/pages/Australia/Tasmania.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 36).
  • src/pages/Australia/Victoria.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 36).
  • src/pages/Australia/WesternAustralia.tsx
    • Imported TCoordinate (line 8).
    • Added currentSearchResult prop (line 13).
    • Passed currentSearchResult prop to FeatureMap (line 63).
  • src/pages/NewZealand/NewZealand.tsx
    • Imported TCoordinate (line 6).
    • Added currentSearchResult prop (line 11).
    • Passed currentSearchResult prop to FeatureMap (line 23).
  • src/setupTests.ts
    • New file for Vitest test setup.
    • Imports @testing-library/jest-dom/vitest for Jest DOM matchers.
    • Sets global.IS_REACT_ACT_ENVIRONMENT for React 18+ testing.
  • vite.config.ts
    • Added <reference types="vitest" /> (line 1).
    • Added test configuration block for Vitest, specifying globals, environment, setupFiles, and css (lines 16-21).
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

  1. 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.

gemini-code-assist[bot]

This comment was marked as outdated.

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.
@jricardo27

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.tsx component has a slightly complex flow for obtaining the decrypted API key if the session state isn't immediately updated. Refactoring attemptDecryptionAndSetKey to 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.

Comment on lines +92 to +130
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;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +36 to +38
// Remove old insecurely stored items if they exist
localStorage.removeItem('googleApiKeyPassword'); // Remove insecure password
localStorage.removeItem('googleApiKey'); // Remove old key if name changed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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!

Comment on lines +53 to +58
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant