feat: Add verification code detection with hover effects#9
feat: Add verification code detection with hover effects#9yushanwebdev wants to merge 8 commits intolegacyfrom
Conversation
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>
📝 WalkthroughWalkthroughThe 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 debugconsole.logstatements from tests.Lines 11, 17, and 23 contain
console.logstatements 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
nullper 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
hasVerificationKeywordsandgetConfidenceScorefunctions 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: ExtracthandleDetectAndCopyto 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.tsor extendinglib/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: Adddata-*test attribute for consistency withemail-list.tsx.The verification code button in
email-list.tsxhasdata-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 todetectedCodeto 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; + }
| textArea.select(); | ||
| document.execCommand("copy"); | ||
| document.body.removeChild(textArea); |
There was a problem hiding this comment.
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.
| 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.
| // 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]; | ||
| } |
There was a problem hiding this comment.
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.
Summary
Changes
@media(hover:hover)Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes