diff --git a/public/contents/blog/netmasking-a-band-aid-solution-for-a-bleeding-internet.mdx b/public/contents/blog/netmasking-a-band-aid-solution-for-a-bleeding-internet.mdx
index f372921..490c39e 100644
--- a/public/contents/blog/netmasking-a-band-aid-solution-for-a-bleeding-internet.mdx
+++ b/public/contents/blog/netmasking-a-band-aid-solution-for-a-bleeding-internet.mdx
@@ -6,3 +6,4 @@ description: 'A detailed explanation on netmask, IP4, IPV6, and how we keep shif
banner: 'nilaysharan/blog/netmask/bannerr'
tags: 'tech,network'
---
+
diff --git a/public/contents/projects/echoes.mdx b/public/contents/projects/echoes.mdx
new file mode 100644
index 0000000..e69de29
diff --git a/public/contents/projects/hestia.mdx b/public/contents/projects/hestia.mdx
new file mode 100644
index 0000000..e69de29
diff --git a/public/contents/projects/humantd.mdx b/public/contents/projects/humantd.mdx
new file mode 100644
index 0000000..0510a3b
--- /dev/null
+++ b/public/contents/projects/humantd.mdx
@@ -0,0 +1,11 @@
+---
+title: 'HumanTD'
+slug: humantd
+publishedAt: '2023-06-08'
+description: 'Portal that tracks down a person of interest by using backtracking and video footage from CCTV cameras.'
+banner: 'nilaysharan/project/humantd/qwlr8aaqfn7xwb1dazqr'
+tags: 'react,tailwindcss,typescript'
+github: 'github.com/nilaysharan/humantd'
+---
+
+sabdakbsdj
diff --git a/public/contents/projects/medbud.mdx b/public/contents/projects/medbud.mdx
new file mode 100644
index 0000000..e69de29
diff --git a/public/contents/projects/social-media-backend.mdx b/public/contents/projects/social-media-backend.mdx
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index cb70c00..ccf2750 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -61,8 +61,7 @@ const Page = () => {
I find great joy in learning from feedback and criticism, so
please feel free to reach out to me. I also enjoy occasional
writing and creating unconventional projects, both of which
- you can find featured here. Thank you for visiting, and I hope
- you enjoy it!
+ you can find featured here.
diff --git a/src/app/api/blog/[slug]/route.ts b/src/app/api/blog/[slug]/route.ts
index 55119ef..261e3a4 100644
--- a/src/app/api/blog/[slug]/route.ts
+++ b/src/app/api/blog/[slug]/route.ts
@@ -12,7 +12,8 @@ export async function GET(req: NextRequest) {
try {
const preRoutes = preFetch({ type: 'blog' });
- const preRoute = preRoutes.find((route) => route.slug === slug);
+ const preRoute = preRoutes?.find((route) => route.slug === slug);
+ if (!preRoute) return new NextResponse(null, { status: 404 });
const file = await getFileBySlug(
preRoute?.source as string,
preRoute?.slug as string
diff --git a/src/app/api/projects/[slug]/route.ts b/src/app/api/projects/[slug]/route.ts
new file mode 100644
index 0000000..d379fe4
--- /dev/null
+++ b/src/app/api/projects/[slug]/route.ts
@@ -0,0 +1,28 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getFileBySlug, preFetch } from '@/lib/mdx.server';
+
+export async function GET(req: NextRequest) {
+ const BASE_URL = `${req.nextUrl.origin}/api/projects/`;
+ const url = new URL(req.url || '', BASE_URL);
+
+ const slug = url.pathname.split('/').pop() || '';
+
+ if (!slug) return new NextResponse(null, { status: 404 });
+
+ try {
+ const preRoutes = preFetch({ type: 'projects' });
+ const preRoute = preRoutes?.find((route) => route.slug === slug);
+ const file = await getFileBySlug(
+ preRoute?.source as string,
+ preRoute?.slug as string
+ );
+
+ if (!file)
+ return new NextResponse(null, { status: 404, statusText: 'Not found ' });
+
+ return NextResponse.json(file);
+ } catch (error) {
+ return NextResponse.json({ error: error });
+ }
+}
diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx
index 8847d44..1125b90 100644
--- a/src/app/blog/page.tsx
+++ b/src/app/blog/page.tsx
@@ -11,6 +11,7 @@ import BlogCard from '@/components/content/blogs/BlogCard';
import ContentPlaceholder from '@/components/content/ContenPlaceholder';
import StyledInput from '@/components/content/form/StyledInput';
import Tag, { SkipNavTag } from '@/components/content/Tag';
+import Seo from '@/components/Seo';
import { BlogFrontmatter } from '@/types/frontmatters';
@@ -77,6 +78,10 @@ const Page = () => {
return (
<>
+
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 74be786..94b2c18 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -14,6 +14,7 @@ import ProjectCard from '@/components/content/projects/ProjectCard';
import ButtonLink from '@/components/links/ButtonLink';
import CustomLink from '@/components/links/CustomLink';
import UnstyledLink from '@/components/links/UnstyledLink';
+import Seo from '@/components/Seo';
import { BlogFrontmatter, ProjectFrontmatter } from '@/types/frontmatters';
@@ -33,157 +34,166 @@ export default function HomePage() {
useInjectContentMeta('projects', 'featuredProjects') || [];
const populatedPosts = useInjectContentMeta('blog', 'featuredBlogs') || [];
return (
-
-
-
-
-
-
-
- You can call me Nilay
-
-
- I'm a programmer based in India. I try to solve real-world problems
- and create value. I build products with robust functionality and
- secure code.
-
-
- Don't forget to sign my{' '}
- guestbook !
-
-
- About me!
-
-
-
+
+
+
-
Nilay
+
+
-
- nilay.sharan
-
-
+
-
- SubstantialCattle5
-
-
-
-
-
-
-
- {/* Projects */}
-
-
-
- Curated Projects
-
-
- Below are some of my favorite projects over the years, a few of
- which have been featured in Yantra, Social Transformers, Devsoc and
- more.
-
-
- {populatedProjects.map((project, i) => (
- 2 && 'hidden sm:block')}
- />
- ))}
-
-
- See more project
-
-
-
- {/* Blogs */}
-
-
-
- Blogs Archive
-
-
- When I'm not working on projects, I enjoy travelling, taking
- pictures and writing blogs. Here are a few select.
-
-
- {populatedPosts.map((post, i) => (
- 2 && 'hidden sm:block')}
- />
- ))}
-
-
- See more Blogs
-
-
-
-
+ Don't forget to sign my{' '}
+
guestbook !
+
+
+ About me!
+
+
+
+
+ Resume
+
+
+
+ nilay.sharan
+
+
+
+ SubstantialCattle5
+
+
+
+
+
+
+
+ {/* Projects */}
+
+
+
+ Curated Projects
+
+
+ Below are some of my favorite projects over the years, a few of
+ which have been featured in Yantra, Social Transformers, Devsoc
+ and more.
+
+
+ {populatedProjects.map((project, i) => (
+ 2 && 'hidden sm:block')}
+ />
+ ))}
+
+
+ See more project
+
+
+
+ {/* Blogs */}
+
+
+
+ Blogs Archive
+
+
+ When I'm not working on projects, I enjoy travelling, taking
+ pictures and writing blogs. Here are a few select.
+
+
+ {populatedPosts.map((post, i) => (
+ 2 && 'hidden sm:block')}
+ />
+ ))}
+
+
+ See more Blogs
+
+
+
+
+ >
);
}
diff --git a/src/app/projects/[slug]/page.tsx b/src/app/projects/[slug]/page.tsx
new file mode 100644
index 0000000..d6ce233
--- /dev/null
+++ b/src/app/projects/[slug]/page.tsx
@@ -0,0 +1,59 @@
+'use client';
+import { usePathname } from 'next/navigation';
+import * as React from 'react';
+import Typewriter from 'typewriter-effect';
+
+import ProjectPage from '@/components/content/projects/ProjectPage';
+import Seo from '@/components/Seo';
+
+import { ProjectFrontmatter } from '@/types/frontmatters';
+
+export default function PagePage() {
+ const [data, setData] = React.useState<{
+ code: string;
+ frontmatter: ProjectFrontmatter;
+ }>();
+ const [slug, setSlug] = React.useState
('');
+
+ const pathName = usePathname().split('/').pop();
+
+ React.useEffect(() => {
+ if (pathName) {
+ setSlug(pathName);
+ }
+ }, [pathName]);
+
+ React.useEffect(() => {
+ fetch(`/api/projects/${slug}`)
+ .then((res) => res.json())
+ .then((data) => {
+ return setData(data);
+ });
+ }, [slug]);
+
+ if (!data) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx
new file mode 100644
index 0000000..e187fcf
--- /dev/null
+++ b/src/components/Seo.tsx
@@ -0,0 +1,200 @@
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+
+import { openGraph } from '@/lib/helper.client';
+
+const defaultMeta = {
+ title: 'Nilay Nath Sharan',
+ siteName: 'nilaysharan.vercel.app',
+ description:
+ 'An online portfolio and blog by Nilay Sharan. Showcase of my projects, and some of my thoughts.',
+ url: 'https://nilaysharan.vercel.app',
+ image: 'https://nilaysharan.verce.app/favicon/large-og.jpg',
+ type: 'website',
+ robots: 'follow, index',
+};
+
+type SeoProps = {
+ date?: string;
+ templateTitle?: string;
+ isBlog?: boolean;
+ banner?: string;
+ canonical?: string;
+} & Partial;
+
+export default function Seo(props: SeoProps) {
+ const router = useRouter();
+ const meta = {
+ ...defaultMeta,
+ ...props,
+ };
+ meta['title'] = props.templateTitle
+ ? `${props.templateTitle} | ${meta.siteName}`
+ : meta.title;
+
+ // Use siteName if there is templateTitle
+ // but show full title if there is none
+ meta.image = openGraph({
+ description: meta.description,
+ siteName: props.templateTitle ? meta.siteName : meta.title,
+ templateTitle: props.templateTitle,
+ banner: props.banner,
+ isBlog: props.isBlog,
+ });
+
+ return (
+
+ {meta.title}
+
+
+
+
+ {/* Open Graph */}
+
+
+
+
+
+ {/* Twitter */}
+
+
+
+
+
+ {meta.date && (
+ <>
+
+
+
+ >
+ )}
+ {meta.isBlog && (
+
+ )}
+
+ {/* Favicons */}
+ {favicons.map((linkProps) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+type Favicons = {
+ rel: string;
+ href: string;
+ sizes?: string;
+ type?: string;
+};
+
+const favicons: Array = [
+ {
+ rel: 'apple-touch-icon',
+ sizes: '57x57',
+ href: '/favicon/apple-icon-57x57.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '60x60',
+ href: '/favicon/apple-icon-60x60.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '72x72',
+ href: '/favicon/apple-icon-72x72.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '76x76',
+ href: '/favicon/apple-icon-76x76.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '114x114',
+ href: '/favicon/apple-icon-114x114.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '120x120',
+ href: '/favicon/apple-icon-120x120.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '144x144',
+ href: '/favicon/apple-icon-144x144.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '152x152',
+ href: '/favicon/apple-icon-152x152.png',
+ },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '180x180',
+ href: '/favicon/apple-icon-180x180.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '192x192',
+ href: '/favicon/android-icon-192x192.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '32x32',
+ href: '/favicon/favicon-32x32.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '96x96',
+ href: '/favicon/favicon-96x96.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '16x16',
+ href: '/favicon/favicon-16x16.png',
+ },
+ {
+ rel: 'manifest',
+ href: '/favicon/manifest.json',
+ },
+];
diff --git a/src/components/content/projects/ProjectPage.tsx b/src/components/content/projects/ProjectPage.tsx
new file mode 100644
index 0000000..18fd16c
--- /dev/null
+++ b/src/components/content/projects/ProjectPage.tsx
@@ -0,0 +1,124 @@
+import { getMDXComponent } from 'mdx-bundler/client';
+import React from 'react';
+import { HiLink, HiUser } from 'react-icons/hi';
+import { SiGithub } from 'react-icons/si';
+
+import useScrollSpy from '@/hooks/useScrollSpy';
+
+import MDXComponents from '@/components/content/MDXComponents';
+import TableOfContents, {
+ HeadingScrollSpy,
+} from '@/components/content/TableOfContents';
+import CloudinaryImg from '@/components/images/CloudinaryImg';
+import CustomLink from '@/components/links/CustomLink';
+import Seo from '@/components/Seo';
+
+import { ProjectType } from '@/types/frontmatters';
+
+const BlogPage = ({ code, frontmatter }: ProjectType) => {
+ const Component = React.useMemo(() => getMDXComponent(code), [code]);
+ //#region //*=========== Scrollspy ===========
+ const activeSection = useScrollSpy();
+
+ const [toc, setToc] = React.useState();
+ const minLevel =
+ toc?.reduce((min, item) => (item.level < min ? item.level : min), 10) ?? 0;
+
+ React.useEffect(() => {
+ const headings = document.querySelectorAll('.mdx h1, .mdx h2, .mdx h3');
+
+ const headingArr: HeadingScrollSpy = [];
+ headings.forEach((heading) => {
+ const id = heading.id;
+ const level = +heading.tagName.replace('H', '');
+ const text = heading.textContent + '';
+
+ headingArr.push({ id, level, text });
+ });
+
+ setToc(headingArr);
+ }, [frontmatter.slug]);
+ //#endregion //*======== Scrollspy ===========
+
+ return (
+ <>
+
+
+
+
+
+
+
{frontmatter.title}
+
+ {frontmatter.description}
+
+
+
+ {frontmatter.github && (
+
+
+
+ Repository
+
+
+ )}
+ {frontmatter.github &&
+ (frontmatter.youtube || frontmatter.link) &&
+ ' - '}
+ {frontmatter.link && (
+
+
+
+ Open Live Site
+
+
+ )}
+ {frontmatter.category && (
+
+ {' '}
+ {frontmatter.category}
+
+ )}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default BlogPage;
diff --git a/src/lib/helper.client.ts b/src/lib/helper.client.ts
index 7366008..05704eb 100644
--- a/src/lib/helper.client.ts
+++ b/src/lib/helper.client.ts
@@ -15,7 +15,7 @@ export function openGraph({
templateTitle,
description,
banner,
- logo = 'https://og.clarence.link/images/logo.jpg',
+ logo = 'https://nilaynsharan.vercel.app/logo.png',
isBlog = false,
}: OpenGraphType): string {
const ogLogo = encodeURIComponent(logo);
@@ -28,10 +28,10 @@ export function openGraph({
if (isBlog) {
const ogBanner = banner ? encodeURIComponent(banner.trim()) : undefined;
- return `https://og.clarence.link/api/blog?templateTitle=${ogTemplateTitle}&banner=${ogBanner}`;
+ return `https://nilaynsharan.vercel.app/api/blog?templateTitle=${ogTemplateTitle}&banner=${ogBanner}`;
}
- return `https://og.clarence.link/api/gradient?siteName=${ogSiteName}&description=${ogDesc}&logo=${ogLogo}${
+ return `https://nilaynsharan.vercel.app/api/gradient?siteName=${ogSiteName}&description=${ogDesc}&logo=${ogLogo}${
ogTemplateTitle ? `&templateTitle=${ogTemplateTitle}` : ''
}`;
}
diff --git a/src/lib/mdx.server.ts b/src/lib/mdx.server.ts
index c3c8f3d..854d641 100644
--- a/src/lib/mdx.server.ts
+++ b/src/lib/mdx.server.ts
@@ -154,43 +154,53 @@ export function getFeatured(
);
}
-export function preFetch({ type }: { type: string }) {
- const service = readFileSync(
- join(process.cwd(), 'public', 'contents', type, 'service-animal.mdx'),
- 'utf8'
- );
- const pather = readFileSync(
- join(
- process.cwd(),
- 'public',
- 'contents',
- type,
- 'pather-panchali-the-enduring-impact-on-modern-indian-cinema.mdx'
- ),
- 'utf8'
- );
- const routing = readFileSync(
- join(
- process.cwd(),
- 'public',
- 'contents',
- type,
- 'routing-protocol-the-invisible-force-that-powers-the-internet.mdx'
- ),
- 'utf8'
- );
+export function preFetch({ type }: { type: ContentType }) {
+ if (type === 'blog') {
+ const service = readFileSync(
+ join(process.cwd(), 'public', 'contents', type, 'service-animal.mdx'),
+ 'utf8'
+ );
+ const pather = readFileSync(
+ join(
+ process.cwd(),
+ 'public',
+ 'contents',
+ type,
+ 'pather-panchali-the-enduring-impact-on-modern-indian-cinema.mdx'
+ ),
+ 'utf8'
+ );
+ const routing = readFileSync(
+ join(
+ process.cwd(),
+ 'public',
+ 'contents',
+ type,
+ 'routing-protocol-the-invisible-force-that-powers-the-internet.mdx'
+ ),
+ 'utf8'
+ );
+
+ const blogs = [
+ { slug: 'service-animal', source: service },
+ {
+ slug: 'pather-panchali-the-enduring-impact-on-modern-indian-cinema',
+ source: pather,
+ },
+ {
+ slug: 'routing-protocol-the-invisible-force-that-powers-the-internet',
+ source: routing,
+ },
+ ];
- const blogs = [
- { slug: 'service-animal', source: service },
- {
- slug: 'pather-panchali-the-enduring-impact-on-modern-indian-cinema',
- source: pather,
- },
- {
- slug: 'routing-protocol-the-invisible-force-that-powers-the-internet',
- source: routing,
- },
- ];
+ return blogs;
+ } else if (type === 'projects') {
+ const humantd = readFileSync(
+ join(process.cwd(), 'public', 'contents', type, 'humantd.mdx'),
+ 'utf8'
+ );
+ const projects = [{ slug: 'humantd', source: humantd }];
- return blogs;
+ return projects;
+ }
}