hesabixCore/webUI/src/views/public/WarrantyActivation.vue

2292 lines
61 KiB
Vue

<template>
<div class="warranty-activation">
<!-- Hero Section -->
<section class="hero-section">
<div class="hero-background">
<div class="hero-overlay"></div>
<div class="hero-pattern"></div>
</div>
<v-container class="hero-content">
<v-row justify="center" align="center" class="min-height-screen">
<v-col cols="12" md="10" lg="8" xl="6">
<!-- Main Card -->
<v-card class="main-card" elevation="0">
<v-card-text class="pa-6 pa-md-8">
<!-- Modern Progress Steps -->
<div class="modern-stepper mb-10">
<div class="stepper-track">
<div
class="stepper-progress"
:style="{ width: `${((currentStep - 1) / (steps.length - 1)) * 100}%` }"
></div>
</div>
<div class="stepper-steps">
<div
v-for="(step, index) in steps"
:key="index"
class="stepper-step"
:class="{
'active': currentStep === index + 1,
'completed': currentStep > index + 1,
'pending': currentStep < index + 1
}"
>
<div class="stepper-circle">
<div class="stepper-circle-inner">
<v-icon v-if="currentStep > index + 1" size="18">mdi-check</v-icon>
<span v-else class="stepper-number">{{ index + 1 }}</span>
</div>
</div>
<div class="stepper-content">
<div class="stepper-title">{{ step.title }}</div>
<div class="stepper-description">{{ step.subtitle }}</div>
</div>
</div>
</div>
</div>
<!-- Step 1: Enter Code -->
<div v-if="currentStep === 1" class="step-content-wrapper">
<div class="step-header text-center mb-6">
<div class="step-icon-wrapper mb-4">
<v-icon size="48" color="primary">mdi-qrcode-scan</v-icon>
</div>
<h2 class="step-main-title mb-2">کد گارانتی را وارد کنید</h2>
<p class="step-description">
کد گارانتی روی برچسب کالا یا فاکتور خرید موجود است
</p>
</div>
<v-form ref="codeForm" v-model="codeFormValid" @submit.prevent="checkWarrantyCode">
<div class="input-section mb-6">
<v-text-field
v-model="warrantyCode"
label="کد گارانتی"
placeholder="مثال: WR-123456789"
outlined
:rules="codeRules"
:loading="loading"
prepend-inner-icon="mdi-shield-outline"
class="custom-input"
hide-details="auto"
@keyup.enter="checkWarrantyCode"
>
<template v-slot:append>
<v-btn
icon
small
@click="scanQrCode"
:disabled="loading"
class="qr-scan-btn"
>
<v-icon size="20">mdi-qrcode-scan</v-icon>
</v-btn>
</template>
</v-text-field>
</div>
<v-alert
v-if="errorMessage"
type="error"
dismissible
class="mb-4 custom-alert"
border="left"
colored-border
>
{{ errorMessage }}
</v-alert>
<div class="action-buttons mb-6">
<v-btn
color="primary"
large
block
:loading="loading"
:disabled="!codeFormValid"
@click="checkWarrantyCode"
class="primary-btn mb-4"
elevation="2"
>
<v-icon left>mdi-magnify</v-icon>
بررسی کد گارانتی
</v-btn>
</div>
</v-form>
</div>
<!-- Step 2: Confirm Information -->
<div v-if="currentStep === 2" class="step-content-wrapper">
<div class="step-header text-center mb-6">
<div class="step-icon-wrapper mb-4">
<v-icon size="48" color="primary">mdi-check-circle-outline</v-icon>
</div>
<h2 class="step-main-title mb-2">تأیید اطلاعات کالا</h2>
<p class="step-description">
لطفاً اطلاعات زیر را بررسی و در صورت صحت، تأیید کنید
</p>
</div>
<div class="product-info-card mb-6">
<div class="product-info-grid">
<div class="info-item">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-package-variant</v-icon>
</div>
<div class="info-content">
<div class="info-label">نام کالا</div>
<div class="info-value">{{ productInfo.productName }}</div>
</div>
</div>
<div class="info-item">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-qrcode-scan</v-icon>
</div>
<div class="info-content">
<div class="info-label">کد کالا</div>
<div class="info-value">{{ productInfo.productCode }}</div>
</div>
</div>
<div class="info-item">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-calendar</v-icon>
</div>
<div class="info-content">
<div class="info-label">تاریخ ثبت</div>
<div class="info-value">{{ formatDate(productInfo.submitDate) }}</div>
</div>
</div>
<div class="info-item">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-shield-check</v-icon>
</div>
<div class="info-content">
<div class="info-label">وضعیت گارانتی</div>
<div class="info-value">{{ warrantyStatusText }}</div>
</div>
</div>
<div class="info-item info-item-wide">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-barcode</v-icon>
</div>
<div class="info-content">
<div class="info-label">سریال کالا</div>
<div class="info-value">{{ productInfo.commoditySerial || 'بدون سریال' }}</div>
</div>
</div>
<div class="info-item info-item-wide">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-receipt</v-icon>
</div>
<div class="info-content">
<div class="info-label">توضیحات</div>
<div class="info-value">{{ productInfo.description || 'بدون توضیح' }}</div>
</div>
</div>
</div>
</div>
<!-- Activation Secret Input (isolated from info grid to avoid rerenders) -->
<div class="product-info-card mb-6" v-show="productInfo.requireActivationSecret">
<div class="info-item info-item-wide">
<div class="info-icon">
<v-icon size="24" color="primary">mdi-lock-check</v-icon>
</div>
<div class="info-content">
<div class="info-label">کد فعال‌سازی حواله</div>
<v-text-field
v-model.trim="activationSecret"
placeholder="کد 8 کاراکتری فعال‌سازی"
:rules="activationSecretRules"
maxlength="8"
variant="outlined"
density="comfortable"
hide-details="auto"
autocomplete="one-time-code"
spellcheck="false"
autocapitalize="off"
/>
</div>
</div>
</div>
<!-- Time Warning -->
<div v-if="timeRemaining && productInfo.activation === 'deactive'" class="time-warning mb-6">
<div class="warning-content">
<div class="warning-icon">
<v-icon size="28" color="warning">mdi-clock-alert-outline</v-icon>
</div>
<div class="warning-text">
<div class="warning-title">مهلت فعال‌سازی</div>
<div class="warning-subtitle">{{ timeRemaining }} روز باقی مانده</div>
</div>
</div>
</div>
<!-- Status Warning -->
<div v-if="productInfo.activation !== 'deactive' && productInfo.activation !== 'active'" class="status-warning mb-6">
<div class="warning-content">
<div class="warning-icon" :class="{ 'error-icon': productInfo.activation === 'expired' }">
<v-icon size="28" color="error">mdi-alert-circle-outline</v-icon>
</div>
<div class="warning-text">
<div class="warning-title" :class="{ 'error-title': productInfo.activation === 'expired' }">وضعیت گارانتی</div>
<div class="warning-subtitle" :class="{ 'error-subtitle': productInfo.activation === 'expired' }">
این گارانتی {{ warrantyStatusText }} است و قابل فعال‌سازی نمی‌باشد
</div>
</div>
</div>
</div>
<div class="action-buttons">
<v-row no-gutters class="gap-3">
<v-col>
<v-btn
outlined
large
block
@click="currentStep = 1"
class="back-btn"
elevation="0"
>
<v-icon left>mdi-arrow-right</v-icon>
بازگشت
</v-btn>
</v-col>
<v-col>
<v-btn
color="success"
large
block
:loading="loading"
:disabled="productInfo.activation !== 'deactive' || productInfo.status !== 'consumed' || !canActivate"
@click="activateWarranty"
:class="{ 'success-btn': productInfo.activation === 'deactive' || productInfo.activation === 'active', 'error-btn': productInfo.activation === 'expired' }"
elevation="2"
>
<v-icon left>mdi-shield-check</v-icon>
{{ productInfo.activation === 'deactive' ? 'فعال‌سازی گارانتی' : productInfo.activation === 'active' ? 'این گارانتی قبلا فعال شده است' : productInfo.activation === 'expired' ? 'این گارانتی منقضی شده است' : '' }}
</v-btn>
</v-col>
</v-row>
<v-alert
v-if="activationErrors.length"
type="error"
dismissible
class="mt-4 custom-alert"
border="left"
colored-border
>
{{ activationErrors }}
</v-alert>
</div>
</div>
<!-- Step 3: Success -->
<div v-if="currentStep === 3" class="step-content-wrapper">
<div class="step-header text-center mb-6">
<div class="success-icon-wrapper mb-4">
<v-icon size="64" color="success">mdi-check-circle</v-icon>
<div class="success-ring"></div>
</div>
<h2 class="success-title mb-3">گارانتی فعال شد!</h2>
<p class="success-description">
گارانتی کالا شما با موفقیت فعال شد و اطلاعات آن ثبت گردید
</p>
</div>
<div class="success-info-card mb-6">
<div class="success-info-grid">
<div class="success-info-item success-info-main">
<div class="success-info-icon">
<v-icon size="28" color="success">mdi-shield-check</v-icon>
</div>
<div class="success-info-content">
<div class="success-info-label">کد گارانتی</div>
<div class="success-info-value">{{ warrantyCode }}</div>
</div>
</div>
<div class="success-info-item">
<div class="success-info-icon">
<v-icon size="24" color="success">mdi-calendar-check</v-icon>
</div>
<div class="success-info-content">
<div class="success-info-label">تاریخ فعال‌سازی</div>
<div class="success-info-value">{{ activationDate }}</div>
</div>
</div>
<div class="success-info-item">
<div class="success-info-icon">
<v-icon size="24" color="success">mdi-calendar-end</v-icon>
</div>
<div class="success-info-content">
<div class="success-info-label">تاریخ انقضا</div>
<div class="success-info-value">{{ productInfo.warrantyEndDate ? formatDate(productInfo.warrantyEndDate) : 'بدون تاریخ انقضا' }}</div>
</div>
</div>
</div>
</div>
<div class="action-buttons">
<v-row no-gutters class="gap-3">
<!-- <v-col>
<v-btn
outlined
large
block
@click="downloadCertificate"
class="download-btn"
elevation="0"
>
<v-icon left>mdi-download</v-icon>
دانلود گواهی
</v-btn>
</v-col> -->
<v-col>
<v-btn
color="primary"
large
block
@click="resetForm"
class="primary-btn"
elevation="2"
>
<v-icon left>mdi-plus</v-icon>
فعال‌سازی جدید
</v-btn>
</v-col>
</v-row>
</div>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</section>
<!-- QR Scanner Dialog -->
<v-dialog v-model="showQrScanner" max-width="500" persistent>
<v-card class="qr-dialog-card">
<v-card-title class="qr-dialog-title">
<v-icon left color="primary">mdi-qrcode-scan</v-icon>
اسکن کد QR/بارکد
</v-card-title>
<v-card-text>
<div class="text-center">
<!-- Loading State -->
<div v-show="!cameraReady && !scanError" class="qr-scanner-placeholder">
<v-icon size="100" color="primary" class="qr-icon">mdi-qrcode-scan</v-icon>
<p class="mt-4 qr-loading-text">{{ cameraStatus }}</p>
<div class="qr-loading-spinner mt-4">
<v-progress-circular indeterminate color="primary" size="32"></v-progress-circular>
</div>
<p class="text-caption qr-instruction mt-4">
اگر پیام دسترسی به دوربین نمایش داده شد، لطفاً "اجازه دادن" را انتخاب کنید
</p>
</div>
<!-- Error State -->
<div v-show="scanError && !cameraReady" class="qr-scanner-error">
<v-icon size="100" color="error" class="qr-icon">mdi-camera-off</v-icon>
<p class="mt-4 qr-error-text">{{ scanError }}</p>
<v-btn
color="primary"
class="mt-4"
@click="scanQrCode"
:loading="loading"
>
تلاش مجدد
</v-btn>
</div>
<!-- Camera Video - Always in DOM -->
<div class="qr-camera-container" v-show="cameraReady && !scanError">
<video
ref="qrVideo"
class="qr-video"
autoplay
muted
playsinline
></video>
<!-- <div class="qr-overlay">
<div class="qr-scan-area">
<div class="qr-corner qr-corner-tl"></div>
<div class="qr-corner qr-corner-tr"></div>
<div class="qr-corner qr-corner-bl"></div>
<div class="qr-corner qr-corner-br"></div>
<div class="qr-scan-line"></div>
</div>
</div> -->
<p class="qr-scan-instruction">کد QR یا بارکد را در کادر قرار دهید</p>
</div>
<!-- Hidden video element to ensure it's always available -->
<video
v-if="!qrVideo"
ref="qrVideo"
style="display: none;"
autoplay
muted
playsinline
></video>
<!-- Error Message -->
<v-alert
v-if="scanError"
type="error"
dense
class="mt-4"
>
{{ scanError }}
</v-alert>
</div>
</v-card-text>
<v-card-actions>
<v-btn
v-if="cameraReady"
text
color="primary"
@click="toggleFlashlight"
class="qr-flash-btn"
>
<v-icon left>{{ flashlightOn ? 'mdi-flashlight-off' : 'mdi-flashlight' }}</v-icon>
{{ flashlightOn ? 'خاموش کردن فلش' : 'روشن کردن فلش' }}
</v-btn>
<v-spacer></v-spacer>
<v-btn
text
@click="stopScanner"
class="qr-cancel-btn"
>
انصراف
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Modern Footer -->
<footer class="modern-footer">
<div class="footer-content">
<v-container>
<v-row justify="center" align="center">
<v-col cols="12" md="8" class="text-center">
<!-- Footer Links -->
<!-- <div class="footer-links mb-6">
<v-btn
text
class="footer-link-btn mx-2"
@click="showHelpDialog = true"
>
<v-icon left small>mdi-help-circle-outline</v-icon>
راهنما
</v-btn>
<v-btn
text
class="footer-link-btn mx-2"
href="tel:02188888888"
>
<v-icon left small>mdi-phone</v-icon>
پشتیبانی
</v-btn>
<v-btn
text
class="footer-link-btn mx-2"
href="mailto:support@example.com"
>
<v-icon left small>mdi-email-outline</v-icon>
تماس
</v-btn>
</div> -->
<!-- Footer Copyright -->
<div class="footer-copyright">
<div class="copyright-divider mb-4"></div>
<!-- <p class="copyright-text">
© 1404 حسابیکس - تمامی حقوق محفوظ است
</p> -->
<!-- Developer Credit -->
<div class="developer-credit">
<div class="developer-info">
<v-icon size="16" color="white" class="mr-2">mdi-code-tags</v-icon>
<span class="developer-text">توسعه‌دهنده:</span>
<a
href="https://pirouz.xyz"
target="_blank"
class="developer-link"
rel="noopener noreferrer"
>
محمد رضائی
</a>
</div>
<div class="developer-website" style="display: flex; align-items: center; gap: 5px;">
<v-icon size="14" color="rgba(255,255,255,0.6)" class="mr-1">mdi-web</v-icon>
<a
href="https://pirouz.xyz"
target="_blank"
class="website-link"
rel="noopener noreferrer"
>
pirouz.xyz
</a>
</div>
</div>
</div>
</v-col>
</v-row>
</v-container>
</div>
</footer>
</div>
</template>
<script>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import axios from 'axios'
import QrScanner from 'qr-scanner'
export default {
name: 'WarrantyActivation',
setup() {
// Route
const route = useRoute()
// Reactive data
const currentStep = ref(1)
const warrantyCode = ref('')
const loading = ref(false)
const codeFormValid = ref(false)
const errorMessage = ref('')
const activationErrors = ref([])
const businessId = ref(route.params.businessId)
const showHelpDialog = ref(false)
const showQrScanner = ref(false)
// QR Scanner data
const qrVideo = ref(null)
const cameraReady = ref(false)
const cameraStatus = ref('در حال دسترسی به دوربین...')
const scanError = ref('')
const flashlightOn = ref(false)
// QR Scanner instance variables
let qrScanner = null
let currentStream = null
// Product information (will be fetched from backend)
const productInfo = ref({
serialNumber: '',
productName: '',
productCode: '',
description: '',
submitDate: '',
warrantyStartDate: '',
warrantyEndDate: '',
status: '',
notes: '',
activationTimeLimit: 0,
daysRemaining: 0,
submitter: '',
businessName: '',
activationTicketCode: '',
requireActivationSecret: false
})
const activationSecret = ref('')
const activationSecretRules = [
v => !productInfo.value.requireActivationSecret || (!!v && v.length === 8) || 'کد فعال‌سازی 8 کاراکتری الزامی است'
]
const canActivate = computed(() => {
if (productInfo.value.requireActivationSecret) {
return activationSecret.value && activationSecret.value.length === 8
}
return true
})
// Steps data
const steps = ref([
{
title: 'وارد کردن کد',
subtitle: 'کد گارانتی را وارد کنید'
},
{
title: 'تأیید اطلاعات',
subtitle: 'اطلاعات کالا را بررسی کنید'
},
{
title: 'فعال‌سازی',
subtitle: 'گارانتی فعال شد'
}
])
// Form validation rules
const codeRules = [
v => !!v || 'کد گارانتی الزامی است',
// v => (v && v.length >= 8) || 'کد گارانتی باید حداقل 8 کاراکتر باشد',
// v => /^[A-Za-z0-9-]+$/.test(v) || 'کد گارانتی فقط شامل حروف، اعداد و خط تیره باشد'
]
// Computed properties
const timeRemaining = computed(() => {
// Use daysRemaining from backend response
return productInfo.value.daysRemaining || 0
})
// Convert warranty status to Persian
const warrantyStatusText = computed(() => {
const status = productInfo.value.activation
switch (status) {
case 'active':
return 'فعال'
case 'deactive':
return 'غیرفعال'
case 'expired':
return 'منقضی شده'
case 'suspended':
return 'معلق'
default:
return status || 'نامشخص'
}
})
// Convert date to Jalali
const formatDate = (dateString) => {
if (!dateString) return 'نامشخص'
try {
const date = new Date(dateString)
return date.toLocaleDateString('fa-IR')
} catch {
return dateString
}
}
const activationDate = computed(() => {
const today = new Date()
return today.toLocaleDateString('fa-IR')
})
const expirationDate = computed(() => {
// Mock calculation - will be calculated from backend
const today = new Date()
const expiry = new Date(today.getTime() + (18 * 30 * 24 * 60 * 60 * 1000)) // 18 months
return expiry.toLocaleDateString('fa-IR')
})
// Methods
const checkWarrantyCode = async () => {
if (!codeFormValid.value) return
loading.value = true
errorMessage.value = ''
try {
const response = await axios.get(`/api/public/${businessId.value}/warranty/check/${warrantyCode.value}`)
if (response.data.success) {
productInfo.value = response.data.data
currentStep.value = 2
} else {
throw new Error(response.data.message || 'خطا در بررسی کد گارانتی')
}
} catch (error) {
if (error.response && error.response.data && error.response.data.message) {
errorMessage.value = error.response.data.message
} else {
errorMessage.value = 'خطا در بررسی کد گارانتی'
}
} finally {
loading.value = false
}
}
const activateWarranty = async () => {
loading.value = true
try {
const response = await axios.post(`/api/public/${businessId.value}/warranty/activate/${warrantyCode.value}`, {
activationSecret: activationSecret.value
})
if (response.data.success) {
currentStep.value = 3
} else {
const msgs = Array.response.data?.message ? response.data.message : ''
activationErrors.value = msgs ? msgs : response.data.message || 'خطا در فعال‌سازی گارانتی'
throw new Error(response.data.message || 'خطا در فعال‌سازی گارانتی')
}
} catch (error) {
const data = error?.response?.data
const msgs = Array.data?.message ? data.message : ''
activationErrors.value = msgs ? msgs : data?.message || 'خطا در فعال‌سازی گارانتی'
} finally {
loading.value = false
}
}
const scanQrCode = async () => {
showQrScanner.value = true
cameraReady.value = false
cameraStatus.value = 'در حال دسترسی به دوربین...'
scanError.value = ''
try {
// First check if we have camera permissions
try {
await navigator.mediaDevices.getUserMedia({ video: true })
} catch (permissionError) {
throw new Error('لطفاً دسترسی به دوربین را مجاز کنید')
}
// Check if QrScanner is supported
const hasCamera = await QrScanner.hasCamera()
if (!hasCamera) {
throw new Error('دوربین در دسترس نیست یا پشتیبانی نمی‌شود')
}
cameraStatus.value = 'در حال آماده‌سازی دوربین...'
// Wait for DOM to update and video element to be available with retry logic
let retries = 0
const maxRetries = 10
while (!qrVideo.value && retries < maxRetries) {
await nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
retries++
}
if (!qrVideo.value) {
throw new Error('عنصر ویدئو در دسترس نیست - لطفاً صفحه را رفرش کنید')
}
// Initialize QR Scanner with better error handling
qrScanner = new QrScanner(
qrVideo.value,
result => {
warrantyCode.value = result.data
stopScanner()
},
{
onDecodeError: (error) => {
// Silent error - normal when no QR code is visible
},
highlightScanRegion: true,
highlightCodeOutline: true,
maxScansPerSecond: 5,
preferredCamera: 'environment', // Use back camera if available
returnDetailedScanResult: true
}
)
// Start the scanner
await qrScanner.start()
cameraReady.value = true
cameraStatus.value = 'دوربین آماده است - کد QR یا بارکد را در کادر قرار دهید'
} catch (error) {
let errorMsg = 'خطا در راه‌اندازی دوربین'
if (error.name === 'NotAllowedError') {
errorMsg = 'لطفاً دسترسی به دوربین را مجاز کنید'
} else if (error.name === 'NotFoundError') {
errorMsg = 'دوربین پیدا نشد'
} else if (error.name === 'NotSupportedError') {
errorMsg = 'دوربین پشتیبانی نمی‌شود'
} else if (error.message) {
errorMsg = error.message
}
scanError.value = errorMsg
cameraReady.value = false
}
}
const downloadCertificate = async () => {
try {
const response = await axios.get(`/api/public/${businessId.value}/warranty/certificate/${warrantyCode.value}`)
if (response.data.success && response.data.downloadUrl) {
// Open download link in new tab
window.open(response.data.downloadUrl, '_blank')
} else {
alert('خطا در دانلود گواهی گارانتی')
}
} catch (error) {
alert('خطا در دانلود گواهی گارانتی')
}
}
const stopScanner = () => {
try {
if (qrScanner) {
qrScanner.stop()
qrScanner.destroy()
qrScanner = null
}
// Stop all video tracks
const video = qrVideo.value
if (video && video.srcObject) {
const stream = video.srcObject
stream.getTracks().forEach(track => track.stop())
video.srcObject = null
}
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop())
currentStream = null
}
} catch (error) {
} finally {
showQrScanner.value = false
cameraReady.value = false
flashlightOn.value = false
scanError.value = ''
}
}
const toggleFlashlight = async () => {
if (qrScanner && cameraReady.value) {
try {
if (flashlightOn.value) {
await qrScanner.turnFlashlightOff()
flashlightOn.value = false
} else {
await qrScanner.turnFlashlightOn()
flashlightOn.value = true
}
} catch (error) {
scanError.value = 'خطا در کنترل فلش دوربین یا دستگاه از فلش پشتیبانی نمی‌کند'
}
}
}
const resetForm = () => {
stopScanner() // Stop scanner if running
currentStep.value = 1
warrantyCode.value = ''
errorMessage.value = ''
productInfo.value = {
serialNumber: '',
productName: '',
productCode: '',
description: '',
submitDate: '',
warrantyStartDate: '',
warrantyEndDate: '',
status: '',
notes: '',
activationTimeLimit: 0,
daysRemaining: 0,
submitter: '',
businessName: ''
}
}
// Cleanup on component unmount
onUnmounted(() => {
stopScanner()
})
return {
// Reactive data
currentStep,
warrantyCode,
loading,
codeFormValid,
errorMessage,
showHelpDialog,
showQrScanner,
productInfo,
steps,
// QR Scanner data
qrVideo,
cameraReady,
cameraStatus,
scanError,
flashlightOn,
// Form rules
codeRules,
// Computed
timeRemaining,
warrantyStatusText,
formatDate,
activationDate,
expirationDate,
// Methods
checkWarrantyCode,
activateWarranty,
scanQrCode,
stopScanner,
toggleFlashlight,
downloadCertificate,
resetForm,
activationErrors,
activationSecret,
activationSecretRules,
canActivate,
}
}
}
</script>
<style scoped>
/* ===== MAIN CONTAINER ===== */
.warranty-activation {
min-height: 80vh;
background: linear-gradient(135deg, #3b82f6 0%, #1e40af 50%, #1e293b 100%);
font-family: 'IranSans', sans-serif;
position: relative;
}
.warranty-activation::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(30, 41, 59, 0.1) 100%);
pointer-events: none;
}
/* ===== HERO SECTION ===== */
.hero-section {
position: relative;
min-height: 80vh;
display: flex;
align-items: center;
overflow: hidden;
}
.hero-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.05);
}
.hero-pattern {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
background-size: 100px 100px;
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.hero-content {
position: relative;
z-index: 3;
margin-bottom: 50px;
}
.min-height-screen {
min-height: 80vh;
}
/* ===== HERO TEXT ===== */
.hero-icon-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
backdrop-filter: blur(20px);
border: 2px solid rgba(255, 255, 255, 0.25);
animation: pulse 2s infinite;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.4); }
70% { transform: scale(1.05); box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
.hero-title {
font-size: 3.5rem;
font-weight: 700;
color: white;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin: 0;
}
.hero-subtitle {
font-size: 1.25rem;
color: rgba(255, 255, 255, 0.9);
font-weight: 300;
max-width: 500px;
margin: 0 auto;
line-height: 1.6;
}
/* ===== MAIN CARD ===== */
.main-card {
background: rgba(255, 255, 255, 0.9);
border-radius: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
overflow: hidden;
}
.main-card:hover {
transform: translateY(-5px);
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.15);
}
/* ===== MODERN STEPPER ===== */
.modern-stepper {
position: relative;
padding: 0 20px;
}
.stepper-track {
position: absolute;
top: 32px;
left: 50px;
right: 50px;
height: 4px;
background: #f1f5f9;
border-radius: 2px;
z-index: 1;
}
.stepper-progress {
height: 100%;
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
border-radius: 2px;
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.stepper-progress::after {
content: '';
position: absolute;
top: -2px;
right: -4px;
width: 8px;
height: 8px;
background: #059669;
border-radius: 50%;
box-shadow: 0 0 0 2px white, 0 2px 8px rgba(5, 150, 105, 0.3);
}
.stepper-steps {
display: flex;
justify-content: space-between;
position: relative;
z-index: 2;
}
.stepper-step {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
position: relative;
}
.stepper-circle {
width: 64px;
height: 64px;
border-radius: 50%;
background: white;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.stepper-circle-inner {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #f8fafc;
color: #94a3b8;
font-weight: 600;
font-size: 18px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 2px solid #e2e8f0;
}
.stepper-step.active .stepper-circle {
transform: scale(1.1);
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.2);
}
.stepper-step.active .stepper-circle-inner {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
border-color: #3b82f6;
animation: pulse-ring 2s infinite;
}
.stepper-step.completed .stepper-circle {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.2);
}
.stepper-step.completed .stepper-circle-inner {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border-color: #10b981;
}
.stepper-step.pending .stepper-circle-inner {
background: #f8fafc;
color: #cbd5e1;
border-color: #f1f5f9;
}
@keyframes pulse-ring {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
.stepper-content {
max-width: 120px;
}
.stepper-title {
font-weight: 700;
color: #1e293b;
font-size: 15px;
margin-bottom: 6px;
line-height: 1.3;
}
.stepper-step.active .stepper-title {
color: #3b82f6;
}
.stepper-step.completed .stepper-title {
color: #10b981;
}
.stepper-description {
color: #64748b;
font-size: 13px;
line-height: 1.4;
font-weight: 400;
}
.stepper-step.pending .stepper-description {
color: #94a3b8;
}
/* ===== STEP CONTENT ===== */
.step-content-wrapper {
animation: fadeInUp 0.5s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.step-header {
margin-bottom: 2rem;
}
.step-icon-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.2) 100%);
border-radius: 20px;
margin-bottom: 1rem;
border: 1px solid rgba(59, 130, 246, 0.2);
backdrop-filter: blur(10px);
}
.step-main-title {
font-size: 1.875rem;
font-weight: 700;
color: #1e293b;
margin: 0;
}
.step-description {
font-size: 1rem;
color: #64748b;
line-height: 1.6;
margin: 0;
}
/* ===== FORM ELEMENTS ===== */
.input-section {
margin-bottom: 2rem;
}
.input-section >>> .v-field__field {
display: flex;
align-items: center;
}
.custom-input >>> .v-input__control {
min-height: 60px;
}
.custom-input >>> .v-text-field__details {
margin-top: 8px;
}
.custom-input >>> .v-input__slot {
border-radius: 12px !important;
background: #f8fafc;
border: 2px solid #e2e8f0 !important;
transition: all 0.3s ease;
}
.custom-input >>> .v-input__slot:hover {
border-color: #3b82f6 !important;
}
.custom-input >>> .v-text-field--focused .v-input__slot {
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.qr-scan-btn {
background: #f1f5f9 !important;
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
}
.qr-scan-btn:hover {
background: #e2e8f0 !important;
transform: scale(1.05);
}
/* ===== BUTTONS ===== */
.primary-btn {
height: 56px !important;
border-radius: 12px !important;
font-weight: 600 !important;
font-size: 1rem !important;
text-transform: none !important;
letter-spacing: 0 !important;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%) !important;
transition: all 0.3s ease !important;
}
.primary-btn >>> .v-btn__content {
gap: 8px !important;
display: flex !important;
align-items: center !important;
}
.primary-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3) !important;
}
.primary-btn:disabled {
opacity: 0.6 !important;
transform: none !important;
}
.help-btn {
color: #64748b !important;
font-weight: 500 !important;
text-transform: none !important;
transition: all 0.3s ease !important;
}
.help-btn:hover {
color: #3b82f6 !important;
background: rgba(59, 130, 246, 0.05) !important;
}
/* ===== ALERTS ===== */
.custom-alert {
border-radius: 12px;
border-left-width: 4px !important;
}
/* ===== RESPONSIVE ===== */
@media (max-width: 960px) {
.hero-title {
font-size: 2.5rem;
}
.hero-subtitle {
font-size: 1.125rem;
}
.hero-icon-wrapper {
width: 100px;
height: 100px;
}
/* Mobile Stepper */
.stepper-track {
display: none;
}
.stepper-steps {
flex-direction: column;
gap: 24px;
}
.stepper-step {
flex-direction: row;
text-align: right;
gap: 16px;
align-items: center;
}
.stepper-circle {
width: 56px;
height: 56px;
margin-bottom: 0;
flex-shrink: 0;
}
.stepper-circle-inner {
width: 40px;
height: 40px;
font-size: 16px;
}
.stepper-content {
max-width: none;
text-align: right;
}
.stepper-title {
font-size: 16px;
}
.stepper-description {
font-size: 14px;
}
/* Footer Mobile */
.footer-links {
flex-direction: column;
align-items: center;
}
.footer-link-btn {
width: 200px;
justify-content: center;
}
}
@media (max-width: 600px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
.step-main-title {
font-size: 1.5rem;
}
.custom-input >>> .v-input__control {
min-height: 50px;
}
.primary-btn {
height: 48px !important;
}
/* Mobile Step Content */
.product-info-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.info-item {
padding: 12px;
gap: 12px;
}
.info-icon {
width: 40px;
height: 40px;
}
.info-value {
font-size: 14px;
}
.warning-content {
flex-direction: column;
text-align: center;
gap: 12px;
}
.warning-icon {
width: 48px;
height: 48px;
}
.success-icon-wrapper {
width: 100px;
height: 100px;
}
.success-title {
font-size: 1.75rem;
}
.success-description {
font-size: 1rem;
}
.success-info-item {
padding: 12px;
gap: 12px;
}
.success-info-main {
padding: 16px;
}
.success-info-icon {
width: 40px;
height: 40px;
}
.action-buttons .v-btn {
height: 48px !important;
font-size: 0.9rem !important;
}
}
/* ===== ANIMATIONS ===== */
.v-enter-active, .v-leave-active {
transition: all 0.3s ease;
}
.v-enter-from, .v-leave-to {
opacity: 0;
transform: translateY(20px);
}
/* ===== QR SCANNER ===== */
.qr-dialog-card {
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px !important;
}
.qr-dialog-title {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%);
border-bottom: 1px solid rgba(59, 130, 246, 0.1);
font-weight: 600 !important;
color: #1e293b;
}
.qr-scanner-placeholder {
padding: 3rem 2rem;
border: 2px dashed rgba(59, 130, 246, 0.3);
border-radius: 16px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(59, 130, 246, 0.1) 100%);
text-align: center;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.qr-scanner-placeholder:hover {
border-color: rgba(59, 130, 246, 0.5);
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.15) 100%);
}
.qr-scanner-error {
padding: 3rem 2rem;
border: 2px dashed rgba(239, 68, 68, 0.3);
border-radius: 16px;
background: linear-gradient(135deg, rgba(239, 68, 68, 0.05) 0%, rgba(239, 68, 68, 0.1) 100%);
text-align: center;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.qr-error-text {
color: #dc2626;
font-weight: 500;
font-size: 1rem;
}
.qr-loading-spinner {
display: flex;
justify-content: center;
align-items: center;
}
.qr-icon {
opacity: 0.8;
animation: qr-pulse 2s infinite;
}
@keyframes qr-pulse {
0%, 100% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.05); opacity: 1; }
}
.qr-loading-text {
font-weight: 600;
color: #3b82f6;
margin: 0;
}
.qr-instruction {
color: #64748b;
margin: 0;
font-weight: 500;
}
.qr-cancel-btn {
color: #64748b !important;
font-weight: 600 !important;
text-transform: none !important;
transition: all 0.3s ease !important;
}
.qr-cancel-btn:hover {
color: #3b82f6 !important;
background: rgba(59, 130, 246, 0.05) !important;
}
.qr-flash-btn {
color: #64748b !important;
font-weight: 600 !important;
text-transform: none !important;
transition: all 0.3s ease !important;
}
.qr-flash-btn:hover {
color: #f59e0b !important;
background: rgba(245, 158, 11, 0.05) !important;
}
/* ===== QR CAMERA ===== */
.qr-camera-container {
position: relative;
width: 100%;
max-width: 400px;
margin: 0 auto;
border-radius: 16px;
overflow: hidden;
background: #000;
}
.qr-video {
width: 100%;
height: 300px;
object-fit: cover;
border-radius: 16px;
}
.qr-overlay {
position: absolute;
top: -75px;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.qr-scan-area {
position: relative;
width: 200px;
height: 200px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
}
.qr-corner {
position: absolute;
width: 20px;
height: 20px;
border: 3px solid #3b82f6;
}
.qr-corner-tl {
top: -3px;
left: -3px;
border-right: none;
border-bottom: none;
border-top-left-radius: 12px;
}
.qr-corner-tr {
top: -3px;
right: -3px;
border-left: none;
border-bottom: none;
border-top-right-radius: 12px;
}
.qr-corner-bl {
bottom: -3px;
left: -3px;
border-right: none;
border-top: none;
border-bottom-left-radius: 12px;
}
.qr-corner-br {
bottom: -3px;
right: -3px;
border-left: none;
border-top: none;
border-bottom-right-radius: 12px;
}
.qr-scan-line {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent 0%, #3b82f6 50%, transparent 100%);
animation: qr-scan-line 2s infinite;
}
@keyframes qr-scan-line {
0% {
top: 0;
opacity: 1;
}
50% {
top: 50%;
opacity: 0.8;
}
100% {
top: 100%;
opacity: 1;
}
}
.qr-scan-instruction {
color: rgba(255, 255, 255, 0.9);
font-weight: 600;
margin-top: 16px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
font-size: 14px;
}
/* ===== MODERN FOOTER ===== */
.modern-footer {
background: rgba(0, 0, 0, 0.2);
/* margin-top: 80px; */
position: relative;
overflow: hidden;
backdrop-filter: blur(20px);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.modern-footer::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(255, 255, 255, 0.03) 0%, transparent 50%);
background-size: 200px 200px;
}
.footer-content {
position: relative;
z-index: 2;
padding: 0px 60px 0 40px;
}
.footer-brand {
margin-bottom: 2rem;
}
.footer-logo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
background: rgba(59, 130, 246, 0.1);
border-radius: 20px;
margin: 0 auto;
backdrop-filter: blur(10px);
border: 1px solid rgba(59, 130, 246, 0.2);
}
.footer-title {
font-size: 1.75rem;
font-weight: 700;
color: white;
margin: 0 0 8px 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.footer-subtitle {
font-size: 1rem;
color: rgba(255, 255, 255, 0.7);
margin: 0;
line-height: 1.5;
}
.footer-links {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
}
.footer-link-btn {
color: rgba(255, 255, 255, 0.8) !important;
background: rgba(59, 130, 246, 0.3) !important;
font-weight: 500 !important;
text-transform: none !important;
border-radius: 12px !important;
padding: 8px 16px !important;
transition: all 0.3s ease !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
min-height: 40px !important;
}
.footer-link-btn >>> .v-btn__content {
gap: 8px !important;
display: flex !important;
align-items: center !important;
}
.footer-link-btn:hover {
color: white !important;
background: rgba(59, 130, 246, 0.2) !important;
border-color: rgba(59, 130, 246, 0.3) !important;
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.2);
}
.footer-copyright {
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: 24px;
}
.copyright-divider {
width: 60px;
height: 3px;
background: linear-gradient(90deg, #3b82f6 0%, #10b981 100%);
border-radius: 2px;
margin: 0 auto 16px;
}
.copyright-text {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
margin: 0 0 8px 0;
font-weight: 500;
}
.copyright-subtext {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
margin: 0;
font-weight: 400;
}
.developer-credit {
margin-top: 16px;
padding: 16px;
background: transparent;
}
.developer-info {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
flex-wrap: wrap;
gap: 4px;
}
.developer-text {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
margin-left: 6px;
font-weight: 500;
}
.developer-link {
font-size: 14px;
color: #3b82f6 !important;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
padding: 2px 6px;
border-radius: 6px;
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.2);
}
.developer-link:hover {
color: white !important;
background: rgba(59, 130, 246, 0.3);
border-color: rgba(59, 130, 246, 0.4);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.developer-website {
display: flex;
align-items: center;
justify-content: center;
}
.website-link {
font-size: 12px;
color: rgba(255, 255, 255, 0.6) !important;
text-decoration: none;
transition: all 0.3s ease;
font-weight: 400;
}
.website-link:hover {
color: #3b82f6 !important;
transform: translateY(-1px);
}
/* ===== MODERN STEP CONTENT ===== */
/* Product Info Card - Step 2 */
.product-info-card {
background: rgba(255, 255, 255, 0.8);
border-radius: 20px;
padding: 24px;
border: 1px solid rgba(59, 130, 246, 0.1);
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.08);
}
.product-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.info-item {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 16px;
background: rgba(248, 250, 252, 0.8);
border-radius: 16px;
border: 1px solid rgba(226, 232, 240, 0.5);
transition: all 0.3s ease;
}
.info-item:hover {
background: rgba(59, 130, 246, 0.05);
border-color: rgba(59, 130, 246, 0.2);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.1);
}
.info-item-wide {
grid-column: 1 / -1;
}
.info-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.2) 100%);
border-radius: 12px;
border: 1px solid rgba(59, 130, 246, 0.2);
flex-shrink: 0;
}
.info-content {
flex: 1;
}
.info-label {
font-size: 13px;
font-weight: 600;
color: #64748b;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-value {
font-size: 16px;
font-weight: 600;
color: #1e293b;
line-height: 1.4;
}
/* Time Warning */
.time-warning {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(245, 158, 11, 0.2) 100%);
border: 1px solid rgba(245, 158, 11, 0.3);
border-radius: 16px;
padding: 20px;
backdrop-filter: blur(10px);
}
/* Status Warning */
.status-warning {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.2) 100%);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 16px;
padding: 20px;
backdrop-filter: blur(10px);
}
.warning-content {
display: flex;
align-items: center;
gap: 16px;
}
.warning-icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
background: rgba(245, 158, 11, 0.1);
border-radius: 12px;
border: 1px solid rgba(245, 158, 11, 0.2);
flex-shrink: 0;
}
.error-icon {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.2);
}
.error-subtitle {
color: #b91c1c !important;
}
.error-title {
color: #b91c1c !important;
}
.warning-text {
flex: 1;
}
.warning-title {
font-size: 16px;
font-weight: 700;
color: #92400e;
margin-bottom: 4px;
}
.warning-subtitle {
font-size: 14px;
color: #a16207;
font-weight: 500;
}
/* Success Step */
.success-icon-wrapper {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(16, 185, 129, 0.2) 100%);
border-radius: 50%;
border: 2px solid rgba(16, 185, 129, 0.3);
backdrop-filter: blur(10px);
animation: success-pulse 2s infinite;
}
.success-ring {
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
border: 2px solid rgba(16, 185, 129, 0.2);
border-radius: 50%;
animation: success-ring 2s infinite;
}
@keyframes success-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes success-ring {
0% { transform: scale(1); opacity: 1; }
100% { transform: scale(1.2); opacity: 0; }
}
.success-title {
font-size: 2.25rem;
font-weight: 700;
color: #10b981;
margin: 0;
text-shadow: 0 2px 4px rgba(16, 185, 129, 0.1);
}
.success-description {
font-size: 1.125rem;
color: #64748b;
line-height: 1.6;
margin: 0;
max-width: 400px;
margin: 0 auto;
}
/* Success Info Card */
.success-info-card {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(16, 185, 129, 0.1) 100%);
border-radius: 20px;
padding: 24px;
border: 1px solid rgba(16, 185, 129, 0.2);
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(16, 185, 129, 0.1);
}
.success-info-grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.success-info-item {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: rgba(255, 255, 255, 0.8);
border-radius: 16px;
border: 1px solid rgba(16, 185, 129, 0.2);
transition: all 0.3s ease;
}
.success-info-main {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(16, 185, 129, 0.15) 100%);
border: 2px solid rgba(16, 185, 129, 0.3);
padding: 20px;
}
.success-info-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: rgba(16, 185, 129, 0.1);
border-radius: 12px;
border: 1px solid rgba(16, 185, 129, 0.2);
flex-shrink: 0;
}
.success-info-content {
flex: 1;
}
.success-info-label {
font-size: 13px;
font-weight: 600;
color: #059669;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.success-info-value {
font-size: 16px;
font-weight: 700;
color: #064e3b;
line-height: 1.4;
}
.success-info-main .success-info-value {
font-size: 18px;
font-weight: 800;
color: #10b981;
}
/* Action Buttons */
.back-btn {
height: 56px !important;
border-radius: 12px !important;
font-weight: 600 !important;
font-size: 1rem !important;
text-transform: none !important;
letter-spacing: 0 !important;
border: 2px solid rgba(100, 116, 139, 0.3) !important;
color: #64748b !important;
transition: all 0.3s ease !important;
}
.back-btn >>> .v-btn__content {
gap: 8px !important;
display: flex !important;
align-items: center !important;
}
.back-btn:hover {
border-color: #3b82f6 !important;
color: #3b82f6 !important;
background: rgba(59, 130, 246, 0.05) !important;
transform: translateY(-2px);
}
.success-btn {
height: 56px !important;
border-radius: 12px !important;
font-weight: 600 !important;
font-size: 1rem !important;
text-transform: none !important;
letter-spacing: 0 !important;
background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
transition: all 0.3s ease !important;
}
.success-btn >>> .v-btn__content {
gap: 8px !important;
display: flex !important;
align-items: center !important;
}
.success-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(16, 185, 129, 0.3) !important;
}
.error-btn {
height: 56px !important;
border-radius: 12px !important;
font-weight: 600 !important;
font-size: 1rem !important;
text-transform: none !important;
letter-spacing: 0 !important;
background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%) !important;
transition: all 0.3s ease !important;
}
.error-btn >>> .v-btn__content {
gap: 8px !important;
display: flex !important;
align-items: center !important;
}
.error-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(239, 68, 68, 0.3) !important;
}
.download-btn {
height: 56px !important;
border-radius: 12px !important;
font-weight: 600 !important;
font-size: 1rem !important;
text-transform: none !important;
letter-spacing: 0 !important;
border: 2px solid rgba(16, 185, 129, 0.3) !important;
color: #10b981 !important;
transition: all 0.3s ease !important;
}
.download-btn:hover {
border-color: #10b981 !important;
background: rgba(16, 185, 129, 0.05) !important;
transform: translateY(-2px);
}
.v-btn__content {
gap: 8px !important;
}
</style>