diff --git a/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx b/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
index 0bae1cf6..d0ead28d 100644
--- a/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
+++ b/app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
@@ -1,21 +1,16 @@
"use client";
import { usePublicationData } from "./PublicationSWRProvider";
import { ButtonPrimary } from "components/Buttons";
-import { getPublicationURL } from "app/(app)/lish/createPub/getPublicationURL";
import { useSmoker } from "components/Toast";
-import { Menu, MenuItem } from "components/Menu";
import { Separator } from "components/Layout";
-import { MoreOptionsVerticalTiny } from "components/Icons/MoreOptionsVerticalTiny";
import { useLocalizedDate } from "src/hooks/useLocalizedDate";
import { useDashboardState } from "components/PageLayouts/dashboardState";
import { AtmosphereAccount } from "components/Icons/AtmosphereAccount";
import { EmailTiny } from "components/Icons/EmailTiny";
-type subscriber = { email: string | undefined; did: string | undefined };
+export type SubscriberStatus = "subscribed" | "unconfirmed" | "unsubscribed";
-type SubscriberStatus = "subscribed" | "unconfirmed" | "unsubscribed";
-
-type MergedSubscriber = {
+export type MergedSubscriber = {
key: string;
did: string | undefined;
handle: string | undefined;
@@ -86,31 +81,13 @@ export function useMergedSubscribers(): MergedSubscriber[] | null {
return [...byDid.values(), ...emailOnly];
}
-export function PublicationSubscribers(props: {
+export function SubscribersListView(props: {
+ subscribers: MergedSubscriber[];
+ publicationShareUrl: string;
showPageBackground?: boolean;
}) {
let smoker = useSmoker();
- let { data: publication } = usePublicationData();
let { subscriberStatus } = useDashboardState();
- let subscribers = useMergedSubscribers();
-
- if (!publication || !subscribers) return
null
;
-
- // useEffect(() => {
- // const allSubscribersSelected = subscribers.every((subscriber) =>
- // checkedSubscribers.some(
- // (checked) =>
- // checked.email === "dummyemail@email.com" &&
- // checked.did === subscriber.identities?.bsky_profiles?.did,
- // ),
- // );
-
- // if (allSubscribersSelected && subscribers.length > 0) {
- // setCheckAll(true);
- // } else {
- // setCheckAll(false);
- // }
- // }, [checkedSubscribers]);
let activeStatuses = (
Object.keys(subscriberStatus) as SubscriberStatus[]
@@ -118,14 +95,10 @@ export function PublicationSubscribers(props: {
let isDefaultStatusFilter =
activeStatuses.length === 1 && activeStatuses[0] === "subscribed";
- if (subscribers.length === 0) {
+ let filtered = props.subscribers.filter((s) => subscriberStatus[s.status]);
+
+ if (filtered.length === 0) {
if (!isDefaultStatusFilter) {
- let label =
- activeStatuses.length === 0
- ? "any status"
- : activeStatuses
- .map((s) => (s === "unconfirmed" ? "unconfirmed" : s))
- .join(", ");
return (
{
e.preventDefault();
let rect = (e.currentTarget as Element)?.getBoundingClientRect();
- navigator.clipboard.writeText(
- getPublicationURL(publication.publication!),
- );
+ navigator.clipboard.writeText(props.publicationShareUrl);
smoker({
position: {
x: rect ? rect.left + (rect.right - rect.left) / 2 : 0,
@@ -191,26 +162,52 @@ export function PublicationSubscribers(props: {
}
>
- {subscribers
+ {filtered
.sort((a, b) => b.created_at.localeCompare(a.created_at))
.map((subscriber) => (
- <>
+
-
- >
+
+
))}
);
}
+export function SubscribersListSkeleton(props: { showPageBackground?: boolean }) {
+ return (
+
+
+ {[0, 1, 2, 3].map((i) => (
+
+ ))}
+
+
+ );
+}
+
const SubscriberListItem = (props: {
handle: string | undefined;
did: string | undefined;
@@ -263,31 +260,6 @@ const SubscriberListItem = (props: {
);
};
-const SubscriberOptions = (props: {
- checkedSubscribers: subscriber[];
- allSelected: boolean;
-}) => {
- return (
-
- );
-};
-
function SubscriberDate(props: { createdAt: string }) {
const formattedDate = useLocalizedDate(props.createdAt, {
year: "2-digit",
diff --git a/app/(app)/lish/[did]/[publication]/dashboard/SubsPageShell.tsx b/app/(app)/lish/[did]/[publication]/dashboard/SubsPageShell.tsx
new file mode 100644
index 00000000..bac567e3
--- /dev/null
+++ b/app/(app)/lish/[did]/[publication]/dashboard/SubsPageShell.tsx
@@ -0,0 +1,98 @@
+"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
new file mode 100644
index 00000000..d362edc9
--- /dev/null
+++ b/app/(app)/lish/[did]/[publication]/dashboard/SubscribersList.tsx
@@ -0,0 +1,99 @@
+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?.bsky_profiles?.did;
+ 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?.bsky_profiles?.did;
+ 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 fd88c02c..4979e0f6 100644
--- a/app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx
+++ b/app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx
@@ -1,106 +1,20 @@
-"use client";
+import { Suspense } from "react";
+import { SubsPageShell } from "../SubsPageShell";
+import { SubscribersList } from "../SubscribersList";
+import { SubscribersListSkeleton } from "../PublicationSubscribers";
-import { DashboardPageLayout } from "components/PageLayouts/DashboardPageLayout";
-import {
- PublicationSubscribers,
- useMergedSubscribers,
-} from "../PublicationSubscribers";
-import {
- usePublicationData,
- useNormalizedPublicationRecord,
-} from "../PublicationSWRProvider";
-import { Popover } from "components/Popover";
-import { Checkbox } from "components/Checkbox";
-import {
- useDashboardState,
- useSetDashboardState,
-} from "components/PageLayouts/dashboardState";
-
-export default function SubsPage() {
- let { data } = usePublicationData();
- let record = useNormalizedPublicationRecord();
- let pubUri = data?.publication?.uri || "";
- const showPageBackground = !!record?.theme?.showPageBackground;
- let subscribers = useMergedSubscribers();
- let count = subscribers?.length ?? 0;
+export default async function SubsPage(props: {
+ params: Promise<{ did: string; publication: string }>;
+}) {
+ const params = await props.params;
+ const did = decodeURIComponent(params.did);
+ const publication = decodeURIComponent(params.publication);
return (
- }
- publication={pubUri}
- showHeader={true}
- controls={
-
-
- {count} Subscriber{count !== 1 && "s"}
-
-
-
- }
- >
-
-
+
+ }>
+
+
+
);
}
-
-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
-
-
- );
-};