# 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 `attemptedChargeKeys` Set (line 13)
  - [ ] Understand shared promise pattern with `chargeTasks` Map (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
- [ ] 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 `mapErrorToUserMessage` utility
  - [ ] Replace line 66: `const msg = ax?.response?.data?.message || ax?.message || 'Gagal membuat VA.'`
    - [ ] With: `const msg = mapErrorToUserMessage(ax)`
  - [ ] 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
- [ ] Update error handling in `GoPayPanel.tsx` (AC: #3, #4)
  - [ ] Import `mapErrorToUserMessage` utility
  - [ ] Replace line 178: `toast.error(\`Gagal membuat QR: ${(e as Error).message}\`)`
    - [ ] With: `toast.error(mapErrorToUserMessage(e))`
  - [ ] 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
- [ ] Update error handling in `CStorePanel.tsx` (AC: #3, #4)
  - [ ] Import `mapErrorToUserMessage` utility
  - [ ] Replace line 39: `toast.error(\`Gagal membuat kode pembayaran: ${(e as Error).message}\`)`
    - [ ] With: `toast.error(mapErrorToUserMessage(e))`
  - [ ] 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
- [ ] Verify duplicate prevention still works in all panels (AC: #2)
  - [ ] Ensure `attemptedChargeKeys` / `attemptedCStoreKeys` Set logic unchanged
  - [ ] Ensure `chargeTasks` / `cstoreTasks` Map logic unchanged
  - [ ] Ensure ref-based guards unchanged

**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 `selected` bank changes (line 35-129)
- **Duplicate prevention:** Uses `attemptedChargeKeys` Set to track attempted charges (line 13)
- **Shared promises:** Uses `chargeTasks` Map to share in-flight requests across remounts (line 15)
- **Loading state:** `busy` state variable controls loading spinner (line 21)
- **Error handling:** Catches errors and sets `errorMessage` state (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 `attemptedChargeKeys` and `chargeTasks` pattern 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.ts`
  - `src/components/LoadingOverlay.tsx`
  - `src/lib/__tests__/errorMessages.test.ts`
  - `src/components/__tests__/LoadingOverlay.test.tsx`

- **Expected test locations:**
  - `src/lib/__tests__/` - Utility function tests
  - `src/components/__tests__/` - Component tests
  - `src/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 (`busy` state), 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_AutoEffect` component)
- **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 (`busy` state), 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 (`busy` state), 104-108 (inline spinner)
- **Error display:** Lines 39, 63 (toast.error)

**API Service:**
- `postCharge` function imported from `src/services/api` (line 7)
- Called at lines 90, 250 with payload containing `payment_type`, `transaction_details`, `bank_transfer`

**Important Functions:**
1. `postCharge(payload)` - API call to create VA (lines 90, 250)
2. `run()` async function - Main auto-generation logic (lines 37-125)
3. 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](../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](../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](../src/features/payments/components/BankTransferPanel.tsx) - Current implementation with:
- Auto-generation via useEffect
- Duplicate prevention with `attemptedChargeKeys` Set
- Shared promises with `chargeTasks` Map
- Inline loading spinner
- Technical error messages (needs improvement)

**Architecture:** Feature-based modular architecture with 16 feature modules

<!-- Additional context XML paths will be added here if story-context workflow is run -->

---

## Dev Agent Record

### Agent Model Used

<!-- Will be populated during dev-story execution -->

### Debug Log References

<!-- Will be populated during dev-story execution -->

### Completion Notes

<!-- Will be populated during dev-story execution -->

### Files Modified

<!-- Will be populated during dev-story execution -->

### Test Results

<!-- Will be populated during dev-story execution -->

---

## Review Notes

<!-- Will be populated during code review -->
