Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions apps/example/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
'use client'

import Link from 'next/link'
import { useParams } from 'next/navigation'
import { getPostBySlug, getAllPosts } from '../posts'
import { whenny } from 'whenny'
import ReactMarkdown from 'react-markdown'

export default function BlogPostPage() {
const params = useParams()
const slug = params.slug as string
const post = getPostBySlug(slug)

if (!post) {
return (
<main className="min-h-screen bg-white">
<header className="border-b border-slate-200 bg-white/90 backdrop-blur-sm sticky top-0 z-10">
<div className="max-w-5xl mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between">
<Link href="/" className="font-semibold text-slate-900">Whenny</Link>
<div className="flex items-center gap-3 sm:gap-6">
<Link href="/blog" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900">Blog</Link>
<Link href="/docs" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900">Docs</Link>
</div>
</div>
</header>
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-16 text-center">
<h1 className="text-2xl font-bold text-slate-900 mb-4">Post Not Found</h1>
<p className="text-slate-600 mb-8">This post doesn't exist or hasn't been published yet.</p>
<Link href="/blog" className="text-blue-600 hover:text-blue-700">
← Back to Blog
</Link>
</div>
</main>
)
}

return (
<main className="min-h-screen bg-white">
{/* Header */}
<header className="border-b border-slate-200 bg-white/90 backdrop-blur-sm sticky top-0 z-10">
<div className="max-w-5xl mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between">
<div className="flex items-center gap-3 sm:gap-6">
<Link href="/" className="font-semibold text-slate-900">Whenny</Link>
<Link href="/blog" className="text-xs sm:text-sm text-slate-400 hover:text-slate-600">Blog</Link>
</div>
<div className="flex items-center gap-3 sm:gap-6">
<Link href="/demo" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900 transition-colors">Demo</Link>
<Link href="/docs" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900 transition-colors">Docs</Link>
<a href="https://github.com/ZVN-DEV/whenny" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900 transition-colors">GitHub</a>
</div>
</div>
</header>

<article className="max-w-3xl mx-auto px-4 sm:px-6 py-12 sm:py-16">
{/* Back link */}
<Link href="/blog" className="text-sm text-slate-500 hover:text-slate-700 mb-8 inline-block">
← Back to Blog
</Link>

{/* Header */}
<header className="mb-8">
<div className="flex items-center gap-3 text-sm text-slate-500 mb-4">
<time dateTime={post.publishDate}>
{whenny(new Date(post.publishDate + 'T12:00:00')).lg}
</time>
<span>·</span>
<span>{post.readTime}</span>
</div>
<h1 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-3">
{post.title}
</h1>
<p className="text-xl text-slate-600">{post.subtitle}</p>
<div className="flex gap-2 mt-4">
{post.tags.map((tag) => (
<span
key={tag}
className="px-2 py-1 text-xs bg-slate-100 text-slate-600 rounded"
>
#{tag}
</span>
))}
</div>
</header>

{/* Content */}
<div className="prose prose-slate max-w-none prose-headings:font-semibold prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h3:text-xl prose-h3:mt-6 prose-h3:mb-3 prose-p:text-slate-600 prose-p:leading-relaxed prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline prose-strong:text-slate-900 prose-code:text-sm prose-code:bg-slate-100 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none prose-pre:bg-slate-900 prose-pre:text-slate-100">
<ReactMarkdown
components={{
code: ({ className, children, ...props }) => {
const isInline = !className
if (isInline) {
return (
<code className="bg-slate-100 text-slate-800 px-1.5 py-0.5 rounded text-sm" {...props}>
{children}
</code>
)
}
return (
<code className={className} {...props}>
{children}
</code>
)
},
pre: ({ children }) => (
<pre className="bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm">
{children}
</pre>
),
}}
>
{post.content}
</ReactMarkdown>
</div>

{/* Footer */}
<footer className="mt-12 pt-8 border-t border-slate-200">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<p className="text-sm text-slate-500">Share this post</p>
<div className="flex gap-3 mt-2">
<a
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(post.title)}&url=${encodeURIComponent(`https://whenny.dev/blog/${post.slug}`)}`}
target="_blank"
rel="noopener noreferrer"
className="text-slate-400 hover:text-slate-600"
>
Twitter
</a>
<a
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(`https://whenny.dev/blog/${post.slug}`)}`}
target="_blank"
rel="noopener noreferrer"
className="text-slate-400 hover:text-slate-600"
>
LinkedIn
</a>
</div>
</div>
<Link href="/blog" className="text-blue-600 hover:text-blue-700 text-sm">
← More posts
</Link>
</div>
</footer>
</article>
</main>
)
}
27 changes: 27 additions & 0 deletions apps/example/app/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Metadata } from 'next'

