damus.io

damus.io website
git clone git://jb55.com/damus.io
Log | Files | Refs | README | LICENSE

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 }