neptune-web-wallet/src/components/auth/ConfirmSeedComponent.vue
2025-10-22 01:43:20 +07:00

437 lines
11 KiB
Vue

<script setup lang="ts">
import { ref, defineEmits, onMounted } from 'vue'
import { ButtonCommon } from '@/components'
import { useSeedStore } from '@/stores'
const emit = defineEmits<{
next: []
back: []
}>()
const seedStore = useSeedStore()
const seedWords = ref<string[]>([])
const currentQuestionIndex = ref(0)
const selectedAnswer = ref('')
const isCorrect = ref(false)
const showResult = ref(false)
const generateQuiz = (): {
position: number
correctWord: string
options: string[]
} | null => {
if (seedWords.value.length === 0) return null
const randomPosition = Math.floor(Math.random() * 12) + 1
currentQuestionIndex.value = randomPosition - 1
const correctWord = seedWords.value[randomPosition - 1]
const options = [correctWord]
const BIP39_WORDS = [
'abandon',
'ability',
'able',
'about',
'above',
'absent',
'absorb',
'abstract',
'absurd',
'abuse',
'access',
'accident',
'account',
'accuse',
'achieve',
'acid',
'acoustic',
'acquire',
'across',
'act',
'action',
'actor',
'actress',
'actual',
'adapt',
'add',
'addict',
'address',
'adjust',
'admit',
'adult',
'advance',
'advice',
'aerobic',
'affair',
'afford',
'afraid',
'again',
'age',
'agent',
'agree',
'ahead',
'aim',
'air',
'airport',
'aisle',
'alarm',
'album',
'alcohol',
'alert',
'alien',
'all',
'alley',
'allow',
'almost',
'alone',
'alpha',
'already',
'also',
'alter',
'always',
'amateur',
'amazing',
'among',
'amount',
'amused',
'analyst',
'anchor',
'ancient',
'anger',
'angle',
'angry',
'animal',
'ankle',
'announce',
'annual',
'another',
'answer',
'antenna',
'antique',
'anxiety',
'any',
'apart',
'apology',
'appear',
'apple',
'approve',
'april',
'arch',
'arctic',
'area',
'arena',
'argue',
'arm',
'armed',
'armor',
'army',
'around',
'arrange',
'arrest',
]
while (options.length < 4) {
const randomWord = BIP39_WORDS[Math.floor(Math.random() * BIP39_WORDS.length)]
if (!options.includes(randomWord)) {
options.push(randomWord)
}
}
options.sort(() => Math.random() - 0.5)
return {
position: randomPosition,
correctWord,
options,
}
}
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) {
emit('next')
} else {
showResult.value = false
selectedAnswer.value = ''
const newQuiz = generateQuiz()
if (newQuiz) {
quizData.value = newQuiz
}
}
}
onMounted(() => {
const words = seedStore.getSeedWords()
if (words.length > 0) {
seedWords.value = words
const newQuiz = generateQuiz()
if (newQuiz) {
quizData.value = newQuiz
}
} else {
const sampleWords = [
'abandon',
'ability',
'able',
'about',
'above',
'absent',
'absorb',
'abstract',
'absurd',
'abuse',
'access',
'accident',
]
seedWords.value = sampleWords
const newQuiz = generateQuiz()
if (newQuiz) {
quizData.value = newQuiz
}
}
})
</script>
<template>
<div class="confirm-container">
<div class="confirm-card">
<div class="confirm-header">
<h1 class="confirm-title">Recovery Seed</h1>
</div>
<div class="confirm-content">
<div class="progress-indicators">
<div class="progress-circle"></div>
<div class="progress-circle active"></div>
<div class="progress-circle"></div>
</div>
<div class="instruction-text">
<p>
Make sure you wrote the phrase down correctly by answering this quick
checkup.
</p>
</div>
<div class="quiz-section">
<h2 class="quiz-question">What is the {{ quizData?.position }}th word?</h2>
<div class="answer-options">
<button
v-for="(option, index) in quizData?.options"
:key="index"
class="answer-button"
:class="{
selected: selectedAnswer === option,
correct: showResult && option === quizData?.correctWord,
incorrect:
showResult &&
selectedAnswer === option &&
option !== quizData?.correctWord,
}"
@click="handleAnswerSelect(option)"
:disabled="showResult"
>
{{ option }}
</button>
</div>
<div v-if="showResult" class="result-message">
<p v-if="isCorrect" class="success-message"> Correct! You can proceed.</p>
<p v-else class="error-message"> Incorrect. Please try again.</p>
</div>
</div>
<div class="confirm-actions">
<ButtonCommon
v-if="showResult && isCorrect"
type="primary"
size="large"
@click="handleNext"
>
CONTINUE
</ButtonCommon>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.confirm-container {
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
background: var(--bg-light);
min-height: 100vh;
}
.confirm-card {
@include card-base;
max-width: 500px;
width: 100%;
border: 2px solid var(--primary-color);
}
.confirm-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-xl);
border-bottom: 1px solid var(--border-color);
.confirm-title {
font-size: var(--font-2xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin: 0;
}
}
.confirm-content {
.progress-indicators {
display: flex;
justify-content: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-2xl);
.progress-circle {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--border-light);
transition: background-color 0.3s ease;
&.active {
background: var(--primary-color);
}
}
}
.instruction-text {
text-align: center;
margin-bottom: var(--spacing-2xl);
p {
font-size: var(--font-sm);
color: var(--text-secondary);
line-height: var(--leading-normal);
margin: 0;
}
}
.quiz-section {
margin-bottom: var(--spacing-2xl);
.quiz-question {
font-size: var(--font-lg);
font-weight: var(--font-bold);
color: var(--text-primary);
text-align: center;
margin-bottom: var(--spacing-xl);
}
.answer-options {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
.answer-button {
padding: var(--spacing-md);
border: 2px solid var(--border-light);
background: var(--bg-white);
border-radius: var(--radius-md);
font-size: var(--font-sm);
font-weight: var(--font-medium);
color: var(--text-primary);
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(:disabled) {
border-color: var(--primary-color);
background: var(--primary-light);
}
&.selected {
border-color: var(--primary-color);
background: var(--primary-light);
}
&.correct {
border-color: var(--success-color);
background: var(--success-light);
color: var(--success-color);
}
&.incorrect {
border-color: var(--error-color);
background: var(--error-light);
color: var(--error-color);
}
&:disabled {
cursor: not-allowed;
opacity: 0.7;
}
}
}
.result-message {
text-align: center;
.success-message {
color: var(--success-color);
font-weight: var(--font-medium);
margin: 0;
}
.error-message {
color: var(--error-color);
font-weight: var(--font-medium);
margin: 0;
}
}
}
.confirm-actions {
display: flex;
justify-content: space-between;
gap: var(--spacing-md);
}
}
@media (max-width: 640px) {
.confirm-container {
padding: var(--spacing-md);
}
.confirm-card {
max-width: 100%;
}
.quiz-section {
.answer-options {
grid-template-columns: 1fr;
}
}
.confirm-actions {
flex-direction: column;
}
}
</style>