suraj_shah_2005

Untitled

Aug 8th, 2026
51
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // app/(site)/blog/[id]/page.tsx
  2. import Image from "next/image";
  3. import { notFound } from "next/navigation";
  4. import { Calendar, Clock } from "lucide-react";
  5.  
  6. import { getBlogbySlug } from "@/server/blogs";
  7. import { getReadTime } from "@/lib/utils";
  8.  
  9. import "@/components/tiptap-templates/simple/simple-editor.scss";
  10. import "@/components/tiptap-templates/simple/keyframe-animations.scss";
  11. import "@/components/tiptap-templates/simple/variables.scss";
  12. import BlogPageContent from "./_components/content";
  13.  
  14. export const instant = false;
  15.  
  16. async function BlogPage(props: PageProps<"/blog/[id]">) {
  17.   const { id } = await props.params;
  18.   const blogData = await getBlogbySlug(id);
  19.  
  20.   if (!blogData || blogData.status !== "published") {
  21.     notFound();
  22.   }
  23.  
  24.   return (
  25.     <div className="min-h-screen">
  26.       <div className="mx-auto max-w-5xl px-4 py-12 sm:px-6 lg:px-8 lg:py-20">
  27.         <article className="space-y-10">
  28.           {/* Meta */}
  29.           <div className="flex flex-wrap items-center gap-3 text-sm text-stone-500">
  30.             <div className="flex items-center gap-2 rounded-full border border-stone-200 bg-stone-50 px-3 py-1.5">
  31.               <Calendar className="h-4 w-4" />
  32.                <span>
  33.                 {blogData.createdAt.toLocaleDateString("en-US", {
  34.                   year: "numeric",
  35.                   month: "long",
  36.                   day: "numeric",
  37.                 })}
  38.               </span>
  39.             </div>
  40.  
  41.             <div className="flex items-center gap-2 rounded-full border border-stone-200 bg-stone-50 px-3 py-1.5">
  42.               <Clock className="h-4 w-4" />
  43.               <span>{getReadTime(blogData.content)}</span>
  44.             </div>
  45.           </div>
  46.  
  47.           {/* Header */}
  48.           <div className="space-y-6">
  49.             <h1 className="text-3xl font-semibold tracking-tight text-stone-900 sm:text-4xl lg:text-5xl">
  50.               {blogData.title}
  51.             </h1>
  52.  
  53.             <p className="max-w-3xl text-lg leading-8 text-stone-600">
  54.               {blogData.excerpt}
  55.             </p>
  56.  
  57.             <div className="flex flex-wrap gap-2">
  58.               {blogData.tags.map((tag) => (
  59.                 <span
  60.                   key={tag}
  61.                   className="rounded-full border border-stone-200 bg-stone-50 px-3 py-1 text-sm text-stone-600"
  62.                 >
  63.                   {tag}
  64.                 </span>
  65.               ))}
  66.             </div>
  67.           </div>
  68.  
  69.           {/* Cover */}
  70.           <div className="relative aspect-video overflow-hidden rounded-2xl bg-stone-100">
  71.             <Image
  72.               src={blogData.image}
  73.               alt={blogData.title}
  74.               fill
  75.               sizes="(max-width: 768px) 100vw, 75vw"
  76.               className="object-cover"
  77.             />
  78.           </div>
  79.  
  80.           <BlogPageContent value={blogData.content} />
  81.         </article>
  82.       </div>
  83.     </div>
  84.   );
  85. }
  86.  
  87. export default BlogPage;
  88.  
  89.  
  90.  
  91.  
  92. // app/(site)/blog/[id]/_components/content.tsx
  93.  
  94. "use client";
  95.  
  96. import { EditorContent, useEditor } from "@tiptap/react";
  97.  
  98. // --- Tiptap Core Extensions ---
  99. import { StarterKit } from "@tiptap/starter-kit";
  100. import { Image } from "@tiptap/extension-image";
  101. import { TaskItem, TaskList } from "@tiptap/extension-list";
  102. import { TextAlign } from "@tiptap/extension-text-align";
  103. import { Typography } from "@tiptap/extension-typography";
  104. import { Highlight } from "@tiptap/extension-highlight";
  105. import { Subscript } from "@tiptap/extension-subscript";
  106. import { Superscript } from "@tiptap/extension-superscript";
  107. import { Selection } from "@tiptap/extensions";
  108.  
  109. // --- Tiptap Node ---
  110. import { HorizontalRule } from "@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension";
  111. import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
  112. import "@/components/tiptap-node/blockquote-node/blockquote-node.scss";
  113. import "@/components/tiptap-node/code-block-node/code-block-node.scss";
  114. import "@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss";
  115. import "@/components/tiptap-node/list-node/list-node.scss";
  116. import "@/components/tiptap-node/image-node/image-node.scss";
  117. import "@/components/tiptap-node/heading-node/heading-node.scss";
  118. import "@/components/tiptap-node/paragraph-node/paragraph-node.scss";
  119. import "highlight.js/styles/xcode.css";
  120.  
  121. // --- Lib ---
  122. import { EditorContentType } from "@/lib/tiptap-utils";
  123.  
  124. // --- Styles ---
  125. import "@/components/tiptap-templates/simple/simple-editor.scss";
  126. import { lowlight } from "@/components/tiptap-node/code-block-lowlight/code-block-lowlight-extension";
  127. import { LoaderCircleIcon } from "lucide-react";
  128. function BlogPageContent({ value }: { value: EditorContentType }) {
  129.   const editor = useEditor({
  130.     immediatelyRender: false,
  131.     editorProps: {
  132.       attributes: {
  133.         autocomplete: "off",
  134.         autocorrect: "off",
  135.         autocapitalize: "off",
  136.         "aria-label": "Main content area, start typing to enter text.",
  137.         class: "simple-editor",
  138.       },
  139.     },
  140.     extensions: [
  141.       StarterKit.configure({
  142.         horizontalRule: false,
  143.         link: {
  144.           openOnClick: false,
  145.           enableClickSelection: true,
  146.         },
  147.         codeBlock: false,
  148.       }),
  149.       CodeBlockLowlight.configure({
  150.         lowlight: lowlight,
  151.       }),
  152.       HorizontalRule,
  153.       TextAlign.configure({ types: ["heading", "paragraph"] }),
  154.       TaskList,
  155.       TaskItem.configure({ nested: true }),
  156.       Highlight.configure({ multicolor: true }),
  157.       Image,
  158.       Typography,
  159.       Superscript,
  160.       Subscript,
  161.       Selection,
  162.     ],
  163.     content: value,
  164.     editable: false,
  165.   });
  166.  
  167.   if (!editor) {
  168.     return (
  169.       <div className="simple-editor-content flex min-h-50 items-center justify-center rounded-md border">
  170.         <LoaderCircleIcon className="h-6 w-6 animate-spin text-gray-500" />
  171.       </div>
  172.     );
  173.   }
  174.   return (
  175.     <EditorContent
  176.       editor={editor}
  177.       role="presentation"
  178.       className="simple-editor-content"
  179.       id="simple-editor-content"
  180.     />
  181.   );
  182. }
  183.  
  184. export default BlogPageContent;
Advertisement
Add Comment
Please, Sign In to add comment