261 lines
7.5 KiB
JavaScript
261 lines
7.5 KiB
JavaScript
import { defineStore } from 'pinia'
|
||
import { ref, computed } from 'vue'
|
||
|
||
export const useQuizStore = defineStore('quiz', () => {
|
||
// State
|
||
const userPoints = ref(0)
|
||
const currentStreak = ref(0)
|
||
const lastPlayedDate = ref(null)
|
||
const questions = ref([])
|
||
const isLoading = ref(false)
|
||
const error = ref(null)
|
||
|
||
// Getters
|
||
const canPlayToday = computed(() => {
|
||
if (!lastPlayedDate.value) return true
|
||
const last = new Date(lastPlayedDate.value)
|
||
const now = new Date()
|
||
// Reset at midnight (different calendar day)
|
||
const lastDay = last.toDateString()
|
||
const today = now.toDateString()
|
||
return lastDay !== today
|
||
})
|
||
|
||
const totalQuestions = computed(() => questions.value.length)
|
||
|
||
// Helper function to get auth token
|
||
function getAuthToken() {
|
||
if (typeof window !== 'undefined') {
|
||
// Try both token keys for compatibility
|
||
return localStorage.getItem('token') || localStorage.getItem('auth_token')
|
||
}
|
||
return null
|
||
}
|
||
|
||
// Helper function for API calls
|
||
async function apiFetch(url, options = {}) {
|
||
const token = getAuthToken()
|
||
const headers = {
|
||
'Content-Type': 'application/json',
|
||
...options.headers
|
||
}
|
||
|
||
if (token) {
|
||
headers['Authorization'] = `Bearer ${token}`
|
||
}
|
||
|
||
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'https://dev.servicefinder.hu'}${url}`, {
|
||
...options,
|
||
headers
|
||
})
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text()
|
||
throw new Error(`API error ${response.status}: ${errorText}`)
|
||
}
|
||
|
||
return response.json()
|
||
}
|
||
|
||
// Actions
|
||
async function fetchQuizStats() {
|
||
isLoading.value = true
|
||
error.value = null
|
||
try {
|
||
const data = await apiFetch('/api/v1/gamification/quiz/stats')
|
||
userPoints.value = data.total_quiz_points || 0
|
||
currentStreak.value = data.current_streak || 0
|
||
lastPlayedDate.value = data.last_played || null
|
||
return data
|
||
} catch (err) {
|
||
console.error('Failed to fetch quiz stats:', err)
|
||
error.value = err.message
|
||
// Fallback to localStorage if API fails
|
||
userPoints.value = getStoredPoints()
|
||
currentStreak.value = getStoredStreak()
|
||
lastPlayedDate.value = getStoredLastPlayedDate()
|
||
return {
|
||
total_quiz_points: userPoints.value,
|
||
current_streak: currentStreak.value,
|
||
last_played: lastPlayedDate.value,
|
||
can_play_today: canPlayToday.value
|
||
}
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function fetchDailyQuiz() {
|
||
isLoading.value = true
|
||
error.value = null
|
||
try {
|
||
const data = await apiFetch('/api/v1/gamification/quiz/daily')
|
||
questions.value = data.questions || []
|
||
return data
|
||
} catch (err) {
|
||
console.error('Failed to fetch daily quiz:', err)
|
||
error.value = err.message
|
||
// Fallback to mock questions if API fails
|
||
questions.value = getMockQuestions()
|
||
return {
|
||
questions: questions.value,
|
||
total_questions: questions.value.length,
|
||
date: new Date().toISOString().split('T')[0]
|
||
}
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function answerQuestion(questionId, selectedOptionIndex) {
|
||
try {
|
||
const response = await apiFetch('/api/v1/gamification/quiz/answer', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
question_id: questionId,
|
||
selected_option: selectedOptionIndex
|
||
})
|
||
})
|
||
|
||
if (response.is_correct) {
|
||
userPoints.value += response.points_awarded
|
||
currentStreak.value += 1
|
||
persistState() // Update localStorage as fallback
|
||
} else {
|
||
currentStreak.value = 0
|
||
}
|
||
|
||
return response
|
||
} catch (err) {
|
||
console.error('Failed to submit quiz answer:', err)
|
||
error.value = err.message
|
||
// Fallback to local logic
|
||
return answerQuestionLocal(questionId, selectedOptionIndex)
|
||
}
|
||
}
|
||
|
||
async function completeDailyQuiz() {
|
||
try {
|
||
await apiFetch('/api/v1/gamification/quiz/complete', {
|
||
method: 'POST'
|
||
})
|
||
lastPlayedDate.value = new Date().toISOString()
|
||
persistState()
|
||
} catch (err) {
|
||
console.error('Failed to complete daily quiz:', err)
|
||
error.value = err.message
|
||
// Fallback to local storage
|
||
lastPlayedDate.value = new Date().toISOString()
|
||
persistState()
|
||
}
|
||
}
|
||
|
||
// Local fallback functions
|
||
function answerQuestionLocal(questionId, selectedOptionIndex) {
|
||
const question = questions.value.find(q => q.id === questionId)
|
||
if (!question) return { is_correct: false, correct_answer: -1, explanation: 'Question not found' }
|
||
|
||
const isCorrect = selectedOptionIndex === question.correctAnswer
|
||
if (isCorrect) {
|
||
userPoints.value += 10
|
||
currentStreak.value += 1
|
||
} else {
|
||
currentStreak.value = 0
|
||
}
|
||
|
||
persistState()
|
||
return {
|
||
is_correct: isCorrect,
|
||
correct_answer: question.correctAnswer,
|
||
points_awarded: isCorrect ? 10 : 0,
|
||
explanation: question.explanation
|
||
}
|
||
}
|
||
|
||
function resetStreak() {
|
||
currentStreak.value = 0
|
||
persistState()
|
||
}
|
||
|
||
function addPoints(points) {
|
||
userPoints.value += points
|
||
persistState()
|
||
}
|
||
|
||
// SSR-safe localStorage persistence (fallback only)
|
||
function persistState() {
|
||
if (typeof window !== 'undefined') {
|
||
localStorage.setItem('quiz_points', userPoints.value.toString())
|
||
localStorage.setItem('quiz_streak', currentStreak.value.toString())
|
||
localStorage.setItem('quiz_last_played', lastPlayedDate.value)
|
||
}
|
||
}
|
||
|
||
function getStoredPoints() {
|
||
if (typeof window !== 'undefined') {
|
||
return parseInt(localStorage.getItem('quiz_points') || '0')
|
||
}
|
||
return 0
|
||
}
|
||
|
||
function getStoredStreak() {
|
||
if (typeof window !== 'undefined') {
|
||
return parseInt(localStorage.getItem('quiz_streak') || '0')
|
||
}
|
||
return 0
|
||
}
|
||
|
||
function getStoredLastPlayedDate() {
|
||
if (typeof window !== 'undefined') {
|
||
return localStorage.getItem('quiz_last_played') || null
|
||
}
|
||
return null
|
||
}
|
||
|
||
function getMockQuestions() {
|
||
return [
|
||
{
|
||
id: 1,
|
||
question: 'Melyik alkatrész felelős a motor levegő‑üzemanyag keverékének szabályozásáért?',
|
||
options: ['Generátor', 'Lambda‑szonda', 'Féktárcsa', 'Olajszűrő'],
|
||
correctAnswer: 1,
|
||
explanation: 'A lambda‑szonda méri a kipufogógáz oxigéntartalmát, és ezen alapul a befecskendezés.'
|
||
},
|
||
{
|
||
id: 2,
|
||
question: 'Mennyi ideig érvényes egy gépjármű műszaki vizsgája Magyarországon?',
|
||
options: ['1 év', '2 év', '4 év', '6 év'],
|
||
correctAnswer: 1,
|
||
explanation: 'A személygépkocsik műszaki vizsgája 2 évre érvényes, kivéve az újonnan forgalomba helyezett autókat.'
|
||
},
|
||
{
|
||
id: 3,
|
||
question: 'Melyik anyag NEM része a hibrid autók akkumulátorának?',
|
||
options: ['Lítium', 'Nikkel', 'Ólom', 'Kobalt'],
|
||
correctAnswer: 2,
|
||
explanation: 'A hibrid és elektromos autók akkumulátoraiban általában lítium, nikkel és kobalt található, ólom az ólom‑savas akkukban van.'
|
||
}
|
||
]
|
||
}
|
||
|
||
// Initialize store with stats - DISABLED for debugging
|
||
// fetchQuizStats()
|
||
console.log('🚨 Quiz store: Auto-fetch DISABLED for debugging')
|
||
|
||
return {
|
||
userPoints,
|
||
currentStreak,
|
||
lastPlayedDate,
|
||
questions,
|
||
canPlayToday,
|
||
totalQuestions,
|
||
isLoading,
|
||
error,
|
||
fetchQuizStats,
|
||
fetchDailyQuiz,
|
||
answerQuestion,
|
||
completeDailyQuiz,
|
||
resetStreak,
|
||
addPoints
|
||
}
|
||
}) |