Skip to content

Conversation

@sunithvs
Copy link
Owner

Add localStorage expiry for SupportModal to improve UX

📋 Summary

Implements localStorage-based expiry functionality for the SupportModal to prevent it from showing on every page visit. The modal now shows only on first visit and then respects a 1-day cooldown period.

🎯 Problem Statement

The SupportModal was showing on every page visit after a 4-second delay, which could be intrusive for returning users. Users needed a way to dismiss the modal temporarily without it reappearing immediately on subsequent visits.

💡 Solution

Added localStorage-based expiry system that:

  • Shows modal on first-time visits
  • Saves timestamp when modal is closed
  • Respects 24-hour cooldown period before showing again
  • Includes robust error handling for localStorage edge cases

🔧 Technical Changes

Modified Files

  • www/components/modal/support-modal.tsx

Key Implementation Details

  1. localStorage Check Function

    const checkShouldShowModal = () => {
      try {
        const modalData = localStorage.getItem('devb-support-modal');
        
        if (!modalData) {
          // First time visit - show modal
          return true;
        }
        
        const { timestamp } = JSON.parse(modalData);
        const oneDayInMs = 24 * 60 * 60 * 1000; // 1 day in milliseconds
        const now = Date.now();
        
        // Show modal if more than 1 day has passed
        return (now - timestamp) > oneDayInMs;
      } catch (error) {
        // If there's any error with localStorage, show modal
        console.warn('Error checking modal state:', error);
        return true;
      }
    };
  2. Timestamp Storage on Close

    const handleModalClose = (isOpen: boolean) => {
      setOpen(isOpen);
      if (!isOpen) {
        try {
          // Save timestamp when modal is closed
          const modalData = {
            timestamp: Date.now(),
          };
          localStorage.setItem('devb-support-modal', JSON.stringify(modalData));
        } catch (error) {
          console.warn('Error saving modal state:', error);
        }
      }
    };
  3. Updated Modal Logic

    • Modified useEffect to use the new check function
    • Updated Dialog's onOpenChange to use the new handler
    • Maintained existing 4-second delay behavior

🧪 Testing

Test Cases

  1. First Visit: Modal should appear after 4 seconds
  2. Modal Dismissal: Closing modal should save timestamp to localStorage
  3. Immediate Revisit: Modal should not appear on immediate page refresh
  4. After Expiry: Modal should appear again after 24+ hours
  5. localStorage Error: Modal should show if localStorage fails (graceful fallback)

Manual Testing Steps

  1. Clear localStorage or use incognito mode
  2. Visit a profile page
  3. Verify modal appears after 4 seconds
  4. Close the modal
  5. Refresh the page - modal should not appear
  6. Check localStorage for devb-support-modal entry
  7. Manually modify timestamp to test expiry logic

🔒 Edge Cases Handled

  • localStorage Unavailable: Falls back to showing modal (safe default)
  • Invalid JSON: Catches parse errors and shows modal
  • Missing Timestamp: Treats as first visit
  • Browser Compatibility: Uses standard localStorage API

📊 Impact

User Experience

  • ✅ Reduced modal fatigue for returning users
  • ✅ Maintains visibility for new users
  • ✅ Configurable expiry period (currently 24 hours)

Technical

  • ✅ Minimal performance impact
  • ✅ No external dependencies
  • ✅ Backward compatible
  • ✅ Robust error handling

🎨 Configuration

The expiry period can be easily adjusted by modifying:

const oneDayInMs = 24 * 60 * 60 * 1000; // Change this value

Possible configurations:

  • 1 hour: 60 * 60 * 1000
  • 1 week: 7 * 24 * 60 * 60 * 1000
  • 1 month: 30 * 24 * 60 * 60 * 1000

🔮 Future Enhancements

  • Add user preference toggle to disable modal entirely
  • Implement different expiry periods based on user engagement
  • Add analytics to track modal interaction rates
  • Consider server-side user preferences for logged-in users

✅ Checklist

  • Code changes implemented
  • Error handling added
  • Backward compatibility maintained
  • No linting errors
  • Manual testing completed
  • Unit tests (if required)
  • Integration tests (if required)
  • Performance impact assessment

@vercel
Copy link
Contributor

vercel bot commented Sep 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
devb-io Ready Ready Preview Comment Sep 20, 2025 6:24am

@gemini-code-assist
Copy link

Summary of Changes

Hello @sunithvs, 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!

