Repository navigation
Improvements/p2p ramp - #715
Conversation
- Add TradesCompleted and TradesFailed i18n keys to all languages - Display completed trades (green) and failed trades (red) alongside trade count and completion percentage - Fix ad owner stat paths in profile.vue (ad.owner.* instead of ad.*)
- 'Escrow bch' → 'Escrow BCH' - 'verifying ESCROW' → 'Verifying Escrow' - 'verifying RELEASE' → 'Verifying Release'
…k chips - Fix 'Rate your Experience' -> 'Rate Your Experience' - Add 10 clickable chips with pre-composed feedback options - Green chips for positive, orange for slightly negative - Chips populate the comment field when clicked
Switch from long-form to compact display: 3 completed · 1 failed · 75% completion Drop the 'X trades' prefix and parenthetical notation in favor of colored labels separated by middots
- Report button in profile page and user profile dialog - Visible only when viewing another user (not self) - Confirm dialog before submitting report - Success/error notifications with i18n messages - 6 new i18n keys for report flow
- Add isReported() helper checking reported_at within 24h - Apply .reported-blur CSS (filter: blur, opacity, no-select) - Blur rating, completed/failed counts, and completion percentage - Applied in StoreListings, profile page, and UserProfileDialog
Use filter: grayscale(1) instead of blur(4px) for reported users. Stars turn grey, text fades — properly 'greyed out' look.
Backend: - Add REASON_CHOICES and reason CharField to Report model - Validate and save reason from request.data in report view - Add migration 0239 for reason field Frontend: - Create ReportDialog component with radio options and caption descriptions - Replace confirm dialog with reason selection in profile.vue and UserProfileDialog.vue - Send reason in POST body to report endpoint - Add REPORTED badge next to username in StoreListings, profile, and UserProfileDialog - Add i18n keys for report reasons across all 19 language files
- Add completed/failed trades and completion rate display - Add REPORTED badge after name and online dot indicator - Add greyed-out styling for reported users - Reposition REPORTED badge in profile.vue after online indicator
- Change 'BUYING FROM'/'SELLING TO' to 'Buying From'/'Selling To' in TradeInfoCard and OrderListings
- Change 'BUY BCH WITH'/'SELL BCH FOR' to 'Buy BCH With'/'Sell BCH For' in order-form
- Change 'REPORTED' badge to 'Reported' in StoreListings, profile, UserProfileDialog, TradeInfoCard
- Change 'PAYMENT METHODS' to 'Payment Methods' in StandByDisplay
- Change 'CONFIRM AD DETAILS' fallback to 'Confirm Ad Details' in DisplayConfirmation
- Change 'Swipe To Escrow' to 'Swipe to Escrow' in EscrowTransfer
- Fix ALL-CAPS translation keys: ('BUYER') to ('Buyer'), ('SELLER') to ('Seller'), ('REVIEWS') to ('Reviews'), ('ADS') to ('Ads'), ('MIN') to ('Min'), ('MAX') to ('Max'), ('BUY') to ('Buy'), ('SELL') to ('Sell'), ('PAYMENTMETHODS') to ('PaymentMethods'), ('AMOUNT') to ('Amount')
- Fix hardcoded 'ADS' button to use ('Ads') in UserProfileDialog
- Pass marketPrice from order-form to TradeInfoCard - Calculate percentage difference between ad price and market price - Display green (+) or red (-) percentage indicator next to price
- Add marketPriceLoading state in order-form.vue - Pass marketPriceLoading prop to TradeInfoCard - Show q-skeleton while market price is loading - Hide percentage until market price is available
- Add q-tooltip on price percentage span explaining it's the percentage difference from market price - Add PriceVsMarketTooltip i18n key to all 19 language files and phrases.js
- PaymentConfirmation: use order data from parent prop instead of re-fetching on mount; extracted processPaymentMethods() for reuse - Store contractBalance in order.vue and pass to child components to avoid redundant /balance/bch/ calls in PaymentConfirmation, StandByDisplay, and VerifyTransaction - Cache chat members response in fetchChatUnread() and reuse in loadChatData() to eliminate duplicate /chat/members/full_info/ call
- Skip generateContract() in loadData() when escrowContract already exists (avoids duplicate /contract/fees/ and /contract/ calls for ESCRW_PN status) - Move escrowContract guard before API calls in generateContract() to prevent redundant fetches when contract already exists - Debounce WebSocket fetchOrder() calls to prevent rapid-fire duplicates
…crowTransfer - Add loadingData guard in loadData() to prevent second call if refreshPage/relogged fires during initial page load - Guard refreshPage() with loadingData to skip redundant loads - Pass contractBalance in escrowTransferData and use it in EscrowTransfer to avoid redundant /balance/bch/ call
Using $route.params.order instead of $route.path prevents remounting when URL is normalized (e.g. trailing slash changes), which would cause duplicate API calls via mounted() -> loadData()
…inate duplicates - Convert fetchOrder() from .then() to async/await so handleNewStatus() is properly awaited (prevents race condition where loadData() continues before updateStateByStatus() sets contractBalance and state) - Move fetchChatUnread() from fetchOrder() to loadData() where it's awaited BEFORE activeTab potentially switches to 'chat' tab, ensuring chatMembersRaw is cached before loadChatData() runs
…scade Changed router-view keys from $route.path to $route.name in: - exchange/index.vue (top level) - peer_to_peer/index.vue (middle level) - orders.vue already uses $route.params.order (leaf level) This prevents URL path normalization (e.g. trailing slash changes) from destroying and remounting the entire component subtree, which was causing duplicate API calls via mounted() -> loadData().
- Add dedup for concurrent getBalance calls in RampContract - Add cooldown to prevent websocket-triggered duplicate fetchOrder - Skip updateStateByStatus when status value hasn't changed - Label fetchOrder callers for better traceability
…t NaN - Fetch counterparty trade stats (completed_trades, failed_trades, completion_rate, reported_at) from peer API in order page - Guard formatCompletionRate with fallback to 0 in all locations
…P2P order page
The trade stats (completed trades, failed trades, completion percentage) were
showing as 'completed · failed · 0% completion' instead of actual values like
'2 completed · 4 failed · 33% completion'.
Root causes:
1. Vue I18n's () treats undefined interpolation params as empty strings,
so {count: undefined} renders the {count} placeholder as blank
2. The old fetchCounterpartyTradeStats used Vue 2's this.() to mutate
ad/owner objects directly, but the TradeInfoCard component reads counterparty
data from order.members - a different object entirely
3. The third argument to () was being ignored (Vue I18n doesn't support it)
Fixes:
- Use ?? 0 fallback for completed_trades, failed_trades, and completion_rate
in all () calls so undefined values show '0' instead of blank
- Replace this.() approach with passing counterpartyPeerData as a prop
to TradeInfoCard, which merges it with the order member data
- Derive counterparty from order.members (buyer/seller) instead of
order.owner/ad.owner for consistency with other P2P components
- Remove unused third argument from all () calls
Affected components:
- TradeInfoCard.vue (order page counterparty display)
- UserProfileDialog.vue (peer profile dialog)
- profile.vue (peer profile page - user stats & ad owner stats)
- StoreListings.vue (marketplace listing owner stats)
- AdListings.vue (ad management listing stats)
- order.vue (fetchCounterpartyTradeStats refactor)
Create a PeerInfo component that encapsulates peer display logic: - Name (clickable or plain text, with truncation option) - Online status dot (conditional) - Rating stars with numeric value - Trade stats (completed · failed · completion%) - Last online timestamp - Reported badge (within 24h) with reported-greyed styling Refactored components to use PeerInfo: - TradeInfoCard: counterparty, orderOwner, adOwner sections - UserProfileDialog: user stats section - StoreListings: listing owner info PeerInfo props allow fine-grained control: - showName, showOnlineStatus, showRating, showTradeStats - showLastOnline, showReportedBadge, clickableName - truncateName, largeName, alignRight Kept profile.vue and AdListings.vue as-is since their layouts are too different from the standard peer info pattern.
…tainer The reported-greyed class applies grayscale+opacity which made the red Reported badge appear greyed out. Moved the badge outside the name-row div so it stays visually red.
When type='ad' (store form page), order.members is null so counterparty was returning null, causing PeerInfo to show nothing. Fall back to ad.owner when order members aren't available, matching original behavior.
On the store form page (type='ad'), order.members is null so counterparty falls back to ad.owner which lacks trade stats. Added fetchAdOwnerPeerData() to fetch peer data from /peer/:id/ and pass it as counterpartyPeerData prop to TradeInfoCard. Also updated counterparty computed to merge counterpartyPeerData with ad.owner when order members are unavailable.
The async call was fired without await, causing isloaded=true before the peer data arrived. The component rendered with ad.owner (which lacks trade stats) and never updated. Awaiting ensures peer data is available before the component renders.
…eDialog syntax error - order-form.vue, order.vue: use spread operator on peer API response to ensure reactive tracking of completed_trades/failed_trades fields - UserProfileDialog.vue: remove duplicate '},' that prematurely closed methods object, causing vue-loader compile failure
- Add centered prop to PeerInfo that applies text-center on root, justify-center on name-row and rating row - Pass centered prop in UserProfileDialog to fix broken layout
- Change selectedReasons array to selectedReason single value - Remove updateAppealReasons toggle method - Submit reasons as single-element array for API compatibility
- Replace hardcoded 'You have appealed' with dynamic appealSubject - Determine appellant via appeal.owner.id vs current user - Omit recipient phrase when appellant is the natural beneficiary (buyer appealing release, seller appealing refund) - Capitalize appellant label: 'The buyer', 'The seller'
…footer menu - Remove AppealFooterMenu from arbiter shell (index.vue) - Remove footer-related data, events, and methods - Add profile icon to HeaderNav top-right-menu slot on appeals.vue and appeal.vue - Add _loadingData guard on loadData() to prevent concurrent calls - Add _fetchAppealInProgress guard on fetchAppeal() - Add 10s websocket cooldown for fetchAppeal() - Label fetchAppeal callers for traceability
- Wrap RampDragSlide in <Teleport to="body"> to escape the scroll container - Reduce scroll container bottom padding from 180px to 20px (no longer needed)
PR Review:
|
| File | Assessment |
|---|---|
src/pages/exchange/peer_to_peer/order.vue |
✅ Major improvements (WS debouncing, chat, skeletons) |
src/components/ramp/fiat/PaymentConfirmation.vue |
✅ Good optimization, minor bug (errorDialogActive) |
src/components/ramp/fiat/PeerInfo.vue |
✅ Clean new component |
src/components/ramp/fiat/ReportDialog.vue |
✅ Clean new component |
src/components/ramp/fiat/TradeInfoCard.vue |
✅ Good bug fix (ad.owner fallback) |
src/components/ramp/fiat/dialogs/UserProfileDialog.vue |
this.userId bug |
src/exchange/contract.js |
✅ Good balance deduplication |
Verdict: The PR is ready to merge after fixing the two high-priority issues (this.userId and errorDialogActive). The performance improvements and new components are high-quality additions to the codebase.
- Remove invalid to_peer param in UserProfileDialog.fetchAds() - Fix errorDialogActive never set to true in PaymentConfirmation - Remove debug console.log/trace statements from production code - Add fallback for fiatAmount symbol in PaymentConfirmation - Align completion rate formatting between AdListings and PeerInfo - Remove debug console.log from AdListings and contract.js
PR Review:
|
| File | Assessment |
|---|---|
src/pages/exchange/peer_to_peer/order.vue |
✅ Major improvements (WS debouncing, chat pagination, skeleton loaders) |
src/components/ramp/fiat/PaymentConfirmation.vue |
✅ Good optimization, previous bug fixed |
src/components/ramp/fiat/PeerInfo.vue |
✅ Clean new component |
src/components/ramp/fiat/ReportDialog.vue |
✅ Clean new component |
src/components/ramp/fiat/TradeInfoCard.vue |
✅ Good bug fix (ad.owner fallback), minor debug log remaining |
src/components/ramp/fiat/dialogs/UserProfileDialog.vue |
✅ Previous bug fixed |
src/exchange/contract.js |
✅ Good balance deduplication |
Verdict: The PR is ready to merge after removing the 6 remaining debug console.log statements. The performance improvements and new components are excellent additions to the codebase. All high-priority issues from the previous review cycle have been properly resolved.
PR Review:
|
PR Review:
|
| File | Line | Content |
|---|---|---|
src/pages/apps/exchange/index.vue |
~79 | console.log('[exchange/index.vue] $route watcher:', ...) |
src/pages/apps/exchange/index.vue |
~88 | console.log('[exchange/index.vue] continue watcher fired:', ...) |
src/pages/apps/exchange/index.vue |
~94 | console.trace('[exchange/index.vue] mounted() called:', ...) |
src/pages/apps/exchange/index.vue |
~139 | console.log('[exchange/index.vue] getUser success:', ...) |
src/pages/apps/exchange/index.vue |
~149 | console.log('[exchange/index.vue] getUser error:', ...) |
src/pages/apps/exchange/peer_to_peer/index.vue |
~89 | console.log('[p2p/index.vue] mounted(), route:', ...) |
src/pages/apps/exchange/peer_to_peer/orders.vue |
~26 | console.log('[orders.vue] created(), route:', ...) |
src/pages/apps/exchange/peer_to_peer/orders.vue |
~59 | console.trace('[orders.vue] mounted(), route:', ...) |
src/pages/apps/exchange/peer_to_peer/orders.vue |
~69 | console.log('[orders.vue] beforeUnmount(), route:', ...) |
2. profile.vue — Inconsistent completion rate formatting
Line 151 uses user.completion_rate.toFixed(1):
{{ $t('CompletionPercentage', { percentage: user.completion_rate ? user.completion_rate.toFixed(1) : '0' }) }}But the same file defines formatCompletionRate() at line 587 (used at line 231) which returns Math.floor(value || 0).toString(). This produces inconsistent display (e.g., "95.3%" vs "95%").
Fix: Replace line 151 with:
{{ $t('CompletionPercentage', { percentage: formatCompletionRate(user.completion_rate) }) }}✅ Strengths
- Performance:
_fetchOrderInProgress/_fetchOrderLastTimecooldowns inorder.vueand_getBalancePromisededuplication incontract.jseffectively prevent API spam. - New Components:
PeerInfo.vueis a clean, reusable abstraction with proper props API andreported-greyedCSS.ReportDialog.vuehas cleanv-modelbinding. - i18n: All new strings (
ReportUser,TradesCompleted,TradesFailed,CompletionPercentage, etc.) are present across all 19 locale files. - UI/UX: Trade stats display, reported user greyscale handling, appeal form single-choice enforcement, and sentence case normalization are all solid improvements.
📋 Recommendations
- Remove the 9 debug
console.log/console.tracestatements listed above. - Fix the
profile.vuecompletion rate inconsistency by usingformatCompletionRate(user.completion_rate)instead oftoFixed(1). - Merge after the two cleanup items above.
Verdict: The PR is ready to merge after removing the debug logs and fixing the profile.vue inconsistency. The performance improvements and new components are excellent additions to the P2P Ramp module.
…rmatting in profile.vue
Co-authored-by: joemarct <joemarct@users.noreply.github.com>
PR Review:
|
|
User opencode-agent[bot] does not have write permissions |
|
APIError: Insufficient balance. Manage your billing here: https://opencode.ai/workspace/wrk_01KHR5A6MHE7QYY19ZJ7XHBY32/billing |
PR Review:
|
| Issue | File | Status |
|---|---|---|
Invalid params.to_peer = this.userId |
UserProfileDialog.vue |
✅ Removed; fetchAds() now uses owner_id: vm.user.id |
errorDialogActive never set to true |
PaymentConfirmation.vue |
✅ Fixed; guard now works correctly |
fiatAmount currency symbol fallback |
PaymentConfirmation.vue |
✅ Falls back from data.order?.ad?.fiat_currency?.symbol to data?.ad?.fiat_currency?.symbol |
| Completion rate formatting inconsistency | AdListings.vue, profile.vue |
✅ All files now use Math.floor(value || 0).toString() |
AppealForm.vue promise chain |
AppealForm.vue |
✅ addArbiterToChat() returns its promise; caller uses .then(() => vm.addArbiterToChat()) |
Invalid params.to_peer = this.userId |
profile.vue |
✅ Removed |
Debug log in createGroupChat() |
order-form.vue |
✅ Removed |
Debug logs in order.vue, contract.js, PaymentConfirmation.vue, AdListings.vue, TradeInfoCard.vue, StandByDisplay.vue, FeedbackForm.vue |
Various | ✅ Removed |
✅ Strengths
1. Performance & Data Fetching
order.vue: The_fetchOrderInProgressflag and_fetchOrderLastTime10-second cooldown correctly prevent duplicate API calls and websocket spam.arbiter/appeal.vue: The_fetchAppealInProgressflag and_fetchAppealLastTimecooldown provide the same protection for the arbiter appeal page.contract.js: The_getBalancePromise/_getBalanceKeydeduplication pattern is a clean, robust way to serialize concurrent balance lookups.
2. New Components
PeerInfo.vue: Clean, reusable component with a well-designed props API. Thereported-greyedCSS class correctly appliesgrayscale,opacity, andpointer-events: noneto prevent interaction with reported users while preserving the red "Reported" badge.ReportDialog.vue: Properq-radiobinding with clear category selection andv-model+update:modelValuepattern.FeedbackForm.vue: Well-implemented with pre-composed feedback chips.
3. UI/UX Improvements
- Trade stats display on ad listings and trade cards is a valuable addition.
- Reported user handling (greyscale/blur) correctly preserves the red "Reported" badge visibility.
- Appeal form correctly restricts reason selection to single choice and shows the correct statement based on filer identity.
- Sentence case normalization across headers, labels, and buttons improves consistency.
4. i18n
- All new UI strings (
ReportUser,TradesCompleted,TradesFailed,CompletionPercentage,ReportReason*,FastTransaction, etc.) are present across all 19 locale files. AppealForm.vuedynamically selects the correct translation key (AppealFormReasonOpt1SellervsAppealFormReasonOpt1Buyer) based on filer identity.
⚠️ Minor Notes (Non-blocking)
1. Pre-existing debug logs in modified files
The following console.log statements were not introduced by this PR and are acceptable as pre-existing artifacts, but should be cleaned up in a future housekeeping pass:
src/components/ramp/fiat/EscrowTransfer.vue(lines 317, 323, 326) — BCH escrow flow logssrc/components/ramp/appeal/AppealDetail.vue(lines 271, 396, 428) — Appeal action / release-refund logs
2. PaymentConfirmation.vue — Contract balance display when balance is zero
Line 45 uses v-if="contractBalance", which hides the balance when it is 0. It should use v-if="contractBalance !== null" to correctly show "0 BCH" when the contract is empty. This is a pre-existing UX bug.
3. PaymentConfirmation.vue — fetchOrderDetail null reference risk
fetchOrderDetail() accesses vm.data.order.id directly. If data.order is ever falsy, this will throw. In current usage the parent always passes order, so this is not exercised, but a defensive check would be safer.
4. PaymentConfirmation.vue — sendConfirmPayment does not re-throw
The .catch(error => { this.showErrorDialog(error) }) in sendConfirmPayment swallows the error and returns undefined, causing response.data to throw on the next line. This is a pre-existing bug.
5. order.vue — loadData catch block leaves skeleton on error
If loadData() throws before isloaded = true, the skeleton loader remains indefinitely. The catch block only logs to console.error. This is a pre-existing UX issue.
📋 Final Verdict
This PR is ready to merge. All high-priority issues from the previous review cycle have been properly resolved. The performance improvements, new reusable components, and UI/UX enhancements are excellent additions to the P2P Ramp module. The remaining notes are minor, pre-existing issues that do not block merge.






Summary
This PR consolidates multiple fixes and enhancements across the P2P Ramp module to improve stability, UI consistency, and user experience.
Key Changes
Performance & Data Fetching
contractBalancedirectly toEscrowTransferto avoid redundant balance lookupsfetchOrderDetailcall when data is already availableNew Components
PeerInfocomponent (src/components/ramp/fiat/PeerInfo.vue) for reusable peer identity display across the P2P flowReportDialogwith category selection and description support for reporting usersFeedbackFormfor post-trade feedback with pre-composed chipsUI/UX Improvements
DragSlideto body so it properly overlays above scroll containers in the appeal formUserProfileDialogfor better visual alignmenti18n
Bug Fixes
ad.ownerfallback inTradeInfoCardcounterparty computed propertyformatCompletionRateUserProfileDialogFiles Changed
Testing