Skip to content
Open
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
5 changes: 5 additions & 0 deletions app/(frontend)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
return {
title: `${post.meta?.title || post.title} | Khushal Bhardwaj`,
description: post.meta?.description || post.excerpt,
alternates: {
types: {
"text/markdown": `/blog/${slug}.md`,
},
},
};
}

Expand Down
5 changes: 5 additions & 0 deletions app/(frontend)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export const dynamic = "force-dynamic";
export const metadata: Metadata = {
title: "Blog | Khushal Bhardwaj",
description: "Blog posts by Khushal Bhardwaj",
alternates: {
types: {
"text/markdown": "/blog.md",
},
},
};

export default async function BlogPage() {
Expand Down
1 change: 1 addition & 0 deletions app/(frontend)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GeistSans } from "geist/font/sans";
import { GeistPixelSquare } from "geist/font/pixel";

export const metadata: Metadata = {
metadataBase: new URL("https://celeroncoder.tech"),
title: "Khushal Bhardwaj",
description: "Full-stack Software Engineer",
icons: {
Expand Down
130 changes: 130 additions & 0 deletions app/(frontend)/llm-markdown/[[...path]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { SerializedEditorState } from "lexical";
import { getPayload, type Payload } from "payload";
import {
convertLexicalToMarkdown,
editorConfigFactory,
} from "@payloadcms/richtext-lexical";
import config from "@payload-config";
import {
markdownResponse,
renderBlogIndexMarkdown,
renderBlogPostMarkdown,
renderHomeMarkdown,
} from "@/lib/markdown-pages";

export const dynamic = "force-dynamic";

type RouteContext = {
params: Promise<{ path?: string[] }>;
};

function isSerializedEditorState(value: unknown): value is SerializedEditorState {
if (!value || typeof value !== "object" || !("root" in value)) return false;

const root = value.root;
return Boolean(
root &&
typeof root === "object" &&
"type" in root &&
root.type === "root" &&
"children" in root &&
Array.isArray(root.children),
);
}

function getPostEditorConfig(payload: Payload) {
const postsCollection = payload.config.collections.find(
(collection) => collection.slug === "posts",
);
const contentField = postsCollection?.fields.find(
(field) => "name" in field && field.name === "content",
);

if (!contentField || contentField.type !== "richText") {
throw new Error("The posts.content rich text field is not configured.");
}

return editorConfigFactory.fromField({ field: contentField });
}

function notFoundResponse() {
return markdownResponse(
"# 404\n\nThe requested page does not exist.",
"404.md",
404,
);
}

export async function GET(request: Request, { params }: RouteContext) {
const { path = [] } = await params;
const origin = new URL(request.url).origin;
const isHome = path.length === 0 || (path.length === 1 && path[0] === "index");
const isBlogIndex = path.length === 1 && path[0] === "blog";
const isBlogPost = path.length === 2 && path[0] === "blog";

if (!isHome && !isBlogIndex && !isBlogPost) return notFoundResponse();

try {
const payload = await getPayload({ config });

if (isHome) {
const { docs: posts } = await payload.find({
collection: "posts",
sort: "-publishedAt",
limit: 5,
});

return markdownResponse(
renderHomeMarkdown(posts, origin),
"index.md",
);
}

if (isBlogIndex) {
const { docs: posts } = await payload.find({
collection: "posts",
sort: "-publishedAt",
limit: 50,
depth: 1,
});

return markdownResponse(
renderBlogIndexMarkdown(posts, origin),
"blog.md",
);
}

const slug = path[1];
const {
docs: [post],
} = await payload.find({
collection: "posts",
where: { slug: { equals: slug } },
limit: 1,
depth: 2,
});

if (!post) return notFoundResponse();

if (!isSerializedEditorState(post.content)) {
throw new Error(`Post "${post.slug}" has invalid rich text content.`);
}

const body = convertLexicalToMarkdown({
data: post.content,
editorConfig: getPostEditorConfig(payload),
});

return markdownResponse(
renderBlogPostMarkdown(post, body, origin),
`${post.slug}.md`,
);
} catch (error) {
console.error("Failed to render Markdown page", error);
return markdownResponse(
"# 500\n\nThe Markdown version of this page is temporarily unavailable.",
"500.md",
500,
);
}
}
5 changes: 5 additions & 0 deletions app/(frontend)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export const metadata: Metadata = {
title: "Khushal Bhardwaj | Full Stack Web Developer",
description:
"Portfolio of Khushal Bhardwaj, a Full Stack Web Developer based in Jaipur, India, building web apps with React and Next.js.",
alternates: {
types: {
"text/markdown": "/.md",
},
},
};

export default function Home() {
Expand Down
16 changes: 6 additions & 10 deletions components/about.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { about, profile } from "@/lib/site-content";

export function About() {
return (
<section className="space-y-5">
<h2 className="text-lg font-medium tracking-tight font-pixel">About</h2>
<div className="space-y-4 text-sm text-neutral-400 leading-relaxed">
<p>
Hey, I&apos;m{" "}
<span className="text-white font-medium">Khushal Bhardwaj</span>, a
Full Stack Web Developer based in Jaipur, India. Passionate about
building web apps with React/NextJS. I&apos;m also an undergrad at VIT
Bhopal University.
</p>
<p>
Other than programming, I write blogs. I&apos;ve developed a hobby of
writing blogs, mostly technical. Although I may not be following it
that regularly, you might think, but I do love it.
{about.beforeName}
<span className="text-white font-medium">{profile.name}</span>
{about.afterName}
</p>
<p>{about.writing}</p>
</div>
</section>
);
Expand Down
12 changes: 7 additions & 5 deletions components/footer.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import Link from "next/link";
import { profile, writingLinks } from "@/lib/site-content";

const footerLinks = [
{ name: "GitHub", url: "https://github.com/celeroncoder/celeroncoder.tech" },
{ name: "Dev.to", url: "https://dev.to/celeron" },
{ name: "Hashnode", url: "https://hashnode.com/@celeroncoder" },
{ name: "Medium", url: "https://medium.com/@celeroncoder" },
{
name: "GitHub",
url: "https://github.com/celeroncoder/celeroncoder.tech",
},
...writingLinks,
];

export function Footer() {
return (
<footer className="border-t border-neutral-800 pt-8 mt-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<p className="text-neutral-500 text-xs" suppressHydrationWarning>
&copy; {new Date().getFullYear()} Khushal Bhardwaj. Built with
&copy; {new Date().getFullYear()} {profile.name}. Built with
Next.js.
</p>
<div className="flex gap-4">
Expand Down
9 changes: 4 additions & 5 deletions components/greeting.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Image from "next/image";
import { profile } from "@/lib/site-content";

export function Greeting() {
return (
<section className="space-y-6">
<div className="size-20 rounded-full overflow-hidden bg-neutral-800 ring-2 ring-neutral-700">
<Image
src="https://github.com/celeroncoder.png"
alt="Khushal Bhardwaj"
alt={profile.name}
width={80}
height={80}
className="object-cover size-full"
Expand All @@ -15,13 +16,11 @@ export function Greeting() {

<div className="space-y-4 max-w-md">
<h1 className="text-2xl font-medium tracking-tight font-pixel">
Hey, I&apos;m Khushal Bhardwaj.
Hey, I&apos;m {profile.name}.
</h1>

<p className="text-neutral-400 text-sm leading-relaxed">
Full Stack Web Developer based in Jaipur, India. Passionate about
building web apps with React and Next.js. Currently an undergrad at
VIT Bhopal University.
{profile.introduction}
</p>
</div>
</section>
Expand Down
7 changes: 1 addition & 6 deletions components/links.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import Link from "next/link";

const socialLinks = [
{ name: "GitHub", url: "https://github.com/celeroncoder" },
{ name: "Twitter", url: "https://twitter.com/celeroncoder" },
{ name: "LinkedIn", url: "https://linkedin.com/in/celeroncoder" },
];
import { socialLinks } from "@/lib/site-content";

export function Links() {
return (
Expand Down
46 changes: 5 additions & 41 deletions components/projects.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,6 @@
import Link from "next/link";
import { ArcadeArrowUpRight, ArcadeLock } from "@/components/icons/arcade-icons";

type Project = {
title: string;
description: string;
github_url?: string;
live_url?: string;
};

const projects: Project[] = [
{
title: "Shire",
description:
"A macOS native Claude Code wrapper with all the functionalities of Claude Code",
github_url: "https://github.com/celeroncoder/shire",
live_url: "https://shire.celeroncoder.com",
},
{
title: "Curewell Admin",
description:
"Extensive CRM Dashboard for Homeopathic Clinic built with Next.js and tRPC",
},
{
title: "PlayerStatPML",
description:
"Express-TypeScript API proxy for Premier League statistics",
github_url: "https://github.com/celeronCoder/playerstatpml",
live_url:
"https://rapidapi.com/celeronCoder/api/premier-league-player-and-club-statistics",
},
{
title: "winston-highstorm",
description:
"NPM Package for winston Transport to ingest logs to highstorm.app",
github_url: "https://github.com/celeronCoder/winston-highstorm",
live_url: "https://link.celeroncoder.tech/winston-transport-npm",
},
];
import { projects } from "@/lib/site-content";

export function Projects() {
return (
Expand All @@ -50,9 +14,9 @@ export function Projects() {
{project.description}
</p>
<div className="flex gap-4 mt-2">
{project.github_url ? (
{project.githubUrl ? (
<Link
href={project.github_url}
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-[0.3em] text-neutral-500 text-xs hover:text-white transition-colors duration-300"
Expand All @@ -66,9 +30,9 @@ export function Projects() {
<span className="font-pixel">Private</span>
</span>
)}
{project.live_url && (
{project.liveUrl && (
<Link
href={project.live_url}
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-[0.3em] text-neutral-500 text-xs hover:text-white transition-colors duration-300"
Expand Down
9 changes: 1 addition & 8 deletions components/stack.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
const skills = [
"TypeScript",
"Next.js",
"tRPC",
"Express.js",
"Prisma",
"Tailwind CSS",
];
import { skills } from "@/lib/site-content";

export function Stack() {
return (
Expand Down
Loading