export const metadata: Metadata = {
title: 'Blog - Whenny Date Library',
description: 'Thoughts on dates, timezones, AI-friendly APIs, and building developer tools. Learn about TypeScript date handling, the timestamp trap, and more.',
keywords: [
'typescript blog',
'javascript dates',
'timezone handling',
'ai coding',
'developer tools',
'date formatting',
],
openGraph: {
title: 'Blog - Whenny Date Library',
description: 'Thoughts on dates, timezones, AI-friendly APIs, and building developer tools.',
url: 'https://whenny.dev/blog',
},
}

export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return children
}
82 changes: 82 additions & 0 deletions apps/example/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use client'

import Link from 'next/link'
import { getPublishedPosts } from './posts'
import { whenny } from 'whenny'

export default function BlogPage() {
const posts = getPublishedPosts()

return (
<main className="min-h-screen bg-white">
{/* Header */}
<header className="border-b border-slate-200 bg-white/90 backdrop-blur-sm sticky top-0 z-10">
<div className="max-w-5xl mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between">
<div className="flex items-center gap-3 sm:gap-6">
<Link href="/" className="font-semibold text-slate-900">Whenny</Link>
<span className="text-xs sm:text-sm text-slate-400">Blog</span>
</div>
<div className="flex items-center gap-3 sm:gap-6">
<Link href="/demo" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900 transition-colors">Demo</Link>
<Link href="/docs" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900 transition-colors">Docs</Link>
<a href="https://github.com/ZVN-DEV/whenny" className="text-xs sm:text-sm text-slate-600 hover:text-slate-900 transition-colors">GitHub</a>
</div>
</div>
</header>

<div className="max-w-3xl mx-auto px-4 sm:px-6 py-12 sm:py-16">
<h1 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4">Blog</h1>
<p className="text-slate-600 mb-12">
Thoughts on dates, timezones, AI, and building developer tools.
</p>

{posts.length === 0 ? (
<div className="text-center py-12">
<p className="text-slate-500">No posts published yet. Check back soon!</p>
</div>
) : (
<div className="space-y-8">
{posts.map((post) => (
<article key={post.slug} className="group">
<Link href={`/blog/${post.slug}`} className="block">
<div className="flex items-center gap-3 text-sm text-slate-500 mb-2">
<time dateTime={post.publishDate}>
{whenny(new Date(post.publishDate + 'T12:00:00')).lg}
</time>
<span>·</span>
<span>{post.readTime}</span>
</div>
<h2 className="text-xl sm:text-2xl font-semibold text-slate-900 group-hover:text-blue-600 transition-colors mb-2">
{post.title}
</h2>
<p className="text-slate-600 mb-3">{post.subtitle}</p>
<div className="flex gap-2">
{post.tags.map((tag) => (
<span
key={tag}
className="px-2 py-1 text-xs bg-slate-100 text-slate-600 rounded"
>
#{tag}
</span>
))}
</div>
</Link>
</article>
))}
</div>
)}

{/* Coming Soon Section */}
<div className="mt-16 pt-8 border-t border-slate-200">
<h3 className="text-lg font-medium text-slate-900 mb-4">Coming Soon</h3>
<div className="space-y-3 text-slate-500">
<p className="flex items-center gap-2">
<span className="w-2 h-2 bg-slate-300 rounded-full"></span>
More posts on TypeScript date handling and AI integration
</p>
</div>
</div>
</div>
</main>
)
}
Loading