void;
diff --git a/app/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx b/app/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
index f7072058..809a9808 100644
--- a/app/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
+++ b/app/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
@@ -8,6 +8,8 @@ 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 };
@@ -22,14 +24,11 @@ type MergedSubscriber = {
status: SubscriberStatus;
};
-export function PublicationSubscribers(props: {
- showPageBackground?: boolean;
-}) {
- let smoker = useSmoker();
+export function useMergedSubscribers(): MergedSubscriber[] | null {
let { data: publication } = usePublicationData();
let { subscriberStatus } = useDashboardState();
- if (!publication) return
null
;
+ 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
@@ -84,7 +83,18 @@ export function PublicationSubscribers(props: {
status,
});
}
- let subscribers: MergedSubscriber[] = [...byDid.values(), ...emailOnly];
+ return [...byDid.values(), ...emailOnly];
+}
+
+export function PublicationSubscribers(props: {
+ 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) =>
@@ -118,7 +128,8 @@ export function PublicationSubscribers(props: {
.join(", ");
return (
-
No subscribers match this filter
-
Showing: {label}
+
No subscribers match your filters!
);
}
return (
- {/*
- {
- if (checkAll === false) {
- const allSubscribers = subscribers.map((subscriber) => ({
- email: "dummyemail@email.com",
- did: subscriber.identities?.bsky_profiles?.did,
- }));
- setCheckedSubscribers(allSubscribers);
- } else {
- setCheckedSubscribers([]);
- }
- }}
- className="!font-bold text-secondary mb-1"
- >
- {subscribers.length} Subscriber{subscribers.length !== 1 && "s"}
-
- {checkedSubscribers.length !== 0 && (
-
- )}
-
*/}
-
- {subscribers.length} Subscriber{subscribers.length !== 1 && "s"}
-
-
-
+
{subscribers
.sort((a, b) => b.created_at.localeCompare(a.created_at))
.map((subscriber) => (
-
+ <>
+
+
+ >
))}
@@ -234,35 +218,47 @@ const SubscriberListItem = (props: {
createdAt: string;
status: SubscriberStatus;
}) => {
+ let contactClassName =
+ "flex flex-row gap-2 items-center border rounded-md px-1 text-sm w-full max-w-fit no-underline! hover:bg-[var(--accent-light)] hover:border-accent-contrast ";
+ let subscribedClassName = " border-transparent font-bold text-secondary";
+ let mutedClassName = "border-border bg-border-light text-tertiary";
+ let unconfirmedClassName = "border-border-light animate-pulse text-tertiary";
+
return (
-
- {props.handle && (
-
- @{props.handle}
-
- )}
- {props.handle && props.email && (
-
- )}
- {props.email && (
-
- {props.email}
-
- )}
- {props.status !== "subscribed" && (
-
- {props.status === "unconfirmed" ? "unconfirmed" : "unsubscribed"}
-
- )}
-
+
+
+
+ {props.status !== "subscribed" && (
+
+ {props.status === "unconfirmed" ? "unconfirmed" : "unsubscribed"}
+
+ )}
+
+
);
};
diff --git a/app/lish/[did]/[publication]/dashboard/analytics/page.tsx b/app/lish/[did]/[publication]/dashboard/analytics/page.tsx
index 491c56ea..6c85cfef 100644
--- a/app/lish/[did]/[publication]/dashboard/analytics/page.tsx
+++ b/app/lish/[did]/[publication]/dashboard/analytics/page.tsx
@@ -3,7 +3,11 @@
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { DashboardPageLayout } from "components/PageLayouts/DashboardPageLayout";
-import { PublicationAnalytics } from "../PublicationAnalytics";
+import {
+ PublicationAnalytics,
+ DateRangeSelector,
+ useAnalyticsDateState,
+} from "../PublicationAnalytics";
import { NewDraftActionButton } from "../NewDraftButton";
import {
usePublicationData,
@@ -19,6 +23,8 @@ export default function AnalyticsPage() {
let pubUri = data?.publication?.uri || "";
const showPageBackground = !!record?.theme?.showPageBackground;
+ let [dateState, setDateState] = useAnalyticsDateState();
+
useEffect(() => {
if (canSeePro === false) router.replace("../");
}, [canSeePro, router]);
@@ -31,9 +37,22 @@ export default function AnalyticsPage() {
pageTitle="Analytics"
mobileActions={
}
publication={pubUri}
- showHeader={false}
+ showHeader={true}
+ controls={
+
+
+
+ }
>
-
+
);
}
diff --git a/app/lish/[did]/[publication]/dashboard/subs/page.tsx b/app/lish/[did]/[publication]/dashboard/subs/page.tsx
index 5d6377c4..fd88c02c 100644
--- a/app/lish/[did]/[publication]/dashboard/subs/page.tsx
+++ b/app/lish/[did]/[publication]/dashboard/subs/page.tsx
@@ -1,32 +1,106 @@
"use client";
import { DashboardPageLayout } from "components/PageLayouts/DashboardPageLayout";
-import { SubscriberStatusFilter } from "components/PageLayouts/PageSearch";
-import { PublicationSubscribers } from "../PublicationSubscribers";
-import { NewDraftActionButton } from "../NewDraftButton";
+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;
return (
}
+ mobileActions={
}
publication={pubUri}
- showHeader={false}
+ 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
+
+
+ );
+};
diff --git a/app/lish/[did]/[publication]/theme-settings/page.tsx b/app/lish/[did]/[publication]/theme-settings/page.tsx
index caffe8fd..80040b0d 100644
--- a/app/lish/[did]/[publication]/theme-settings/page.tsx
+++ b/app/lish/[did]/[publication]/theme-settings/page.tsx
@@ -6,6 +6,7 @@ import { AtUri } from "@atproto/syntax";
import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout";
import { normalizePublicationRecord } from "src/utils/normalizeRecords";
import { ThemeSettingsContent } from "./ThemeSettingsContent";
+import { LoginModal } from "components/LoginButton";
export default async function ThemeSettingsPage(props: {
params: Promise<{ publication: string; did: string }>;
@@ -15,13 +16,23 @@ export default async function ThemeSettingsPage(props: {
if (!identity || !identity.atp_did)
return (
- Looks like you're not logged in.
+
+ Looks like you're not logged in.{" "}
+ Log in here
+ }
+ />
+ !
+
If the issue persists please{" "}
send us a note.
);
+
let did = decodeURIComponent(params.did);
if (!did) return
;
let { result: publication_data } = await get_publication_data.handler(
diff --git a/components/ActionBar/MobileNavigation.tsx b/components/ActionBar/MobileNavigation.tsx
index 8c7093b3..bb31ef2a 100644
--- a/components/ActionBar/MobileNavigation.tsx
+++ b/components/ActionBar/MobileNavigation.tsx
@@ -11,7 +11,8 @@ import { useCardBorderHidden } from "components/Pages/useCardBorderHidden";
import { useIdentityData } from "components/IdentityProvider";
export const MobileNavigation = (props: {
- search?: React.ReactNode;
+ controls?: React.ReactNode;
+ hasSearch?: boolean;
mobileActions?: React.ReactNode;
pageTitle: string;
hiddenOnScroll?: boolean;
@@ -113,7 +114,7 @@ export const MobileNavigation = (props: {
- {props.search && (
+ {props.controls && props.hasSearch && (
);
diff --git a/components/PageLayouts/PageSearch.tsx b/components/PageLayouts/PageSearch.tsx
index 0aa751ab..8289b580 100644
--- a/components/PageLayouts/PageSearch.tsx
+++ b/components/PageLayouts/PageSearch.tsx
@@ -167,62 +167,6 @@ const FilterCheckboxes = (props: {
);
};
-export const SubscriberStatusFilter = () => {
- let { subscriberStatus } = useDashboardState();
- let setState = useSetDashboardState();
- let count = Object.values(subscriberStatus).filter(Boolean).length;
-
- return (
-