2026-01-07 09:31:02 +00:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Button } from '../../ui/button';
|
|
|
|
|
import { Input } from '../../ui/input';
|
2026-01-13 18:47:57 +00:00
|
|
|
import {
|
|
|
|
|
Smartphone,
|
|
|
|
|
QrCode,
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
Copy,
|
|
|
|
|
Download,
|
|
|
|
|
AlertTriangle,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
} from 'lucide-react';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { useToast } from '../../../context/ToastContext';
|
|
|
|
|
|
|
|
|
|
interface TwoFactorSetupProps {
|
|
|
|
|
onBack: () => void;
|
|
|
|
|
onComplete: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export const TwoFactorSetup: React.FC<TwoFactorSetupProps> = ({
|
|
|
|
|
onBack,
|
|
|
|
|
onComplete,
|
|
|
|
|
}) => {
|
2026-01-07 09:31:02 +00:00
|
|
|
const { addToast } = useToast();
|
|
|
|
|
const [step, setStep] = useState(1);
|
|
|
|
|
const [method, setMethod] = useState<'totp' | 'sms'>('totp');
|
|
|
|
|
const [verificationCode, setVerificationCode] = useState('');
|
2026-01-13 18:47:57 +00:00
|
|
|
const [backupCodes] = useState(
|
|
|
|
|
Array.from({ length: 10 }, () =>
|
|
|
|
|
Math.random().toString(36).substr(2, 8).toUpperCase(),
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
const handleVerify = () => {
|
|
|
|
|
if (verificationCode.length < 6) {
|
|
|
|
|
addToast('Invalid code', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setStep(3);
|
|
|
|
|
addToast('2FA Verified Successfully', 'success');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const copyCodes = () => {
|
|
|
|
|
navigator.clipboard.writeText(backupCodes.join('\n'));
|
|
|
|
|
addToast('Backup codes copied to clipboard');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const downloadCodes = () => {
|
2026-01-13 18:47:57 +00:00
|
|
|
const element = document.createElement('a');
|
2026-01-07 18:39:21 +00:00
|
|
|
const file = new Blob([backupCodes.join('\n')], { type: 'text/plain' });
|
2026-01-07 09:31:02 +00:00
|
|
|
element.href = URL.createObjectURL(file);
|
2026-01-13 18:47:57 +00:00
|
|
|
element.download = 'veza-backup-codes.txt';
|
2026-01-07 09:31:02 +00:00
|
|
|
document.body.appendChild(element);
|
|
|
|
|
element.click();
|
|
|
|
|
addToast('Backup codes downloaded');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="animate-fadeIn max-w-2xl mx-auto">
|
|
|
|
|
<div className="mb-6 flex items-center gap-4">
|
2026-01-13 18:47:57 +00:00
|
|
|
<button
|
|
|
|
|
onClick={onBack}
|
2026-01-16 00:56:27 +00:00
|
|
|
className="p-2 hover:bg-white/5 rounded-full text-kodo-content-dim hover:text-white transition-colors"
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
<ArrowLeft className="w-5 h-5" />
|
|
|
|
|
</button>
|
|
|
|
|
<div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<h2 className="text-2xl font-bold text-white">
|
|
|
|
|
Enable Two-Factor Authentication
|
|
|
|
|
</h2>
|
2026-01-16 00:56:27 +00:00
|
|
|
<p className="text-kodo-content-dim text-sm">
|
2026-01-13 18:47:57 +00:00
|
|
|
Protect your account with an extra layer of security.
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* STEP 1: CHOOSE METHOD */}
|
|
|
|
|
{step === 1 && (
|
|
|
|
|
<div className="grid gap-4">
|
2026-01-07 18:39:21 +00:00
|
|
|
<div
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
setMethod('totp');
|
|
|
|
|
setStep(2);
|
|
|
|
|
}}
|
2026-01-07 09:31:02 +00:00
|
|
|
className="p-6 border border-kodo-steel rounded-xl bg-kodo-ink hover:bg-white/5 cursor-pointer transition-all hover:border-kodo-cyan group"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-4 mb-2">
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-kodo-cyan/10 flex items-center justify-center group-hover:bg-kodo-cyan/20">
|
|
|
|
|
<QrCode className="w-6 h-6 text-kodo-cyan" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<h3 className="text-lg font-bold text-white group-hover:text-kodo-cyan">
|
|
|
|
|
Authenticator App
|
|
|
|
|
</h3>
|
2026-01-16 00:56:27 +00:00
|
|
|
<p className="text-sm text-kodo-content-dim">
|
2026-01-13 18:47:57 +00:00
|
|
|
Use Google Authenticator, Authy, or 1Password. (Recommended)
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-07 18:39:21 +00:00
|
|
|
<div
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
setMethod('sms');
|
|
|
|
|
setStep(2);
|
|
|
|
|
}}
|
2026-01-07 09:31:02 +00:00
|
|
|
className="p-6 border border-kodo-steel rounded-xl bg-kodo-ink hover:bg-white/5 cursor-pointer transition-all hover:border-kodo-gold group"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-4 mb-2">
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-kodo-gold/10 flex items-center justify-center group-hover:bg-kodo-gold/20">
|
|
|
|
|
<Smartphone className="w-6 h-6 text-kodo-gold" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<h3 className="text-lg font-bold text-white group-hover:text-kodo-gold">
|
|
|
|
|
SMS / Text Message
|
|
|
|
|
</h3>
|
2026-01-16 00:56:27 +00:00
|
|
|
<p className="text-sm text-kodo-content-dim">
|
2026-01-13 18:47:57 +00:00
|
|
|
Receive a code via text message to your phone.
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* STEP 2: CONFIGURE & VERIFY */}
|
|
|
|
|
{step === 2 && method === 'totp' && (
|
|
|
|
|
<div className="space-y-8 bg-kodo-ink p-8 rounded-xl border border-kodo-steel">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<div className="bg-white p-4 inline-block rounded-xl mb-4">
|
|
|
|
|
{/* Mock QR */}
|
2026-01-16 00:56:27 +00:00
|
|
|
<div className="w-48 h-48 bg-kodo-ink flex items-center justify-center relative overflow-hidden">
|
2026-01-07 18:39:21 +00:00
|
|
|
<QrCode className="w-full h-full text-black opacity-20 absolute" />
|
2026-01-13 18:47:57 +00:00
|
|
|
<span className="relative font-bold text-black text-xs">
|
|
|
|
|
MOCK QR CODE
|
|
|
|
|
</span>
|
2026-01-07 18:39:21 +00:00
|
|
|
<div className="absolute inset-0 border-4 border-black/10"></div>
|
|
|
|
|
{/* Decorative pixel pattern simulated */}
|
|
|
|
|
<div className="absolute top-2 left-2 w-10 h-10 bg-black"></div>
|
|
|
|
|
<div className="absolute top-2 right-2 w-10 h-10 bg-black"></div>
|
|
|
|
|
<div className="absolute bottom-2 left-2 w-10 h-10 bg-black"></div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-16 00:56:27 +00:00
|
|
|
<p className="text-sm text-kodo-text-main mb-2">
|
2026-01-13 18:47:57 +00:00
|
|
|
Scan this QR code with your authenticator app.
|
|
|
|
|
</p>
|
|
|
|
|
<p
|
2026-01-16 00:56:27 +00:00
|
|
|
className="text-xs text-kodo-content-dim font-mono bg-black/30 py-1 px-2 rounded inline-block cursor-pointer hover:text-white"
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
navigator.clipboard.writeText('VEZA-SECRET-KEY-123');
|
|
|
|
|
addToast('Key copied');
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
KEY: VEZA-SECRET-KEY-123
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="border-t border-kodo-steel pt-6">
|
|
|
|
|
<h4 className="font-bold text-white mb-4">Verify Configuration</h4>
|
|
|
|
|
<div className="flex gap-3">
|
2026-01-07 18:39:21 +00:00
|
|
|
<Input
|
|
|
|
|
placeholder="Enter 6-digit code"
|
|
|
|
|
value={verificationCode}
|
2026-01-13 18:47:57 +00:00
|
|
|
onChange={(e) =>
|
|
|
|
|
setVerificationCode(
|
|
|
|
|
e.target.value.replace(/\D/g, '').slice(0, 6),
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
className="font-mono text-center tracking-widest text-lg"
|
|
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={handleVerify}
|
|
|
|
|
disabled={verificationCode.length !== 6}
|
|
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
VERIFY
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{step === 2 && method === 'sms' && (
|
|
|
|
|
<div className="bg-kodo-ink p-8 rounded-xl border border-kodo-steel text-center">
|
2026-01-07 18:39:21 +00:00
|
|
|
<Smartphone className="w-16 h-16 text-kodo-gold mx-auto mb-4" />
|
|
|
|
|
<h3 className="text-xl font-bold text-white mb-2">SMS Setup</h3>
|
2026-01-16 00:56:27 +00:00
|
|
|
<p className="text-kodo-content-dim mb-6">
|
2026-01-13 18:47:57 +00:00
|
|
|
Enter your phone number to receive a verification code.
|
|
|
|
|
</p>
|
2026-01-07 18:39:21 +00:00
|
|
|
<div className="flex gap-2 max-w-sm mx-auto">
|
|
|
|
|
<Input placeholder="+1 (555) 000-0000" />
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={() => addToast('Code sent to your phone', 'info')}
|
|
|
|
|
>
|
|
|
|
|
SEND
|
|
|
|
|
</Button>
|
2026-01-07 18:39:21 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-8 border-t border-kodo-steel pt-6 text-left">
|
2026-01-13 18:47:57 +00:00
|
|
|
<h4 className="font-bold text-white mb-4">
|
|
|
|
|
Enter Verification Code
|
|
|
|
|
</h4>
|
2026-01-07 18:39:21 +00:00
|
|
|
<div className="flex gap-3">
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="000000"
|
|
|
|
|
value={verificationCode}
|
2026-01-13 18:47:57 +00:00
|
|
|
onChange={(e) =>
|
|
|
|
|
setVerificationCode(
|
|
|
|
|
e.target.value.replace(/\D/g, '').slice(0, 6),
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-01-07 18:39:21 +00:00
|
|
|
className="font-mono text-center tracking-widest text-lg"
|
|
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={handleVerify}
|
|
|
|
|
disabled={verificationCode.length !== 6}
|
|
|
|
|
>
|
2026-01-07 18:39:21 +00:00
|
|
|
VERIFY
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* STEP 3: BACKUP CODES */}
|
|
|
|
|
{step === 3 && (
|
|
|
|
|
<div className="space-y-6 bg-kodo-ink p-8 rounded-xl border border-kodo-steel">
|
|
|
|
|
<div className="flex items-center gap-4 text-kodo-lime mb-2">
|
|
|
|
|
<CheckCircle className="w-8 h-8" />
|
|
|
|
|
<h3 className="text-xl font-bold">2FA Enabled Successfully</h3>
|
|
|
|
|
</div>
|
2026-01-07 18:39:21 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
<div className="bg-kodo-orange/10 border border-kodo-orange/30 p-4 rounded-lg flex gap-3">
|
|
|
|
|
<AlertTriangle className="w-6 h-6 text-kodo-orange flex-shrink-0" />
|
|
|
|
|
<div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<h4 className="font-bold text-kodo-orange text-sm mb-1">
|
|
|
|
|
Save these backup codes
|
|
|
|
|
</h4>
|
2026-01-16 00:56:27 +00:00
|
|
|
<p className="text-xs text-kodo-text-main">
|
2026-01-13 18:47:57 +00:00
|
|
|
If you lose your device, these codes are the only way to access
|
|
|
|
|
your account. Keep them safe.
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-16 00:56:27 +00:00
|
|
|
<div className="grid grid-cols-2 gap-3 bg-black/40 p-4 rounded-lg font-mono text-sm text-kodo-text-main text-center border border-kodo-steel/50">
|
2026-01-13 18:47:57 +00:00
|
|
|
{backupCodes.map((code) => (
|
|
|
|
|
<div key={code} className="py-1 tracking-wider">
|
|
|
|
|
{code}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-3 pt-2">
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className="flex-1"
|
|
|
|
|
icon={<Copy className="w-4 h-4" />}
|
|
|
|
|
onClick={copyCodes}
|
|
|
|
|
>
|
|
|
|
|
Copy All
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className="flex-1"
|
|
|
|
|
icon={<Download className="w-4 h-4" />}
|
|
|
|
|
onClick={downloadCodes}
|
|
|
|
|
>
|
|
|
|
|
Download
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="primary" className="flex-1" onClick={onComplete}>
|
|
|
|
|
Done
|
|
|
|
|
</Button>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
};
|