15 KiB
Story 1.1: Prevent Duplicate VA Generation & Improve Feedback
Status: Draft
User Story
As a non-tech-savvy user (ibu-ibu), I want clear feedback during automatic payment code generation (VA/QR/C-Store) and user-friendly error messages, So that I don't see confusing technical errors and I understand what to do when problems occur across all payment methods (Bank Transfer, GoPay/QRIS, Convenience Store).
Acceptance Criteria
AC #1: Auto-Generation Loading State
Given user enters BankTransferPanel with selected bank
When VA generation automatically starts (via useEffect on component mount)
Then loading spinner is displayed with "Membuat VA…" message
And user sees clear feedback that generation is in progress
And loading overlay appears with message "Sedang membuat kode pembayaran..."
And user cannot interact with page during generation
AC #2: Prevent Duplicate Generation on Reload/Remount
Given VA generation is already in progress or completed for an order+bank combination
When user reloads page or component remounts (React StrictMode, navigation back/forward)
Then system detects existing in-flight or completed charge via attemptedChargeKeys Set
And does not create duplicate VA generation request to Midtrans
And reuses existing VA code if already generated
And shows existing loading state if generation still in progress
AC #3: User-Friendly Error Messages
Given VA generation fails with HTTP 409 (duplicate VA)
When error is displayed to user
Then message shows "Kode pembayaran Anda sudah dibuat! Klik di sini untuk melihat" (instead of technical "Request Failed 409")
And "Lihat Kode Pembayaran" button is displayed
And clicking button navigates to payment status page with existing VA
AC #4: Error Recovery Options
Given VA generation fails with any error (404, 500, network error)
When error is displayed to user
Then "Buat VA" retry button is displayed (currently exists in code line 201-288)
And clicking button retries VA generation
And error message is in Bahasa Indonesia (not technical English)
And user has clear path to recover from error
AC #5: Loading Overlay Prevents Interaction
Given VA generation is in progress
When loading overlay is displayed
Then user cannot interact with page content (no clicks, no navigation)
And overlay shows clear message in Bahasa Indonesia
And overlay is responsive on mobile devices
And overlay has proper z-index (50+) to stay above all content
Implementation Details
Tasks / Subtasks
Phase 1: Understand Existing Implementation (~30 minutes)
- Review
BankTransferPanel.tsx(AC: #1, #2)- Understand auto-generation logic in useEffect (lines 35-129)
- Understand duplicate prevention with
attemptedChargeKeysSet (line 13) - Understand shared promise pattern with
chargeTasksMap (line 15) - Understand retry button logic (lines 201-288)
- Identify what needs to be improved (AC: #3, #4, #5)
- Current error handling (lines 110-116, 268-271)
- Current loading feedback (lines 162-165, 281-286)
- Missing: Full-screen loading overlay
- Missing: User-friendly error message mapping
Phase 2: Create Utility Functions (~30 minutes)
- Create
src/lib/errorMessages.ts(AC: #3, #4)- Implement
mapErrorToUserMessage(error: AxiosError): string - Map HTTP 409 → "Kode pembayaran Anda sudah dibuat! Klik di sini untuk melihat"
- Map HTTP 404 → "Terjadi kesalahan. Silakan coba lagi"
- Map HTTP 500 → "Terjadi kesalahan server. Silakan coba lagi nanti"
- Map network error → "Tidak dapat terhubung ke server. Periksa koneksi internet Anda"
- Map "Gagal membuat VA." → More specific message based on error type
- Add default fallback message
- Implement
- Write unit tests for error mapping
- Test HTTP 409 mapping
- Test HTTP 404 mapping
- Test HTTP 500 mapping
- Test network error mapping
- Test default fallback
Phase 3: Create LoadingOverlay Component (~1 hour)
- Create
src/components/LoadingOverlay.tsx(AC: #1, #5)- Implement full-screen overlay with semi-transparent backdrop
- Add Framer Motion fade-in animation (200ms)
- Add spinner (reuse existing spinner from line 163 or create new)
- Add message prop: "Sedang membuat kode pembayaran..."
- Make responsive for mobile (TailwindCSS)
- Add z-index: 50 to stay above all content
- Add accessibility (ARIA labels, role="status", aria-live="polite")
- Props interface:
{ isLoading: boolean; message: string }
- Write component tests for LoadingOverlay
- Test renders when isLoading=true
- Test doesn't render when isLoading=false
- Test displays correct message
- Test fade-in animation works
- Test accessibility attributes
Phase 4: Modify All Payment Panels (~2 hours)
- Update error handling in
BankTransferPanel.tsx(AC: #3, #4)- Import
mapErrorToUserMessageutility - Replace line 66:
const msg = ax?.response?.data?.message || ax?.message || 'Gagal membuat VA.'- With:
const msg = mapErrorToUserMessage(ax)
- With:
- Replace line 112: Same error handling
- Replace line 234: Same error handling
- Replace line 270: Same error handling
- Ensure all 4 error catch blocks use new mapping
- Add LoadingOverlay integration (AC: #1, #5)
- Import LoadingOverlay component
- Add LoadingOverlay to JSX (line 139, before main content)
- Pass
isLoading={busy}prop - Pass
message="Sedang membuat kode pembayaran..."prop
- Enhance error display (AC: #3, #4)
- Update Alert component usage (line 148-150)
- Add conditional "Lihat Kode Pembayaran" button for HTTP 409
- Ensure "Buat VA" retry button (line 201-288) is visible on errors
- Import
- Update error handling in
GoPayPanel.tsx(AC: #3, #4)- Import
mapErrorToUserMessageutility - Replace line 178:
toast.error(\Gagal membuat QR: ${(e as Error).message}`)`- With:
toast.error(mapErrorToUserMessage(e))
- With:
- Replace line 207: Same error handling
- Add LoadingOverlay integration (AC: #1, #5)
- Import LoadingOverlay component
- Add LoadingOverlay to JSX (line 55, before main content)
- Pass
isLoading={busy}prop - Pass
message="Sedang membuat kode QR..."prop
- Import
- Update error handling in
CStorePanel.tsx(AC: #3, #4)- Import
mapErrorToUserMessageutility - Replace line 39:
toast.error(\Gagal membuat kode pembayaran: ${(e as Error).message}`)`- With:
toast.error(mapErrorToUserMessage(e))
- With:
- Replace line 63: Same error handling
- Add LoadingOverlay integration (AC: #1, #5)
- Import LoadingOverlay component
- Add LoadingOverlay to JSX (line 81, before main content)
- Pass
isLoading={busy}prop - Pass
message="Sedang membuat kode pembayaran..."prop
- Import
- Verify duplicate prevention still works in all panels (AC: #2)
- Ensure
attemptedChargeKeys/attemptedCStoreKeysSet logic unchanged - Ensure
chargeTasks/cstoreTasksMap logic unchanged - Ensure ref-based guards unchanged
- Ensure
Phase 5: Testing & Refinement (~1.5 hours)
- Manual testing on desktop - Bank Transfer (AC: #1, #2, #3, #4, #5)
- Test auto-generation on panel mount → loading overlay appears
- Test page reload during generation → no duplicate request
- Test React StrictMode double-mount → no duplicate request
- Test HTTP 409 error → user-friendly message + "Lihat Kode" button
- Test HTTP 404 error → user-friendly message + "Buat VA" retry
- Test network error → user-friendly message
- Test loading overlay prevents interaction
- Test retry button works after error
- Manual testing on desktop - GoPay/QRIS (AC: #1, #2, #3, #4, #5)
- Test auto-generation when mode=QRIS → loading overlay appears
- Test page reload during QR generation → no duplicate request
- Test mode switch (GoPay ↔ QRIS) → proper generation
- Test error scenarios → user-friendly messages via toast
- Test loading overlay prevents interaction
- Manual testing on desktop - Convenience Store (AC: #1, #2, #3, #4, #5)
- Test auto-generation on panel mount → loading overlay appears
- Test page reload during code generation → no duplicate request
- Test error scenarios → user-friendly messages via toast
- Test loading overlay prevents interaction
- Manual testing on mobile devices (all payment methods)
- Test on Chrome Android (Bank, QRIS, C-Store)
- Test on Safari iOS (Bank, QRIS, C-Store)
- Verify loading overlay is full-screen and readable
- Verify error messages are readable
- Verify buttons are tappable (44x44px minimum)
- Test page reload on mobile → no duplicate for all methods
- Fix any issues found
- Code review and cleanup
Technical Summary
Current Implementation (BankTransferPanel.tsx):
- Auto-generation: VA generation happens automatically via useEffect when
selectedbank changes (line 35-129) - Duplicate prevention: Uses
attemptedChargeKeysSet to track attempted charges (line 13) - Shared promises: Uses
chargeTasksMap to share in-flight requests across remounts (line 15) - Loading state:
busystate variable controls loading spinner (line 21) - Error handling: Catches errors and sets
errorMessagestate (lines 110-116, 268-271) - Retry button: "Buat VA" button appears when
!vaCode || errorMessage(line 201)
What Needs to Be Added:
- LoadingOverlay component: Full-screen overlay during VA generation (currently only inline spinner)
- Error message mapping: User-friendly Bahasa Indonesia messages (currently technical English)
- Enhanced error recovery: Conditional buttons based on error type (409 vs others)
Key Technical Decisions:
- Don't change duplicate prevention logic: Existing
attemptedChargeKeysandchargeTaskspattern works well - Don't change auto-generation trigger: Keep useEffect pattern (line 35-129)
- Add overlay on top: LoadingOverlay wraps existing UI, doesn't replace inline spinner
- Map errors at catch sites: Replace error message at all 4 catch blocks
Files/Modules Involved:
- Modified:
src/features/payments/components/BankTransferPanel.tsx(main changes) - New utility:
src/lib/errorMessages.ts - New component:
src/components/LoadingOverlay.tsx - Test files: Tests for new utility and component
Project Structure Notes
-
Files to modify:
src/features/payments/components/BankTransferPanel.tsx(confirmed - Bank VA generation)src/features/payments/components/GoPayPanel.tsx(confirmed - QRIS/GoPay QR generation)src/features/payments/components/CStorePanel.tsx(confirmed - Convenience Store code generation)
-
Files to create:
src/lib/errorMessages.tssrc/components/LoadingOverlay.tsxsrc/lib/__tests__/errorMessages.test.tssrc/components/__tests__/LoadingOverlay.test.tsx
-
Expected test locations:
src/lib/__tests__/- Utility function testssrc/components/__tests__/- Component testssrc/features/payments/__tests__/- Integration tests (optional)
-
Estimated effort: 5 story points (~2-3 days) - Covers all 3 payment methods
-
Prerequisites: None - This is the first story, no dependencies
Key Code References
Actual Existing Code:
BankTransferPanel.tsx (Bank VA):
- Auto-generation useEffect: Lines 35-129
- Duplicate prevention: Lines 13 (Set), 15 (Map), 39-77 (check logic), 78-124 (main logic)
- Error handling: Lines 64-67 (catch #1), 110-116 (catch #2), 232-236 (catch #3), 268-272 (catch #4)
- Loading state: Line 21 (
busystate), 162-165 (inline spinner), 281-286 (button spinner) - Retry button: Lines 201-288 (manual "Buat VA" button)
- Error display: Lines 148-150 (Alert component)
GoPayPanel.tsx (QRIS/GoPay):
- Auto-generation useEffect: Lines 157-220 (in
GoPayPanel_AutoEffectcomponent) - Duplicate prevention: Lines 11 (Set), 12 (Map), 163-186 (check logic), 188-215 (main logic)
- Error handling: Lines 177-178 (catch #1), 206-208 (catch #2)
- Loading state: Line 33 (
busystate), 94-98 (inline spinner), 140-144 (button spinner) - Error display: Lines 178, 207 (toast.error)
CStorePanel.tsx (Convenience Store):
- Auto-generation useEffect: Lines 23-72
- Duplicate prevention: Lines 12 (Set), 13 (Map), 30-44 (check logic), 46-68 (main logic)
- Error handling: Lines 38-39 (catch #1), 62-64 (catch #2)
- Loading state: Line 19 (
busystate), 104-108 (inline spinner) - Error display: Lines 39, 63 (toast.error)
API Service:
postChargefunction imported fromsrc/services/api(line 7)- Called at lines 90, 250 with payload containing
payment_type,transaction_details,bank_transfer
Important Functions:
postCharge(payload)- API call to create VA (lines 90, 250)run()async function - Main auto-generation logic (lines 37-125)- Button onClick handler - Manual retry logic (lines 205-277)
Code Patterns to Follow:
- Functional components with TypeScript
- TailwindCSS utility classes
- Existing spinner pattern (line 163):
<span className="h-3 w-3 animate-spin rounded-full border-2 border-black/40 border-t-transparent" /> - Existing Alert component (line 148):
<Alert title="...">{message}</Alert> - Existing Button component (line 202):
<Button disabled={...} onClick={...}>
Context References
Tech-Spec: tech-spec.md - Primary context document containing:
- Brownfield codebase analysis (React 19, TypeScript, Vite, TailwindCSS stack)
- Framework and library details with exact versions
- Existing patterns to follow (functional components, TailwindCSS, Framer Motion)
- Integration points (Midtrans API via
postCharge, React Router) - Complete implementation guidance with code examples
- Testing strategy and acceptance criteria
- Deployment and rollback plan
Problem-Solution Analysis: problem-solution-2025-11-25.md - Root cause analysis identifying:
- Problem #1: Repeated VA Generation (3-5 second delay, duplicate clicks)
- Root cause: Lack of defensive UX design + no user behavior anticipation
- Solution: Loading overlay + user-friendly errors + duplicate prevention
Actual Code: BankTransferPanel.tsx - Current implementation with:
- Auto-generation via useEffect
- Duplicate prevention with
attemptedChargeKeysSet - Shared promises with
chargeTasksMap - Inline loading spinner
- Technical error messages (needs improvement)
Architecture: Feature-based modular architecture with 16 feature modules