@@ -4,13 +4,14 @@ import {
44 CalendarIcon ,
55 ClockIcon ,
66 UserIcon ,
7+ WarningCircleIcon ,
78} from '@phosphor-icons/react/ssr' ;
89import type { Metadata } from 'next' ;
910import Image from 'next/image' ;
1011import Link from 'next/link' ;
11- import { notFound } from 'next/navigation' ;
1212import { SITE_URL } from '@/app/util/constants' ;
1313import { Footer } from '@/components/footer' ;
14+ import { SciFiButton } from '@/components/landing/scifi-btn' ;
1415import { Prose } from '@/components/prose' ;
1516import { getPosts , getSinglePost } from '@/lib/blog-query' ;
1617
@@ -36,61 +37,122 @@ export async function generateMetadata({
3637} : PageProps ) : Promise < Metadata > {
3738 const slug = ( await params ) . slug ;
3839
39- const data = await getSinglePost ( slug ) ;
40+ try {
41+ const data = await getSinglePost ( slug ) ;
42+ if ( ! data ?. post ) {
43+ return { title : 'Not Found | Databuddy' } ;
44+ }
4045
41- if ( ! data ?. post ) {
42- return notFound ( ) ;
43- }
44-
45- return {
46- title : `${ data . post . title } | Databuddy` ,
47- description : data . post . description ,
48- twitter : {
49- title : `${ data . post . title } | Databuddy` ,
50- description : data . post . description ,
51- card : 'summary_large_image' ,
52- images : [
53- {
54- url : data . post . coverImage ?? `${ SITE_URL } /og.webp` ,
55- width : '1200' ,
56- height : '630' ,
57- alt : data . post . title ,
58- } ,
59- ] ,
60- } ,
61- openGraph : {
46+ return {
6247 title : `${ data . post . title } | Databuddy` ,
6348 description : data . post . description ,
64- type : 'article' ,
65- images : [
66- {
67- url : data . post . coverImage ?? `${ SITE_URL } /og.webp` ,
68- width : '1200' ,
69- height : '630' ,
70- alt : data . post . title ,
71- } ,
72- ] ,
73- publishedTime : new Date ( data . post . publishedAt ) . toISOString ( ) ,
74- authors : [
75- ...data . post . authors . map ( ( author : { name : string } ) => author . name ) ,
76- ] ,
77- } ,
78- } ;
49+ twitter : {
50+ title : `${ data . post . title } | Databuddy` ,
51+ description : data . post . description ,
52+ card : 'summary_large_image' ,
53+ images : [
54+ {
55+ url : data . post . coverImage ?? `${ SITE_URL } /og.webp` ,
56+ width : '1200' ,
57+ height : '630' ,
58+ alt : data . post . title ,
59+ } ,
60+ ] ,
61+ } ,
62+ openGraph : {
63+ title : `${ data . post . title } | Databuddy` ,
64+ description : data . post . description ,
65+ type : 'article' ,
66+ images : [
67+ {
68+ url : data . post . coverImage ?? `${ SITE_URL } /og.webp` ,
69+ width : '1200' ,
70+ height : '630' ,
71+ alt : data . post . title ,
72+ } ,
73+ ] ,
74+ publishedTime : new Date ( data . post . publishedAt ) . toISOString ( ) ,
75+ authors : [
76+ ...data . post . authors . map ( ( author : { name : string } ) => author . name ) ,
77+ ] ,
78+ } ,
79+ } ;
80+ } catch {
81+ return { title : 'Not Found | Databuddy' } ;
82+ }
7983}
8084
8185export default async function PostPage ( {
8286 params,
8387} : {
8488 params : Promise < { slug : string } > ;
8589} ) {
86- const slug = ( await params ) . slug ;
87-
88- const { post } = await getSinglePost ( slug ) ;
90+ const { slug } = await params ;
91+ const result = ( await getSinglePost ( slug ) ) as {
92+ post ?: import ( '@/types/post' ) . Post ;
93+ error ?: boolean ;
94+ status ?: number ;
95+ statusText ?: string ;
96+ } ;
8997
90- if ( ! post ) {
91- return notFound ( ) ;
98+ if ( ! result ?. post ) {
99+ return (
100+ < >
101+ < div className = "relative flex min-h-[60vh] w-full items-center justify-center overflow-hidden px-4 pt-10 sm:px-6 sm:pt-12 lg:px-8" >
102+ { /* Main Content */ }
103+ < div className = "relative z-10 mx-auto w-full max-w-lg text-center" >
104+ < div className = "group relative" >
105+ < div className = "relative rounded border border-border bg-card/50 p-8 backdrop-blur-sm transition-all duration-300 hover:border-border/80 hover:bg-card/70 sm:p-12" >
106+ < WarningCircleIcon
107+ className = "mx-auto mb-4 h-12 w-12 text-muted-foreground transition-colors duration-300 group-hover:text-foreground sm:h-16 sm:w-16"
108+ weight = "duotone"
109+ />
110+ < h1 className = "mb-3 text-balance font-semibold text-2xl leading-tight tracking-tight sm:text-3xl md:text-4xl" >
111+ Post Not Found
112+ </ h1 >
113+ < p className = "mb-6 font-medium text-muted-foreground text-sm leading-relaxed tracking-tight sm:text-base" >
114+ The article you're looking for seems to have been moved or no
115+ longer exists.
116+ </ p >
117+ < div className = "flex flex-col gap-3 sm:flex-row sm:justify-center" >
118+ < SciFiButton asChild className = "flex-1 sm:flex-initial" >
119+ < Link aria-label = "Back to blog" href = "/blog" >
120+ < ArrowLeftIcon className = "h-4 w-4" weight = "fill" />
121+ Back to Blog
122+ </ Link >
123+ </ SciFiButton >
124+ </ div >
125+ </ div >
126+
127+ { /* Sci-fi corners */ }
128+ < div className = "pointer-events-none absolute inset-0" >
129+ < div className = "absolute top-0 left-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]" >
130+ < div className = "absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
131+ < div className = "absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
132+ </ div >
133+ < div className = "-scale-x-[1] absolute top-0 right-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]" >
134+ < div className = "absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
135+ < div className = "absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
136+ </ div >
137+ < div className = "-scale-y-[1] absolute bottom-0 left-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]" >
138+ < div className = "absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
139+ < div className = "absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
140+ </ div >
141+ < div className = "-scale-[1] absolute right-0 bottom-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]" >
142+ < div className = "absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
143+ < div className = "absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
144+ </ div >
145+ </ div >
146+ </ div >
147+ </ div >
148+ </ div >
149+ < Footer />
150+ </ >
151+ ) ;
92152 }
93153
154+ const post = result . post ;
155+
94156 const estimateReadingTime = ( htmlContent : string ) : string => {
95157 const text = htmlContent . replace ( STRIP_HTML_REGEX , ' ' ) ;
96158 const words = text . trim ( ) . split ( WORD_SPLIT_REGEX ) . filter ( Boolean ) . length ;
0 commit comments