280 lines
8.5 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { ChevronLeft, Check, CheckCircle2, XCircle } from 'lucide-vue-next'
interface Props {
seedPhrase: string[]
}
const props = defineProps<Props>()
const emit = defineEmits<{
next: []
back: []
}>()
const seedWords = computed(() => props.seedPhrase || [])
const currentQuestionIndex = ref(0)
const selectedAnswer = ref('')
const isCorrect = ref(false)
const showResult = ref(false)
const correctCount = ref(0)
const totalQuestions = 3
const askedPositions = ref<Set<number>>(new Set())
const answeredQuestions = ref<number[]>([])
const generateQuiz = (): {
position: number
correctWord: string
options: string[]
} | null => {
if (!seedWords.value || seedWords.value.length === 0) return null
let randomPosition: number
let attempts = 0
const maxAttempts = 50
do {
randomPosition = Math.floor(Math.random() * seedWords.value.length) + 1
attempts++
if (attempts > maxAttempts) return null
} while (askedPositions.value.has(randomPosition))
currentQuestionIndex.value = randomPosition - 1
const correctWord = seedWords.value[randomPosition - 1]
const options = [correctWord]
const otherWords = seedWords.value.filter((_, index) => index !== randomPosition - 1)
while (options.length < 4 && otherWords.length > 0) {
const randomIndex = Math.floor(Math.random() * otherWords.length)
const randomWord = otherWords[randomIndex]
if (!options.includes(randomWord)) {
options.push(randomWord)
otherWords.splice(randomIndex, 1)
}
}
options.sort(() => Math.random() - 0.5)
return {
position: randomPosition,
correctWord: correctWord as string,
options: options as string[],
}
}
const quizData = ref<{
position: number
correctWord: string
options: string[]
} | null>(null)
const handleAnswerSelect = (answer: string) => {
selectedAnswer.value = answer
isCorrect.value = answer === quizData.value?.correctWord
showResult.value = true
}
const handleNext = () => {
if (isCorrect.value) {
correctCount.value++
askedPositions.value.add(quizData.value!.position)
answeredQuestions.value.push(quizData.value!.position)
if (correctCount.value >= totalQuestions) {
emit('next')
} else {
setTimeout(() => {
showResult.value = false
selectedAnswer.value = ''
const newQuiz = generateQuiz()
if (newQuiz) {
quizData.value = newQuiz
}
}, 800)
}
} else {
setTimeout(() => {
showResult.value = false
selectedAnswer.value = ''
const newQuiz = generateQuiz()
if (newQuiz) {
quizData.value = newQuiz
}
}, 1500)
}
}
const handleBack = () => {
emit('back')
}
onMounted(() => {
const newQuiz = generateQuiz()
if (newQuiz) {
quizData.value = newQuiz
}
})
</script>
<template>
<div class="space-y-6">
<!-- Header -->
<div class="space-y-2 text-center">
<h1 class="text-2xl font-bold text-foreground">Confirm Recovery Phrase</h1>
<p class="text-sm text-muted-foreground">
Select the correct word for each position
</p>
</div>
<Separator />
<!-- Progress -->
<div class="space-y-3">
<div class="flex items-center justify-center gap-2">
<div
v-for="i in totalQuestions"
:key="i"
class="flex h-8 w-8 items-center justify-center rounded-full transition-all"
:class="
answeredQuestions.includes(i)
? 'bg-green-500 text-white'
: i === correctCount + 1
? 'border-2 border-primary bg-primary/10 text-primary'
: 'bg-muted text-muted-foreground'
"
>
<CheckCircle2 v-if="answeredQuestions.includes(i)" :size="16" />
<span v-else class="text-xs font-bold">{{ i }}</span>
</div>
</div>
<p class="text-center text-sm font-semibold">
Question <span class="text-primary">{{ correctCount + 1 }}</span> of {{ totalQuestions }}
</p>
</div>
<!-- Quiz Section -->
<div v-if="quizData" class="space-y-5">
<!-- Question Card -->
<Card class="border-2 border-primary/30 bg-gradient-to-br from-primary/5 to-accent/5">
<CardContent class="py-8">
<h2 class="text-center text-xl font-bold text-foreground">
Select word
<span class="text-primary">#{{ quizData.position }}</span>
</h2>
<p class="mt-2 text-center text-sm text-muted-foreground">
What is the
{{
quizData.position === 1
? '1st'
: quizData.position === 2
? '2nd'
: quizData.position === 3
? '3rd'
: `${quizData.position}th`
}}
word in your recovery phrase?
</p>
</CardContent>
</Card>
<!-- Answer Options -->
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
<button
v-for="(option, index) in quizData.options"
:key="index"
class="group relative overflow-hidden rounded-xl border-2 p-5 text-left transition-all disabled:cursor-not-allowed"
:class="{
'border-primary bg-primary/10 shadow-lg': selectedAnswer === option && !showResult,
'border-green-500 bg-green-500/10':
showResult && option === quizData.correctWord,
'border-destructive bg-destructive/10':
showResult && selectedAnswer === option && option !== quizData.correctWord,
'border-border hover:border-primary/50 hover:bg-accent': !selectedAnswer || (selectedAnswer !== option && !showResult),
}"
:disabled="showResult"
@click="handleAnswerSelect(option)"
>
<div class="flex items-center gap-3">
<!-- Option Number -->
<div
class="flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold transition-colors"
:class="{
'bg-primary text-primary-foreground': selectedAnswer === option && !showResult,
'bg-green-500 text-white': showResult && option === quizData.correctWord,
'bg-destructive text-destructive-foreground': showResult && selectedAnswer === option && option !== quizData.correctWord,
'bg-muted text-muted-foreground': !selectedAnswer || (selectedAnswer !== option && !showResult),
}"
>
{{ String.fromCharCode(65 + index) }}
</div>
<!-- Word -->
<span class="flex-1 text-base font-semibold">{{ option }}</span>
<!-- Check/X Icon -->
<CheckCircle2
v-if="showResult && option === quizData.correctWord"
:size="24"
class="text-green-500"
/>
<XCircle
v-else-if="showResult && selectedAnswer === option && option !== quizData.correctWord"
:size="24"
class="text-destructive"
/>
</div>
</button>
</div>
<!-- Result Message -->
<div v-if="showResult" class="animate-in fade-in slide-in-from-top-4 duration-500">
<Alert
:variant="isCorrect ? 'default' : 'destructive'"
class="border-2"
>
<CheckCircle2 v-if="isCorrect" :size="20" class="text-green-500" />
<XCircle v-else :size="20" class="text-destructive" />
<AlertDescription class="text-base font-medium">
<span v-if="isCorrect && correctCount + 1 >= totalQuestions">
Perfect! You've verified your recovery phrase. 🎉
</span>
<span v-else-if="isCorrect">
Correct! Moving to next question...
</span>
<span v-else>
That's not correct. Please try again.
</span>
</AlertDescription>
</Alert>
</div>
</div>
<!-- Action Buttons -->
<div class="flex gap-3">
<Button
v-if="!showResult || !isCorrect || (isCorrect && correctCount + 1 < totalQuestions)"
variant="outline"
size="lg"
class="flex-1 gap-2"
@click="handleBack"
>
<ChevronLeft :size="18" />
Back
</Button>
<Button
v-if="showResult && isCorrect && correctCount + 1 >= totalQuestions"
size="lg"
class="flex-1 gap-2 text-base font-semibold"
@click="handleNext"
>
Continue
<Check :size="18" />
</Button>
</div>
</div>
</template>