Skip to content

feat: Add verification code detection with hover effects#9

Open
yushanwebdev wants to merge 8 commits intolegacyfrom
feature/verification-code-detection
Open

feat: Add verification code detection with hover effects#9
yushanwebdev wants to merge 8 commits intolegacyfrom
feature/verification-code-detection

Conversation

@yushanwebdev
Copy link
Copy Markdown
Owner

@yushanwebdev yushanwebdev commented Feb 2, 2026

Summary

  • Add automatic verification code detection and copy functionality to email views
  • Implement hover-based action buttons for desktop with always-visible buttons on mobile
  • Optimize button sizing and spacing for improved mobile UX

Changes

  • Verification code detection: Auto-detect and copy verification codes from email subjects using 17+ keyword patterns
  • Toast notifications: User feedback for successful/failed detection
  • Hover effects: Action buttons hidden by default on desktop, shown on hover using @media(hover:hover)
  • Mobile optimization: Smaller buttons (h-7 vs h-8), tighter spacing, hidden text labels for compact layout
  • Unit tests: Comprehensive test coverage for detection algorithm

Test plan

  • Verify code detection works with various formats (6-digit, hyphenated, etc.)
  • Test hover effect on desktop (buttons appear on hover)
  • Test mobile view (buttons always visible and appropriately sized)
  • Confirm toast notifications appear for success/failure
  • Validate accessibility (ARIA labels, keyboard navigation)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Detect and copy verification codes from email subjects with a single click; codes automatically copy to clipboard with success/error notifications
    • Added toast notifications throughout the app to provide real-time feedback on user actions
    • Improved responsive design with enhanced hover state support for better accessibility and user experience

yushanwebdev and others added 8 commits February 2, 2026 22:38
Add sonner v2.0.7 for toast notifications in verification code feature.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement email subject verification code detection using industry-standard
patterns based on research from web.dev, Google, Facebook, and Amazon OTP
formats.

Features:
- Detects numeric codes (4-8 digits, 6-digit standard)
- Detects alphanumeric codes (mixed case with digit requirement)
- Keyword context matching (17 common verification keywords)
- Pattern priority system (prefixed > numeric > alphanumeric)
- Performance optimized with 500 char limit and O(n) complexity
- Common word filtering to prevent false positives
- Confidence scoring system for detection quality

Algorithm follows SMS Retriever API standards and TOTP/HOTP RFC patterns.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add Toaster component wrapper for sonner library and integrate into root
layout for application-wide toast notifications. Configured with themed
styling for light/dark modes.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add one-click OTP detection and copy functionality to inbox email lists.

Changes:
- Add ScanText icon button to inbox email rows (hover reveal)
- Implement handleDetectAndCopy for immediate code detection and clipboard copy
- Add toast notifications for success/error feedback
- Include clipboard API fallback for older browsers
- Add ARIA labels for accessibility (screen reader support)
- Apply to both main inbox list and dashboard recent unread section

User flow: Click scan icon → Code detected and copied → Toast confirmation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add comprehensive test suite for OTP detection algorithm with Vitest.

Test coverage:
- Numeric codes at beginning and end of subject
- Alphanumeric code detection
- Keyword requirement validation
- False positive prevention (no keyword = null)
- Edge case: no code present with keywords

All tests pass, ensuring reliable code detection across various email formats.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove opacity/hover logic - buttons are now always visible at all screen
sizes for consistent UX across mobile, tablet, and desktop devices.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Re-enable hover behavior using custom 'can-hover' Tailwind variant:
- Devices with hover capability (desktop): buttons hidden, show on hover
- Touch devices (mobile/tablets): buttons always visible

Uses @media (hover: hover) to properly detect hover capability instead
of screen size, ensuring correct behavior across all device types.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Optimize button sizes and spacing for better mobile UX:
- Reduce button size to h-7/w-7 on mobile (h-8/w-8 on desktop)
- Scale icons to 3.5x3.5 on mobile (4x4 on desktop)
- Tighten button gap to 0.5 for compact mobile layout
- Hide "Mark read" text label on mobile screens
- Position buttons closer to edge (right-2) in dashboard

These changes make action buttons less intrusive on mobile while
maintaining full functionality and visibility on desktop.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

