import { t, i18n } from '../i18n.js' import { verificationService } from '../services/verification.js' class VerificationWidget extends HTMLElement { static get observedAttributes() { return ['listing-id', 'verified', 'verified-date'] } constructor() { super() this.state = 'idle' this.currentCode = null this.timerInterval = null this.i18nUnsubscribe = null } connectedCallback() { const verified = this.getAttribute('verified') if (verified === 'true') { this.state = 'verified' } this.render() this.i18nUnsubscribe = i18n.subscribe(() => this.render()) } disconnectedCallback() { this.clearTimer() if (this.i18nUnsubscribe) this.i18nUnsubscribe() } attributeChangedCallback() { if (this.isConnected) { const verified = this.getAttribute('verified') if (verified === 'true') { this.state = 'verified' } this.render() } } clearTimer() { if (this.timerInterval) { clearInterval(this.timerInterval) this.timerInterval = null } } startVerification() { this.currentCode = verificationService.generateCode() this.state = 'active' this.render() this.startCountdown() } startCountdown() { this.clearTimer() this.timerInterval = setInterval(() => { if (!this.currentCode) { this.clearTimer() return } const remaining = this.getRemainingTime() if (remaining <= 0) { this.clearTimer() this.state = 'expired' this.render() this.setupEventListeners() return } const timerEl = this.querySelector('.verification-timer') if (timerEl) { timerEl.textContent = this.formatTime(remaining) if (remaining <= 60000) { timerEl.classList.add('verification-timer--warning') } } }, 1000) } getRemainingTime() { if (!this.currentCode) return 0 const expiresAt = new Date(this.currentCode.expiresAt).getTime() return Math.max(0, expiresAt - Date.now()) } formatTime(ms) { const totalSeconds = Math.floor(ms / 1000) const minutes = Math.floor(totalSeconds / 60) const seconds = totalSeconds % 60 return `${minutes}:${seconds.toString().padStart(2, '0')}` } async handleUpload(e) { const file = e.target.files?.[0] if (!file) return const listingId = this.getAttribute('listing-id') if (!listingId || !this.currentCode) return this.state = 'uploading' this.render() try { const success = await verificationService.verify(listingId, this.currentCode.code, file) if (success) { this.clearTimer() this.state = 'verified' this.render() this.dispatchEvent(new CustomEvent('verification-complete', { bubbles: true, detail: { listingId } })) } else { this.state = 'active' this.render() } } catch (err) { console.error('Verification failed:', err) this.state = 'active' this.render() } } render() { if (this.state === 'verified') { this.innerHTML = this.renderVerified() } else if (this.state === 'active' || this.state === 'uploading') { this.innerHTML = this.renderActive() } else if (this.state === 'expired') { this.innerHTML = this.renderExpired() } else { this.innerHTML = this.renderIdle() } this.setupEventListeners() } renderIdle() { return `
` } renderActive() { const remaining = this.getRemainingTime() const isUploading = this.state === 'uploading' return `
${this.currentCode.code}
${this.formatTime(remaining)}

${t('verification.instructions')}

` } renderExpired() { return `

${t('verification.expired')}

` } renderVerified() { const verifiedDate = this.getAttribute('verified-date') const dateStr = verifiedDate ? t('verification.verifiedDate', { date: new Date(verifiedDate).toLocaleDateString() }) : '' return `
${t('verification.verified')}
${dateStr ? `${dateStr}` : ''}
` } setupEventListeners() { const toggleBtn = this.querySelector('.verification-toggle') if (toggleBtn) { toggleBtn.addEventListener('click', () => this.startVerification()) } const regenerateBtn = this.querySelector('.verification-regenerate') if (regenerateBtn) { regenerateBtn.addEventListener('click', () => this.startVerification()) } const fileInput = this.querySelector('input[type="file"]') if (fileInput) { fileInput.addEventListener('change', (e) => this.handleUpload(e)) } } } customElements.define('verification-widget', VerificationWidget) const style = document.createElement('style') style.textContent = ` .verification-widget { padding: var(--space-md); border-radius: var(--radius-md); } .verification-toggle { display: flex; flex-direction: column; align-items: center; gap: var(--space-xs); width: 100%; padding: var(--space-md) var(--space-lg); background: var(--color-bg-secondary); border: 1px solid var(--color-border); border-radius: var(--radius-md); cursor: pointer; transition: var(--transition-fast); } .verification-toggle:hover { border-color: var(--color-primary); } .verification-toggle-label { font-size: var(--font-size-base); font-weight: 500; color: var(--color-text); } .verification-toggle-subtitle { font-size: var(--font-size-xs); color: var(--color-text-muted); } .verification-widget--active { display: flex; flex-direction: column; align-items: center; gap: var(--space-md); border: 1px solid var(--color-border); background: var(--color-bg-secondary); } .verification-code { font-family: monospace; font-size: var(--font-size-3xl); font-weight: 700; letter-spacing: 0.5em; color: var(--color-text); text-align: center; padding: var(--space-md) var(--space-lg); background: var(--color-bg); border-radius: var(--radius-md); user-select: all; } .verification-timer { font-size: var(--font-size-lg); color: var(--color-text-muted); font-variant-numeric: tabular-nums; } .verification-timer--warning { color: var(--color-warning); font-weight: 600; } .verification-instructions { font-size: var(--font-size-sm); color: var(--color-text-muted); text-align: center; margin: 0; } .verification-upload { position: relative; cursor: pointer; } .verification-upload--loading { pointer-events: none; opacity: 0.7; } .verification-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--color-border); border-top-color: var(--color-primary); border-radius: var(--radius-full); animation: verification-spin 0.8s linear infinite; } @keyframes verification-spin { to { transform: rotate(360deg); } } .verification-widget--expired { display: flex; flex-direction: column; align-items: center; gap: var(--space-md); border: 1px solid var(--color-border); background: var(--color-bg-secondary); } .verification-expired-text { font-size: var(--font-size-sm); color: var(--color-error); margin: 0; } .verification-widget--verified { display: flex; flex-direction: column; align-items: center; gap: var(--space-xs); } .verification-success { display: flex; align-items: center; gap: var(--space-sm); color: var(--color-success); font-weight: 600; font-size: var(--font-size-base); } .verification-success-icon { width: 20px; height: 20px; } .verification-date { font-size: var(--font-size-xs); color: var(--color-text-muted); } ` document.head.appendChild(style) export { VerificationWidget }