167 lines
5.2 KiB
TypeScript
167 lines
5.2 KiB
TypeScript
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<number | null>(null);
|
|
const spinResult = ref<any>(null);
|
|
const showResultModal = ref(false);
|
|
const errorMessage = ref<string | null>(null);
|
|
const pendingIdempotencyKey = ref<string | null>(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,
|
|
};
|
|
});
|