Phase 4 Stage 5: pairing UX upgrades (searching + match + targeted-wait)
Searching screen: soft-prompt card reskin, pulsing-dots panel replaces
the spinner, inline 5-min timeout panel with `coba cari lagi` (resets
pairing notifier + routes to /payment/entry for a fresh funnel — the
server-side payment is failed_pairing at that point so a stale retry
isn't valid) and `kembali ke home` ghost CTA.
Bestie-found screen: S9 Match-V4 reskin — HaloOrb + status dot +
'halo, aku bestie {name}' + `mulai sesi {N} menit →` with N pulled from
the active session's duration_minutes.
Targeted-wait overlay (new) at /chat/waiting-targeted/:mitraId. Three
sub-states from pairingProvider's PairingTargetedWaitingData:
waiting (20s countdown) / accepted (routes to chat) / declined (stubbed
BestieOfflinePopup with a TODO pointing to Stage 8). Reached via
payment_screen._routeToSearchOnConfirmed when the confirm carried a
targetedMitraId — keeps the mandatory payment-before-pairing invariant.
Dev-only POST /internal/_test/force-pairing-timeout drives the 5-min
timeout shortcut for the Maestro flow without waiting live.
Maestro 05_searching_timeout.yaml + force_pairing_timeout.js helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,9 @@
|
||||
// test phone numbers or fixed codes into production code paths.
|
||||
|
||||
import { peekStubOtp } from '../../services/otp.service.js'
|
||||
import { expirePairingRequest } from '../../services/pairing.service.js'
|
||||
import { getDb } from '../../db/client.js'
|
||||
import { PairingFailureCause, SessionStatus } from '../../constants.js'
|
||||
|
||||
const sql = getDb()
|
||||
|
||||
@@ -77,4 +79,33 @@ export const internalTestRoutes = async (fastify) => {
|
||||
}
|
||||
return { ok: true, ...updated }
|
||||
})
|
||||
|
||||
// Force-expire a pairing blast (used by Maestro Stage 5 flow to drive the
|
||||
// searching screen into the timeout state without waiting 5 minutes). Marks
|
||||
// the most-recently-created blast chat_session as no_mitra_available.
|
||||
//
|
||||
// Body shape:
|
||||
// { session_id: '<uuid>' } → expire this specific session
|
||||
// { latest: true } → expire the most-recent SEARCHING session
|
||||
fastify.post('/force-pairing-timeout', async (request, reply) => {
|
||||
const { session_id, latest } = request.body ?? {}
|
||||
let target = session_id
|
||||
if (latest === true) {
|
||||
const [row] = await sql`
|
||||
SELECT id FROM chat_sessions
|
||||
WHERE status = ${SessionStatus.SEARCHING}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`
|
||||
if (!row) {
|
||||
return reply.code(404).send({ error: 'no_searching_session' })
|
||||
}
|
||||
target = row.id
|
||||
}
|
||||
if (!target) {
|
||||
return reply.code(400).send({ error: 'session_id or latest:true required in body' })
|
||||
}
|
||||
await expirePairingRequest(target, PairingFailureCause.NO_MITRA_AVAILABLE)
|
||||
return { ok: true, session_id: target }
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user