The changes introduce a verification code detection feature for email subjects, adding a new utility module with keyword-based pattern matching and regex detection. A Toaster component from the Sonner library is integrated globally, and both the email list and recent unread section components are updated with UI buttons to detect and copy verification codes with toast feedback.

Changes

Cohort / File(s) Summary
Global Setup
app/globals.css, app/layout.tsx, package.json
Added can-hover CSS variant, integrated Toaster component globally, and added Sonner dependency.
Toast UI Component
components/ui/toaster.tsx
New client-side Toaster wrapper around Sonner library with project-specific styling and ARIA accessibility.
Email Components with Detection
components/dashboard/recent-unread-section.tsx, components/email-list.tsx
Added handler to detect and copy verification codes from email subjects to clipboard; introduced new icon button alongside existing actions with toast feedback on success/failure.
Verification Code Detection Logic
lib/verificationCodeDetector.ts, lib/verificationCodeDetector.test.ts
New utility module with keyword filtering, multi-pattern regex detection (prefixed, numeric, alphanumeric), confidence scoring, and comprehensive unit tests covering various code formats and edge cases.

Sequence Diagram

sequenceDiagram
    actor User
    participant EmailUI as Email Component
    participant Detector as VerificationCodeDetector
    participant Clipboard as Clipboard API
    participant Toast as Toaster

    User->>EmailUI: Click detect & copy button
    EmailUI->>Detector: detectVerificationCode(subject)
    alt Code detected
        Detector-->>EmailUI: Returns code string
        EmailUI->>Clipboard: navigator.clipboard.writeText(code)
        alt Clipboard success
            Clipboard-->>EmailUI: Copy successful
            EmailUI->>Toast: Show success toast
            Toast-->>User: "Code copied!"
        else Clipboard unavailable
            Clipboard-->>EmailUI: Error or fallback needed
            EmailUI->>Toast: Show error toast
            Toast-->>User: "Copy failed"
        end
    else No code detected
        Detector-->>EmailUI: Returns null
        EmailUI->>Toast: Show error toast
        Toast-->>User: "No code found"
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A verification code, oh what a sight,
Detected and copied with button so bright,
Toast notifications pop, confirming with cheer,
No more manual entry—the code's in the clear! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: adding verification code detection with hover effects, which is the main feature across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/verification-code-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@components/email-list.tsx`:
- Around line 72-74: The clipboard fallback code calls
document.execCommand("copy") but doesn't check its boolean result; update the
copy routine around textArea.select()/document.execCommand("copy") to capture
the return value and, if it returns false (or throws), fall back to
navigator.clipboard.writeText(textArea.value) when available, otherwise handle
the failure (e.g., show an error/console.warn) before removing the element via
document.body.removeChild(textArea); ensure the logic around
document.execCommand("copy"), navigator.clipboard.writeText and
document.body.removeChild(textArea) cleanly handles both success and failure
paths.

In `@lib/verificationCodeDetector.ts`:
- Around line 97-118: The numeric detection can pick an earlier number (e.g.,
order ID) because it returns the first 4–8 digit match; change the logic in
verificationCodeDetector (around the keyword check and the numericMatch
handling) to find all numeric matches (use matchAll or repeated exec on
PATTERNS.numeric), locate the keyword position using keywordPattern (or the
prefixMatch index), and return the first numeric match whose index is after or
nearest following the keyword/prefix; if none found, fall back to the existing
first/last-match behavior. Ensure you reference PATTERNS.numeric, keywordPattern
(and prefixMatch when present) when selecting the correct match.
🧹 Nitpick comments (7)
lib/verificationCodeDetector.test.ts (2)

9-25: Remove debug console.log statements from tests.

Lines 11, 17, and 23 contain console.log statements that appear to be debug artifacts. These should be removed to keep test output clean.

♻️ Proposed fix
   it("should detect code at the beginning of subject", () => {
     const result = detectVerificationCode("123456 is your Google verification code");
-    console.log("Result:", result);
     expect(result).toBe("123456");
   });

   it("should detect code at the end of subject", () => {
     const result = detectVerificationCode("Your verification code is 789012");
-    console.log("Result:", result);
     expect(result).toBe("789012");
   });

   it("should detect alphanumeric code", () => {
     const result = detectVerificationCode("Your OTP is A3B9X7");
-    console.log("Result:", result);
     expect(result).toBe("A3B9X7");
   });

