331 lines
15 KiB
Markdown
331 lines
15 KiB
Markdown
# 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 -->
|