damus.io

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

commit ad1b86abf81554c0e80f863e8696e8d6cb0c9317
parent 4a18875581def6ce69a0c93a0a3426e91b850edd
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 27 Feb 2024 11:04:21 -0800

Merge remote-tracking branch 'github/master'

Diffstat:
Mcontent/compiled-locales/en.json | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcontent/locales/en.json | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/components/sections/PurpleBanner.tsx | 4++--
Msrc/components/sections/PurpleBenefits.tsx | 2+-
Msrc/components/sections/PurpleCheckout.tsx | 13+++++++++++--
Msrc/components/sections/PurpleFAQ.tsx | 2+-
Msrc/components/sections/PurpleFinalCTA.tsx | 2+-
Msrc/components/sections/PurpleHero.tsx | 2+-
Asrc/components/ui/AlertDialog.tsx | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 315 insertions(+), 37 deletions(-)

diff --git a/content/compiled-locales/en.json b/content/compiled-locales/en.json @@ -253,12 +253,6 @@ "value": "Download now" } ], - "home.hero.follow-us-on-nostr": [ - { - "type": 0, - "value": "Follow us on Nostr" - } - ], "home.hero.headline": [ { "type": 0, @@ -338,7 +332,7 @@ "purple.benefits.description": [ { "type": 0, - "value": "Help us stay independent in our mission for Freedom tech with our Purple subscription, and look cool doing it!" + "value": "Help us stay independent in our mission for Freedom tech with our Purple membership, and look cool doing it!" } ], "purple.benefits.headline": [ @@ -347,6 +341,90 @@ "value": "Get more from Damus." } ], + "purple.checkout.back": [ + { + "type": 0, + "value": "Go back" + } + ], + "purple.checkout.continue": [ + { + "type": 0, + "value": "Continue in the app to set it up" + } + ], + "purple.checkout.continue.hide-qr": [ + { + "type": 0, + "value": "Hide QR code" + } + ], + "purple.checkout.continue.show-qr": [ + { + "type": 0, + "value": "Show QR code" + } + ], + "purple.checkout.open-in-app": [ + { + "type": 0, + "value": "Open in Damus" + } + ], + "purple.checkout.open-in-wallet": [ + { + "type": 0, + "value": "Open in wallet" + } + ], + "purple.checkout.payment-received": [ + { + "type": 0, + "value": "Payment received" + } + ], + "purple.checkout.purchased-for": [ + { + "type": 0, + "value": "Purchased Damus Purple for:" + } + ], + "purple.checkout.purchasing-for": [ + { + "type": 0, + "value": "Verified. Purchasing Damus Purple for:" + } + ], + "purple.checkout.step-1": [ + { + "type": 0, + "value": "Choose your plan" + } + ], + "purple.checkout.step-2": [ + { + "type": 0, + "value": "Verify your npub" + } + ], + "purple.checkout.step-3": [ + { + "type": 0, + "value": "Lightning payment" + } + ], + "purple.checkout.title": [ + { + "type": 0, + "value": "Checkout" + } + ], + "purple.checkout.waiting-for-payment": [ + { + "type": 0, + "value": "Waiting for payment" + } + ], "purple.faq.1.a": [ { "type": 0, @@ -374,7 +452,7 @@ "purple.faq.3.a": [ { "type": 0, - "value": "Damus Purple is a paid subscription to Damus that gives you access to exclusive features, and helps us fund the project" + "value": "Damus Purple is a paid membership to Damus that gives you access to exclusive features, and helps us fund the project" } ], "purple.faq.3.q": [ @@ -449,6 +527,12 @@ "value": "Frequent Questions" } ], + "purple.final_cta.become-a-member": [ + { + "type": 0, + "value": "Become a member" + } + ], "purple.final_cta.headline": [ { "type": 0, @@ -464,31 +548,31 @@ "purple.final_cta.subheadline": [ { "type": 0, - "value": "for as little as U$6.99/month" + "value": "for as little as 15k sats/month" } ], - "purple.final_cta.subscribe": [ + "purple.final_cta.text": [ { "type": 0, - "value": "Subscribe" + "value": "Contribute to the future of the free internet, access exclusive features, and look cool doing it." } ], - "purple.final_cta.text": [ + "purple.hero.become-a-member": [ { "type": 0, - "value": "Contribute to the future of the free internet, access exclusive features, and look cool doing it." + "value": "Become a member" } ], - "purple.hero.learn-more": [ + "purple.hero.description": [ { "type": 0, - "value": "Learn more" + "value": "For free-speech maximalists" } ], - "purple.hero.subscribe": [ + "purple.hero.learn-more": [ { "type": 0, - "value": "Subscribe" + "value": "Learn more" } ], "roles.brand-ambassador": [ diff --git a/content/locales/en.json b/content/locales/en.json @@ -122,9 +122,6 @@ "home.hero.download_now": { "string": "Download now" }, - "home.hero.follow-us-on-nostr": { - "string": "Follow us on Nostr" - }, "home.hero.headline": { "string": "The social network you control" }, @@ -162,11 +159,53 @@ "string": "Supporter Badge" }, "purple.benefits.description": { - "string": "Help us stay independent in our mission for Freedom tech with our Purple subscription, and look cool doing it!" + "string": "Help us stay independent in our mission for Freedom tech with our Purple membership, and look cool doing it!" }, "purple.benefits.headline": { "string": "Get more from Damus." }, + "purple.checkout.back": { + "string": "Go back" + }, + "purple.checkout.continue": { + "string": "Continue in the app to set it up" + }, + "purple.checkout.continue.hide-qr": { + "string": "Hide QR code" + }, + "purple.checkout.continue.show-qr": { + "string": "Show QR code" + }, + "purple.checkout.open-in-app": { + "string": "Open in Damus" + }, + "purple.checkout.open-in-wallet": { + "string": "Open in wallet" + }, + "purple.checkout.payment-received": { + "string": "Payment received" + }, + "purple.checkout.purchased-for": { + "string": "Purchased Damus Purple for:" + }, + "purple.checkout.purchasing-for": { + "string": "Verified. Purchasing Damus Purple for:" + }, + "purple.checkout.step-1": { + "string": "Choose your plan" + }, + "purple.checkout.step-2": { + "string": "Verify your npub" + }, + "purple.checkout.step-3": { + "string": "Lightning payment" + }, + "purple.checkout.title": { + "string": "Checkout" + }, + "purple.checkout.waiting-for-payment": { + "string": "Waiting for payment" + }, "purple.faq.1.a": { "string": "Yes! You can pay with Lightning if you register on the website. For the iOS app, you can pay with Apple Pay." }, @@ -180,7 +219,7 @@ "string": "Can I pay with lightning via the app?" }, "purple.faq.3.a": { - "string": "Damus Purple is a paid subscription to Damus that gives you access to exclusive features, and helps us fund the project" + "string": "Damus Purple is a paid membership to Damus that gives you access to exclusive features, and helps us fund the project" }, "purple.faq.3.q": { "string": "What is Damus Purple?" @@ -218,6 +257,9 @@ "purple.faq.headline": { "string": "Frequent Questions" }, + "purple.final_cta.become-a-member": { + "string": "Become a member" + }, "purple.final_cta.headline": { "string": "Get Purple today!" }, @@ -225,20 +267,20 @@ "string": "Open in Damus" }, "purple.final_cta.subheadline": { - "string": "for as little as U$6.99/month" - }, - "purple.final_cta.subscribe": { - "string": "Subscribe" + "string": "for as little as 15k sats/month" }, "purple.final_cta.text": { "string": "Contribute to the future of the free internet, access exclusive features, and look cool doing it." }, + "purple.hero.become-a-member": { + "string": "Become a member" + }, + "purple.hero.description": { + "string": "For free-speech maximalists" + }, "purple.hero.learn-more": { "string": "Learn more" }, - "purple.hero.subscribe": { - "string": "Subscribe" - }, "roles.brand-ambassador": { "string": "Brand Ambassador" }, diff --git a/src/components/sections/PurpleBanner.tsx b/src/components/sections/PurpleBanner.tsx @@ -37,7 +37,7 @@ export function PurpleBanner() { </div> <div className="text-purple-200/70 text-md md:text-lg text-center max-w-2xl mb-2 break-keep"> {/* TODO: Localize */} - Help us stay independent in our mission for Freedom tech with our Purple subscription, get exclusive benefits, and look cool doing it! + Help us stay independent in our mission for Freedom tech with our Purple membership, get exclusive benefits, and look cool doing it! </div> <motion.div className="mt-10 md:mt-6 flex flex-col md:flex-row items-center md:items-center gap-y-4 gap-x-6 w-full md:w-auto" @@ -46,7 +46,7 @@ export function PurpleBanner() { > <Link href="/purple/checkout" className="w-full md:w-auto"> <Button variant="default" className="w-full"> - {intl.formatMessage({ id: "purple.hero.subscribe", defaultMessage: "Subscribe" })} + {intl.formatMessage({ id: "purple.hero.become-a-member", defaultMessage: "Become a member" })} </Button> </Link> <Link href="/purple" className="w-full md:w-auto"> diff --git a/src/components/sections/PurpleBenefits.tsx b/src/components/sections/PurpleBenefits.tsx @@ -41,7 +41,7 @@ export function PurpleBenefits({ className }: { className?: string }) { {intl.formatMessage({ id: "purple.benefits.headline", defaultMessage: "Get more from Damus." })} </motion.h2> <motion.div className="text-white/60 text-xl text-center max-w-2xl mb-6 mt-6 break-keep"> - {intl.formatMessage({ id: "purple.benefits.description", defaultMessage: "Help us stay independent in our mission for Freedom tech with our Purple subscription, and look cool doing it!" })} + {intl.formatMessage({ id: "purple.benefits.description", defaultMessage: "Help us stay independent in our mission for Freedom tech with our Purple membership, and look cool doing it!" })} </motion.div> </div> {(intl.locale != "ja" || process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( diff --git a/src/components/sections/PurpleCheckout.tsx b/src/components/sections/PurpleCheckout.tsx @@ -312,10 +312,18 @@ export function PurpleCheckout() { {productTemplates ? Object.entries(productTemplates).map(([name, productTemplate]) => ( <button key={name} - className={`flex flex-col items-center justify-center p-3 border rounded-lg ${name == lnCheckout?.product_template_name ? "border-green-500" : "border-purple-200/50"} disabled:opacity-50 disabled:cursor-not-allowed`} + 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> @@ -374,7 +382,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" /> + <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" /> {/* 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"> @@ -482,6 +490,7 @@ interface LNCheckout { interface ProductTemplate { description: string, + special_label?: string | null, amount_msat: number, expiry: number, } diff --git a/src/components/sections/PurpleFAQ.tsx b/src/components/sections/PurpleFAQ.tsx @@ -17,7 +17,7 @@ export function PurpleFAQ({ className }: { className?: string }) { const faq = [ { question: intl.formatMessage({ id: "purple.faq.3.q", defaultMessage: "What is Damus Purple?" }), - answer: intl.formatMessage({ id: "purple.faq.3.a", defaultMessage: "Damus Purple is a paid subscription to Damus that gives you access to exclusive features, and helps us fund the project" }), + answer: intl.formatMessage({ id: "purple.faq.3.a", defaultMessage: "Damus Purple is a paid membership to Damus that gives you access to exclusive features, and helps us fund the project" }), }, { question: intl.formatMessage({ id: "purple.faq.4.q", defaultMessage: "What are the exclusive features?" }), diff --git a/src/components/sections/PurpleFinalCTA.tsx b/src/components/sections/PurpleFinalCTA.tsx @@ -41,7 +41,7 @@ export function PurpleFinalCTA({ className }: { className?: string }) { > <Link href="/purple/checkout" className="w-full md:w-auto"> <Button variant="default" className="w-full"> - {intl.formatMessage({ id: "purple.final_cta.subscribe", defaultMessage: "Subscribe" })} + {intl.formatMessage({ id: "purple.final_cta.become-a-member", defaultMessage: "Become a member" })} </Button> </Link> <Link href="damus:purple:landing" target="_blank" className="w-full md:w-auto"> diff --git a/src/components/sections/PurpleHero.tsx b/src/components/sections/PurpleHero.tsx @@ -39,7 +39,7 @@ export function PurpleHero() { > <Link href="/purple/checkout" className="w-full md:w-auto"> <Button variant="default" className="w-full"> - {intl.formatMessage({ id: "purple.hero.subscribe", defaultMessage: "Subscribe" })} + {intl.formatMessage({ id: "purple.hero.become-a-member", defaultMessage: "Become a member" })} </Button> </Link> <Link href="#benefits" className="w-full md:w-auto"> diff --git a/src/components/ui/AlertDialog.tsx b/src/components/ui/AlertDialog.tsx @@ -0,0 +1,143 @@ +// Based on https://ui.shadcn.com and MIT licensed. + +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "./Button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Overlay>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Overlay + className={cn( + "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", + className + )} + {...props} + ref={ref} + /> +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> +>(({ className, ...props }, ref) => ( + <AlertDialogPortal> + <AlertDialogOverlay /> + <AlertDialogPrimitive.Content + ref={ref} + className={cn( + "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", + className + )} + {...props} + /> + </AlertDialogPortal> +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + "flex flex-col space-y-2 text-center sm:text-left", + className + )} + {...props} + /> +) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", + className + )} + {...props} + /> +) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Title>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Title + ref={ref} + className={cn("text-lg font-semibold", className)} + {...props} + /> +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Description>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Description + ref={ref} + className={cn("text-sm text-gray-500", className)} + {...props} + /> +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Action>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Action + ref={ref} + className={cn(buttonVariants(), className)} + {...props} + /> +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Cancel>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> +>(({ className, ...props }, ref) => ( + <AlertDialogPrimitive.Cancel + ref={ref} + className={cn( + buttonVariants({ variant: "default" }), + "mt-2 sm:mt-0", + className + )} + {...props} + /> +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}