From 5006a0360376f127f80ac0b98ad5bfb88f94880a Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 12 Apr 2026 21:51:42 -0700 Subject: [PATCH] feat(community): TrustFeedbackButtons + useTrustFeedback -- trust signal UI on ListingCard [MIT] Files: web/src/composables/useTrustFeedback.ts, web/src/components/TrustFeedbackButtons.vue, web/src/components/ListingCard.vue - useTrustFeedback composable: FeedbackState machine (idle/sending/confirmed/disputed), fail-soft fetch, always confirms regardless of network outcome - TrustFeedbackButtons.vue: "This score looks right / This score is wrong" button pair with calm "Thanks, noted." confirmation; uses --trust-high/--trust-low theme CSS vars; aria-live="polite", aria-busy, focus-visible, prefers-reduced-motion, no countdown timers - ListingCard.vue: TrustFeedbackButtons slotted after trust badge inside .card__score-col --- web/src/components/ListingCard.vue | 7 ++ web/src/components/TrustFeedbackButtons.vue | 91 +++++++++++++++++++++ web/src/composables/useTrustFeedback.ts | 28 +++++++ 3 files changed, 126 insertions(+) create mode 100644 web/src/components/TrustFeedbackButtons.vue create mode 100644 web/src/composables/useTrustFeedback.ts diff --git a/web/src/components/ListingCard.vue b/web/src/components/ListingCard.vue index 2dbf3e7..1244224 100644 --- a/web/src/components/ListingCard.vue +++ b/web/src/components/ListingCard.vue @@ -128,6 +128,12 @@ >⚑ + + +
+ + + + + + diff --git a/web/src/composables/useTrustFeedback.ts b/web/src/composables/useTrustFeedback.ts new file mode 100644 index 0000000..66a0e77 --- /dev/null +++ b/web/src/composables/useTrustFeedback.ts @@ -0,0 +1,28 @@ +// web/src/composables/useTrustFeedback.ts +// MIT -- component layer; the API call routes to a BSL endpoint. +import { ref } from 'vue' + +export type FeedbackState = 'idle' | 'sending' | 'confirmed' | 'disputed' + +export function useTrustFeedback(sellerId: string) { + const state = ref('idle') + + async function submitFeedback(confirmed: boolean): Promise { + if (state.value !== 'idle') return + state.value = 'sending' + try { + await fetch('/api/community/signal', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ seller_id: sellerId, confirmed }), + }) + // Always confirm regardless of response -- fail-soft contract. + } catch { + // Network unreachable -- still confirm to the user. Signal is best-effort. + } finally { + state.value = confirmed ? 'confirmed' : 'disputed' + } + } + + return { state, submitFeedback } +}