damus.io

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

NostrUserInput.tsx (4028B)


      1 import { FormattedMessage, useIntl } from "react-intl";
      2 import Link from "next/link";
      3 import Image from "next/image";
      4 import { useEffect, useRef, useState } from "react";
      5 import { NostrEvent, Relay, nip19 } from "nostr-tools"
      6 import { Loader2, Radius, Shell } from "lucide-react";
      7 import { Input } from "@/components/ui/Input"
      8 import { Label } from "@/components/ui/Label"
      9 import { AccountInfo, Profile, getProfile, getPurpleAccountInfo } from "@/utils/PurpleUtils";
     10 import { ErrorDialog } from "./ErrorDialog";
     11 import { NostrProfile } from "./NostrProfile";
     12 
     13 
     14 // TODO: Double-check this regex and make it more accurate
     15 const NPUB_REGEX = /^npub[0-9A-Za-z]+$/
     16 
     17 export function NostrUserInput(props: { pubkey: string | null, setPubkey: (pubkey: string | null) => void, onProfileChange: (profile: Profile | undefined | null) => void, disabled?: boolean | undefined, profileHeader?: React.ReactNode, profileFooter?: React.ReactNode }) {
     18   const intl = useIntl()
     19   const [profile, setProfile] = useState<Profile | undefined | null>(undefined) // The profile info fetched from the Damus relay
     20   const [error, setError] = useState<string | null>(null)  // An error message to display to the user
     21   const [npubValidationError, setNpubValidationError] = useState<string | null>(null)
     22   const [npub, setNpub] = useState<string>("")
     23 
     24   // MARK: - Functions
     25 
     26   const fetchProfile = async () => {
     27     if (!props.pubkey) {
     28       return
     29     }
     30     try {
     31       const profile = await getProfile(props.pubkey)
     32       setProfile(profile)
     33     }
     34     catch (e) {
     35       console.error(e)
     36       setError("Failed to get profile info from the relay. Please wait a few minutes and refresh the page. If the problem persists, please contact support.")
     37     }
     38   }
     39 
     40   // MARK: - Effects and hooks
     41 
     42   // Load the profile when the pubkey changes
     43   useEffect(() => {
     44     if (props.pubkey) {
     45       fetchProfile()
     46     }
     47   }, [props.pubkey])
     48 
     49   useEffect(() => {
     50     if (npub.length > 0 && !NPUB_REGEX.test(npub)) {
     51       setNpubValidationError(intl.formatMessage({ id: "purple.login.npub-validation-error", defaultMessage: "Please enter a valid npub" }))
     52       setProfile(undefined)
     53     }
     54     else {
     55       setNpubValidationError(null)
     56       if (npub.length > 0) {
     57         try {
     58           const decoded = nip19.decode(npub)
     59           props.setPubkey(decoded.data as string)
     60         }
     61         catch (e) {
     62           props.setPubkey(null)
     63           setNpubValidationError(intl.formatMessage({ id: "purple.login.npub-validation-error", defaultMessage: "Please enter a valid npub" }))
     64         }
     65       }
     66       else {
     67         setProfile(undefined)
     68         props.setPubkey(null)
     69       }
     70     }
     71   }, [npub])
     72 
     73   // MARK: - Render
     74 
     75   return (<>
     76     <ErrorDialog error={error} setError={setError} />
     77     <Label htmlFor="npub" className="text-purple-200/70 font-normal">
     78       {intl.formatMessage({ id: "purple.login.npub-label", defaultMessage: "Please enter your public key (npub) below" })}
     79     </Label>
     80     <Input id="npub" placeholder={intl.formatMessage({ id: "purple.login.npub-placeholder", defaultMessage: "npub…" })} type="text" className="mt-2" value={npub} onChange={(e) => setNpub(e.target.value)} required disabled={props.disabled} />
     81     {npubValidationError &&
     82       <Label htmlFor="npub" className="text-red-500 font-normal">
     83         {npubValidationError}
     84       </Label>
     85     }
     86     {(profile === undefined && props.pubkey && props.pubkey.length > 0) && (
     87       <div className="mt-2 flex items-center justify-center">
     88         <Loader2 className="mr-2 animate-spin text-purple-200/90" size={16} />
     89         <div className="text-purple-200/70 font-normal text-sm">
     90           {intl.formatMessage({ id: "purple.login.fetching-profile", defaultMessage: "Fetching profile info…" })}
     91         </div>
     92       </div>
     93     )}
     94     {((profile || profile === null) && props.pubkey) && (<>
     95       <NostrProfile
     96         pubkey={props.pubkey}
     97         profile={profile}
     98         profileHeader={props.profileHeader}
     99         profileFooter={props.profileFooter}
    100       />
    101     </>)}
    102   </>)
    103 }