{pages.length === 0 ? (
- |
+ |
No pages yet.
|
@@ -139,6 +152,11 @@ export default async function PagesManagementPage({
{page.title} |
/{page.slug} |
{page.status} |
+
+ {page.scheduledPublishAt
+ ? page.scheduledPublishAt.toLocaleString("en-US")
+ : "-"}
+ |
{page.updatedAt.toLocaleDateString("en-US")}
|
diff --git a/apps/admin/src/components/pages/create-page-form.tsx b/apps/admin/src/components/pages/create-page-form.tsx
index cfc85fb..7eeba08 100644
--- a/apps/admin/src/components/pages/create-page-form.tsx
+++ b/apps/admin/src/components/pages/create-page-form.tsx
@@ -77,6 +77,15 @@ export function CreatePageForm({ action }: CreatePageFormProps) {
+
+
)
diff --git a/packages/content/src/pages-navigation.ts b/packages/content/src/pages-navigation.ts
index e03057d..5380e2f 100644
--- a/packages/content/src/pages-navigation.ts
+++ b/packages/content/src/pages-navigation.ts
@@ -103,6 +103,7 @@ export const createPageInputSchema = z.object({
content: z.string().min(1),
seoTitle: z.string().max(180).nullable().optional(),
seoDescription: z.string().max(320).nullable().optional(),
+ scheduledPublishAt: z.date().nullable().optional(),
})
export const updatePageInputSchema = z.object({
@@ -114,6 +115,7 @@ export const updatePageInputSchema = z.object({
content: z.string().min(1).optional(),
seoTitle: z.string().max(180).nullable().optional(),
seoDescription: z.string().max(320).nullable().optional(),
+ scheduledPublishAt: z.date().nullable().optional(),
})
export const upsertPageTranslationInputSchema = z.object({
diff --git a/packages/db/prisma/migrations/20260213020500_page_scheduled_publish/migration.sql b/packages/db/prisma/migrations/20260213020500_page_scheduled_publish/migration.sql
new file mode 100644
index 0000000..eec0565
--- /dev/null
+++ b/packages/db/prisma/migrations/20260213020500_page_scheduled_publish/migration.sql
@@ -0,0 +1,4 @@
+ALTER TABLE "Page"
+ADD COLUMN "scheduledPublishAt" TIMESTAMP(3);
+
+CREATE INDEX "Page_scheduledPublishAt_idx" ON "Page"("scheduledPublishAt");
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index d81dfda..201ee8a 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -291,6 +291,7 @@ model Page {
seoTitle String?
seoDescription String?
publishedAt DateTime?
+ scheduledPublishAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
navItems NavigationItem[]
diff --git a/packages/db/src/pages-navigation.ts b/packages/db/src/pages-navigation.ts
index 6f8e079..74a277d 100644
--- a/packages/db/src/pages-navigation.ts
+++ b/packages/db/src/pages-navigation.ts
@@ -29,6 +29,15 @@ function resolvePublishedAt(status: string): Date | null {
return status === "published" ? new Date() : null
}
+function resolvePublicPageWhere(slug?: string) {
+ const now = new Date()
+
+ return {
+ ...(slug ? { slug } : {}),
+ OR: [{ status: "published" }, { scheduledPublishAt: { lte: now } }],
+ }
+}
+
export async function listPages(limit = 50) {
return db.page.findMany({
orderBy: [{ updatedAt: "desc" }],
@@ -38,7 +47,7 @@ export async function listPages(limit = 50) {
export async function listPublishedPageSlugs() {
const pages = await db.page.findMany({
- where: { status: "published" },
+ where: resolvePublicPageWhere(),
orderBy: { updatedAt: "desc" },
select: {
slug: true,
@@ -57,19 +66,13 @@ export async function getPageById(id: string) {
export async function getPublishedPageBySlug(slug: string) {
return db.page.findFirst({
- where: {
- slug,
- status: "published",
- },
+ where: resolvePublicPageWhere(slug),
})
}
export async function getPublishedPageBySlugForLocale(slug: string, locale: string) {
const page = await db.page.findFirst({
- where: {
- slug,
- status: "published",
- },
+ where: resolvePublicPageWhere(slug),
include: {
translations: {
where: {
@@ -98,11 +101,13 @@ export async function getPublishedPageBySlugForLocale(slug: string, locale: stri
export async function createPage(input: unknown) {
const payload = createPageInputSchema.parse(input)
+ const isImmediatelyPublished = payload.status === "published"
return db.page.create({
data: {
...payload,
publishedAt: resolvePublishedAt(payload.status),
+ scheduledPublishAt: isImmediatelyPublished ? null : (payload.scheduledPublishAt ?? null),
},
})
}
@@ -110,6 +115,7 @@ export async function createPage(input: unknown) {
export async function updatePage(input: unknown) {
const payload = updatePageInputSchema.parse(input)
const { id, ...data } = payload
+ const isImmediatelyPublished = data.status === "published"
return db.page.update({
where: { id },
@@ -117,6 +123,12 @@ export async function updatePage(input: unknown) {
...data,
publishedAt:
data.status === undefined ? undefined : data.status === "published" ? new Date() : null,
+ scheduledPublishAt:
+ data.scheduledPublishAt === undefined
+ ? undefined
+ : isImmediatelyPublished
+ ? null
+ : data.scheduledPublishAt,
},
})
}