Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
6.7 kB · 208 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209"use client";import { useState } from "react";import { useRouter } from "next/navigation";import { SubscribeWithHandle } from "./HandleSubscribe";import { EmailInput, EmailConfirm } from "./EmailSubscribe";import { EmailSubscribeSuccess } from "./EmailSubscribeSuccess";import { Modal } from "components/Modal";import { ButtonPrimary } from "components/Buttons";import { ManageSubscription } from "./ManageSubscribe";import { useToaster } from "components/Toast";import { requestPublicationEmailSubscription, confirmPublicationEmailSubscription,} from "actions/publications/subscribeEmail";
import { useViewerSubscription } from "./viewerSubscription";
export type SubscribeProps = { autoFocus?: boolean; publicationUri: string; publicationUrl?: string; publicationName: string; publicationDescription?: string; newsletterMode: boolean;};
export const SubscribePanel = (props: SubscribeProps) => { return ( <div className="px-3 sm:px-4 w-full"> <div className="accent-container rounded-lg! w-full px-3 pt-3 pb-4 sm:px-4 sm:pt-4 sm:pb-5 text-center justify-center"> <h3 className="leading-snug text-secondary">{props.publicationName}</h3> {props.publicationDescription && ( <div className="text-tertiary pb-3"> {props.publicationDescription} </div> )}
<div className="max-w-sm mx-auto "> <SubscribeInput {...props} /> </div> </div> </div> );};
export const SubscribeButton = (props: SubscribeProps) => { const user = useViewerSubscription(props.publicationUri); return ( <Modal className="px-0! py-3! sm:py-4! w-[1000px] sm:max-w-md max-w-full" asChild trigger={ user.subscribed ? ( <ManageSubscription publicationUri={props.publicationUri} publicationUrl={props.publicationUrl} newsletterMode={props.newsletterMode} user={user} /> ) : ( <ButtonPrimary compact className="text-sm"> Subscribe </ButtonPrimary> ) } > <SubscribePanel {...props} autoFocus /> </Modal> );};
export const SubscribeInput = (props: SubscribeProps) => { let toaster = useToaster(); let router = useRouter(); const user = useViewerSubscription(props.publicationUri); let [email, setEmail] = useState(user.email ?? ""); let [confirmState, setConfirmState] = useState<"confirm" | "success">( "confirm", ); let [confirmOpen, setConfirmOpen] = useState(false); let [requesting, setRequesting] = useState(false); let [confirming, setConfirming] = useState(false); let [locallySubscribed, setLocallySubscribed] = useState(false);
const isSubscribed = user.subscribed || locallySubscribed; return ( <> {isSubscribed ? ( <ManageSubscription publicationUri={props.publicationUri} publicationUrl={props.publicationUrl} newsletterMode={props.newsletterMode} user={user} /> ) : props.newsletterMode ? ( <EmailInput value={email} onChange={setEmail} disabled={user.loggedIn && !!user.email} autoFocus={props.autoFocus} loading={requesting} action={ <ButtonPrimary compact className="leading-tight! outline-none! text-sm!" disabled={requesting || !email} onClick={async () => { if (requesting) return; setRequesting(true); let res = await requestPublicationEmailSubscription( props.publicationUri, email, ); setRequesting(false); if (!res.ok) { toaster({ type: "error", content: ERROR_MESSAGES[res.error], }); return; } if (res.value.confirmed) { setConfirmState("success"); router.refresh(); } setConfirmOpen(true); }} > Subscribe </ButtonPrimary> } /> ) : ( <SubscribeWithHandle user={user} autoFocus={props.autoFocus} publicationUri={props.publicationUri} onSubscribed={() => setLocallySubscribed(true)} /> )} {props.newsletterMode && ( <Modal open={confirmOpen} onOpenChange={(open) => { setConfirmOpen(open); if (!open) { if (confirmState === "success") setLocallySubscribed(true); setConfirmState("confirm"); } }} > {confirmState === "success" ? ( <EmailSubscribeSuccess email={email} handle={user.handle} /> ) : ( <EmailConfirm autoFocus loading={confirming} onBack={() => setConfirmOpen(false)} emailValue={email} onSubmit={async (code) => { if (confirming) return; setConfirming(true); let res = await confirmPublicationEmailSubscription( props.publicationUri, email, code, ); setConfirming(false); if (!res.ok) { toaster({ type: "error", content: ERROR_MESSAGES[res.error], }); return; } setConfirmState("success"); router.refresh(); }} /> )} </Modal> )} </> );};
type SubscribeError = | "invalid_email" | "newsletter_disabled" | "email_send_failed" | "subscriber_not_found" | "invalid_code" | "database_error" | "suppressed_spam_complaint" | "suppression_delete_failed";
const ERROR_MESSAGES: Record<SubscribeError, string> = { invalid_email: "Please enter a valid email address.", newsletter_disabled: "This publication isn't accepting email subscriptions.", email_send_failed: "We couldn't send the confirmation email. Try again.", subscriber_not_found: "No pending subscription. Start over.", invalid_code: "That code didn't match. Try again.", database_error: "Something went wrong. Try again.", suppressed_spam_complaint: "This address was previously marked as spam and can't be resubscribed. Contact the publication to resolve.", suppression_delete_failed: "We couldn't clear a prior delivery issue on this address. Try again later.",};