This pull request significantly enhances the user experience by introducing intelligent visibility logic for the SupportModal. Instead of appearing on every page load, the modal now leverages local storage to determine if it should be displayed, showing only on a user's initial visit and then respecting a 24-hour cooldown period after being closed. This change aims to reduce user fatigue and make the modal less intrusive for returning users.

Highlights

  • Improved User Experience: The SupportModal now prevents intrusive reappearance on every page visit by implementing a 24-hour cooldown period after dismissal, significantly reducing modal fatigue for returning users.
  • LocalStorage Expiry: A new localStorage-based mechanism tracks modal visibility, showing it only on first visits and saving a timestamp upon closure to enforce the cooldown.
  • Robust Error Handling: Comprehensive error handling for localStorage operations has been added, ensuring graceful fallback behavior (showing the modal) if storage is unavailable or data is invalid.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description

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 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. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

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.

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.

@sunithvs sunithvs merged commit 49de183 into main Sep 20, 2025
2 of 3 checks passed
Copy link

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

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 well-thought-out feature to improve user experience by controlling the visibility of the support modal using localStorage. The implementation is solid and the PR description is excellent. I've added a couple of suggestions to enhance the robustness of the localStorage handling and improve code maintainability by using constants and useCallback.

Comment on lines +23 to +43
const checkShouldShowModal = () => {
try {
const modalData = localStorage.getItem('devb-support-modal');

if (!modalData) {
// First time visit - show modal
return true;
}

const { timestamp } = JSON.parse(modalData);
const oneDayInMs = 24 * 60 * 60 * 1000; // 1 day in milliseconds
const now = Date.now();

// Show modal if more than 1 day has passed
return (now - timestamp) > oneDayInMs;
} catch (error) {
// If there's any error with localStorage, show modal
console.warn('Error checking modal state:', error);
return true;
}
};

Choose a reason for hiding this comment

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

high

This function can be made more robust and maintainable.

  1. Data Validation: The code currently assumes JSON.parse will yield an object with a numeric timestamp. If localStorage contains malformed data (e.g., "{}" or {"timestamp": "abc"}), now - timestamp will be NaN. This causes the check to fail silently, incorrectly preventing the modal from showing. You should validate the parsed data's structure.
  2. Hardcoded Values: The localStorage key is hardcoded. Defining it as a constant improves readability and makes future changes easier, especially since it's used in another function.
    const checkShouldShowModal = () => {
      const MODAL_STORAGE_KEY = 'devb-support-modal';
      const MODAL_COOLDOWN_MS = 24 * 60 * 60 * 1000; // 1 day in milliseconds

      try {
        const modalDataString = localStorage.getItem(MODAL_STORAGE_KEY);
        if (!modalDataString) {
          return true; // First time visit
        }

        const modalData = JSON.parse(modalDataString);
        if (typeof modalData?.timestamp !== 'number') {
          console.warn('Invalid modal data in localStorage. Resetting.');
          localStorage.removeItem(MODAL_STORAGE_KEY); // Clean up invalid data
          return true;
        }

        const now = Date.now();
        return (now - modalData.timestamp) > MODAL_COOLDOWN_MS;
      } catch (error) {
        console.warn('Error checking modal state:', error);
        return true;
      }
    };

Comment on lines +53 to +66
const handleModalClose = (isOpen: boolean) => {
setOpen(isOpen);
if (!isOpen) {
try {
// Save timestamp when modal is closed
const modalData = {
timestamp: Date.now(),
};
localStorage.setItem('devb-support-modal', JSON.stringify(modalData));
} catch (error) {
console.warn('Error saving modal state:', error);
}
}
};

Choose a reason for hiding this comment

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

medium

This handler function is recreated on every render. While it may not cause performance issues here, it's a good practice to memoize handlers with useCallback to prevent unnecessary re-renders of child components. Additionally, the localStorage key is hardcoded. Using a constant (as suggested for checkShouldShowModal) would be better for maintainability.

  const handleModalClose = React.useCallback((isOpen: boolean) => {
    const MODAL_STORAGE_KEY = 'devb-support-modal';
    setOpen(isOpen);
    if (!isOpen) {
      try {
        // Save timestamp when modal is closed
        const modalData = {
          timestamp: Date.now(),
        };
        localStorage.setItem(MODAL_STORAGE_KEY, JSON.stringify(modalData));
      } catch (error) {
        console.warn('Error saving modal state:', error);
      }
    }
  }, []);

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.

2 participants