feat(pages): add scheduled publish workflow for page management

This commit is contained in:
2026-02-12 23:10:47 +01:00
parent a2bea6326e
commit 18b709b4b0
8 changed files with 95 additions and 12 deletions

View File

@@ -42,6 +42,31 @@ function readNullableString(formData: FormData, field: string): string | null {
return value.length > 0 ? value : null
}
function readNullableDate(formData: FormData, field: string): Date | null {
const value = readInputString(formData, field)
if (!value) {
return null
}
const parsed = new Date(value)
return Number.isNaN(parsed.getTime()) ? null : parsed
}
function formatDateTimeLocalInput(value: Date | null): string {
if (!value) {
return ""
}
const year = value.getFullYear()
const month = String(value.getMonth() + 1).padStart(2, "0")
const day = String(value.getDate()).padStart(2, "0")
const hour = String(value.getHours()).padStart(2, "0")
const minute = String(value.getMinutes()).padStart(2, "0")
return `${year}-${month}-${day}T${hour}:${minute}`
}
function redirectWithState(pageId: string, params: { notice?: string; error?: string }) {
const query = new URLSearchParams()
@@ -109,6 +134,7 @@ export default async function PageEditorPage({ params, searchParams }: PageProps
content: readInputString(formData, "content"),
seoTitle: readNullableString(formData, "seoTitle"),
seoDescription: readNullableString(formData, "seoDescription"),
scheduledPublishAt: readNullableDate(formData, "scheduledPublishAt"),
})
} catch {
redirectWithState(pageId, {
@@ -224,6 +250,16 @@ export default async function PageEditorPage({ params, searchParams }: PageProps
</label>
</div>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Scheduled publish (optional)</span>
<input
name="scheduledPublishAt"
type="datetime-local"
defaultValue={formatDateTimeLocalInput(page.scheduledPublishAt)}
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Slug</span>
<input

View File

@@ -29,6 +29,17 @@ function readNullableString(formData: FormData, field: string): string | null {
return value.length > 0 ? value : null
}
function readNullableDate(formData: FormData, field: string): Date | null {
const value = readInputString(formData, field)
if (!value) {
return null
}
const parsed = new Date(value)
return Number.isNaN(parsed.getTime()) ? null : parsed
}
function redirectWithState(params: { notice?: string; error?: string }) {
const query = new URLSearchParams()
@@ -62,6 +73,7 @@ async function createPageAction(formData: FormData) {
content: readInputString(formData, "content"),
seoTitle: readNullableString(formData, "seoTitle"),
seoDescription: readNullableString(formData, "seoDescription"),
scheduledPublishAt: readNullableDate(formData, "scheduledPublishAt"),
})
} catch {
redirectWithState({
@@ -122,6 +134,7 @@ export default async function PagesManagementPage({
<th className="py-2 pr-4">Title</th>
<th className="py-2 pr-4">Slug</th>
<th className="py-2 pr-4">Status</th>
<th className="py-2 pr-4">Scheduled</th>
<th className="py-2 pr-4">Updated</th>
<th className="py-2 pr-4">Action</th>
</tr>
@@ -129,7 +142,7 @@ export default async function PagesManagementPage({
<tbody>
{pages.length === 0 ? (
<tr>
<td className="py-3 text-neutral-500" colSpan={5}>
<td className="py-3 text-neutral-500" colSpan={6}>
No pages yet.
</td>
</tr>
@@ -139,6 +152,11 @@ export default async function PagesManagementPage({
<td className="py-3 pr-4">{page.title}</td>
<td className="py-3 pr-4 text-neutral-600">/{page.slug}</td>
<td className="py-3 pr-4">{page.status}</td>
<td className="py-3 pr-4 text-neutral-600">
{page.scheduledPublishAt
? page.scheduledPublishAt.toLocaleString("en-US")
: "-"}
</td>
<td className="py-3 pr-4 text-neutral-600">
{page.updatedAt.toLocaleDateString("en-US")}
</td>

View File

@@ -77,6 +77,15 @@ export function CreatePageForm({ action }: CreatePageFormProps) {
</label>
</div>
<label className="space-y-1">
<span className="text-xs text-neutral-600">Scheduled publish (optional)</span>
<input
name="scheduledPublishAt"
type="datetime-local"
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>
<Button type="submit">Create page</Button>
</form>
)