1+ "use client"
2+
3+ import { LinkIcon } from "@heroicons/react/24/outline"
4+ import { useAppDispatch , useAppSelector } from "../lib/hooks"
5+ import Link from "next/link"
6+ import { useForm } from "react-hook-form"
7+ import { useRouter } from "next/navigation"
8+ import { loggedIn , logout , totpLogin } from "../lib/slices/authSlice"
9+ import { tokenIsTOTP } from "../lib/utilities"
10+ import { useEffect } from "react"
11+ import { FieldValues } from "react-hook-form"
12+ import { RootState } from "../lib/store"
13+
14+ const totpSchema = {
15+ claim : { required : true }
16+ }
17+
18+ const redirectAfterLogin = "/"
19+ const loginPage = "/login"
20+
21+ export default function Totp ( ) {
22+ const dispatch = useAppDispatch ( )
23+ const accessToken = useAppSelector ( ( state : RootState ) => state . tokens . access_token )
24+ const isLoggedIn = useAppSelector ( ( state : RootState ) => loggedIn ( state ) )
25+
26+ const router = useRouter ( )
27+
28+ const {
29+ register,
30+ handleSubmit,
31+ formState : { errors } ,
32+ } = useForm ( )
33+
34+ async function submit ( values : FieldValues ) {
35+ await dispatch ( totpLogin ( { claim : values . claim } ) )
36+ }
37+
38+ const removeFingerprint = async ( ) => await dispatch ( logout ( ) )
39+
40+ useEffect ( ( ) => {
41+ if ( isLoggedIn ) router . push ( redirectAfterLogin )
42+ // if there is no access token, nor is it totp, send the user back to login
43+ if ( ! ( accessToken && tokenIsTOTP ( accessToken ) ) ) router . push ( loginPage )
44+ } , [ isLoggedIn , accessToken ] ) // eslint-disable-line react-hooks/exhaustive-deps
45+
46+ return (
47+ < main className = "flex min-h-full" >
48+ < div className = "flex flex-1 flex-col justify-center py-12 px-4 sm:px-6 lg:flex-none lg:px-20 xl:px-24" >
49+ < div className = "mx-auto w-full max-w-sm lg:w-96" >
50+ < div >
51+ < img className = "h-12 w-auto" src = "https://tailwindui.com/img/logos/mark.svg?color=rose& shade = 500 " alt = "Your Company" />
52+ < h2 className = "mt-6 text-3xl font-bold tracking-tight text-gray-900" > Two-factor authentication</ h2 >
53+ < p className = "text-sm font-medium text-rose-500 hover:text-rose-600 mt-6" >
54+ Enter the 6-digit verification code from your app.
55+ </ p >
56+ </ div >
57+
58+ < div className = "mt-8" >
59+ < div className = "mt-6" >
60+ < form onSubmit = { handleSubmit ( submit ) } className = "space-y-6" >
61+ < div >
62+ < label
63+ htmlFor = "claim"
64+ className = "block text-sm font-medium text-gray-700"
65+ >
66+ Verification code
67+ </ label >
68+ < div className = "mt-1 group relative inline-block w-full" >
69+ < input
70+ { ...register ( "claim" , totpSchema . claim ) }
71+ id = "claim"
72+ name = "claim"
73+ type = "text"
74+ autoComplete = "off"
75+ className = "block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-rose-600 focus:outline-none focus:ring-rose-600 sm:text-sm"
76+ />
77+ { errors . claim && (
78+ < div className = "absolute left-5 top-5 translate-y-full w-48 px-2 py-1 bg-gray-700 rounded-lg text-center text-white text-sm after:content-[''] after:absolute after:left-1/2 after:bottom-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-gray-700" >
79+ This field is required.
80+ </ div >
81+ ) }
82+ </ div >
83+ </ div >
84+ < div >
85+ < button type = "submit" className = "flex w-full justify-center rounded-md border border-transparent bg-rose-500 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-rose-600 focus:ring-offset-2" >
86+ Submit
87+ </ button >
88+ </ div >
89+ </ form >
90+ </ div >
91+ </ div >
92+ </ div >
93+ < Link href = "/login?oauth=true" className = "mt-8 flex" onClick = { removeFingerprint } >
94+ < LinkIcon
95+ className = "text-rose-500 h-4 w-4 mr-1"
96+ aria-hidden = "true"
97+ />
98+ < p className = "text-sm text-rose-500 align-middle" >
99+ Log in another way.
100+ </ p >
101+ </ Link >
102+ </ div >
103+ < div className = "relative hidden w-0 flex-1 lg:block" >
104+ < img className = "absolute inset-0 h-full w-full object-cover" src = "https://images.unsplash.com/photo-1561487138-99ccf59b135c?ixlib=rb-4.0.3& ixid = MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8 & auto = format & fit = crop & w = 764 & q = 80 " alt = "" />
105+ </ div >
106+ </ main >
107+ )
108+ }
0 commit comments