From 3875b4e9010a4f7c4fb782d112ba90eacf615605 Mon Sep 17 00:00:00 2001 From: celine Date: Tue, 13 Jan 2026 19:28:39 -0500 Subject: [PATCH] componentize Date Picker, add Time Picker, mock up date and time pickers in publish show and update flow --- actions/publishToPublication.ts | 11 ++- app/[leaflet_id]/publish/PublishPost.tsx | 120 +++++++++++++++++++++-- components/Blocks/DateTimeBlock.tsx | 4 +- components/DatePicker.tsx | 24 ++++- components/Pages/Backdater.tsx | 97 +++++++++--------- 5 files changed, 191 insertions(+), 65 deletions(-) diff --git a/actions/publishToPublication.ts b/actions/publishToPublication.ts index 4f7e8b38..04717287 100644 --- a/actions/publishToPublication.ts +++ b/actions/publishToPublication.ts @@ -66,6 +66,7 @@ export async function publishToPublication({ tags, cover_image, entitiesToDelete, + publishedAt, }: { root_entity: string; publication_uri?: string; @@ -75,6 +76,7 @@ export async function publishToPublication({ tags?: string[]; cover_image?: string | null; entitiesToDelete?: string[]; + publishedAt?: string; }): Promise { let identity = await getIdentityData(); if (!identity || !identity.atp_did) { @@ -147,8 +149,9 @@ export async function publishToPublication({ credentialSession.did!, ); - let existingRecord = - (draft?.documents?.data as PubLeafletDocument.Record | undefined) || {}; + let existingRecord = draft?.documents?.data as + | PubLeafletDocument.Record + | undefined; // Extract theme for standalone documents (not for publications) let theme: PubLeafletPublication.Theme | undefined; @@ -174,8 +177,6 @@ export async function publishToPublication({ } let record: PubLeafletDocument.Record = { - publishedAt: new Date().toISOString(), - ...existingRecord, $type: "pub.leaflet.document", author: credentialSession.did!, ...(publication_uri && { publication: publication_uri }), @@ -199,6 +200,8 @@ export async function publishToPublication({ }; } }), + publishedAt: + existingRecord?.publishedAt || publishedAt || new Date().toISOString(), }; // Keep the same rkey if updating an existing document diff --git a/app/[leaflet_id]/publish/PublishPost.tsx b/app/[leaflet_id]/publish/PublishPost.tsx index 4b890427..ca82eaec 100644 --- a/app/[leaflet_id]/publish/PublishPost.tsx +++ b/app/[leaflet_id]/publish/PublishPost.tsx @@ -23,6 +23,11 @@ import { TagSelector } from "../../../components/Tags"; import { LooseLeafSmall } from "components/Icons/LooseleafSmall"; import { PubIcon } from "components/ActionBar/Publications"; import { OAuthErrorMessage, isOAuthSessionError } from "components/OAuthError"; +import { DatePicker, TimePicker } from "components/DatePicker"; +import { Popover } from "components/Popover"; +import { useLocalizedDate } from "src/hooks/useLocalizedDate"; +import { Separator } from "react-aria-components"; +import { setHours, setMinutes } from "date-fns"; type Props = { title: string; @@ -78,6 +83,9 @@ const PublishPostForm = ( ); let [localTags, setLocalTags] = useState([]); + let [localPublishedAt, setLocalPublishedAt] = useState( + undefined, + ); // Get cover image from Replicache let replicacheCoverImage = useSubscribe(rep, (tx) => tx.get("publication_cover_image"), @@ -116,6 +124,7 @@ const PublishPostForm = ( tags: currentTags, cover_image: replicacheCoverImage, entitiesToDelete: props.entitiesToDelete, + publishedAt: localPublishedAt?.toISOString() || new Date().toISOString(), }); if (!result.success) { @@ -168,15 +177,13 @@ const PublishPostForm = ( record={props.record} />
-
+

Tags

+
+
@@ -219,6 +235,94 @@ const PublishPostForm = ( ); }; +const BackdateOptions = (props: { + publishedAt: Date | undefined; + setPublishedAt: (date: Date | undefined) => void; +}) => { + const formattedDate = useLocalizedDate( + props.publishedAt?.toISOString() || "", + { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }, + ); + + const [timeValue, setTimeValue] = useState(() => { + if (!props.publishedAt) return "12:00"; + return `${props.publishedAt.getHours().toString().padStart(2, "0")}:${props.publishedAt.getMinutes().toString().padStart(2, "0")}`; + }); + + let currentTime = `${new Date().getHours().toString().padStart(2, "0")}:${new Date().getMinutes().toString().padStart(2, "0")}`; + + 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); + const currentDate = new Date(); + + if (newDate > currentDate) { + props.setPublishedAt(currentDate); + setTimeValue(currentTime); + } else props.setPublishedAt(newDate); + }; + + const handleDateChange = (date: Date | undefined) => { + if (!date) { + props.setPublishedAt(undefined); + return; + } + const [hours, minutes] = timeValue + .split(":") + .map((str) => parseInt(str, 10)); + const newDate = new Date( + date.getFullYear(), + date.getMonth(), + date.getDate(), + hours, + minutes, + ); + const currentDate = new Date(); + if (newDate > currentDate) { + props.setPublishedAt(currentDate); + setTimeValue(currentTime); + } else props.setPublishedAt(newDate); + }; + + return ( +
+

Publish Date

+ {formattedDate}
+ ) : ( +
now
+ ) + } + > +
+ date > new Date()} + /> + +
+ +
+
+ +
+ ); +}; + const ShareOptions = (props: { shareOption: "quiet" | "bluesky"; setShareOption: (option: typeof props.shareOption) => void; @@ -232,7 +336,7 @@ const ShareOptions = (props: { }) => { return (
-

Notifications

+

Share and Notify

{ diff --git a/components/Blocks/DateTimeBlock.tsx b/components/Blocks/DateTimeBlock.tsx index ee4f6c7f..b60d8494 100644 --- a/components/Blocks/DateTimeBlock.tsx +++ b/components/Blocks/DateTimeBlock.tsx @@ -10,7 +10,7 @@ import { Checkbox } from "components/Checkbox"; import { useHasPageLoaded } from "components/InitialPageLoadProvider"; import { useSpring, animated } from "@react-spring/web"; import { BlockCalendarSmall } from "components/Icons/BlockCalendarSmall"; -import { DayPicker } from "components/DatePicker"; +import { DatePicker } from "components/DatePicker"; export function DateTimeBlock(props: BlockProps) { const [isClient, setIsClient] = useState(false); @@ -166,7 +166,7 @@ export function BaseDateTimeBlock( } >
- diff --git a/components/DatePicker.tsx b/components/DatePicker.tsx index 2ec6bfde..1967e390 100644 --- a/components/DatePicker.tsx +++ b/components/DatePicker.tsx @@ -13,14 +13,12 @@ interface DayPickerProps { selected: Date | undefined; onSelect: (date: Date | undefined) => void; disabled?: (date: Date) => boolean; - toDate?: Date; } -export const DayPicker = ({ +export const DatePicker = ({ selected, onSelect, disabled, - toDate, }: DayPickerProps) => { return ( + ); +}; + +export const TimePicker = (props: { + value: string; + onChange: (time: string) => void; + className?: string; +}) => { + let handleTimeChange: React.ChangeEventHandler = (e) => { + props.onChange(e.target.value); + }; + + return ( + ); }; diff --git a/components/Pages/Backdater.tsx b/components/Pages/Backdater.tsx index 7c05ab55..8cc5eb99 100644 --- a/components/Pages/Backdater.tsx +++ b/components/Pages/Backdater.tsx @@ -1,69 +1,72 @@ "use client"; -import { DayPicker } from "components/DatePicker"; -import { backdatePost } from "actions/backdatePost"; -import { mutate } from "swr"; -import { DotLoader } from "components/utils/DotLoader"; -import { useToaster } from "components/Toast"; -import { useLeafletPublicationData } from "components/PageSWRDataProvider"; +import { DatePicker, TimePicker } from "components/DatePicker"; import { useState } from "react"; import { timeAgo } from "src/utils/timeAgo"; import { Popover } from "components/Popover"; +import { Separator } from "react-aria-components"; export const Backdater = (props: { publishedAt: string }) => { - let { data: pub } = useLeafletPublicationData(); - let [isUpdating, setIsUpdating] = useState(false); - let [localPublishedAt, setLocalPublishedAt] = useState(props.publishedAt); - let toaster = useToaster(); + let [localPublishedAt, setLocalPublishedAt] = useState( + new Date(props.publishedAt), + ); - const handleDaySelect = async (date: Date | undefined) => { - if (!date || !pub?.doc || isUpdating) return; + let [timeValue, setTimeValue] = useState( + `${localPublishedAt.getHours().toString().padStart(2, "0")}:${localPublishedAt.getMinutes().toString().padStart(2, "0")}`, + ); - // Prevent future dates - if (date > new Date()) return; + let currentTime = `${new Date().getHours().toString().padStart(2, "0")}:${new Date().getMinutes().toString().padStart(2, "0")}`; - setIsUpdating(true); - try { - const result = await backdatePost({ - uri: pub.doc, - publishedAt: date.toISOString(), - }); + const handleTimeChange = (time: string) => { + setTimeValue(time); + const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10)); + const newDate = new Date(localPublishedAt); + newDate.setHours(hours); + newDate.setMinutes(minutes); - if (result.success) { - // Update local state immediately - setLocalPublishedAt(date.toISOString()); - // Refresh the publication data - await mutate(`/api/pub/${pub.doc}`); - } - } catch (error) { - console.error("Failed to backdate document:", error); - } finally { - toaster({ - content:
Updated publish date!
, - type: "success", - }); - setIsUpdating(false); - } + let currentDate = new Date(); + if (newDate > currentDate) { + setLocalPublishedAt(currentDate); + setTimeValue(currentTime); + } else setLocalPublishedAt(newDate); }; - const selectedDate = new Date(localPublishedAt); + const handleDateChange = (date: Date | undefined) => { + if (!date) return; + const [hours, minutes] = timeValue + .split(":") + .map((str) => parseInt(str, 10)); + const newDate = new Date(date); + newDate.setHours(hours); + newDate.setMinutes(minutes); + + let currentDate = new Date(); + if (newDate > currentDate) { + setLocalPublishedAt(currentDate); + setTimeValue(currentTime); + } else setLocalPublishedAt(newDate); + }; + console.log(localPublishedAt); return ( - ) : ( -
{timeAgo(localPublishedAt)}
- ) +
+ {timeAgo(localPublishedAt.toISOString())} +
} > - date > new Date()} - toDate={new Date()} - /> +
+ date > new Date()} + /> + +
+ +
+
); }; -- 2.51.2