damus.io

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

commit 31d40331f09283f95d98e8d7803eb80d475ada21
parent a295d66bc501c3c54357776c72049fa3dc6fc7a9
Author: William Casarin <jb55@jb55.com>
Date:   Mon, 11 Mar 2024 10:16:03 +0000

Merge remote-tracking branch 'github/release_madeira_2024_03'

Diffstat:
Mcontent/compiled-locales/en.json | 38+++++++++++++++++++++++++++++++++++++-
Mcontent/locales/en.json | 20+++++++++++++++++++-
Msrc/components/sections/PurpleCheckout.tsx | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/components/sections/PurpleHero.tsx | 13++++++++++++-
4 files changed, 151 insertions(+), 15 deletions(-)

diff --git a/content/compiled-locales/en.json b/content/compiled-locales/en.json @@ -293,6 +293,18 @@ "value": "shortName" } ], + "purple-checkout.account-will-renew": [ + { + "type": 0, + "value": "Paying will renew or extend your membership." + } + ], + "purple-checkout.this-account-exists": [ + { + "type": 0, + "value": "Yay! We found your account" + } + ], "purple.benefits.benefit1.description": [ { "type": 0, @@ -350,7 +362,7 @@ "purple.checkout.continue": [ { "type": 0, - "value": "Continue in the app to set it up" + "value": "Continue in the app" } ], "purple.checkout.continue.hide-qr": [ @@ -365,6 +377,18 @@ "value": "Show QR code" } ], + "purple.checkout.description": [ + { + "type": 0, + "value": "New accounts and renewals" + } + ], + "purple.checkout.description-2": [ + { + "type": 0, + "value": "Use this page to purchase a new account, or to renew an existing one. You will need the latest Damus version to complete the checkout." + } + ], "purple.checkout.open-in-app": [ { "type": 0, @@ -395,6 +419,18 @@ "value": "Verified. Purchasing Damus Purple for:" } ], + "purple.checkout.renewed-for": [ + { + "type": 0, + "value": "Renewed Damus Purple for:" + } + ], + "purple.checkout.renewing-for": [ + { + "type": 0, + "value": "Verified. Renewing Damus Purple for:" + } + ], "purple.checkout.step-1": [ { "type": 0, diff --git a/content/locales/en.json b/content/locales/en.json @@ -140,6 +140,12 @@ "meet_the_team.view_profile": { "string": "Follow {shortName}" }, + "purple-checkout.account-will-renew": { + "string": "Paying will renew or extend your membership." + }, + "purple-checkout.this-account-exists": { + "string": "Yay! We found your account" + }, "purple.benefits.benefit1.description": { "string": "Support Damus development to help build the future of decentralized communication on the web." }, @@ -168,7 +174,7 @@ "string": "Go back" }, "purple.checkout.continue": { - "string": "Continue in the app to set it up" + "string": "Continue in the app" }, "purple.checkout.continue.hide-qr": { "string": "Hide QR code" @@ -176,6 +182,12 @@ "purple.checkout.continue.show-qr": { "string": "Show QR code" }, + "purple.checkout.description": { + "string": "New accounts and renewals" + }, + "purple.checkout.description-2": { + "string": "Use this page to purchase a new account, or to renew an existing one. You will need the latest Damus version to complete the checkout." + }, "purple.checkout.open-in-app": { "string": "Open in Damus" }, @@ -191,6 +203,12 @@ "purple.checkout.purchasing-for": { "string": "Verified. Purchasing Damus Purple for:" }, + "purple.checkout.renewed-for": { + "string": "Renewed Damus Purple for:" + }, + "purple.checkout.renewing-for": { + "string": "Verified. Renewing Damus Purple for:" + }, "purple.checkout.step-1": { "string": "Choose your plan" }, diff --git a/src/components/sections/PurpleCheckout.tsx b/src/components/sections/PurpleCheckout.tsx @@ -1,4 +1,4 @@ -import { ArrowLeft, ArrowUpRight, CheckCircle, ChevronRight, Copy, Globe2, Loader2, LucideZapOff, Zap, ZapIcon, ZapOff } from "lucide-react"; +import { ArrowLeft, ArrowUpRight, CheckCircle, ChevronRight, Copy, Globe2, Loader2, LucideZapOff, Sparkles, Zap, ZapIcon, ZapOff } from "lucide-react"; import { Button } from "../ui/Button"; import { FormattedMessage, useIntl } from "react-intl"; import Link from "next/link"; @@ -24,6 +24,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/AlertDialog"; +import { Info } from "lucide-react"; export function PurpleCheckout() { @@ -35,7 +36,8 @@ export function PurpleCheckout() { const [continueShowQRCodes, setContinueShowQRCodes] = useState<boolean>(false) // Whether the user wants to show a QR code for the final step const [lnInvoicePaid, setLNInvoicePaid] = useState<boolean | undefined>(undefined) // Whether the ln invoice has been paid const [waitingForInvoice, setWaitingForInvoice] = useState<boolean>(false) // Whether we are waiting for a response from the LN node about the invoice - const [error, setError] = useState<string|null>(null) // An error message to display to the user + const [error, setError] = useState<string | null>(null) // An error message to display to the user + const [existingAccountInfo, setExistingAccountInfo] = useState<AccountInfo | null | undefined>(undefined) // The account info fetched from the server const [lnConnectionRetryCount, setLnConnectionRetryCount] = useState<number>(0) // The number of times we have tried to connect to the LN node const lnConnectionRetryLimit = 5 // The maximum number of times we will try to connect to the LN node before displaying an error @@ -58,6 +60,21 @@ export function PurpleCheckout() { } } + const fetchAccountInfo = async () => { + if (!pubkey) { + setExistingAccountInfo(undefined) + return + } + try { + const accountInfo = await getPurpleAccountInfo(pubkey) + setExistingAccountInfo(accountInfo) + } + catch (e) { + console.error(e) + 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.") + } + } + const fetchProductTemplates = async () => { try { const response = await fetch(process.env.NEXT_PUBLIC_PURPLE_API_BASE_URL + "/products", { @@ -224,6 +241,7 @@ export function PurpleCheckout() { useEffect(() => { if (pubkey) { fetchProfile() + fetchAccountInfo() } }, [pubkey]) @@ -299,9 +317,20 @@ export function PurpleCheckout() { Purple </motion.h2> </div> - <h2 className="text-2xl text-left text-purple-200 font-semibold break-keep mb-4"> + <h2 className="text-2xl text-left text-purple-200 font-semibold break-keep mb-2"> {intl.formatMessage({ id: "purple.checkout.title", defaultMessage: "Checkout" })} </h2> + <div className="text-purple-200/70 text-normal text-left mb-6 font-semibold flex flex-col md:flex-row gap-3 rounded-lg bg-purple-200/10 p-3 items-center md:items-start"> + <Info className="w-6 h-6 shrink-0 mt-0 md:mt-1" /> + <div className="flex flex-col text-center md:text-left"> + <span className="text-normal md:text-lg mb-2"> + {intl.formatMessage({ id: "purple.checkout.description", defaultMessage: "New accounts and renewals" })} + </span> + <span className="text-xs text-purple-200/50"> + {intl.formatMessage({ id: "purple.checkout.description-2", defaultMessage: "Use this page to purchase a new account, or to renew an existing one. You will need the latest Damus version to complete the checkout." })} + </span> + </div> + </div> <StepHeader stepNumber={1} title={intl.formatMessage({ id: "purple.checkout.step-1", defaultMessage: "Choose your plan" })} @@ -347,7 +376,6 @@ export function PurpleCheckout() { active={lnCheckout?.product_template_name != null} /> {lnCheckout && !lnCheckout.verified_pubkey && <> - <QRCodeSVG value={"damus:purple:verify?id=" + lnCheckout.id} className="mt-6 w-[300px] h-[300px] max-w-full max-h-full mx-auto mb-6" /> <Link href={"damus:purple:verify?id=" + lnCheckout.id} className="w-full md:w-auto opacity-70 hover:opacity-100 transition"> <Button variant="link" className="w-full text-sm"> @@ -355,22 +383,40 @@ export function PurpleCheckout() { </Button> </Link> <div className="text-white/40 text-xs text-center mt-4 mb-6"> - {/* TODO: Localize later */} - Issues with this step? Please ensure you are running the latest Damus iOS version from <Link href={DAMUS_TESTFLIGHT_URL} className="text-damuspink-500 underline" target="_blank">TestFlight</Link> — or <Link href="mailto:support@damus.io" className="text-damuspink-500 underline">contact us</Link> + {/* TODO: Localize later */} + Issues with this step? Please ensure you are running the latest Damus iOS version from <Link href={DAMUS_TESTFLIGHT_URL} className="text-damuspink-500 underline" target="_blank">TestFlight</Link> — or <Link href="mailto:support@damus.io" className="text-damuspink-500 underline">contact us</Link> </div> </> } {profile && <div className="mt-2 mb-4 flex flex-col items-center"> <div className="text-purple-200/50 font-normal text-sm"> - {lnCheckout?.verified_pubkey && !lnCheckout?.invoice?.paid && intl.formatMessage({ id: "purple.checkout.purchasing-for", defaultMessage: "Verified. Purchasing Damus Purple for:" })} - {lnCheckout?.invoice?.paid && intl.formatMessage({ id: "purple.checkout.purchased-for", defaultMessage: "Purchased Damus Purple for:" })} + {existingAccountInfo === null || existingAccountInfo === undefined ? <> + {lnCheckout?.verified_pubkey && !lnCheckout?.invoice?.paid && intl.formatMessage({ id: "purple.checkout.purchasing-for", defaultMessage: "Verified. Purchasing Damus Purple for:" })} + {lnCheckout?.invoice?.paid && intl.formatMessage({ id: "purple.checkout.purchased-for", defaultMessage: "Purchased Damus Purple for:" })} + </> : <> + {lnCheckout?.verified_pubkey && !lnCheckout?.invoice?.paid && intl.formatMessage({ id: "purple.checkout.renewing-for", defaultMessage: "Verified. Renewing Damus Purple for:" })} + {lnCheckout?.invoice?.paid && intl.formatMessage({ id: "purple.checkout.renewed-for", defaultMessage: "Renewed Damus Purple for:" })} + </>} </div> <div className="mt-4 flex flex-col gap-1 items-center justify-center"> <Image src={profile.picture || "https://robohash.org/" + profile.pubkey} width={64} height={64} className="rounded-full" alt={profile.name} /> <div className="text-purple-100/90 font-semibold text-lg"> {profile.name} </div> + {existingAccountInfo !== null && existingAccountInfo !== undefined && ( + <div className="text-purple-200/50 font-normal flex items-center gap-2 bg-purple-300/10 rounded-full px-6 py-2 justify-center"> + <Sparkles className="w-4 h-4 shrink-0 text-purple-50" /> + <div className="flex flex-col"> + <div className="text-purple-200/90 font-semibold text-sm"> + {intl.formatMessage({ id: "purple-checkout.this-account-exists", defaultMessage: "Yay! We found your account" })} + </div> + <div className="text-purple-200/70 font-normal text-xs break-normal"> + {intl.formatMessage({ id: "purple-checkout.account-will-renew", defaultMessage: "Paying will renew or extend your membership." })} + </div> + </div> + </div> + )} </div> </div> } @@ -382,7 +428,7 @@ export function PurpleCheckout() { /> {lnCheckout?.invoice?.bolt11 && !lnCheckout?.invoice?.paid && <> - <QRCodeSVG value={"lightning:" + lnCheckout.invoice.bolt11} className="mt-6 w-[300px] h-[300px] max-w-full max-h-full mx-auto mb-6 border-[5px] border-white" /> + <QRCodeSVG value={"lightning:" + lnCheckout.invoice.bolt11} className="mt-6 w-[300px] h-[300px] max-w-full max-h-full mx-auto mb-6 border-[5px] border-white bg-white" /> {/* Shows the bolt11 in for copy-paste with a copy and paste button */} <div className="flex items-center justify-between rounded-md bg-purple-200/20"> <div className="w-full text-sm text-purple-200/50 font-normal px-4 py-2 overflow-x-scroll"> @@ -415,11 +461,11 @@ export function PurpleCheckout() { {intl.formatMessage({ id: "purple.checkout.payment-received", defaultMessage: "Payment received" })} </div> <Link - href={`damus:purple:welcome?id=${lnCheckout.id}`} + href={existingAccountInfo !== null && existingAccountInfo !== undefined ? `damus:purple:landing` : `damus:purple:welcome?id=${lnCheckout.id}`} className="w-full text-sm flex justify-center" > <Button variant="default" className="w-full text-sm"> - {intl.formatMessage({ id: "purple.checkout.continue", defaultMessage: "Continue in the app to set it up" })} + {intl.formatMessage({ id: "purple.checkout.continue", defaultMessage: "Continue in the app" })} <ChevronRight className="ml-1" /> </Button> </Link> @@ -431,7 +477,10 @@ export function PurpleCheckout() { </button> {continueShowQRCodes && ( <> - <QRCodeSVG value={"damus:purple:welcome?id=" + lnCheckout.id} className="mt-6 w-[300px] h-[300px] max-w-full max-h-full mx-auto mb-6" /> + <QRCodeSVG + value={existingAccountInfo !== null && existingAccountInfo !== undefined ? "damus:purple:landing" : "damus:purple:welcome?id=" + lnCheckout.id} + className="mt-6 w-[300px] h-[300px] max-w-full max-h-full mx-auto mb-6" + /> </> )} <div className="text-white/40 text-xs text-center mt-4 mb-6"> @@ -470,6 +519,14 @@ function StepHeader({ stepNumber, title, done, active }: { stepNumber: number, t // MARK: - Types + +interface AccountInfo { + pubkey: string, + created_at: number, // Unix timestamp in seconds + expiry: number | null, // Unix timestamp in seconds + active: boolean, +} + interface LNCheckout { id: string, verified_pubkey?: string, @@ -506,6 +563,20 @@ interface Profile { // MARK: - Helper functions +const getPurpleAccountInfo = async (pubkey: string): Promise<AccountInfo | null> => { + const response = await fetch(process.env.NEXT_PUBLIC_PURPLE_API_BASE_URL + "/accounts/" + pubkey, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + }) + if (response.status === 404) { + return null + } + const data: AccountInfo = await response.json() + return data +} + const getProfile = async (pubkey: string): Promise<Profile | null> => { const profile_event: NostrEvent | null = await getProfileEvent(pubkey) if (!profile_event) { diff --git a/src/components/sections/PurpleHero.tsx b/src/components/sections/PurpleHero.tsx @@ -1,4 +1,4 @@ -import { ArrowUpRight, ChevronRight, Globe2, LucideZapOff, Zap, ZapIcon, ZapOff } from "lucide-react"; +import { ArrowUpRight, ChevronRight, Globe2, LucideZapOff, Sparkles, Star, Zap, ZapIcon, ZapOff } from "lucide-react"; import { MeshGradient1 } from "../effects/MeshGradient.1"; import { TopMenu } from "./TopMenu"; import { Button } from "../ui/Button"; @@ -23,6 +23,17 @@ export function PurpleHero() { <div className="container z-10 mx-auto px-6 pt-12 h-full min-h-screen flex flex-col justify-center"> <TopMenu className="w-full" /> <div className="flex flex-col items-center justify-center h-full grow"> + <Link href="/purple/checkout"> + <motion.div + className="inline-flex items-center text-xs rounded-full bg-purple-100/10 backdrop-blur-sm shadow-lg p-1 px-3 text-purple-100/80 border border-purple-100/30 active:scale-95 transition cursor-pointer mb-16" + style={{ opacity: 0 }} + animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} + > + <Sparkles className="h-4 mr-1 text-purple-50" aria-hidden="true" /> + {intl.formatMessage({ id: "purple.hero.renew", defaultMessage: "Already a member? Click here to renew!" })} + <ChevronRight className="ml-2" /> + </motion.div> + </Link> <div className="flex gap-x-4 items-center mb-12 md:mb-6"> <PurpleIcon className="w-16 h-16 md:w-24 md:h-24" /> <motion.h2 className="text-6xl md:text-8xl text-center text-transparent bg-clip-text bg-gradient-to-r from-damuspink-500 from-30% to-damuspink-600 to-100% font-semibold break-keep tracking-tight">