feat(blog): implement scheduled and draft posts filtering and preview UI
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 13s
Build & Deploy / 🧪 QA (push) Successful in 1m59s
Build & Deploy / 🏗️ Build (push) Failing after 34s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-02-20 15:41:07 +01:00
parent c646815a3a
commit 8d77ca45f7
5 changed files with 48 additions and 4 deletions

View File

@@ -69,6 +69,11 @@ export default async function BlogPost({ params }: BlogPostProps) {
category={post.frontmatter.category} category={post.frontmatter.category}
readingTime={getReadingTime(post.content)} readingTime={getReadingTime(post.content)}
/> />
{(new Date(post.frontmatter.date) > new Date() || post.frontmatter.public === false) && (
<div className="bg-orange-500 text-white text-center py-2 px-4 font-bold text-sm tracking-wider uppercase relative z-50">
Preview (Not visible in production)
</div>
)}
{/* Featured Image Header */} {/* Featured Image Header */}
{post.frontmatter.featuredImage ? ( {post.frontmatter.featuredImage ? (
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group"> <div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">

View File

@@ -75,9 +75,16 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
<Container className="relative z-10"> <Container className="relative z-10">
<div className="max-w-4xl animate-slide-up"> <div className="max-w-4xl animate-slide-up">
<Badge variant="saturated" className="mb-4 md:mb-6"> <div className="flex flex-wrap items-center gap-3 mb-4 md:mb-6">
{t('featuredPost')} <Badge variant="saturated">{t('featuredPost')}</Badge>
</Badge> {featuredPost &&
(new Date(featuredPost.frontmatter.date) > new Date() ||
featuredPost.frontmatter.public === false) && (
<Badge variant="accent" className="bg-orange-500 text-white border-none">
Preview
</Badge>
)}
</div>
{featuredPost && ( {featuredPost && (
<> <>
<Heading level={1} className="text-white mb-4 md:mb-8"> <Heading level={1} className="text-white mb-4 md:mb-8">
@@ -168,6 +175,15 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
{post.frontmatter.category} {post.frontmatter.category}
</Badge> </Badge>
)} )}
{(new Date(post.frontmatter.date) > new Date() ||
post.frontmatter.public === false) && (
<Badge
variant="accent"
className="absolute top-3 right-3 md:top-6 md:right-6 shadow-lg bg-orange-500 text-white border-none"
>
Preview
</Badge>
)}
</div> </div>
)} )}
<div className="p-5 md:p-10 flex flex-col flex-1"> <div className="p-5 md:p-10 flex flex-col flex-1">

View File

@@ -5,6 +5,7 @@ featuredImage: /uploads/2026/01/1767353529807.jpg
locale: de locale: de
category: Kabel Technologie category: Kabel Technologie
excerpt: 'KLZ Cables startet mit einer starken Verstärkung ins neue Jahr: Johannes Gleich übernimmt die Rolle des Senior Key Account Managers. Erfahren Sie mehr über unseren neuen Experten für Infrastruktur und Energieversorger.' excerpt: 'KLZ Cables startet mit einer starken Verstärkung ins neue Jahr: Johannes Gleich übernimmt die Rolle des Senior Key Account Managers. Erfahren Sie mehr über unseren neuen Experten für Infrastruktur und Energieversorger.'
public: false
--- ---
# Herzlich willkommen bei KLZ: Johannes Gleich startet als Senior Key Account Manager durch # Herzlich willkommen bei KLZ: Johannes Gleich startet als Senior Key Account Manager durch

View File

@@ -5,6 +5,7 @@ featuredImage: /uploads/2026/01/1767353529807.jpg
locale: en locale: en
category: Cable Technology category: Cable Technology
excerpt: 'KLZ Cables kicks off the new year with a strong addition: Johannes Gleich takes on the role of Senior Key Account Manager. Learn more about our new expert for infrastructure and energy suppliers.' excerpt: 'KLZ Cables kicks off the new year with a strong addition: Johannes Gleich takes on the role of Senior Key Account Manager. Learn more about our new expert for infrastructure and energy suppliers.'
public: false
--- ---
# Welcome to KLZ: Johannes Gleich starts as Senior Key Account Manager # Welcome to KLZ: Johannes Gleich starts as Senior Key Account Manager

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import matter from 'gray-matter'; import matter from 'gray-matter';
import { mapSlugToFileSlug } from './slugs'; import { mapSlugToFileSlug } from './slugs';
import { config } from '@/lib/config';
export interface PostFrontmatter { export interface PostFrontmatter {
title: string; title: string;
@@ -10,6 +11,7 @@ export interface PostFrontmatter {
featuredImage?: string | null; featuredImage?: string | null;
category?: string; category?: string;
locale: string; locale: string;
public?: boolean;
} }
export interface PostMdx { export interface PostMdx {
@@ -18,6 +20,17 @@ export interface PostMdx {
content: string; content: string;
} }
export function isPostVisible(post: { frontmatter: { date: string; public?: boolean } }) {
// If explicitly marked as not public, hide in production
if (post.frontmatter.public === false && config.isProduction) {
return false;
}
const postDate = new Date(post.frontmatter.date);
const now = new Date();
return !(postDate > now && config.isProduction);
}
export async function getPostBySlug(slug: string, locale: string): Promise<PostMdx | null> { export async function getPostBySlug(slug: string, locale: string): Promise<PostMdx | null> {
// Map translated slug to file slug // Map translated slug to file slug
const fileSlug = await mapSlugToFileSlug(slug, locale); const fileSlug = await mapSlugToFileSlug(slug, locale);
@@ -31,11 +44,17 @@ export async function getPostBySlug(slug: string, locale: string): Promise<PostM
const fileContent = fs.readFileSync(filePath, 'utf8'); const fileContent = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(fileContent); const { data, content } = matter(fileContent);
return { const postInfo = {
slug: fileSlug, slug: fileSlug,
frontmatter: data as PostFrontmatter, frontmatter: data as PostFrontmatter,
content, content,
}; };
if (!isPostVisible(postInfo)) {
return null;
}
return postInfo;
} }
export async function getAllPosts(locale: string): Promise<PostMdx[]> { export async function getAllPosts(locale: string): Promise<PostMdx[]> {
@@ -55,6 +74,7 @@ export async function getAllPosts(locale: string): Promise<PostMdx[]> {
content, content,
}; };
}) })
.filter(isPostVisible)
.sort( .sort(
(a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime(), (a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime(),
); );
@@ -78,6 +98,7 @@ export async function getAllPostsMetadata(locale: string): Promise<Partial<PostM
frontmatter: data as PostFrontmatter, frontmatter: data as PostFrontmatter,
}; };
}) })
.filter(isPostVisible)
.sort( .sort(
(a, b) => (a, b) =>
new Date(b.frontmatter.date as string).getTime() - new Date(b.frontmatter.date as string).getTime() -