File size: 3,829 Bytes
945e815
 
5f5514e
945e815
5f5514e
945e815
 
 
5f5514e
 
945e815
5f5514e
945e815
5f5514e
 
383d246
 
5f5514e
945e815
 
 
5f5514e
945e815
5f5514e
383d246
945e815
 
383d246
 
5f5514e
 
 
383d246
945e815
 
383d246
 
945e815
5f5514e
 
945e815
5f5514e
 
 
945e815
5f5514e
 
945e815
 
383d246
5f5514e
 
 
383d246
 
 
5f5514e
 
 
 
 
 
383d246
945e815
5f5514e
 
945e815
 
5f5514e
945e815
5f5514e
 
 
945e815
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import React, { useState, useRef, useEffect } from 'react';

interface Props { onUnlock: (pk: string) => void; }

export default function LockScreen({ onUnlock }: Props) {
  const [pin, setPin] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);
  const [bio, setBio] = useState(false);
  const ref = useRef<HTMLInputElement>(null);

  useEffect(() => { ref.current?.focus(); if (window.solvox) window.solvox.auth.biometricAvailable().then(b => { setBio(b); if (b) doBio(); }); }, []);

  const doBio = async () => {
    if (!window.solvox) return; setLoading(true);
    const r = await window.solvox.auth.biometric('Unlock SolVox');
    if (r.success) { const pk = await window.solvox.wallet.getPublicKey(); if (pk) onUnlock(pk); }
    else setError(r.error || 'Failed');
    setLoading(false);
  };

  const submit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (pin.length < 6) return setError('PIN must be at least 6 digits');
    setLoading(true); setError('');
    try {
      if (window.solvox) {
        const r = await window.solvox.auth.unlock(pin);
        if (r.success) { const pk = await window.solvox.wallet.getPublicKey(); if (pk) onUnlock(pk); }
        else setError(r.remainingAttempts !== undefined ? `${r.error} (${r.remainingAttempts} left)` : r.error || 'Failed');
      } else onUnlock('Dev' + Date.now());
    } catch (e: any) { setError(e.message); }
    setLoading(false); setPin('');
  };

  const dots = Array.from({ length: 8 }, (_, i) => i < pin.length);

  return (
    <div className="h-screen flex flex-col items-center justify-center bg-canvas">
      <div className="w-full max-w-xs">
        {/* Logo */}
        <div className="text-center mb-12">
          <div className="w-12 h-12 mx-auto rounded-full bg-primary flex items-center justify-center mb-4">
            <span className="text-on-primary font-bold text-title-md">SV</span>
          </div>
          <h1 className="display-text text-display-sm text-ink mb-1">SolVox</h1>
          <p className="text-body-sm text-muted">Voice-First AI Wallet</p>
        </div>

        {/* PIN Dots */}
        <div className="flex justify-center gap-3 mb-6">
          {dots.map((f, i) => (
            <div key={i} className={`w-3 h-3 rounded-full transition-all duration-200 ${f ? 'bg-primary' : 'bg-surface-strong'}`} />
          ))}
        </div>

        <form onSubmit={submit}>
          <input ref={ref} type="password" inputMode="numeric" value={pin} onChange={e => setPin(e.target.value.replace(/\D/g, ''))}
            maxLength={8} className="sr-only" disabled={loading} />
          <div onClick={() => ref.current?.focus()} className="bg-surface-soft rounded-xl p-4 text-center cursor-text mb-4 border border-hairline hover:border-primary/30 transition-colors">
            <div className="text-title-lg font-mono text-ink h-8 flex items-center justify-center tracking-[0.5em]">
              {pin ? '●'.repeat(pin.length) : <span className="text-body-sm text-muted tracking-normal">Tap to enter PIN</span>}
            </div>
          </div>
          {error && <div className="text-body-sm text-semantic-down text-center mb-4 bg-semantic-down/5 rounded-md p-2">{error}</div>}
          <button type="submit" disabled={loading || pin.length < 6} className="btn-primary w-full disabled:opacity-40">{loading ? 'Unlocking…' : 'Unlock'}</button>
        </form>

        {bio && <button onClick={doBio} className="btn-secondary w-full mt-3">Use Touch ID</button>}

        <div className="mt-8 text-center text-caption text-muted-soft flex items-center justify-center gap-1.5">
          <div className="w-1.5 h-1.5 rounded-full bg-semantic-up" />
          Encrypted locally · No cloud · QVAC on-device
        </div>
      </div>
    </div>
  );
}