@@ -43,29 +43,35 @@ const Login = () => {
4343 }
4444 } ;
4545
46- const handleFaceID = ( ) => {
46+ const handleFaceID = async ( ) => {
4747 if ( ! userBiometrics [ selectedUser . id ] ) {
48- setStatus ( 'failed' ) ;
49- setTimeout ( ( ) => setStatus ( 'idle' ) , 2000 ) ;
48+ alert ( "Please enroll Face ID in Settings first." ) ;
5049 return ;
5150 }
5251
53- setStatus ( 'scanning' ) ;
54- // Simulate Face ID scanning process with a chance of failure (e.g. 1 in 10)
55- setTimeout ( ( ) => {
56- const isSuccess = Math . random ( ) > 0.1 ;
57- if ( isSuccess ) {
58- setStatus ( 'success' ) ;
59- setTimeout ( ( ) => {
60- handleLoginSuccess ( ) ;
61- } , 1000 ) ;
62- } else {
63- setStatus ( 'failed' ) ;
64- setTimeout ( ( ) => {
65- setStatus ( 'idle' ) ;
66- } , 1500 ) ;
52+ try {
53+ const challenge = new Uint8Array ( 32 ) ;
54+ window . crypto . getRandomValues ( challenge ) ;
55+
56+ const options = {
57+ publicKey : {
58+ challenge,
59+ rpId : window . location . hostname ,
60+ userVerification : "required" ,
61+ timeout : 60000
62+ }
63+ } ;
64+
65+ // --- REAL HARDWARE PROMPT ---
66+ const credential = await navigator . credentials . get ( options ) ;
67+ if ( credential ) {
68+ handleLoginSuccess ( ) ;
6769 }
68- } , 2000 ) ;
70+ } catch ( err ) {
71+ console . error ( "Hardware Authentication Failed:" , err ) ;
72+ setError ( true ) ;
73+ setTimeout ( ( ) => setError ( false ) , 2000 ) ;
74+ }
6975 } ;
7076
7177 const handleLoginSuccess = ( ) => {
@@ -217,90 +223,7 @@ const Login = () => {
217223 ) }
218224 </ AnimatePresence >
219225
220- { /* Face ID Overlay */ }
221- < AnimatePresence >
222- { ( status === 'scanning' || status === 'success' || status === 'failed' ) && (
223- < motion . div
224- initial = { { opacity : 0 } }
225- animate = { { opacity : 1 } }
226- exit = { { opacity : 0 } }
227- style = { {
228- position : 'fixed' , inset : 0 , zIndex : 1000 ,
229- background : 'rgba(0,0,0,0.85)' , backdropFilter : 'blur(20px)' ,
230- display : 'flex' , flexDirection : 'column' , alignItems : 'center' , justifyContent : 'center'
231- } }
232- >
233- < div style = { { position : 'relative' , width : '160px' , height : '160px' } } >
234- < motion . div
235- animate = { {
236- scale : status === 'success' ? [ 1 , 1.1 , 1 ] : 1 ,
237- x : status === 'failed' ? [ 0 , - 10 , 10 , - 10 , 10 , 0 ] : 0
238- } }
239- style = { {
240- width : '160px' , height : '160px' , borderRadius : '40px' ,
241- border : `2px solid ${ status === 'success' ? '#34C759' : status === 'failed' ? '#FF3B30' : 'rgba(255,255,255,0.2)' } ` ,
242- display : 'flex' , alignItems : 'center' , justifyContent : 'center' ,
243- position : 'relative' , overflow : 'hidden'
244- } }
245- >
246- { status === 'scanning' ? (
247- < >
248- < ScanFace size = { 80 } color = "white" />
249- < motion . div
250- animate = { { top : [ '0%' , '100%' , '0%' ] } }
251- transition = { { duration : 2 , repeat : Infinity , ease : "linear" } }
252- style = { {
253- position : 'absolute' , left : 0 , right : 0 , height : '2px' ,
254- background : 'linear-gradient(90deg, transparent, var(--accent-primary), transparent)' ,
255- boxShadow : '0 0 15px var(--accent-primary)' ,
256- zIndex : 2
257- } }
258- />
259- </ >
260- ) : status === 'success' ? (
261- < motion . div
262- initial = { { scale : 0 } }
263- animate = { { scale : 1 } }
264- transition = { { type : 'spring' , damping : 12 } }
265- >
266- < CheckCircle2 size = { 80 } color = "#34C759" />
267- </ motion . div >
268- ) : (
269- < div style = { { textAlign : 'center' , color : '#FF3B30' } } >
270- < ShieldCheck size = { 80 } />
271- </ div >
272- ) }
273- </ motion . div >
274-
275- { /* Scanning corners */ }
276- { [ 0 , 1 , 2 , 3 ] . map ( i => (
277- < div key = { i } style = { {
278- position : 'absolute' , width : '20px' , height : '20px' ,
279- borderTop : `3px solid ${ status === 'failed' ? '#FF3B30' : 'white' } ` ,
280- borderLeft : `3px solid ${ status === 'failed' ? '#FF3B30' : 'white' } ` ,
281- top : i < 2 ? - 10 : 'auto' , bottom : i >= 2 ? - 10 : 'auto' ,
282- left : i % 2 === 0 ? - 10 : 'auto' , right : i % 2 !== 0 ? - 10 : 'auto' ,
283- transform : `rotate(${ i * 90 } deg)` ,
284- opacity : status === 'success' ? 0 : 0.5 ,
285- transition : 'all 0.3s'
286- } } />
287- ) ) }
288- </ div >
289-
290- < motion . p
291- style = { { color : status === 'failed' ? '#FF3B30' : 'white' , marginTop : '40px' , fontSize : '18px' , fontWeight : '700' , letterSpacing : '0.5px' } }
292- >
293- { status === 'scanning' ? 'Scanning Face...' :
294- status === 'success' ? 'Identity Verified' :
295- ! userBiometrics [ selectedUser . id ] ? 'Biometrics Not Enrolled' : 'Face Not Recognized' }
296- </ motion . p >
297-
298- { status === 'failed' && ! userBiometrics [ selectedUser . id ] && (
299- < p style = { { color : 'rgba(255,255,255,0.6)' , fontSize : '13px' , marginTop : '8px' } } > Enable Face ID in Settings first.</ p >
300- ) }
301- </ motion . div >
302- ) }
303- </ AnimatePresence >
226+ { /* Real Hardware Face ID doesn't need overlays — the OS handles it */ }
304227
305228 { status === 'loading' && (
306229 < div style = { { position : 'fixed' , inset : 0 , zIndex : 100 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , background : 'var(--bg-color)' } } >
0 commit comments