damus.io

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

commit 9a4758330311548996185841bae076d35930d3ca
parent 788a6569d3f84a3a1444e36f740c83656b6215eb
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Mon,  1 Apr 2024 14:21:12 +0000

Move common Purple checkout views to reusable components

This commit moves some common layout and other view components from
PurpleCheckout into reusable components

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Asrc/components/ErrorDialog.tsx | 34++++++++++++++++++++++++++++++++++
Asrc/components/PurpleLayout.tsx | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/sections/PurpleCheckout.tsx | 396++++++++++++++++++++++++++++++++++++-------------------------------------------
3 files changed, 275 insertions(+), 217 deletions(-)

diff --git a/src/components/ErrorDialog.tsx b/src/components/ErrorDialog.tsx @@ -0,0 +1,34 @@ +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/AlertDialog"; +import { Button } from "@/components/ui/Button"; +import Link from "next/link"; + +export function ErrorDialog({ error, setError, children }: { error: string | null, setError: (error: string | null) => void, children?: React.ReactNode }) { + return ( + <AlertDialog open={error != null} onOpenChange={(open) => { if (!open) setError(null) }}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle><div className="text-red-700">Error</div></AlertDialogTitle> + <AlertDialogDescription> + <div className="text-left"> + {error} + </div> + <div className="text-black/40 text-xs text-left mt-4 mb-6"> + You can contact support by sending an email to <Link href="mailto:support@damus.io" className="text-damuspink-600 underline">support@damus.io</Link> + </div> + {children} + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <Button variant="default" onClick={() => setError(null)}>OK</Button> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + ); +} diff --git a/src/components/PurpleLayout.tsx b/src/components/PurpleLayout.tsx @@ -0,0 +1,62 @@ +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "./ui/RoundedContainerWithGradientBorder"; +import { PurpleIcon } from "./icons/PurpleIcon"; +import { MeshGradient5 } from "./effects/MeshGradient.5"; +import Image from "next/image"; +import Link from "next/link"; +import { Button } from "./ui/Button"; +import { ArrowLeft, LifeBuoy } from "lucide-react"; +import { useIntl } from "react-intl"; + + +export function PurpleLayout({ children }: { children: React.ReactNode }) { + const intl = useIntl() + + return ( + <div + className="bg-black overflow-hidden relative" + > + <div className="absolute z-0 w-full h-full pointer-events-none"> + <Image src="/stars-bg.webp" fill className="absolute top-0 left-0 object-cover lg:object-contain object-center w-full h-full" alt="" aria-hidden="true" /> + <MeshGradient5 className="translate-y-1/4 translate-x-32" /> + </div> + <div className="container z-10 mx-auto px-6 pt-12 h-full min-h-screen flex flex-col justify-center"> + <div className="flex flex-col items-center justify-center h-full grow"> + <RoundedContainerWithGradientBorder className="w-full max-w-md h-full mb-4" innerContainerClassName="w-full"> + <div className="flex gap-x-4 items-center mb-12 mx-auto justify-center"> + <PurpleIcon className="w-16 h-16" /> + <motion.h2 className="text-6xl 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"> + Purple + </motion.h2> + </div> + {children} + </RoundedContainerWithGradientBorder> + <Link href="/purple" className="w-full md:w-auto opacity-70 hover:opacity-100 transition mb-4"> + <Button variant="link" className="w-full text-sm"> + <ArrowLeft className="text-damuspink-600 mr-2" /> + {intl.formatMessage({ id: "purple.account.back", defaultMessage: "Go back" })} + </Button> + </Link> + <div className="flex flex-col rounded-xl px-4 text-purple-200 w-full mt-4 gap-1"> + <div className="flex justify-center"> + <LifeBuoy className="mr-2" /> + {intl.formatMessage({ id: "purple.account.contact-support", defaultMessage: "Contact support" })} + </div> + <div className="flex justify-center"> + <Link href="mailto:support@damus.io" className="w-full md:w-auto opacity-70 hover:opacity-100 transition text-sm"> + <Button variant="link" className="w-full text-sm"> + {intl.formatMessage({ id: "purple.account.email-support", defaultMessage: "Via email" })} + </Button> + </Link> + <Link href="nostr:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955" className="w-full md:w-auto opacity-70 hover:opacity-100 transition text-sm"> + <Button variant="link" className="w-full text-sm"> + {intl.formatMessage({ id: "purple.account.nostr-support", defaultMessage: "Via Nostr" })} + </Button> + </Link> + </div> + </div> + </div> + </div> + </div> + ) +} diff --git a/src/components/sections/PurpleCheckout.tsx b/src/components/sections/PurpleCheckout.tsx @@ -25,6 +25,8 @@ import { AlertDialogTrigger, } from "@/components/ui/AlertDialog"; import { Info } from "lucide-react"; +import { ErrorDialog } from "../ErrorDialog"; +import { PurpleLayout } from "../PurpleLayout"; export function PurpleCheckout() { @@ -267,238 +269,198 @@ export function PurpleCheckout() { // MARK: - Render return (<> - <AlertDialog open={error != null} onOpenChange={(open) => { if (!open) setError(null) }}> - <AlertDialogContent> - <AlertDialogHeader> - <AlertDialogTitle><div className="text-red-700">Error</div></AlertDialogTitle> - <AlertDialogDescription> - <div className="text-left"> - {error} - </div> - <div className="text-black/40 text-xs text-left mt-4 mb-6"> - You can contact support by sending an email to <Link href="mailto:support@damus.io" className="text-damuspink-600 underline">support@damus.io</Link> - </div> - {lnCheckout && lnCheckout.id && ( - <div className="flex items-center justify-between rounded-md bg-gray-200"> - <div className="text-xs text-gray-400 font-normal px-4 py-2"> - Reference: - </div> - <div className="w-full text-xs text-gray-500 font-normal px-4 py-2 overflow-x-scroll"> - {lnCheckout?.id} + <ErrorDialog error={error} setError={setError}> + {lnCheckout && lnCheckout.id && ( + <div className="flex items-center justify-between rounded-md bg-gray-200"> + <div className="text-xs text-gray-400 font-normal px-4 py-2"> + Reference: + </div> + <div className="w-full text-xs text-gray-500 font-normal px-4 py-2 overflow-x-scroll"> + {lnCheckout?.id} + </div> + <button + className="text-sm text-gray-500 font-normal px-4 py-2 active:text-gray-500/30 hover:text-gray-500/80 transition" + onClick={() => navigator.clipboard.writeText(lnCheckout?.id || "")} + > + <Copy /> + </button> + </div> + )} + </ErrorDialog> + <PurpleLayout> + <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" })} + done={lnCheckout?.product_template_name != null} + active={true} + /> + <div className="mt-3 mb-4 flex gap-2 items-center"> + {productTemplates ? Object.entries(productTemplates).map(([name, productTemplate]) => ( + <button + key={name} + className={`relative flex flex-col items-center justify-center p-3 pt-4 border rounded-lg ${name == lnCheckout?.product_template_name ? "border-green-500" : "border-purple-200/50"} disabled:opacity-50 disabled:cursor-not-allowed`} + onClick={() => selectProduct(name)} + disabled={lnCheckout?.verified_pubkey != null} + > + {productTemplate.special_label && ( + <div className="absolute top-0 right-0 -mt-4 -mr-2 bg-gradient-to-r from-damuspink-500 to-damuspink-600 rounded-full p-1 px-3"> + <div className="text-white text-xs font-semibold"> + {productTemplate.special_label} </div> - <button - className="text-sm text-gray-500 font-normal px-4 py-2 active:text-gray-500/30 hover:text-gray-500/80 transition" - onClick={() => navigator.clipboard.writeText(lnCheckout?.id || "")} - > - <Copy /> - </button> </div> )} - </AlertDialogDescription> - </AlertDialogHeader> - <AlertDialogFooter> - <Button variant="default" onClick={() => setError(null)}>OK</Button> - </AlertDialogFooter> - </AlertDialogContent> - </AlertDialog> - <div - className="bg-black overflow-hidden relative" - > - <div className="absolute z-0 w-full h-full pointer-events-none"> - <Image src="/stars-bg.webp" fill className="absolute top-0 left-0 object-cover lg:object-contain object-center w-full h-full" alt="" aria-hidden="true" /> - <MeshGradient5 className="translate-y-1/4 translate-x-32" /> - </div> - <div className="container z-10 mx-auto px-6 pt-12 h-full min-h-screen flex flex-col justify-center"> - <div className="flex flex-col items-center justify-center h-full grow"> - <RoundedContainerWithGradientBorder className="w-full max-w-md h-full mb-4" innerContainerClassName="w-full"> - <div className="flex gap-x-4 items-center mb-12 mx-auto justify-center"> - <PurpleIcon className="w-16 h-16" /> - <motion.h2 className="text-6xl 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"> - Purple - </motion.h2> + + <div className="text-purple-200/50 font-normal text-sm"> + {productTemplate.description} </div> - <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 className="mt-1 text-purple-100/90 font-semibold text-lg"> + {productTemplate.amount_msat / 1000} sats </div> - <StepHeader - stepNumber={1} - title={intl.formatMessage({ id: "purple.checkout.step-1", defaultMessage: "Choose your plan" })} - done={lnCheckout?.product_template_name != null} - active={true} - /> - <div className="mt-3 mb-4 flex gap-2 items-center"> - {productTemplates ? Object.entries(productTemplates).map(([name, productTemplate]) => ( - <button - key={name} - className={`relative flex flex-col items-center justify-center p-3 pt-4 border rounded-lg ${name == lnCheckout?.product_template_name ? "border-green-500" : "border-purple-200/50"} disabled:opacity-50 disabled:cursor-not-allowed`} - onClick={() => selectProduct(name)} - disabled={lnCheckout?.verified_pubkey != null} - > - {productTemplate.special_label && ( - <div className="absolute top-0 right-0 -mt-4 -mr-2 bg-gradient-to-r from-damuspink-500 to-damuspink-600 rounded-full p-1 px-3"> - <div className="text-white text-xs font-semibold"> - {productTemplate.special_label} - </div> - </div> - )} - - <div className="text-purple-200/50 font-normal text-sm"> - {productTemplate.description} - </div> - <div className="mt-1 text-purple-100/90 font-semibold text-lg"> - {productTemplate.amount_msat / 1000} sats - </div> - </button> - )) : ( - <div className="flex flex-col items-center justify-center"> - <div className="text-purple-200/50 font-normal text-sm flex items-center"> - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> - Loading... - </div> - </div> - )} + </button> + )) : ( + <div className="flex flex-col items-center justify-center"> + <div className="text-purple-200/50 font-normal text-sm flex items-center"> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + Loading... </div> - <StepHeader - stepNumber={2} - title={intl.formatMessage({ id: "purple.checkout.step-2", defaultMessage: "Verify your npub" })} - done={lnCheckout?.verified_pubkey != null} - 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"> - {intl.formatMessage({ id: "purple.checkout.open-in-app", defaultMessage: "Open in Damus" })} - </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> - </div> - </> - } - {profile && - <div className="mt-2 mb-4 flex flex-col items-center"> - <div className="text-purple-200/50 font-normal text-sm"> - {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> + )} + </div> + <StepHeader + stepNumber={2} + title={intl.formatMessage({ id: "purple.checkout.step-2", defaultMessage: "Verify your npub" })} + done={lnCheckout?.verified_pubkey != null} + 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"> + {intl.formatMessage({ id: "purple.checkout.open-in-app", defaultMessage: "Open in Damus" })} + </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> + </div> + </> + } + {profile && + <div className="mt-2 mb-4 flex flex-col items-center"> + <div className="text-purple-200/50 font-normal text-sm"> + {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> - {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> - } - <StepHeader - stepNumber={3} - title={intl.formatMessage({ id: "purple.checkout.step-3", defaultMessage: "Lightning payment" })} - done={lnCheckout?.invoice?.paid === true} - active={lnCheckout?.verified_pubkey != null} - /> - {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 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"> - {lnCheckout.invoice.bolt11} + <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> - <button - className="text-sm text-purple-200/50 font-normal px-4 py-2 active:text-purple-200/30 hover:text-purple-200/80 transition" - onClick={() => navigator.clipboard.writeText(lnCheckout?.invoice?.bolt11 || "")} - > - <Copy /> - </button> - </div> - <Link href={"lightning:" + lnCheckout.invoice.bolt11} className="w-full md:w-auto opacity-70 hover:opacity-100 transition mt-4"> - <Button variant="link" className="w-full text-sm"> - {intl.formatMessage({ id: "purple.checkout.open-in-wallet", defaultMessage: "Open in wallet" })} - <ArrowUpRight className="text-damuspink-600 ml-2" /> - </Button> - </Link> - <div className="mt-6 text-purple-200/50 font-normal text-sm text-center flex justify-center"> - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> - {intl.formatMessage({ id: "purple.checkout.waiting-for-payment", defaultMessage: "Waiting for payment" })} - </div> - </> - } - {/* We use the lnCheckout object to check payment status (NOT lnInvoicePaid) to display the confirmation message, because the server is the ultimate source of truth */} - {lnCheckout?.invoice?.paid && lnCheckout?.completed && ( - <div className="flex flex-col items-center justify-center gap-3 mt-6"> - <CheckCircle className="w-16 h-16 text-green-500" /> - <div className="mt-3 mb-6 text-sm text-center text-green-500 font-bold"> - {intl.formatMessage({ id: "purple.checkout.payment-received", defaultMessage: "Payment received" })} - </div> - <Link - 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" })} - <ChevronRight className="ml-1" /> - </Button> - </Link> - <button className="w-full text-sm text-damuspink-500 flex justify-center" onClick={() => setContinueShowQRCodes(!continueShowQRCodes)}> - {!continueShowQRCodes ? - intl.formatMessage({ id: "purple.checkout.continue.show-qr", defaultMessage: "Show QR code" }) - : intl.formatMessage({ id: "purple.checkout.continue.hide-qr", defaultMessage: "Hide QR code" }) - } - </button> - {continueShowQRCodes && ( - <> - <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"> - {/* 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> </div> )} - </RoundedContainerWithGradientBorder> - <Link href="/purple" className="w-full md:w-auto opacity-70 hover:opacity-100 transition mb-4"> + </div> + </div> + } + <StepHeader + stepNumber={3} + title={intl.formatMessage({ id: "purple.checkout.step-3", defaultMessage: "Lightning payment" })} + done={lnCheckout?.invoice?.paid === true} + active={lnCheckout?.verified_pubkey != null} + /> + {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 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"> + {lnCheckout.invoice.bolt11} + </div> + <button + className="text-sm text-purple-200/50 font-normal px-4 py-2 active:text-purple-200/30 hover:text-purple-200/80 transition" + onClick={() => navigator.clipboard.writeText(lnCheckout?.invoice?.bolt11 || "")} + > + <Copy /> + </button> + </div> + <Link href={"lightning:" + lnCheckout.invoice.bolt11} className="w-full md:w-auto opacity-70 hover:opacity-100 transition mt-4"> <Button variant="link" className="w-full text-sm"> - <ArrowLeft className="text-damuspink-600 mr-2" /> - {intl.formatMessage({ id: "purple.checkout.back", defaultMessage: "Go back" })} + {intl.formatMessage({ id: "purple.checkout.open-in-wallet", defaultMessage: "Open in wallet" })} + <ArrowUpRight className="text-damuspink-600 ml-2" /> </Button> </Link> + <div className="mt-6 text-purple-200/50 font-normal text-sm text-center flex justify-center"> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + {intl.formatMessage({ id: "purple.checkout.waiting-for-payment", defaultMessage: "Waiting for payment" })} + </div> + </> + } + {/* We use the lnCheckout object to check payment status (NOT lnInvoicePaid) to display the confirmation message, because the server is the ultimate source of truth */} + {lnCheckout?.invoice?.paid && lnCheckout?.completed && ( + <div className="flex flex-col items-center justify-center gap-3 mt-6"> + <CheckCircle className="w-16 h-16 text-green-500" /> + <div className="mt-3 mb-6 text-sm text-center text-green-500 font-bold"> + {intl.formatMessage({ id: "purple.checkout.payment-received", defaultMessage: "Payment received" })} + </div> + <Link + 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" })} + <ChevronRight className="ml-1" /> + </Button> + </Link> + <button className="w-full text-sm text-damuspink-500 flex justify-center" onClick={() => setContinueShowQRCodes(!continueShowQRCodes)}> + {!continueShowQRCodes ? + intl.formatMessage({ id: "purple.checkout.continue.show-qr", defaultMessage: "Show QR code" }) + : intl.formatMessage({ id: "purple.checkout.continue.hide-qr", defaultMessage: "Hide QR code" }) + } + </button> + {continueShowQRCodes && ( + <> + <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"> + {/* 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> </div> - </div> - </div > + )} + </PurpleLayout> </>) }