149 lines
5.8 KiB
TypeScript
149 lines
5.8 KiB
TypeScript
'use client';
|
|
import { useState, useEffect } from 'react';
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkGfm from 'remark-gfm';
|
|
import { useParams } from 'next/navigation';
|
|
import Image from 'next/image';
|
|
import { Tag } from 'lucide-react';
|
|
import strings from "@/strings.json"
|
|
|
|
export default function PostPage() {
|
|
const [markdown, setMarkdown] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
const [postTitle, setPostTitle] = useState('');
|
|
const [postDate, setPostDate] = useState('');
|
|
const [postCategory, setPostCategory] = useState('');
|
|
const { slug } = useParams();
|
|
|
|
useEffect(() => {
|
|
interface PostData {
|
|
title?: string;
|
|
date?: string;
|
|
category?: string;
|
|
message?: string;
|
|
}
|
|
|
|
function setPostData(postData: PostData) {
|
|
setPostTitle(postData.title || strings.blankPostTitlePlaceholder);
|
|
|
|
if (postData.date) {
|
|
const date = new Date(Number(postData.date) * 1000)
|
|
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' };
|
|
setPostDate(date.toLocaleDateString(undefined, options));
|
|
} else {
|
|
setPostDate(strings.blankPostDatePlaceholder);
|
|
}
|
|
|
|
if (postData.category) {
|
|
setPostCategory(postData.category);
|
|
} else if (postData.message) {
|
|
setPostCategory(postData.message);
|
|
} else {
|
|
setPostCategory(strings.errorsSuperGeneric);
|
|
}
|
|
}
|
|
|
|
console.log(strings.logsOnSlugNavigate, slug);
|
|
(async () => {
|
|
try {
|
|
const res = await fetch(`http://localhost:3001/api/posts/get/${slug}`, {
|
|
cache: 'no-store',
|
|
});
|
|
|
|
const contentType = res.headers.get('content-type') || '';
|
|
if (contentType.includes('application/json')) {
|
|
const data = await res.json();
|
|
if (data.success === false) {
|
|
if (data.message) {
|
|
setLoading(false);
|
|
setError(data.message);
|
|
throw new Error(data.message);
|
|
} else {
|
|
setLoading(false);
|
|
setError(strings.errorsUnknownError);
|
|
throw new Error(strings.errorsUnknownError);
|
|
}
|
|
}
|
|
} else {
|
|
const text = await res.text();
|
|
const catRes = await fetch(`http://localhost:3001/api/posts/getPostDetails/${slug}`, {
|
|
cache: 'no-store',
|
|
});
|
|
const postData = await catRes.json();
|
|
|
|
if (postData.success) {
|
|
setPostData(postData);
|
|
} else {
|
|
if (postData.message) {
|
|
setPostData(postData);
|
|
} else {
|
|
setPostCategory(strings.errorsSuperGeneric);
|
|
}
|
|
}
|
|
|
|
setMarkdown(text);
|
|
setLoading(false);
|
|
}
|
|
} catch (error) {
|
|
console.error(strings.errorsFetchPostErr, ':', error);
|
|
setError(strings.errorsFetchPostErr);
|
|
setMarkdown(strings.errorsFetchPostErrMd);
|
|
}
|
|
})();
|
|
}, [slug]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-[80vh]">
|
|
<div className="animate-spin rounded-full h-16 w-16 border-4 border-t-slate-800 border-white"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="text-red-500">{error}</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="text-4xl font-bold my-4">{postTitle}</h1>
|
|
<p className="italic text-muted-foreground">{strings.postPublishedOnLabel} {postDate}</p>
|
|
<div className="flex flex-wrap gap-2 my-4">
|
|
<span className="border border-white text-bold px-3 py-1 rounded-md">
|
|
<Tag className="w-4 h-4 inline-block mr-1" /> {postCategory}
|
|
</span>
|
|
</div>
|
|
<hr className="my-4 border-white" />
|
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{
|
|
h1: ({...props}) => <h1 className='text-4xl font-bold my-4' {...props}/>,
|
|
h2: ({...props}) => <h2 className='text-2xl font-semibold my-4' {...props}/>,
|
|
h3: ({...props}) => <h3 className='text-xl my-4' {...props}/>,
|
|
h4: ({...props}) => <h4 className='text-md my-4' {...props}/>,
|
|
h5: ({...props}) => <h5 className='text-sm my-4' {...props}/>,
|
|
em: ({...props}) => <em className='mt-30' {...props}/>,
|
|
hr: ({...props}) => <hr className='border-white solid my-5' {...props}/>,
|
|
p: ({...props}) => <p className='my-3' {...props}/>,
|
|
a: ({...props}) => <a className='text-white underline' {...props}/>,
|
|
img: ({src, alt, width, height, ...props}) => <Image className='my-4 rounded-md' src={src || ''} alt={alt || ''} width={width ? parseInt(width.toString()) : 800} height={height ? parseInt(height.toString()) : 400} {...props}/>,
|
|
blockquote: ({...props}) => <blockquote className="border-l-4 border-white pl-4 my-4 italic" {...props}/>,
|
|
code: ({...props}) => <code className="bg-gray-800 rounded px-1 font-mono text-sm" {...props}/>,
|
|
pre: ({...props}) => <pre className="bg-gray-800 rounded p-4 my-4 overflow-x-auto font-mono text-sm" {...props}/>,
|
|
table: ({...props}) => <table className="border-collapse table-auto w-full my-4" {...props}/>,
|
|
thead: ({...props}) => <thead className="bg-gray-800" {...props}/>,
|
|
th: ({...props}) => <th className="border border-gray-600 px-4 py-2" {...props}/>,
|
|
td: ({...props}) => <td className="border border-gray-600 px-4 py-2" {...props}/>,
|
|
ul: ({...props}) => <ul className="list-disc ml-6 my-2" {...props}/>,
|
|
ol: ({...props}) => <ol className="list-decimal ml-6 my-2" {...props}/>,
|
|
li: ({...props}) => <li className="pl-2" {...props}/>,
|
|
input: ({type, ...props}) => type === 'checkbox' ?
|
|
<input type="checkbox" className="ml-[0.4rem] mr-2" {...props}/> :
|
|
<input type={type} {...props}/>
|
|
}}>
|
|
{markdown}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
} |