diff --git a/app/(app)/(home-pages)/p/[didOrHandle]/comments/page.tsx b/app/(app)/(home-pages)/p/[didOrHandle]/comments/page.tsx index 146fcc31..079222d7 100644 --- a/app/(app)/(home-pages)/p/[didOrHandle]/comments/page.tsx +++ b/app/(app)/(home-pages)/p/[didOrHandle]/comments/page.tsx @@ -36,15 +36,5 @@ async function ProfileCommentsLoader({ did }: { did: string }) { } function ProfileCommentsSkeleton() { - return ( -
- {[0, 1, 2].map((i) => ( -
-
-
-
-
- ))} -
- ); + return null; } diff --git a/app/(app)/lish/[did]/[publication]/[rkey]/Interactions/Comments/CommentsSection.tsx b/app/(app)/lish/[did]/[publication]/[rkey]/Interactions/Comments/CommentsSection.tsx index ec23b672..28d24eef 100644 --- a/app/(app)/lish/[did]/[publication]/[rkey]/Interactions/Comments/CommentsSection.tsx +++ b/app/(app)/lish/[did]/[publication]/[rkey]/Interactions/Comments/CommentsSection.tsx @@ -45,15 +45,5 @@ export async function CommentsSection({ } export function CommentsSkeleton() { - return ( -
- {[0, 1, 2].map((i) => ( -
-
-
-
-
- ))} -
- ); + return null; } diff --git a/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx b/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx index 882690c4..67559b97 100644 --- a/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx +++ b/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx @@ -1,12 +1,17 @@ "use client"; -import { usePublicationData } from "./PublicationSWRProvider"; import { ButtonPrimary } from "components/Buttons"; import { useSmoker } from "components/Toast"; import { Separator } from "components/Layout"; import { useLocalizedDate } from "src/hooks/useLocalizedDate"; -import { useDashboardState } from "components/PageLayouts/dashboardState"; +import { + useDashboardState, + useSetDashboardState, +} from "components/PageLayouts/dashboardState"; import { AtmosphereAccount } from "components/Icons/AtmosphereAccount"; import { EmailTiny } from "components/Icons/EmailTiny"; +import { DashboardPageLayout } from "components/PageLayouts/DashboardPageLayout"; +import { Popover } from "components/Popover"; +import { Checkbox } from "components/Checkbox"; export type SubscriberStatus = "subscribed" | "unconfirmed" | "unsubscribed"; @@ -19,75 +24,15 @@ export type MergedSubscriber = { status: SubscriberStatus; }; -export function useMergedSubscribers(): MergedSubscriber[] | null { - let { data: publication } = usePublicationData(); - let { subscriberStatus } = useDashboardState(); - - if (!publication) return null; - // ATProto subscribers have no email lifecycle state — they're just present - // or absent, so they only count under the "subscribed" status filter. - let atprotoSubs = subscriberStatus.subscribed - ? publication.publication?.publication_subscriptions || [] - : []; - let newsletterEnabled = - !!publication.publication?.publication_newsletter_settings?.enabled; - let emailSubs = newsletterEnabled - ? (publication.publication?.publication_email_subscribers || []).filter( - (s) => { - if (s.state === "confirmed") return subscriberStatus.subscribed; - if (s.state === "pending") return subscriberStatus.unconfirmed; - if (s.state === "unsubscribed") return subscriberStatus.unsubscribed; - return false; - }, - ) - : []; - - let byDid = new Map(); - let emailOnly: MergedSubscriber[] = []; - for (let s of atprotoSubs) { - let did = s.identities?.atp_did ?? undefined; - if (!did) continue; - byDid.set(did, { - key: `did:${did}`, - did, - handle: undefined, - email: undefined, - created_at: s.created_at, - status: "subscribed", - }); - } - for (let s of emailSubs) { - let status: SubscriberStatus = - s.state === "pending" - ? "unconfirmed" - : s.state === "unsubscribed" - ? "unsubscribed" - : "subscribed"; - let linkedDid = s.identities?.atp_did ?? undefined; - let existing = linkedDid ? byDid.get(linkedDid) : undefined; - if (existing && status === "subscribed") { - existing.email = s.email; - continue; - } - emailOnly.push({ - key: `email:${s.id}`, - did: linkedDid, - handle: undefined, - email: s.email, - created_at: s.created_at, - status, - }); - } - return [...byDid.values(), ...emailOnly]; -} - -export function SubscribersListView(props: { +export function PublicationSubscribers(props: { subscribers: MergedSubscriber[]; publicationShareUrl: string; - showPageBackground?: boolean; + publicationUri: string; + showPageBackground: boolean; }) { let smoker = useSmoker(); let { subscriberStatus } = useDashboardState(); + let filtered = props.subscribers.filter((s) => subscriberStatus[s.status]); let activeStatuses = ( Object.keys(subscriberStatus) as SubscriberStatus[] @@ -95,89 +40,83 @@ export function SubscribersListView(props: { let isDefaultStatusFilter = activeStatuses.length === 1 && activeStatuses[0] === "subscribed"; - let filtered = props.subscribers.filter((s) => subscriberStatus[s.status]); - - if (filtered.length === 0) { - if (!isDefaultStatusFilter) { - return ( -
-

No subscribers match your filters!

-
- ); - } - return ( -
-

No subscribers yet

-

Start sharing your publication!

- { - e.preventDefault(); - let rect = (e.currentTarget as Element)?.getBoundingClientRect(); - navigator.clipboard.writeText(props.publicationShareUrl); - smoker({ - position: { - x: rect ? rect.left + (rect.right - rect.left) / 2 : 0, - y: rect ? rect.top + 26 : 0, - }, - text: "Copied Publication URL!", - }); - }} - > - Copy Share Link - -
- ); - } + let bgStyle = props.showPageBackground + ? { backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha)) " } + : { backgroundColor: "transparent" }; + let bgBorder = props.showPageBackground + ? "border-border-light p-2" + : "border-transparent"; return ( -
} + publication={props.publicationUri} + showHeader={true} + controls={ +
+
+ {filtered.length} Subscriber{filtered.length !== 1 && "s"} +
+ +
} > -
- {filtered - .sort((a, b) => b.created_at.localeCompare(a.created_at)) - .map((subscriber) => ( -
- -
-
- ))} -
-
+ {filtered.length === 0 ? ( +
+ {isDefaultStatusFilter ? ( + <> +

No subscribers yet

+

Start sharing your publication!

+ { + e.preventDefault(); + let rect = ( + e.currentTarget as Element + )?.getBoundingClientRect(); + navigator.clipboard.writeText(props.publicationShareUrl); + smoker({ + position: { + x: rect ? rect.left + (rect.right - rect.left) / 2 : 0, + y: rect ? rect.top + 26 : 0, + }, + text: "Copied Publication URL!", + }); + }} + > + Copy Share Link + + + ) : ( +

No subscribers match your filters!

+ )} +
+ ) : ( +
+
+ {filtered + .sort((a, b) => b.created_at.localeCompare(a.created_at)) + .map((subscriber) => ( +
+ +
+
+ ))} +
+
+ )} + ); } @@ -245,3 +184,63 @@ function SubscriberDate(props: { createdAt: string }) {
); } + +const SubscriberStatusFilter = () => { + let { subscriberStatus } = useDashboardState(); + let setState = useSetDashboardState(); + let count = Object.values(subscriberStatus).filter(Boolean).length; + + return ( + + Filters {count > 0 && `(${count})`} +
+ } + > + + setState({ + subscriberStatus: { + ...subscriberStatus, + subscribed: !!e.target.checked, + }, + }) + } + > + Subscribed + + + setState({ + subscriberStatus: { + ...subscriberStatus, + unconfirmed: !!e.target.checked, + }, + }) + } + > + Unconfirmed + + + setState({ + subscriberStatus: { + ...subscriberStatus, + unsubscribed: !!e.target.checked, + }, + }) + } + > + Unsubscribed + + + ); +}; diff --git a/app/(app)/lish/[did]/[publication]/dashboard/SubsPageShell.tsx b/app/(app)/lish/[did]/[publication]/dashboard/SubsPageShell.tsx deleted file mode 100644 index bac567e3..00000000 --- a/app/(app)/lish/[did]/[publication]/dashboard/SubsPageShell.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; - -import { DashboardPageLayout } from "components/PageLayouts/DashboardPageLayout"; -import { Popover } from "components/Popover"; -import { Checkbox } from "components/Checkbox"; -import { - useDashboardState, - useSetDashboardState, -} from "components/PageLayouts/dashboardState"; -import { usePublicationData } from "./PublicationSWRProvider"; -import { useMergedSubscribers } from "./PublicationSubscribers"; - -export function SubsPageShell({ children }: { children: React.ReactNode }) { - let { data } = usePublicationData(); - let pubUri = data?.publication?.uri || ""; - let subscribers = useMergedSubscribers(); - let count = subscribers?.length ?? 0; - - return ( - } - publication={pubUri} - showHeader={true} - controls={ -
-
- {count} Subscriber{count !== 1 && "s"} -
- -
- } - > - {children} -
- ); -} - -const SubscriberStatusFilter = () => { - let { subscriberStatus } = useDashboardState(); - let setState = useSetDashboardState(); - let count = Object.values(subscriberStatus).filter(Boolean).length; - - return ( - - Filters {count > 0 && `(${count})`} -
- } - > - - setState({ - subscriberStatus: { - ...subscriberStatus, - subscribed: !!e.target.checked, - }, - }) - } - > - Subscribed - - - setState({ - subscriberStatus: { - ...subscriberStatus, - unconfirmed: !!e.target.checked, - }, - }) - } - > - Unconfirmed - - - setState({ - subscriberStatus: { - ...subscriberStatus, - unsubscribed: !!e.target.checked, - }, - }) - } - > - Unsubscribed - - - ); -}; diff --git a/app/(app)/lish/[did]/[publication]/dashboard/SubscribersList.tsx b/app/(app)/lish/[did]/[publication]/dashboard/SubscribersList.tsx deleted file mode 100644 index c2a39b11..00000000 --- a/app/(app)/lish/[did]/[publication]/dashboard/SubscribersList.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { supabaseServerClient } from "supabase/serverClient"; -import { getProfiles } from "src/identity"; -import { get_publication_data } from "app/api/rpc/[command]/get_publication_data"; -import { getPublicationURL } from "app/(app)/lish/createPub/getPublicationURL"; -import { normalizePublicationRecord } from "src/utils/normalizeRecords"; -import { - SubscribersListView, - type MergedSubscriber, - type SubscriberStatus, -} from "./PublicationSubscribers"; - -export async function SubscribersList({ - did, - publication, -}: { - did: string; - publication: string; -}) { - const { result } = await get_publication_data.handler( - { did, publication_name: publication }, - { supabase: supabaseServerClient }, - ); - const pub = result.publication; - if (!pub) { - return ( - - ); - } - const record = normalizePublicationRecord(pub.record); - const showPageBackground = !!record?.theme?.showPageBackground; - - const atprotoSubs = pub.publication_subscriptions || []; - const newsletterEnabled = !!pub.publication_newsletter_settings?.enabled; - const emailSubs = newsletterEnabled - ? pub.publication_email_subscribers || [] - : []; - - const dids = new Set(); - for (const s of atprotoSubs) { - const d = s.identities?.atp_did ?? undefined; - if (d) dids.add(d); - } - for (const s of emailSubs) { - if (s.identities?.atp_did) dids.add(s.identities.atp_did); - } - - const profiles = await getProfiles(Array.from(dids)); - - const byDid = new Map(); - const emailOnly: MergedSubscriber[] = []; - - for (const s of atprotoSubs) { - const d = s.identities?.atp_did ?? undefined; - if (!d) continue; - const p = profiles.get(d); - byDid.set(d, { - key: `did:${d}`, - did: d, - handle: p?.handle ?? undefined, - email: undefined, - created_at: s.created_at, - status: "subscribed", - }); - } - - for (const s of emailSubs) { - const status: SubscriberStatus = - s.state === "pending" - ? "unconfirmed" - : s.state === "unsubscribed" - ? "unsubscribed" - : "subscribed"; - const linkedDid = s.identities?.atp_did ?? undefined; - const existing = linkedDid ? byDid.get(linkedDid) : undefined; - if (existing && status === "subscribed") { - existing.email = s.email; - continue; - } - const p = linkedDid ? profiles.get(linkedDid) : null; - emailOnly.push({ - key: `email:${s.id}`, - did: linkedDid, - handle: p?.handle ?? undefined, - email: s.email, - created_at: s.created_at, - status, - }); - } - - const subscribers = [...byDid.values(), ...emailOnly]; - - return ( - - ); -} diff --git a/app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx b/app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx index 64f5d027..c3b45bb7 100644 --- a/app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx +++ b/app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx @@ -1,6 +1,13 @@ -import { Suspense } from "react"; -import { SubsPageShell } from "../SubsPageShell"; -import { SubscribersList } from "../SubscribersList"; +import { supabaseServerClient } from "supabase/serverClient"; +import { getProfiles } from "src/identity"; +import { get_publication_data } from "app/api/rpc/[command]/get_publication_data"; +import { getPublicationURL } from "app/(app)/lish/createPub/getPublicationURL"; +import { normalizePublicationRecord } from "src/utils/normalizeRecords"; +import { + PublicationSubscribers, + type MergedSubscriber, + type SubscriberStatus, +} from "../PublicationSubscribers"; export default async function SubsPage(props: { params: Promise<{ did: string; publication: string }>; @@ -9,11 +16,85 @@ export default async function SubsPage(props: { const did = decodeURIComponent(params.did); const publication = decodeURIComponent(params.publication); + const { result } = await get_publication_data.handler( + { did, publication_name: publication }, + { supabase: supabaseServerClient }, + ); + const pub = result.publication; + if (!pub) { + return ( + + ); + } + + const record = normalizePublicationRecord(pub.record); + const showPageBackground = !!record?.theme?.showPageBackground; + const atprotoSubs = pub.publication_subscriptions || []; + const newsletterEnabled = !!pub.publication_newsletter_settings?.enabled; + const emailSubs = newsletterEnabled + ? pub.publication_email_subscribers || [] + : []; + + const dids = new Set(); + for (const s of atprotoSubs) { + if (s.identities?.atp_did) dids.add(s.identities.atp_did); + } + for (const s of emailSubs) { + if (s.identities?.atp_did) dids.add(s.identities.atp_did); + } + + const profiles = await getProfiles(Array.from(dids)); + + const byDid = new Map(); + const emailOnly: MergedSubscriber[] = []; + + for (const s of atprotoSubs) { + const d = s.identities?.atp_did ?? undefined; + if (!d) continue; + byDid.set(d, { + key: `did:${d}`, + did: d, + handle: profiles.get(d)?.handle ?? undefined, + email: undefined, + created_at: s.created_at, + status: "subscribed", + }); + } + + for (const s of emailSubs) { + const status: SubscriberStatus = + s.state === "pending" + ? "unconfirmed" + : s.state === "unsubscribed" + ? "unsubscribed" + : "subscribed"; + const linkedDid = s.identities?.atp_did ?? undefined; + const existing = linkedDid ? byDid.get(linkedDid) : undefined; + if (existing && status === "subscribed") { + existing.email = s.email; + continue; + } + emailOnly.push({ + key: `email:${s.id}`, + did: linkedDid, + handle: linkedDid ? (profiles.get(linkedDid)?.handle ?? undefined) : undefined, + email: s.email, + created_at: s.created_at, + status, + }); + } + return ( - - - - - + ); }