Step2OTPVerification.tsx (4669B)
1 import { Sparkles } from "lucide-react"; 2 import { Button } from "@/components/ui/Button"; 3 import { useIntl } from "react-intl"; 4 import Link from "next/link"; 5 import Image from "next/image"; 6 import { useEffect, useRef, useState } from "react"; 7 import { QRCodeSVG } from 'qrcode.react'; 8 import { useLocalStorage } from 'usehooks-ts' 9 import { AccountInfo, Profile, getProfile, getPurpleAccountInfo } from "@/utils/PurpleUtils"; 10 import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/Tabs"; 11 import { NostrUserInput } from "@/components/NostrUserInput"; 12 import { OTPAuth } from "@/components/OTPAuth"; 13 import { StepHeader } from "./StepHeader"; 14 import { LNCheckout } from "./Types"; 15 import { profile } from "console"; 16 import React from "react"; 17 import { AccountExistsNoteIfAccountExists } from "./AccountExistsNote"; 18 19 export interface Step2OTPVerificationProps { 20 lnCheckout: LNCheckout 21 setLNCheckout: (checkout: LNCheckout) => void 22 pubkey: string | null, 23 setPubkey: (pubkey: string | null) => void 24 profile: Profile | null | undefined 25 setProfile: (profile: Profile | null | undefined) => void 26 existingAccountInfo: AccountInfo | null | undefined 27 setError: (error: string) => void 28 } 29 30 export function Step2OTPVerification(props: Step2OTPVerificationProps) { 31 const intl = useIntl() 32 const [otpSent, setOTPSent] = useState<boolean>(false) 33 const [otpVerified, setOTPVerified] = useState<boolean>(false) 34 const [otpInvalid, setOTPInvalid] = useState<boolean>(false) 35 const [sessionToken, setSessionToken] = useLocalStorage('session_token', null) 36 37 const step1Done = props.lnCheckout?.product_template_name != null 38 const step2Done = props.lnCheckout?.verified_pubkey != null 39 40 // MARK: - Functions 41 42 const beginOTPAuth = async() => { 43 if (!props.pubkey || !props.lnCheckout) { 44 return 45 } 46 const response = await fetch(process.env.NEXT_PUBLIC_PURPLE_API_BASE_URL + "/ln-checkout/" + props.lnCheckout.id + "/request-otp/" + props.pubkey, { 47 method: 'POST', 48 headers: { 49 'Content-Type': 'application/json' 50 }, 51 }) 52 if (!response.ok) { 53 props.setError("Failed to send OTP. Please try again later.") 54 return 55 } 56 setOTPSent(true) 57 setOTPInvalid(false) 58 } 59 60 const validateOTP = async (otp: string) => { 61 if (!props.pubkey || !props.lnCheckout || !otp) { 62 return 63 } 64 const response = await fetch(process.env.NEXT_PUBLIC_PURPLE_API_BASE_URL + "/ln-checkout/" + props.lnCheckout.id + "/verify-otp/" + props.pubkey, { 65 method: 'PUT', 66 headers: { 67 'Content-Type': 'application/json' 68 }, 69 body: JSON.stringify({ otp_code: otp }) 70 }) 71 if (response.status != 200 && response.status != 401) { 72 props.setError("Failed to verify OTP. Please try again later.") 73 return 74 } 75 const json = await response.json() 76 if (json?.otp?.valid) { 77 /* 78 Response format: 79 { 80 checkout_object: response.checkout_object, 81 otp: { valid: true, session_token: session_token } 82 } 83 */ 84 props.setLNCheckout(json.checkout_object) 85 setSessionToken(json.otp.session_token) 86 setOTPInvalid(false) 87 setOTPVerified(true) 88 } 89 else { 90 setOTPInvalid(true) 91 } 92 } 93 94 // MARK: - Effects and hooks 95 96 97 // MARK: - Render 98 99 return (<> 100 <NostrUserInput 101 pubkey={props.pubkey} 102 setPubkey={props.setPubkey} 103 onProfileChange={props.setProfile} 104 profileHeader={ 105 <div className="text-purple-200/50 font-normal text-sm"> 106 {props.lnCheckout?.invoice?.bolt11 ? 107 intl.formatMessage({ id: "purple.checkout.paying-for", defaultMessage: "Purchasing membership for:" }) 108 : otpSent ? 109 intl.formatMessage({ id: "purple.checkout.logging-into", defaultMessage: "Logging into:" }) 110 : intl.formatMessage({ id: "purple.checkout.is-this-you", defaultMessage: "Is this you?" }) 111 } 112 </div> 113 } 114 profileFooter={<> 115 <AccountExistsNoteIfAccountExists existingAccountInfo={props.existingAccountInfo}/> 116 {!otpSent && !step2Done && ( 117 <Button variant="default" className="w-full mt-2" onClick={() => beginOTPAuth()}>Continue</Button> 118 )} 119 </>} 120 disabled={step2Done} 121 /> 122 {otpSent && !step2Done && 123 <OTPAuth 124 pubkey={props.pubkey} 125 verifyOTP={validateOTP} 126 sendOTP={beginOTPAuth} 127 otpVerified={otpVerified} 128 setOTPVerified={setOTPVerified} 129 otpInvalid={otpInvalid} 130 setOTPInvalid={setOTPInvalid} 131 setError={props.setError} 132 disabled={props.lnCheckout?.invoice?.bolt11 != undefined} 133 /> 134 } 135 </>) 136 }