# 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): `` - Existing Alert component (line 148): `{message}` - Existing Button component (line 202): `