damus.io

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

PurpleAccount.tsx (7212B)


      1 import { ArrowUpRight, Star, Check, LogOut, X } from "lucide-react";
      2 import { Button } from "../ui/Button";
      3 import { FormattedMessage, 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 { AccountInfo, Profile, getProfile, getPurpleAccountInfo } from "@/utils/PurpleUtils";
      8 import { useLocalStorage } from "usehooks-ts";
      9 import { ErrorDialog } from "../ErrorDialog";
     10 import { PurpleLayout } from "../PurpleLayout";
     11 
     12 
     13 export function PurpleAccount() {
     14   const intl = useIntl()
     15   const [sessionToken, setSessionToken] = useLocalStorage('session_token', null)
     16   const [existingAccountInfo, setExistingAccountInfo] = useState<AccountInfo | null | undefined>(undefined)  // The account info fetched from the server
     17   const [error, setError] = useState<string | null>(null)
     18   const [profile, setProfile] = useState<Profile | null>(null)
     19   const [pubkey, setPubkey] = useState<string | null>(null)
     20 
     21   // MARK: - Functions
     22 
     23   const fetchProfile = async () => {
     24     if (!pubkey) {
     25       return
     26     }
     27     try {
     28       const profile = await getProfile(pubkey)
     29       setProfile(profile)
     30     }
     31     catch (e) {
     32       console.error(e)
     33       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.")
     34     }
     35   }
     36 
     37   const fetchAccountInfo = async () => {
     38     try {
     39       const response = await fetch(process.env.NEXT_PUBLIC_PURPLE_API_BASE_URL + "/sessions/account", {
     40         method: 'GET',
     41         headers: {
     42           'Content-Type': 'application/json',
     43           'Authorization': 'Bearer ' + sessionToken
     44         },
     45       })
     46       if (!response.ok) {
     47         setError("Failed to get account info from our servers. Please wait a few minutes and refresh the page. If the problem persists, please contact support.")
     48         return
     49       }
     50       const accountInfo = await response.json()
     51       console.log(accountInfo)
     52       setExistingAccountInfo(accountInfo)
     53       setPubkey(accountInfo.pubkey)
     54     }
     55     catch (e) {
     56       console.error(e)
     57       setError("Failed to get account info from our servers. Please wait a few minutes and refresh the page. If the problem persists, please contact support.")
     58     }
     59   }
     60 
     61   // MARK: - Effects and hooks
     62 
     63   // Load the profile when the pubkey changes
     64   useEffect(() => {
     65     if (pubkey) {
     66       fetchProfile()
     67     }
     68   }, [pubkey])
     69 
     70   useEffect(() => {
     71     if (sessionToken) {
     72       fetchAccountInfo()
     73     }
     74     else if (sessionToken === null) {
     75       // Redirect to the login page
     76       window.location.href = "/purple/login?redirect=" + encodeURIComponent("/purple/account")
     77     }
     78   }, [sessionToken])
     79 
     80   // MARK: - Render
     81 
     82   return (<>
     83     <ErrorDialog error={error} setError={setError} />
     84     <PurpleLayout>
     85       {((profile || profile === null) && pubkey) && (<>
     86         <div className="mt-2 mb-4 flex flex-col items-center">
     87           <div className="mt-4 flex flex-col gap-1 items-center justify-center mb-4">
     88             <Image src={profile?.picture || ("https://robohash.org/" + (profile?.pubkey || pubkey))} width={128} height={128} className="rounded-full" alt={profile?.name || intl.formatMessage({ id: "purple.account.unknown-user", defaultMessage: "Generic user avatar" })} />
     89             <div className="flex flex-wrap gap-1 items-center">
     90               <div className="text-purple-50 font-semibold text-3xl">
     91                 {profile?.name || "No name"}
     92               </div>
     93               <div className="text-purple-200/70 font-normal text-xs flex gap-1">
     94                 <Star strokeWidth={1.75} className="w-4 h-4 shrink-0 text-amber-400" fill="currentColor" />
     95                 {existingAccountInfo?.created_at && unixTimestampToDateString(existingAccountInfo?.created_at)}
     96               </div>
     97             </div>
     98             {existingAccountInfo?.active ? (
     99               <div className="flex gap-1 bg-gradient-to-r from-damuspink-500 to-damuspink-600 rounded-full px-3 py-1 items-center mt-3 mb-6">
    100                 <div className="w-4 h-4 rounded-full bg-white flex justify-center items-center">
    101                   <Check className="w-2 h-2 shrink-0 text-damuspink-500" strokeWidth={5} />
    102                 </div>
    103                 <div className="text-white font-semibold text-sm">
    104                   {intl.formatMessage({ id: "purple.account.active", defaultMessage: "Active account" })}
    105                 </div>
    106               </div>
    107             ) : (
    108               <div className="flex gap-1 bg-red-200 rounded-full px-3 py-1 items-center mt-3 mb-6">
    109                 <div className="w-4 h-4 rounded-full bg-red-500 flex justify-center items-center">
    110                   <X className="w-2 h-2 shrink-0 text-red-200" strokeWidth={5} />
    111                 </div>
    112                 <div className="text-red-500 font-semibold text-sm">
    113                   {intl.formatMessage({ id: "purple.account.expired", defaultMessage: "Expired account" })}
    114                 </div>
    115               </div>
    116             )}
    117           </div>
    118           <div className="flex flex-col bg-purple-50/10 rounded-xl px-4 text-purple-50 w-full">
    119             <AccountInfoRow label={intl.formatMessage({ id: "purple.account.expiry-date", defaultMessage: "Expiry date" })} value={(existingAccountInfo?.expiry && unixTimestampToDateString(existingAccountInfo?.expiry)) || "N/A"} />
    120             <AccountInfoRow label={intl.formatMessage({ id: "purple.account.account-creation", defaultMessage: "Account creation" })} value={(existingAccountInfo?.created_at && unixTimestampToDateString(existingAccountInfo?.created_at)) || "N/A"} />
    121             <AccountInfoRow label={intl.formatMessage({ id: "purple.account.subscriber-number", defaultMessage: "Subscriber number" })} value={(existingAccountInfo?.subscriber_number && "#" + existingAccountInfo?.subscriber_number) || "N/A"} last={existingAccountInfo?.testflight_url ? false : true} />
    122             {existingAccountInfo?.testflight_url && <Link href={existingAccountInfo?.testflight_url} target="_blank">
    123               <Button variant="link" className="w-full text-left my-2">
    124                 <ArrowUpRight className="text-damuspink-600 mr-2" />
    125                 {intl.formatMessage({ id: "purple.account.testflight-link", defaultMessage: "Join TestFlight" })}
    126               </Button>
    127             </Link>}
    128           </div>
    129           <Button className="w-full md:w-auto opacity-70 hover:opacity-100 transition mt-4 text-sm" onClick={() => setSessionToken(null)} variant="link">
    130             <LogOut className="text-damuspink-600 mr-2" />
    131             {intl.formatMessage({ id: "purple.account.sign-out", defaultMessage: "Sign out" })}
    132           </Button>
    133         </div>
    134       </>)}
    135     </PurpleLayout>
    136   </>)
    137 }
    138 
    139 function AccountInfoRow({ label, value, last }: { label: string, value: string | number, last?: boolean }) {
    140   return (
    141     <div className={`flex gap-2 items-center justify-between ${last ? '' : 'border-b border-purple-200/20'} py-4`}>
    142       <div className="font-bold">
    143         {label}
    144       </div>
    145       <div>
    146         {value}
    147       </div>
    148     </div>
    149   );
    150 }
    151 
    152 function unixTimestampToDateString(timestamp: number) {
    153   return new Date(timestamp * 1000).toLocaleDateString()
    154 }