import confetti from 'canvas-confetti'; import { defineStore } from 'pinia'; import { ref } from 'vue'; import { useSoundEffects } from '../composables/useSoundEffects'; import { apiClient } from '../services/api/client'; import { useAuthStore } from './auth.store'; export interface PrizeResult { id: string; title: string; segment: number; code?: string | null; type: string; image?: string | null; } export const useSpinStore = defineStore('spin', () => { const authStore = useAuthStore(); const { playWin, playNoPrize } = useSoundEffects(); const isRequesting = ref(false); const isWheelSpinning = ref(false); const targetSegmentIndex = ref(null); const spinResult = ref(null); const showResultModal = ref(false); const errorMessage = ref(null); const pendingIdempotencyKey = ref(null); function generateIdempotencyKey(): string { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } async function executeSpin(campaignId: string, segments: any[]) { if (isRequesting.value || isWheelSpinning.value) return; if (!authStore.isAuthenticated) { errorMessage.value = 'لطفاً ابتدا وارد حساب کاربری خود شوید.'; return; } if (authStore.chances.remainingChances <= 0) { errorMessage.value = 'شما شانس کافی برای چرخاندن گردونه ندارید!'; return; } isRequesting.value = true; errorMessage.value = null; if (!pendingIdempotencyKey.value) { pendingIdempotencyKey.value = generateIdempotencyKey(); } try { const res = await apiClient.post(`/campaigns/${campaignId}/spin`, { idempotencyKey: pendingIdempotencyKey.value, }); const data = res.data; spinResult.value = data; // Update remaining chances from server if (typeof data.remainingChances === 'number') { authStore.setRemainingChances(data.remainingChances); } else { authStore.decrementChance(); } // Find the segment index in the client segments list let segIdx = 0; if (data.prize) { // Priority 1: Match strictly by prize id first let found = segments.findIndex((s) => s.id === data.prize.id); // Priority 2: Fallback match by exact title if (found === -1 && data.prize.title) { found = segments.findIndex((s) => s.title === data.prize.title); } // Priority 3: Fallback match by sortOrder / segment if (found === -1 && data.prize.segment !== undefined) { found = segments.findIndex((s) => s.sortOrder === data.prize.segment); } segIdx = found !== -1 ? found : 0; } targetSegmentIndex.value = segIdx; isWheelSpinning.value = true; // Clear the idempotency key once successfully acknowledged by server pendingIdempotencyKey.value = null; } catch (err: any) { let msg = err.response?.data?.message || (Array.isArray(err.response?.data?.message) ? err.response?.data?.message.join(' - ') : 'خطا در ارتباط با سرور گردونه شانس'); if (typeof msg === 'string') { if (msg === 'CAMPAIGN_DRAFT') { msg = 'این گردونه شانس در حال آماده‌سازی است و هنوز فعال نشده است.'; } else if (msg === 'CAMPAIGN_PAUSED') { msg = 'این گردونه شانس موقتاً متوقف شده است.'; } else if (msg === 'CAMPAIGN_NOT_STARTED') { msg = 'زمان شروع این گردونه شانس هنوز فرا نرسیده است.'; } else if (msg === 'CAMPAIGN_FINISHED') { msg = 'مهلت شرکت در این گردونه شانس به پایان رسیده است.'; } } errorMessage.value = msg; } finally { isRequesting.value = false; } } function onWheelAnimationFinished() { isWheelSpinning.value = false; targetSegmentIndex.value = null; const prize = spinResult.value?.prize; const isNoPrize = !prize || prize.type === 'NO_PRIZE' || prize.title?.trim() === 'پوچ' || prize.title?.includes('پوچ'); if (!isNoPrize) { playWin(); // Confetti burst confetti({ particleCount: 80, spread: 70, origin: { y: 0.6 }, }); } else { // Play famous iconic Sad Trombone losing sound playNoPrize(); } showResultModal.value = true; // Trigger SMS notification now that the wheel stopped and the modal is visible if (spinResult.value?.spinId) { apiClient.post(`/campaigns/spins/${spinResult.value.spinId}/notify`).catch(() => {}); } } function closeResultModal() { showResultModal.value = false; spinResult.value = null; } return { isRequesting, isWheelSpinning, targetSegmentIndex, spinResult, showResultModal, errorMessage, executeSpin, onWheelAnimationFinished, closeResultModal, }; });