From 53d89267b4ff4721a8d12fc9abbdb3453056875b Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 28 Apr 2026 01:06:40 -0400 Subject: [PATCH] feature flag scheduled posts --- actions/cancelScheduledPost.ts | 4 +++ actions/schedulePost.ts | 22 ++++++++++----- .../home/LeafletList/LeafletInfo.tsx | 6 ++++- app/[leaflet_id]/actions/PublishButton.tsx | 4 ++- app/[leaflet_id]/publish/PublishPost.tsx | 27 ++++++++++++++++--- app/api/rpc/[command]/get_publication_data.ts | 1 + src/hooks/useEntitlement.ts | 4 +++ 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/actions/cancelScheduledPost.ts b/actions/cancelScheduledPost.ts index 273545f2..47ce32b0 100644 --- a/actions/cancelScheduledPost.ts +++ b/actions/cancelScheduledPost.ts @@ -16,6 +16,10 @@ export async function cancelScheduledPost(args: { let identity = await getIdentityData(); if (!identity || !identity.atp_did) return Err({ type: "not_authenticated" }); + if (!identity.entitlements?.can_schedule_posts) { + return Err({ type: "not_found" }); + } + if (args.publication_uri) { const { data: pub } = await supabaseServerClient .from("publications") diff --git a/actions/schedulePost.ts b/actions/schedulePost.ts index 87001181..a5e9f4a0 100644 --- a/actions/schedulePost.ts +++ b/actions/schedulePost.ts @@ -39,6 +39,10 @@ export async function schedulePost(args: { let identity = await getIdentityData(); if (!identity || !identity.atp_did) return Err({ type: "not_authenticated" }); + if (!identity.entitlements?.can_schedule_posts) { + return Err({ type: "not_found" }); + } + if (!identity.entitlements?.publication_analytics) { return Err({ type: "not_pro" }); } @@ -85,13 +89,17 @@ export async function schedulePost(args: { ); if (!found) return Err({ type: "not_found" }); - await inngest.send({ - name: "post/scheduled-publish", - data: { - leaflet_id: args.leaflet_id, - publication_uri: args.publication_uri, - }, - }); + try { + await inngest.send({ + name: "post/scheduled-publish", + data: { + leaflet_id: args.leaflet_id, + publication_uri: args.publication_uri, + }, + }); + } catch (e) { + console.log(e); + } return Ok({ scheduled_publish_at: scheduledAt.toISOString() }); } diff --git a/app/(home-pages)/home/LeafletList/LeafletInfo.tsx b/app/(home-pages)/home/LeafletList/LeafletInfo.tsx index f2fb11de..ef5bd491 100644 --- a/app/(home-pages)/home/LeafletList/LeafletInfo.tsx +++ b/app/(home-pages)/home/LeafletList/LeafletInfo.tsx @@ -11,6 +11,7 @@ import { SCHEDULED_DATE_FORMAT, getFutureScheduledAt, } from "src/utils/scheduledPublish"; +import { useCanSchedulePosts } from "src/hooks/useEntitlement"; export const LeafletInfo = (props: { title?: string; @@ -21,6 +22,7 @@ export const LeafletInfo = (props: { loggedIn: boolean; }) => { const pubStatus = useLeafletPublicationStatus(); + const canSchedule = useCanSchedulePosts(); let prettyCreatedAt = props.added_at ? timeAgo(props.added_at) : ""; let prettyPublishedAt = pubStatus?.publishedAt ? timeAgo(pubStatus.publishedAt) @@ -44,7 +46,9 @@ export const LeafletInfo = (props: { {title}
- {scheduledFor && } + {scheduledFor && canSchedule && ( + + )}
diff --git a/app/[leaflet_id]/actions/PublishButton.tsx b/app/[leaflet_id]/actions/PublishButton.tsx index 14fde8a2..1a859ca8 100644 --- a/app/[leaflet_id]/actions/PublishButton.tsx +++ b/app/[leaflet_id]/actions/PublishButton.tsx @@ -33,6 +33,7 @@ import { normalizePublicationRecord } from "src/utils/normalizeRecords"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useState, useMemo, useEffect } from "react"; import { useIsMobile } from "src/hooks/isMobile"; +import { useCanSchedulePosts } from "src/hooks/useEntitlement"; import { useReplicache, useEntity } from "src/replicache"; import { useSubscribe } from "src/replicache/useSubscribe"; import { Json } from "supabase/database.types"; @@ -54,12 +55,13 @@ export const PublishButton = (props: { entityID: string }) => { let { permission_token } = useReplicache(); let params = useParams(); let router = useRouter(); + const canSchedule = useCanSchedulePosts(); if (!pub) return ; const scheduledFor = getFutureScheduledAt(pub.scheduled_publish_at); - if (scheduledFor) { + if (scheduledFor && canSchedule) { return ( { const isPro = useIsPro(); + const canSchedule = useCanSchedulePosts(); const [showUpgradeModal, setShowUpgradeModal] = useState(false); let editorStateRef = useRef(null); let [state, setState] = useState<"post-details" | "share-options">( @@ -286,6 +287,7 @@ const PublishPostForm = (
@@ -538,6 +540,7 @@ const PublicationSocialPreview = (props: { const DateOptions = (props: { publishedAt: Date | undefined; setPublishedAt: (date: Date | undefined) => void; + canSchedule: boolean; }) => { const formattedDate = useLocalizedDate( props.publishedAt?.toISOString() || "", @@ -549,13 +552,24 @@ const DateOptions = (props: { return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`; }); + const clampToNow = (date: Date): { date: Date; time: string } => { + const now = new Date(); + if (!props.canSchedule && date > now) { + const time = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}`; + return { date: now, time }; + } + return { date, time: timeValue }; + }; + const handleTimeChange = (time: string) => { setTimeValue(time); if (!props.publishedAt) return; const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10)); const newDate = setHours(setMinutes(props.publishedAt, minutes), hours); - props.setPublishedAt(newDate); + const clamped = clampToNow(newDate); + setTimeValue(clamped.time); + props.setPublishedAt(clamped.date); }; const handleDateChange = (date: Date | undefined) => { @@ -573,7 +587,9 @@ const DateOptions = (props: { hours, minutes, ); - props.setPublishedAt(newDate); + const clamped = clampToNow(newDate); + setTimeValue(clamped.time); + props.setPublishedAt(clamped.date); }; const scheduled = !!props.publishedAt && isFuture(props.publishedAt); @@ -600,6 +616,11 @@ const DateOptions = (props: { mode="single" selected={props.publishedAt} onSelect={handleDateChange} + disabled={ + props.canSchedule + ? undefined + : (date: Date) => date > new Date() + } />
diff --git a/app/api/rpc/[command]/get_publication_data.ts b/app/api/rpc/[command]/get_publication_data.ts index 714ad992..6f0401ae 100644 --- a/app/api/rpc/[command]/get_publication_data.ts +++ b/app/api/rpc/[command]/get_publication_data.ts @@ -64,6 +64,7 @@ export const get_publication_data = makeRoute({ .limit(1) .single(); + console.log(error); let leaflet_data = await getFactsFromHomeLeaflets.handler( { tokens: diff --git a/src/hooks/useEntitlement.ts b/src/hooks/useEntitlement.ts index 9f62c33e..e9c1683b 100644 --- a/src/hooks/useEntitlement.ts +++ b/src/hooks/useEntitlement.ts @@ -17,3 +17,7 @@ export function useCanSeePro(): boolean { export function useCanSeeNewsletterMode(): boolean { return useHasEntitlement("can_see_newsletter_mode"); } + +export function useCanSchedulePosts(): boolean { + return useHasEntitlement("can_schedule_posts"); +} -- 2.51.2