shivam + claude changes
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
This commit is contained in:
@@ -1,30 +1,37 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_KEY = process.env.ANALYTICS_KEY || 'default-analytics-key';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const analyticsKey = req.headers.get('X-Analytics-Key');
|
||||
if (analyticsKey !== ANALYTICS_KEY) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
|
||||
if (typeof body.path !== 'string' || typeof body.timestamp !== 'number') {
|
||||
return NextResponse.json({ success: false, error: 'Invalid input' }, { status: 400 });
|
||||
}
|
||||
|
||||
const headers = req.headers;
|
||||
const forwarded = headers.get('x-forwarded-for');
|
||||
const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
|
||||
|
||||
// Get IP here since we are the public facing entity
|
||||
const ip = headers.get('x-forwarded-for') || 'unknown';
|
||||
|
||||
// Relay to Admin Dashboard (Internal Docker Network)
|
||||
// admin_dash must be reachable. If in same docker network, use container name.
|
||||
// If separate, use host.docker.internal or configured URL.
|
||||
const adminUrl = process.env.ADMIN_DASH_URL || 'http://admin_dash:3000/api/track';
|
||||
|
||||
// We fire and forget - don't wait for response to keep this fast
|
||||
fetch(adminUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...body, ip }), // Pass IP along
|
||||
body: JSON.stringify({ path: body.path, timestamp: body.timestamp, ip }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Forwarded-For': ip // Preserve IP
|
||||
'X-Forwarded-For': ip,
|
||||
},
|
||||
}).catch(e => console.error('Relay failed', e));
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ success: false });
|
||||
} catch {
|
||||
return NextResponse.json({ success: false, error: 'Bad request' }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
16
app/blog/[slug]/loading.tsx
Normal file
16
app/blog/[slug]/loading.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export default function BlogPostLoading() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-6 py-24 animate-pulse">
|
||||
<div className="space-y-4 mb-12">
|
||||
<div className="h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-1/4" />
|
||||
<div className="h-12 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" />
|
||||
<div className="h-6 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<div key={i} className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded" style={{ width: `${70 + Math.random() * 30}%` }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getPostBySlug } from '@/lib/mdx';
|
||||
import { getPostBySlug, getPostSlugs } from '@/lib/mdx';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
@@ -11,7 +11,7 @@ import { Citation, Bibliography } from '@/components/mdx/Citation';
|
||||
import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents';
|
||||
|
||||
// Utility to ensure consistent IDs
|
||||
const slugify = (text: any): string => {
|
||||
const slugify = (text: React.ReactNode): string => {
|
||||
if (!text) return '';
|
||||
const str = typeof text === 'string' ? text : String(text);
|
||||
return str
|
||||
@@ -21,16 +21,16 @@ const slugify = (text: any): string => {
|
||||
};
|
||||
|
||||
const components = {
|
||||
h1: (props: any) => <h1 {...props} id={slugify(props.children)} className="text-3xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
||||
h2: (props: any) => <h2 {...props} id={slugify(props.children)} className="text-2xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
||||
h3: (props: any) => <h3 {...props} id={slugify(props.children)} className="text-xl font-bold mt-6 mb-3 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
||||
p: (props: any) => <p {...props} className="mb-4 leading-relaxed text-zinc-700 dark:text-zinc-300" />,
|
||||
ul: (props: any) => <ul {...props} className="list-disc pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
|
||||
ol: (props: any) => <ol {...props} className="list-decimal pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
|
||||
blockquote: (props: any) => <blockquote {...props} className="border-l-4 border-zinc-200 dark:border-zinc-700 pl-4 italic my-6 text-zinc-600 dark:text-zinc-400" />,
|
||||
code: (props: any) => <code {...props} className="bg-zinc-100 dark:bg-zinc-800 text-pink-600 dark:text-pink-400 px-1 py-0.5 rounded text-sm font-mono" />,
|
||||
pre: (props: any) => <pre {...props} className="bg-zinc-900 dark:bg-zinc-900 text-zinc-100 p-4 rounded-lg overflow-x-auto my-6 text-sm font-mono" />,
|
||||
a: (props: any) => <a {...props} className="text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all font-medium" />,
|
||||
h1: (props: React.ComponentPropsWithoutRef<'h1'>) => <h1 {...props} id={slugify(props.children)} className="text-3xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
||||
h2: (props: React.ComponentPropsWithoutRef<'h2'>) => <h2 {...props} id={slugify(props.children)} className="text-2xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
||||
h3: (props: React.ComponentPropsWithoutRef<'h3'>) => <h3 {...props} id={slugify(props.children)} className="text-xl font-bold mt-6 mb-3 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
||||
p: (props: React.ComponentPropsWithoutRef<'p'>) => <p {...props} className="mb-4 leading-relaxed text-zinc-700 dark:text-zinc-300" />,
|
||||
ul: (props: React.ComponentPropsWithoutRef<'ul'>) => <ul {...props} className="list-disc pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
|
||||
ol: (props: React.ComponentPropsWithoutRef<'ol'>) => <ol {...props} className="list-decimal pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
|
||||
blockquote: (props: React.ComponentPropsWithoutRef<'blockquote'>) => <blockquote {...props} className="border-l-4 border-zinc-200 dark:border-zinc-700 pl-4 italic my-6 text-zinc-600 dark:text-zinc-400" />,
|
||||
code: (props: React.ComponentPropsWithoutRef<'code'>) => <code {...props} className="bg-zinc-100 dark:bg-zinc-800 text-pink-600 dark:text-pink-400 px-1 py-0.5 rounded text-sm font-mono" />,
|
||||
pre: (props: React.ComponentPropsWithoutRef<'pre'>) => <pre {...props} className="bg-zinc-900 dark:bg-zinc-900 text-zinc-100 p-4 rounded-lg overflow-x-auto my-6 text-sm font-mono" />,
|
||||
a: (props: React.ComponentPropsWithoutRef<'a'>) => <a {...props} className="text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all font-medium" />,
|
||||
SideNote,
|
||||
Citation,
|
||||
Bibliography,
|
||||
@@ -40,6 +40,12 @@ type Props = {
|
||||
params: Promise<{ slug: string }>;
|
||||
};
|
||||
|
||||
export function generateStaticParams() {
|
||||
return getPostSlugs()
|
||||
.filter((slug) => slug.endsWith('.mdx'))
|
||||
.map((slug) => ({ slug: slug.replace(/\.mdx$/, '') }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props) {
|
||||
const { slug } = await params;
|
||||
try {
|
||||
@@ -47,6 +53,17 @@ export async function generateMetadata({ params }: Props) {
|
||||
return {
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
openGraph: {
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
type: 'article' as const,
|
||||
publishedTime: metadata.date,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary' as const,
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
|
||||
20
app/blog/loading.tsx
Normal file
20
app/blog/loading.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function BlogLoading() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-pulse">
|
||||
<div className="space-y-4">
|
||||
<div className="h-10 bg-zinc-200 dark:bg-zinc-800 rounded w-1/3" />
|
||||
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" />
|
||||
</div>
|
||||
<div className="grid gap-10">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className="space-y-3">
|
||||
<div className="h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-1/4" />
|
||||
<div className="h-7 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" />
|
||||
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-full" />
|
||||
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-5/6" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { getAllPosts } from '@/lib/mdx';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Blog',
|
||||
description: 'Thoughts on software, deep learning, and research by Akshay Kolli.',
|
||||
openGraph: {
|
||||
title: 'Blog',
|
||||
description: 'Thoughts on software, deep learning, and research by Akshay Kolli.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function BlogIndex() {
|
||||
const posts = getAllPosts();
|
||||
|
||||
|
||||
25
app/error.tsx
Normal file
25
app/error.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="min-h-[60vh] flex flex-col items-center justify-center px-6 text-center animate-fade-in">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 mb-4">
|
||||
Something went wrong
|
||||
</h1>
|
||||
<p className="text-lg text-zinc-500 dark:text-zinc-400 mb-8">
|
||||
An unexpected error occurred.
|
||||
</p>
|
||||
<button
|
||||
onClick={reset}
|
||||
className="text-sm font-medium px-4 py-2 rounded bg-zinc-900 dark:bg-zinc-100 text-zinc-50 dark:text-zinc-900 hover:opacity-80 transition-opacity"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Navbar } from "../components/layout/Navbar";
|
||||
import { Footer } from "../components/layout/Footer";
|
||||
import { Analytics } from "../components/Analytics";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
@@ -11,12 +12,24 @@ const inter = Inter({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Akshay Kolli",
|
||||
description: "My personal website",
|
||||
metadataBase: new URL("https://akkolli.net"),
|
||||
title: {
|
||||
default: "Akshay Kolli",
|
||||
template: "%s | Akshay Kolli",
|
||||
},
|
||||
description: "Personal website of Akshay Kolli — CS PhD Student at UMass Lowell researching World Models, Reinforcement Learning, and Multi-Agent Systems.",
|
||||
openGraph: {
|
||||
title: "Akshay Kolli",
|
||||
description: "CS PhD Student at UMass Lowell researching World Models, Reinforcement Learning, and Multi-Agent Systems.",
|
||||
siteName: "Akshay Kolli",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
creator: "@thekolliakshay",
|
||||
},
|
||||
};
|
||||
|
||||
import { Analytics } from "../components/Analytics";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -27,8 +40,11 @@ export default function RootLayout({
|
||||
<body
|
||||
className={`${inter.variable} antialiased flex flex-col min-h-screen`}
|
||||
>
|
||||
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:bg-white focus:dark:bg-zinc-900 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:rounded focus:shadow">
|
||||
Skip to content
|
||||
</a>
|
||||
<Navbar />
|
||||
<main className="flex-grow">
|
||||
<main id="main-content" className="flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
18
app/not-found.tsx
Normal file
18
app/not-found.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="min-h-[60vh] flex flex-col items-center justify-center px-6 text-center animate-fade-in">
|
||||
<h1 className="text-6xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 mb-4">404</h1>
|
||||
<p className="text-lg text-zinc-500 dark:text-zinc-400 mb-8">
|
||||
This page doesn't exist.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="text-sm font-medium text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 underline-offset-4 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all"
|
||||
>
|
||||
← Back to home
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
app/page.tsx
14
app/page.tsx
@@ -1,6 +1,10 @@
|
||||
import Image from "next/image";
|
||||
import { getAllPosts } from "@/lib/mdx";
|
||||
|
||||
export default function Home() {
|
||||
const posts = getAllPosts();
|
||||
const latestPost = posts[0];
|
||||
const latestPostHref = latestPost ? `/blog/${latestPost.slug}` : '/blog';
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center p-8 sm:p-20 relative overflow-hidden bg-zinc-50 dark:bg-zinc-950 text-zinc-900 dark:text-zinc-50 font-sans">
|
||||
|
||||
@@ -47,7 +51,7 @@ export default function Home() {
|
||||
<a href="https://x.com/thekolliakshay" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
||||
Twitter
|
||||
</a>
|
||||
<a href="https://linkedin.com" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
||||
<a href="https://www.linkedin.com/in/akshay-kolli-/" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
||||
LinkedIn
|
||||
</a>
|
||||
</div>
|
||||
@@ -57,7 +61,7 @@ export default function Home() {
|
||||
<div className="pt-2 animate-slide-up" style={{ animationDelay: '0.5s' }}>
|
||||
<p className="text-sm text-zinc-400 dark:text-zinc-500 font-mono">
|
||||
<span className="text-zinc-900 dark:text-zinc-100 mr-2">Currently:</span>
|
||||
Writing about deep learning happenings <a href="/blog/testing-layout" className="text-zinc-600 dark:text-zinc-400 underline decoration-zinc-300 dark:decoration-zinc-700 hover:text-zinc-900 dark:hover:text-zinc-100">Read latest →</a>
|
||||
Writing about deep learning happenings <a href={latestPostHref} className="text-zinc-600 dark:text-zinc-400 underline decoration-zinc-300 dark:decoration-zinc-700 hover:text-zinc-900 dark:hover:text-zinc-100">Read latest →</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,13 +69,7 @@ export default function Home() {
|
||||
{/* Right Column: Photo */}
|
||||
<div className="order-1 md:order-2 flex justify-center md:justify-end animate-slide-up" style={{ animationDelay: '0.2s' }}>
|
||||
<div className="relative w-64 h-64 sm:w-80 sm:h-80 rounded-2xl overflow-hidden bg-zinc-100 dark:bg-zinc-900 shadow-2xl transition-transform duration-500 ease-out">
|
||||
{/* UNCOMMENT AFTER ADDING public/profile.jpg */}
|
||||
<Image src="/profile.jpeg" alt="Akshay Kolli" fill className="object-cover" priority />
|
||||
|
||||
{/* Placeholder */}
|
||||
{/* <div className="absolute inset-0 flex items-center justify-center text-zinc-300 dark:text-zinc-700 border-2 border-dashed border-zinc-200 dark:border-zinc-800 m-2 rounded-xl">
|
||||
<span className="font-mono text-sm">profile.jpg</span>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Resume',
|
||||
description: 'Experience, education, and technical skills of Akshay Kolli.',
|
||||
openGraph: {
|
||||
title: 'Resume',
|
||||
description: 'Experience, education, and technical skills of Akshay Kolli.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function ResumePage() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-fade-in">
|
||||
|
||||
11
app/robots.ts
Normal file
11
app/robots.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
},
|
||||
sitemap: 'https://akkolli.net/sitemap.xml',
|
||||
};
|
||||
}
|
||||
18
app/sitemap.ts
Normal file
18
app/sitemap.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { getAllPosts } from '@/lib/mdx';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const posts = getAllPosts();
|
||||
|
||||
const blogEntries: MetadataRoute.Sitemap = posts.map((post) => ({
|
||||
url: `https://akkolli.net/blog/${post.slug}`,
|
||||
lastModified: new Date(post.date),
|
||||
}));
|
||||
|
||||
return [
|
||||
{ url: 'https://akkolli.net', lastModified: new Date() },
|
||||
{ url: 'https://akkolli.net/blog', lastModified: new Date() },
|
||||
{ url: 'https://akkolli.net/resume', lastModified: new Date() },
|
||||
...blogEntries,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user