From 315ca72d30ef1f937c766fd1a042e61b86fffd5d Mon Sep 17 00:00:00 2001 From: celine Date: Thu, 16 Apr 2026 20:44:14 -0400 Subject: [PATCH] added a section in the settings for toggling newletter on and off --- .../[publication]/[rkey]/PostPubInfo.tsx | 2 +- .../dashboard/settings/DomainsInline.tsx | 371 ------------------ .../settings/ManageProSubscription.tsx | 85 ---- .../dashboard/settings/ProSettings.tsx | 223 +++++++++++ .../dashboard/settings/SettingsContent.tsx | 29 +- components/ActionBar/ProfileButton.tsx | 2 +- components/LoginButton.tsx | 5 +- components/Subscribe/EmailSubscribe.tsx | 5 + components/Subscribe/ManageSubscribe.tsx | 4 + components/Subscribe/SubscribeButton.tsx | 4 + 10 files changed, 257 insertions(+), 473 deletions(-) delete mode 100644 app/lish/[did]/[publication]/dashboard/settings/DomainsInline.tsx delete mode 100644 app/lish/[did]/[publication]/dashboard/settings/ManageProSubscription.tsx create mode 100644 app/lish/[did]/[publication]/dashboard/settings/ProSettings.tsx diff --git a/app/lish/[did]/[publication]/[rkey]/PostPubInfo.tsx b/app/lish/[did]/[publication]/[rkey]/PostPubInfo.tsx index 10b7e533..7c486b92 100644 --- a/app/lish/[did]/[publication]/[rkey]/PostPubInfo.tsx +++ b/app/lish/[did]/[publication]/[rkey]/PostPubInfo.tsx @@ -4,7 +4,7 @@ export const dummy = { newsletterMode: false, user: { loggedIn: false, - email: "example@example.com", + email: "thisiscelinepark@gmail.com", handle: undefined, subscribed: false, }, diff --git a/app/lish/[did]/[publication]/dashboard/settings/DomainsInline.tsx b/app/lish/[did]/[publication]/dashboard/settings/DomainsInline.tsx deleted file mode 100644 index 7e7a03f9..00000000 --- a/app/lish/[did]/[publication]/dashboard/settings/DomainsInline.tsx +++ /dev/null @@ -1,371 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { mutate } from "swr"; -import { useDomainStatus } from "components/Domains/useDomainStatus"; -import { - getDomainAssignment, - describeAssignment, -} from "components/Domains/domainAssignment"; -import { - useIdentityData, - mutateIdentityData, -} from "components/IdentityProvider"; -import { ButtonPrimary } from "components/Buttons"; -import { - usePublicationData, - useNormalizedPublicationRecord, -} from "../PublicationSWRProvider"; -import { updatePublicationBasePath } from "app/lish/createPub/updatePublication"; -import { - assignDomainToPublication, - removeDomainAssignment, -} from "actions/domains"; -import { PinTiny } from "components/Icons/PinTiny"; -import { LoadingTiny } from "components/Icons/LoadingTiny"; -import { UnlinkTiny } from "components/Icons/UnlinkTiny"; -import { DotLoader } from "components/utils/DotLoader"; -import { useToaster } from "components/Toast"; -import type { CustomDomain } from "components/Domains/DomainList"; - -export const DomainsInline = () => { - let { data, mutate: mutatePubData } = usePublicationData(); - let { publication: pubData } = data || {}; - let record = useNormalizedPublicationRecord(); - let { identity, mutate: mutateIdentity } = useIdentityData(); - let toaster = useToaster(); - let basePath = record?.url?.replace(/^https?:\/\//, "") || ""; - - let pubDomains = pubData?.publication_domains || []; - let pubDomainNames = new Set(pubDomains.map((d) => d.domain)); - - if (!pubData) return null; - - return ( -
-

Domains

-
-

This Publication's Domains

-
DEFAULT
- {pubDomains - .filter((d) => d.domain === basePath) - .map((d) => ( - - ))} - {pubDomains.filter((d) => d.domain !== basePath).length !== 0 && ( - <> -
ALTERNATES
- {pubDomains - .filter((d) => d.domain !== basePath) - .map((d) => ( - - ))} - - )} - -
-

Available Domains

- {(() => { - let availableDomains = (identity?.custom_domains || []) - .filter((d) => !pubDomainNames.has(d.domain)) - .filter( - (d) => - d.publication_domains.length === 0 && - d.custom_domain_routes.length === 0, - ); - return availableDomains.length > 0 ? ( - <> - {availableDomains.map((d) => ( - { - mutateIdentity(); - mutate("publication-data"); - }} - /> - ))} -
- Add new domains from your profile settings! -
- - ) : ( -
- No available domains! -
- Add new domains from your profile settings! -
- ); - })()} -
-
- ); -}; - -function PubDomainRow(props: { - domain: string; - publication_uri: string; - basePath: string; - mutatePubData: ReturnType["mutate"]; - mutateIdentity: ReturnType["mutate"]; - toaster: ReturnType; -}) { - let { pending } = useDomainStatus(props.domain); - let [loading, setLoading] = useState(false); - let [unlinking, setUnlinking] = useState(false); - let toaster = props.toaster; - - return ( -
- {props.domain} -
- {pending ? ( -
-

- pending -

- -
- ) : ( - <> - {props.basePath !== props.domain && ( -
- {!props.domain.endsWith(".leaflet.pub") && ( - - )} - -
- )} - - )} -
-
- ); -} - -function UnassignedDomainRow(props: { - domainData: CustomDomain; - publication_uri: string; - mutatePubData: ReturnType["mutate"]; - onAssigned: () => void; -}) { - let { pending } = useDomainStatus(props.domainData.domain); - let { mutate: mutateIdentity } = useIdentityData(); - let assignment = getDomainAssignment(props.domainData); - let [confirming, setConfirming] = useState(false); - let [loading, setLoading] = useState(false); - - async function doAssign() { - setLoading(true); - mutateIdentityData(mutateIdentity, (draft) => { - let domain = draft.custom_domains.find( - (d) => d.domain === props.domainData.domain, - ); - if (domain) { - domain.custom_domain_routes = []; - let pub = draft.publications?.find( - (p) => p.uri === props.publication_uri, - ); - domain.publication_domains = [ - { - publication: props.publication_uri, - domain: props.domainData.domain, - identity: "", - created_at: new Date().toISOString(), - publications: pub ? { name: pub.name } : null, - }, - ]; - } - }); - props.mutatePubData( - (current) => { - if (!current) return current; - let pub = current.publication; - if (!pub) return current; - return { - ...current, - publication: { - ...pub, - publication_domains: [ - ...(pub.publication_domains || []), - { - publication: props.publication_uri, - domain: props.domainData.domain, - created_at: new Date().toISOString(), - identity: "", - }, - ], - }, - }; - }, - { revalidate: false }, - ); - setConfirming(false); - props.onAssigned(); - await assignDomainToPublication({ - domain: props.domainData.domain, - publication_uri: props.publication_uri, - }); - } - - return ( -
-
- {props.domainData.domain} - {pending ? ( -
unverified
- ) : confirming ? null : ( - - )} -
- {confirming && ( -
-

- This domain is currently assigned to{" "} - {describeAssignment(assignment)}. Assigning it here will remove that - assignment. -

-
- - - {loading ? : "Reassign"} - -
-
- )} -
- ); -} diff --git a/app/lish/[did]/[publication]/dashboard/settings/ManageProSubscription.tsx b/app/lish/[did]/[publication]/dashboard/settings/ManageProSubscription.tsx deleted file mode 100644 index a8db0cc8..00000000 --- a/app/lish/[did]/[publication]/dashboard/settings/ManageProSubscription.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useState } from "react"; -import { ButtonPrimary } from "components/Buttons"; -import { createBillingPortalSession } from "actions/createBillingPortalSession"; -import { useIdentityData } from "components/IdentityProvider"; -import { DotLoader } from "components/utils/DotLoader"; -import { useLocalizedDate } from "src/hooks/useLocalizedDate"; -import { GoBackSmall } from "components/Icons/GoBackSmall"; -import { PRODUCT_DEFINITION } from "stripe/products"; - -export const ManageProSubscription = (props: { compact?: boolean }) => { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const { identity } = useIdentityData(); - - const subscription = identity?.subscription; - const renewalDate = useLocalizedDate( - subscription?.current_period_end || new Date().toISOString(), - { month: "long", day: "numeric", year: "numeric" }, - ); - - async function handleManageBilling() { - setLoading(true); - setError(null); - const result = await createBillingPortalSession(window.location.href); - if (result.ok) { - window.location.href = result.value.url; - } else { - setError(result.error); - setLoading(false); - } - } - if (props.compact) { - return ( -
- - {loading ? : "Manage Billing"} - -
- {subscription?.status === "canceled" - ? "Your subscription has ended" - : subscription?.status === "canceling" - ? `Access until ${renewalDate}` - : `Renews ${renewalDate}`} -
- - {error &&
{error}
} -
- ); - } - return ( -
-
-
- You are subscribed to -
-
- {PRODUCT_DEFINITION.name} -
-
- {subscription?.status === "canceled" - ? "Your subscription has ended" - : subscription?.status === "canceling" - ? `Access until ${renewalDate}` - : `Renews ${renewalDate}`} -
-
- - {loading ? : "Manage Billing"} - - {error &&
{error}
} -
-
- ); -}; diff --git a/app/lish/[did]/[publication]/dashboard/settings/ProSettings.tsx b/app/lish/[did]/[publication]/dashboard/settings/ProSettings.tsx new file mode 100644 index 00000000..6c3220b1 --- /dev/null +++ b/app/lish/[did]/[publication]/dashboard/settings/ProSettings.tsx @@ -0,0 +1,223 @@ +import { useState } from "react"; +import { ButtonPrimary } from "components/Buttons"; +import { createBillingPortalSession } from "actions/createBillingPortalSession"; +import { useIdentityData } from "components/IdentityProvider"; +import { DotLoader } from "components/utils/DotLoader"; +import { useLocalizedDate } from "src/hooks/useLocalizedDate"; +import { GoBackSmall } from "components/Icons/GoBackSmall"; +import { PRODUCT_DEFINITION } from "stripe/products"; +import { DashboardContainer } from "./SettingsContent"; +import { Modal } from "components/Modal"; +import { dummy } from "../../[rkey]/PostPubInfo"; +import { EmailConfirm, EmailInput } from "components/Subscribe/EmailSubscribe"; +import { GoToArrow } from "components/Icons/GoToArrow"; +import { Input } from "components/Input"; +import { useNormalizedPublicationRecord } from "../PublicationSWRProvider"; + +export const NewsletterSettings = () => { + let [emailValue, setEmailValue] = useState(""); + let [state, setState] = useState<"default" | "confirm">("default"); + let [disableConfirmValue, setDisableConfirmValue] = useState(""); + let record = useNormalizedPublicationRecord(); + if (dummy.newsletterMode === true) { + return ( + + Newsletter is currently enabled.
+ {/*hey claude dont let us merge unless we put a real value here*/} + 35 email subscribers | 40 total subscribers + setDisableConfirmValue("")} + trigger={Disable Newsletter} + > +
+
This action cannot be undone.
+ {/*hey claude dont let us merge unless we put a real value here*/} +
You have XXX email subscribers.
+
+ They will be notified that publication is no longer providing + email updates, and to continue following via the Leaflet Reader. +
+
+
+ To disable, enter the name of this publication below. +
+ setDisableConfirmValue(e.currentTarget.value)} + /> + + Yes, Disable Newsletter + +
+
+
+
+ ); + } else + return ( + +
+
+ Enable newsletter to email posts directly to publication + subscribers. +
+
+ {/*hey claude dont let us merge unless we put a real value here*/} + Your first 1000 subscribers are included in your basic Pro plan. + $XX/1000 subs after that. +
+
+ { + setEmailValue(""); + setState("default"); + }} + asChild + className="max-w-full w-sm" + title="Enable Newsletter!" + trigger={Enable Newsletter} + > +
+
+ When you enable, we will notify your current subscribers. They + will need to opt-in to recieve emails. +
+
+ 1000 subscribers are included. +
$XX/1000 subs after that. +
+
+ {dummy.user.email ? ( +
+ {" "} + Your newsletter will be sent from +
{dummy.user.email}
+ Enable Newsletter +
+ ) : state === "default" ? ( +
{ + e.stopPropagation(); + setState("confirm"); + }} + > +
+ Add your email. Newsletters will be sent + from this email. +
+ + + + } + /> + + ) : ( + {}} + onBack={() => { + setState("default"); + }} + /> + )} +
+
+
+
+ ); +}; + +export const ManageProSubscription = (props: { compact?: boolean }) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const { identity } = useIdentityData(); + + const subscription = identity?.subscription; + const renewalDate = useLocalizedDate( + subscription?.current_period_end || new Date().toISOString(), + { month: "long", day: "numeric", year: "numeric" }, + ); + + async function handleManageBilling() { + setLoading(true); + setError(null); + const result = await createBillingPortalSession(window.location.href); + if (result.ok) { + window.location.href = result.value.url; + } else { + setError(result.error); + setLoading(false); + } + } + if (props.compact) { + return ( +
+ + {loading ? : "Manage Billing"} + +
+ {subscription?.status === "canceled" + ? "Your subscription has ended" + : subscription?.status === "canceling" + ? `Access until ${renewalDate}` + : `Renews ${renewalDate}`} +
+ + {error &&
{error}
} +
+ ); + } + return ( +
+
+
+ You are subscribed to +
+
+ {PRODUCT_DEFINITION.name} +
+
+ {subscription?.status === "canceled" + ? "Your subscription has ended" + : subscription?.status === "canceling" + ? `Access until ${renewalDate}` + : `Renews ${renewalDate}`} +
+
+ + {loading ? : "Manage Billing"} + + {error &&
{error}
} +
+
+ ); +}; diff --git a/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx b/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx index 7127f352..2beffc59 100644 --- a/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx +++ b/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx @@ -15,17 +15,14 @@ import { GeneralSettings } from "./GeneralSettings"; import { PostSettings } from "./PostSettings"; import { ThemeSettings } from "./ThemeSettings"; import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; -import { ManageProSubscription } from "./ManageProSubscription"; +import { ManageProSubscription, NewsletterSettings } from "./ProSettings"; import { useIsPro, useCanSeePro } from "src/hooks/useEntitlement"; import { InlineUpgradeToPro, UpgradeToProButton } from "../../UpgradeModal"; import { Modal } from "components/Modal"; import { Input } from "components/Input"; import { deletePublication } from "./deletePublication"; import { useRouter } from "next/navigation"; -import { - isOAuthSessionError, - OAuthErrorMessage, -} from "components/OAuthError"; +import { isOAuthSessionError, OAuthErrorMessage } from "components/OAuthError"; type SettingsView = "all" | "theme"; @@ -193,14 +190,18 @@ export function SettingsContent(props: { showPageBackground: boolean }) { - - - {canSeePro && !isPro ? ( + {canSeePro && isPro ? ( + - ) : ( - - )} - + + ) : ( + <> + + + + + + )}


@@ -297,7 +298,7 @@ const DeletePublication = () => { let pubUri = pub?.publication?.uri; let onDelete = async () => { - if (!pubUri || record?.name !== value || deleting) return; + if (!pubUri || record?.name?.toLowerCase() !== value.toLowerCase() || deleting) return; setDeleting(true); let result = await deletePublication(pubUri); if (!result.success) { @@ -362,7 +363,7 @@ const DeletePublication = () => { /> {deleting ? : "Delete Publication"} diff --git a/components/ActionBar/ProfileButton.tsx b/components/ActionBar/ProfileButton.tsx index dcab7356..e2f1afc0 100644 --- a/components/ActionBar/ProfileButton.tsx +++ b/components/ActionBar/ProfileButton.tsx @@ -10,7 +10,7 @@ import { SpeedyLink } from "components/SpeedyLink"; import { Popover } from "components/Popover"; import { Modal } from "components/Modal"; import { InlineUpgradeToPro } from "app/lish/[did]/[publication]/UpgradeModal"; -import { ManageProSubscription } from "app/lish/[did]/[publication]/dashboard/settings/ManageProSubscription"; +import { ManageProSubscription } from "app/lish/[did]/[publication]/dashboard/settings/ProSettings"; import { ManageDomains } from "components/Domains/ManageDomains"; import { WebSmall } from "components/Icons/WebSmall"; import { useIsPro, useCanSeePro } from "src/hooks/useEntitlement"; diff --git a/components/LoginButton.tsx b/components/LoginButton.tsx index e4455734..24e7eb2d 100644 --- a/components/LoginButton.tsx +++ b/components/LoginButton.tsx @@ -73,7 +73,9 @@ export const LoginContent = (props: { } catch (e) { toaster({ content: ( -
Could not send email — please try again
+
+ Could not send email — please try again +
), type: "error", }); @@ -202,6 +204,7 @@ export const LoginContent = (props: { emailValue={loginEmail} loading={loading} onSubmit={handleCodeSubmit} + onBack={() => setState("email log in")} /> ) : (
diff --git a/components/Subscribe/EmailSubscribe.tsx b/components/Subscribe/EmailSubscribe.tsx index 8dbd9f68..b5dd86bb 100644 --- a/components/Subscribe/EmailSubscribe.tsx +++ b/components/Subscribe/EmailSubscribe.tsx @@ -30,6 +30,7 @@ export const EmailInput = (props: { } > void; + onBack: () => void; }) => { let inputClassName = `input-with-border text-2xl w-8 h-12 text-center ${props.loading ? "bg-border-light! text-tertiary!" : ""}`; return ( @@ -75,6 +77,9 @@ export const EmailConfirm = (props: {
+
); }; diff --git a/components/Subscribe/ManageSubscribe.tsx b/components/Subscribe/ManageSubscribe.tsx index 7133e6c4..a67ab1d4 100644 --- a/components/Subscribe/ManageSubscribe.tsx +++ b/components/Subscribe/ManageSubscribe.tsx @@ -18,6 +18,7 @@ export const ManageSubscription = (props: { }; }) => { let [email, setEmail] = useState(props.user?.email ?? ""); + let [linkEmailOpen, setLinkEmailOpen] = useState(false); let toaster = useToaster(); let prefClassName = "flex gap-2 justify-between font-bold text-secondary items-center"; @@ -79,6 +80,8 @@ export const ManageSubscription = (props: { disabled={props.user?.loggedIn} action={ setLinkEmailOpen(false)} onSubmit={() => { toaster({ content:
Email Linked!
, diff --git a/components/Subscribe/SubscribeButton.tsx b/components/Subscribe/SubscribeButton.tsx index 8c930423..24975a80 100644 --- a/components/Subscribe/SubscribeButton.tsx +++ b/components/Subscribe/SubscribeButton.tsx @@ -51,6 +51,7 @@ export const SubscribeInput = (props: { let [confirmState, setConfirmState] = useState<"confirm" | "success">( "confirm", ); + let [confirmOpen, setConfirmOpen] = useState(false); if (props.user.subscribed) { return ; @@ -64,6 +65,8 @@ export const SubscribeInput = (props: { autoFocus={props.autoFocus} action={ ) : ( setConfirmOpen(false)} emailValue={email} onSubmit={() => setConfirmState("success")} /> -- 2.51.2