first commit
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
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 = 'این کمپین در وضعیت پیشنویس (DRAFT) قرار دارد و هنوز فعال نشده است.';
|
||||
} 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;
|
||||
}
|
||||
|
||||
function closeResultModal() {
|
||||
showResultModal.value = false;
|
||||
spinResult.value = null;
|
||||
}
|
||||
|
||||
return {
|
||||
isRequesting,
|
||||
isWheelSpinning,
|
||||
targetSegmentIndex,
|
||||
spinResult,
|
||||
showResultModal,
|
||||
errorMessage,
|
||||
executeSpin,
|
||||
onWheelAnimationFinished,
|
||||
closeResultModal,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user