8-35: Consider expanding test coverage for edge cases.

The current tests cover the happy paths well, but the detector has additional behaviors that could benefit from testing:

  • Null/undefined inputs: detectVerificationCode(null), detectVerificationCode(undefined)
  • Empty string: detectVerificationCode("")
  • Very long subjects: Subjects over 500 characters should return null per the implementation
  • Prefixed patterns: "code: 123456" or "OTP: A3B9X7"
  • Common word filtering: Ensure words like "THE123" don't match if in COMMON_WORDS set

The module also exports hasVerificationKeywords and getConfidenceScore functions that aren't currently tested.

Would you like me to generate additional test cases for these edge cases?

components/email-list.tsx (1)

49-87: Extract handleDetectAndCopy to a shared utility to eliminate duplication.

This function is duplicated verbatim in components/dashboard/recent-unread-section.tsx (lines 29-67). Consider extracting it to a shared utility (e.g., lib/clipboard.ts or extending lib/verificationCodeDetector.ts) to maintain DRY principles.

♻️ Suggested extraction

Create a new utility in lib/clipboard.ts:

import { toast } from "sonner";
import { detectVerificationCode } from "./verificationCodeDetector";

export async function detectAndCopyVerificationCode(subject: string): Promise<boolean> {
  const code = detectVerificationCode(subject);

  if (!code) {
    toast.error("No verification code found", {
      description: "No code detected in the email subject",
      duration: 2000,
    });
    return false;
  }

  try {
    if (navigator.clipboard && window.isSecureContext) {
      await navigator.clipboard.writeText(code);
    } else {
      const textArea = document.createElement("textarea");
      textArea.value = code;
      textArea.style.position = "fixed";
      textArea.style.left = "-999999px";
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
    }

    toast.success(`Code ${code} copied to clipboard`, {
      duration: 2000,
    });
    return true;
  } catch (error) {
    console.error("Failed to copy code:", error);
    toast.error("Failed to copy code", {
      description: "Unable to copy to clipboard",
      duration: 3000,
    });
    return false;
  }
}

Then in both components:

import { detectAndCopyVerificationCode } from "@/lib/clipboard";

// Usage
onClick={() => detectAndCopyVerificationCode(email.subject)}
components/dashboard/recent-unread-section.tsx (1)

110-122: Add data-* test attribute for consistency with email-list.tsx.

The verification code button in email-list.tsx has data-detect-code-btn={email._id} for testability, but this button is missing the equivalent attribute.

♻️ Proposed fix
                   <Button
                     variant="ghost"
                     size="icon-sm"
                     className="h-7 w-7 bg-white shadow-sm hover:bg-blue-50 hover:text-blue-700 dark:bg-zinc-900 dark:hover:bg-blue-950/50 dark:hover:text-blue-400 md:h-8 md:w-8"
                     onClick={(e) => {
                       e.preventDefault();
                       handleDetectAndCopy(email.subject);
                     }}
                     aria-label="Detect and copy verification code"
                     title="Detect and copy verification code"
+                    data-detect-code-btn={email._id}
                   >
lib/verificationCodeDetector.ts (3)

55-59: Escape keyword literals when building the regex.
This avoids accidental regex meta-character behavior if new keywords are added and quiets ReDoS warnings.

Suggested diff
-const keywordPattern = new RegExp(
-  `\\b(${KEYWORDS.join('|').replace(/\s+/g, '\\s+')})\\b`,
-  'i'
-);
+const escapedKeywords = KEYWORDS.map((keyword) =>
+  keyword
+    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+    .replace(/\s+/g, '\\s+')
+);
+const keywordPattern = new RegExp(`\\b(${escapedKeywords.join('|')})\\b`, 'i');

61-68: COMMON_WORDS filter is currently redundant.
The alphanumeric regex already requires at least one digit, so pure words won’t match. Consider removing the set or loosening the regex if you want this filter to be effective.


171-178: Tie the prefixed-score bonus to the detected code.
Right now any prefixed pattern in the subject adds +30, even if a different number was detected. Compare the captured prefixed code to detectedCode to avoid over-scoring.

