diff --git a/frontend/src/stores/crypto.test.ts b/frontend/src/stores/crypto.test.ts index cc5d02b..f2b57ea 100644 --- a/frontend/src/stores/crypto.test.ts +++ b/frontend/src/stores/crypto.test.ts @@ -81,6 +81,33 @@ describe('crypto store', () => { expect(crypto.pendingQuote).toBeNull() }) + it('removes an expired quote so the trade form can request a new one', async () => { + vi.useFakeTimers() + const expiringQuote = { ...quote, expiresAt: Date.now() + 1000 } + mockNuiCall.mockResolvedValueOnce({ data: expiringQuote, success: true }) + const crypto = useCryptoStore() + + await crypto.quote('aurora', 'buy', '1') + expect(crypto.pendingQuote).toEqual(expiringQuote) + + await vi.advanceTimersByTimeAsync(1000) + expect(crypto.pendingQuote).toBeNull() + vi.useRealTimers() + }) + + it('clears a rejected execution so a fresh quote can be requested', async () => { + mockNuiCall.mockResolvedValueOnce({ + error: 'quote_expired', + success: false, + }) + const crypto = useCryptoStore() + crypto.pendingQuote = quote + + expect(await crypto.executeQuote()).toBe(false) + expect(crypto.pendingQuote).toBeNull() + expect(crypto.error).toBe('quote_expired') + }) + it('keeps server errors and does not replace portfolio state', async () => { mockNuiCall.mockResolvedValueOnce({ error: 'quote_expired', diff --git a/frontend/src/stores/crypto.ts b/frontend/src/stores/crypto.ts index 3162195..ed834a1 100644 --- a/frontend/src/stores/crypto.ts +++ b/frontend/src/stores/crypto.ts @@ -82,6 +82,21 @@ export const useCryptoStore = defineStore('crypto', { side, }) this.pendingQuote = response.success ? (response.data ?? null) : null + if (this.pendingQuote) { + const quoteId = this.pendingQuote.id + const expiresAt = this.pendingQuote.expiresAt + globalThis.setTimeout( + () => { + if ( + this.pendingQuote?.id === quoteId && + this.pendingQuote.expiresAt <= Date.now() + ) { + this.pendingQuote = null + } + }, + Math.max(0, expiresAt - Date.now()), + ) + } return response }, async executeQuote(): Promise { @@ -90,9 +105,9 @@ export const useCryptoStore = defineStore('crypto', { idempotencyKey: requestKey('trade'), quoteId: this.pendingQuote.id, }) + this.pendingQuote = null if (!response.success || !response.data) return false this.data = response.data - this.pendingQuote = null return true }, async updateProfile(payload: {