diff --git a/frontend/app/[topic]/TopicLayout.tsx b/frontend/app/[topic]/TopicLayout.tsx index d44d173..5313a64 100644 --- a/frontend/app/[topic]/TopicLayout.tsx +++ b/frontend/app/[topic]/TopicLayout.tsx @@ -12,7 +12,7 @@ import { useEffect, useMemo, useState, useRef, useCallback } from "react"; import { useTheme } from "../../src/context/ThemeContext"; import { useGitHubStats } from "../../src/hooks/useGitHubStats"; import { useRouter } from "next/navigation"; -import { FaGithub, FaLinkedin } from "react-icons/fa"; +import { FaGithub } from "react-icons/fa"; import toast, { Toaster } from "react-hot-toast"; import SearchPalette, { useSearchOSKey, SearchItem } from "../components/SearchPalette"; import { @@ -30,7 +30,6 @@ import { Workflow, X, Home, - Globe, GitPullRequest, GitMerge, CircleDot, @@ -56,7 +55,6 @@ export default function TopicLayout({ prevTopic?: { id: string; title?: string } | null; nextTopic?: { id: string; title?: string } | null; }) { - // Removed `darkMode` - we only need toggleTheme for the button! const { toggleTheme } = useTheme(); const router = useRouter(); @@ -66,7 +64,7 @@ export default function TopicLayout({ const [isMobileTocOpen, setIsMobileTocOpen] = useState(false); const [feedbackSubmitted, setFeedbackSubmitted] = useState(false); - const { modifierKey, isMounted } = useSearchOSKey(); + const { modifierKey } = useSearchOSKey(); const [expandedOutline, setExpandedOutline] = useState>({}); const [activeOutlineId, setActiveOutlineId] = useState(null); @@ -215,7 +213,6 @@ export default function TopicLayout({ }); }, [outlineGroups]); - // REFACTORED: Converted all JS logic to pure Tailwind `dark:` classes const themeClasses = { page: "bg-[#FFFFFF] text-[#111111] dark:bg-[#050505] dark:text-[#F5F5F5]", sidebar: "bg-[#F7F7F7] dark:bg-[#101010]", @@ -286,10 +283,8 @@ export default function TopicLayout({ return (
-
- {/* REFACTORED: Used !important classes to override react-hot-toast defaults via Tailwind instead of JS styles */} - setCmdOpen(false)} onToggle={() => setCmdOpen((prev) => !prev)} @@ -306,7 +301,11 @@ export default function TopicLayout({ placeholder="Search documentation or type a command..." /> - {isMobileMenuOpen &&
setIsMobileMenuOpen(false)} />} + {/* STATIC NODE: Toggles visibility via CSS to prevent React DevTools mounting crashes */} +
setIsMobileMenuOpen(false)} + className={`fixed inset-0 z-50 bg-black/50 backdrop-blur-sm lg:hidden transition-opacity duration-300 ${isMobileMenuOpen ? "opacity-100" : "opacity-0 pointer-events-none"}`} + />
@@ -358,7 +357,6 @@ export default function TopicLayout({
- {/* REFACTORED: Used CSS display classes instead of JS ternary for the text! */} - {frontmatter?.category && ( - <> - - - - )} + {/* STATIC NODES: Toggles flex/hidden based on state. Prevents DevTools crashing during scroll. */} +
+ + +
@@ -418,72 +414,67 @@ export default function TopicLayout({ {currentTopicTitle} - {activeGroupTitle && ( - <> - - - - )} +
+ + +
- {activeChildTitle && ( - <> - - - - )} +
+ + +
- {outlineGroups.length > 0 && ( -
- -
-
-
- {outlineGroups.map((group) => ( -
- { e.preventDefault(); scrollToSection(group.id); setIsMobileTocOpen(false); }} className={`block text-[14px] font-medium ${activeOutlineId === group.id ? "text-[#6f45d6] dark:text-[#C699FF]" : themeClasses.muted}`}> - {group.title} - - {group.items.length > 0 && ( - - )} + {/* STATIC NODE: Toggles block/hidden */} + - )} +

{currentTopicTitle}

- {frontmatter?.description &&

{frontmatter.description}

} + {/* STATIC NODE: Toggles block/hidden */} +

+ {frontmatter?.description} +

{children}
-
Was this page helpful? @@ -509,7 +500,6 @@ export default function TopicLayout({
- {/* REFACTORED: Completely eliminated JS conditional rendering for the GitHub widget */}
@@ -587,25 +577,24 @@ export default function TopicLayout({
- {githubStats.contributors.length > 0 && ( - <> -
- {githubStats.contributors.slice(0, 5).map((user: any, idx: number) => ( - {user.login} - ))} -
-

- {githubStats.contributors.length > 5 ? `+${githubStats.contributors.length - 5} Devs` : 'Contributors'} -

- - )} + {/* STATIC NODE: Toggles flex/hidden */} +
0 ? "flex" : "hidden"}`}> +
+ {githubStats.contributors.slice(0, 5).map((user: any, idx: number) => ( + {user.login} + ))} +
+

+ {githubStats.contributors.length > 5 ? `+${githubStats.contributors.length - 5} Devs` : 'Contributors'} +

+
@@ -632,25 +621,26 @@ export default function TopicLayout({
- {prevTopic ? ( + {/* STATIC NODES: Replaced dynamic ternary with static layout toggled via CSS classes */} +
- ) :
} +
- {nextTopic ? ( +
- ) :
} +
@@ -668,15 +658,14 @@ export default function TopicLayout({ { e.preventDefault(); scrollToSection(group.id); }} className={`block text-[13px] tracking-tight font-semibold leading-snug transition-colors mb-2 ${isActiveGroup ? "text-[#6f45d6] drop-shadow-[0_0_8px_rgba(111,69,214,0.4)] dark:text-[#C699FF] dark:drop-shadow-[0_0_8px_rgba(198,153,255,0.4)]" : `${themeClasses.muted} hover:text-current`}`}> {group.title} - {group.items.length > 0 && ( - - )} + {/* STATIC NODE: Toggles block/hidden */} +
); })} diff --git a/frontend/app/[topic]/loading.tsx b/frontend/app/[topic]/loading.tsx new file mode 100644 index 0000000..b58ab41 --- /dev/null +++ b/frontend/app/[topic]/loading.tsx @@ -0,0 +1,123 @@ +import {Skeleton} from "../components/Skeleton"; + +export default function TopicLoading() { + // Reusing the identical theme classes to ensure the skeleton flashes + // with the exact same background and border colors as the final layout. + const themeClasses = { + page: "bg-[#FFFFFF] text-[#111111] dark:bg-[#050505] dark:text-[#F5F5F5]", + sidebar: "bg-[#F7F7F7] dark:bg-[#101010]", + header: "bg-[#FFFFFF]/90 dark:bg-[#050505]/90", + border: "border-[#E5E5E5] dark:border-[#222222]", + }; + + return ( +
+ {/* Header Placeholder */} +
+
+
+ {/* Mobile Menu Icon / Logo Placeholder */} + + +
+ +
+ {/* Command Palette Search Placeholder */} + +
+ + {/* Theme Toggle Placeholder */} + +
+
+ +
+ + {/* Left Sidebar (Directory) Placeholder */} + + + {/* Main Content Area */} +
+ + {/* Breadcrumbs Placeholder */} +
+ + + +
+ +
+ + {/* Title & Description Placeholder */} + +
+ + +
+ + {/* Paragraph 1 Placeholder */} +
+ + + +
+ + {/* Code Block Placeholder */} + + + {/* Subheading & Paragraph 2 Placeholder */} + +
+ + + +
+ + {/* Secondary Code/Data Block Placeholder */} + +
+
+ + {/* Right Sidebar (Table of Contents) Placeholder */} + + +
+
+ ); +} \ No newline at end of file diff --git a/frontend/app/[topic]/opengraph-image.tsx b/frontend/app/[topic]/opengraph-image.tsx index 326b7a9..f6d9046 100644 --- a/frontend/app/[topic]/opengraph-image.tsx +++ b/frontend/app/[topic]/opengraph-image.tsx @@ -2,21 +2,14 @@ import { ImageResponse } from 'next/og'; export const runtime = 'edge'; -export const alt = 'RefMe Documentation'; +export const alt = 'RefMe_ | Technical Reference for Modern Developers'; export const size = { width: 1200, height: 630, }; export const contentType = 'image/png'; -export default async function Image({ params }: { params: Promise<{ topic: string }> }) { - const { topic } = await params; - - const formattedTitle = topic - .split('-') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '); - +export default async function Image() { return new ImageResponse( (
+ {/* Note: The container wrapper below has a background and border. + Since your SVG also has a background/border, you may want to strip + the wrapper's bg/border if it looks like a box-inside-a-box, + but it is technically sound as-is. */}
- - R_ - + + + + + + R + + + +
RefMe @@ -55,26 +67,26 @@ export default async function Image({ params }: { params: Promise<{ topic: strin
-
+
- Reference Guide + Technical Reference
- {formattedTitle} + RefMe_
- The developer's quick-reference guide. + The ultimate centralized, fast, and minimal technical reference hub for modern full-stack development. Built for clarity and speed.
diff --git a/frontend/app/[topic]/page.tsx b/frontend/app/[topic]/page.tsx index 06b1898..3307af2 100644 --- a/frontend/app/[topic]/page.tsx +++ b/frontend/app/[topic]/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from "next"; import { getAllTopics, getTopicBySlug } from "../../src/lib/mdx"; import { MDXRemote } from "next-mdx-remote/rsc"; import SkeletonTopic from "../components/SkeletonTopic"; @@ -20,6 +21,40 @@ const mdxComponents = { }, }; +export async function generateMetadata({ + params, +}: { + params: Promise<{ topic: string }>; +}): Promise { + const resolvedParams = await params; + const topicData = getTopicBySlug(resolvedParams.topic); + + if (!topicData || !topicData.frontmatter) { + return {}; + } + + const { title, description } = topicData.frontmatter; + const pageTitle = `${title} | RefMe_`; + + return { + title: pageTitle, + description: description, + openGraph: { + title: pageTitle, + description: description, + type: "article", + }, + twitter: { + card: "summary_large_image", + title: pageTitle, + description: description, + }, + alternates: { + canonical: `https://refmev1.vercel.app/${resolvedParams.topic}`, + }, + }; +} + export async function generateStaticParams() { const topics = getAllTopics(); @@ -58,6 +93,46 @@ export default async function TopicPage({ ); } + const { title, description, category } = topicData.frontmatter; + const pageUrl = `https://refmev1.vercel.app/${resolvedParams.topic}`; + + const breadcrumbJsonLd = { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + itemListElement: [ + { + "@type": "ListItem", + position: 1, + name: "Home", + item: "https://refmev1.vercel.app", + }, + { + "@type": "ListItem", + position: 2, + name: category || "Category", + }, + { + "@type": "ListItem", + position: 3, + name: title, + item: pageUrl, + }, + ], + }; + + const articleJsonLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: title, + description: description, + author: { + "@type": "Person", + name: "Yash Vardhan", + url: "https://www.linkedin.com/in/vardhan-yash3105/", + }, + url: pageUrl, + }; + return ( +