Suggested diff
-  if (PATTERNS.prefixed.test(subject)) {
-    score += 30;
-  }
+  const prefixedMatch = subject.match(PATTERNS.prefixed);
+  if (
+    prefixedMatch?.[1] &&
+    prefixedMatch[1].toUpperCase() === detectedCode.toUpperCase()
+  ) {
+    score += 30;
+  }

Comment on lines +72 to +74
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clipboard fallback doesn't handle execCommand failure.

document.execCommand("copy") returns a boolean indicating success/failure, but this isn't checked. While rare, the command can fail silently.

🛡️ Proposed fix
         document.body.appendChild(textArea);
         textArea.select();
-        document.execCommand("copy");
+        const success = document.execCommand("copy");
         document.body.removeChild(textArea);
+        if (!success) {
+          throw new Error("execCommand copy failed");
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
textArea.select();
const success = document.execCommand("copy");
document.body.removeChild(textArea);
if (!success) {
throw new Error("execCommand copy failed");
}
🤖 Prompt for AI Agents
In `@components/email-list.tsx` around lines 72 - 74, The clipboard fallback code
calls document.execCommand("copy") but doesn't check its boolean result; update
the copy routine around textArea.select()/document.execCommand("copy") to
capture the return value and, if it returns false (or throws), fall back to
navigator.clipboard.writeText(textArea.value) when available, otherwise handle
the failure (e.g., show an error/console.warn) before removing the element via
document.body.removeChild(textArea); ensure the logic around
document.execCommand("copy"), navigator.clipboard.writeText and
document.body.removeChild(textArea) cleanly handles both success and failure
paths.

Comment on lines +97 to +118
// Check for keyword presence (required for context)
// This reduces false positives significantly
const hasKeyword = keywordPattern.test(subject);
if (!hasKeyword) {
return null;
}

// Priority 1: Try prefixed pattern (most specific)
// Matches: "code: 123456", "OTP: A3B9X7", "password: 789012"
const prefixMatch = subject.match(PATTERNS.prefixed);
if (prefixMatch && prefixMatch[1]) {
return prefixMatch[1];
}

// Priority 2: Try numeric pattern (most common format)
// Matches: 4-8 digit sequences like "123456"
// Google, Facebook, Amazon typically use 6 digits
const numericMatch = subject.match(PATTERNS.numeric);
if (numericMatch && numericMatch.length > 0) {
// Return first match (most likely to be the code)
return numericMatch[0];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Numeric detection can return the wrong number when other numbers appear before the code.
Because the first 4–8 digit match is returned, subjects like “Order #1234 — your verification code is 567890” will copy 1234. Prefer the first match after (or closest to) the keyword.

Suggested diff
-  const hasKeyword = keywordPattern.test(subject);
-  if (!hasKeyword) {
+  const keywordMatch = keywordPattern.exec(subject);
+  if (!keywordMatch) {
     return null;
   }
@@
-  const numericMatch = subject.match(PATTERNS.numeric);
-  if (numericMatch && numericMatch.length > 0) {
-    // Return first match (most likely to be the code)
-    return numericMatch[0];
-  }
+  const numericMatches = [...subject.matchAll(PATTERNS.numeric)];
+  if (numericMatches.length > 0) {
+    const keywordEnd = (keywordMatch.index ?? 0) + keywordMatch[0].length;
+    const afterKeyword = numericMatches.find(
+      (match) => (match.index ?? 0) >= keywordEnd
+    );
+    return (afterKeyword ?? numericMatches[0])[0];
+  }
🤖 Prompt for AI Agents
In `@lib/verificationCodeDetector.ts` around lines 97 - 118, The numeric detection
can pick an earlier number (e.g., order ID) because it returns the first 4–8
digit match; change the logic in verificationCodeDetector (around the keyword
check and the numericMatch handling) to find all numeric matches (use matchAll
or repeated exec on PATTERNS.numeric), locate the keyword position using
keywordPattern (or the prefixMatch index), and return the first numeric match
whose index is after or nearest following the keyword/prefix; if none found,
fall back to the existing first/last-match behavior. Ensure you reference
PATTERNS.numeric, keywordPattern (and prefixMatch when present) when selecting
the correct match.

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