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 }