Repository: j471n/j471n.in
Branch: main
Commit: 7f76fee83374
Files: 177
Total size: 481.1 KB
Directory structure:
gitextract_2ou2nl1y/
├── .eslintrc.json
├── .gitignore
├── .vercelignore
├── LICENSE
├── README.md
├── components/
│ ├── Blog.tsx
│ ├── BookCard.tsx
│ ├── Contact/
│ │ ├── Contact.tsx
│ │ ├── ContactForm.tsx
│ │ └── index.tsx
│ ├── CreateAnIssue.tsx
│ ├── EpigraphCard.tsx
│ ├── Footer.tsx
│ ├── FramerMotion/
│ │ ├── AnimatedDiv.tsx
│ │ ├── AnimatedHeading.tsx
│ │ └── AnimatedText.tsx
│ ├── GitHubActivityGraph.tsx
│ ├── Home/
│ │ ├── BlogsSection.tsx
│ │ ├── EpigraphsSection.tsx
│ │ ├── HeroSection.tsx
│ │ ├── SkillSection.tsx
│ │ └── StatsSection.tsx
│ ├── Instagram/
│ │ ├── InstagramPost.tsx
│ │ ├── InstagramPostLoading.tsx
│ │ └── InstagramSection.tsx
│ ├── MDXComponents/
│ │ ├── Code.tsx
│ │ ├── CodeSandbox.tsx
│ │ ├── CodeTitle.tsx
│ │ ├── Codepen.tsx
│ │ ├── Danger.tsx
│ │ ├── EmbedBlog.tsx
│ │ ├── Figcaption.tsx
│ │ ├── LinkedInEmbed.tsx
│ │ ├── NextAndPreviousButton.tsx
│ │ ├── Pre.tsx
│ │ ├── Step.tsx
│ │ ├── Tip.tsx
│ │ ├── UrlMetaInfo.tsx
│ │ ├── Warning.tsx
│ │ ├── YouTube.tsx
│ │ └── index.tsx
│ ├── MetaData.tsx
│ ├── MovieCard.tsx
│ ├── Newsletter.tsx
│ ├── OgImage.tsx
│ ├── PageHeader.tsx
│ ├── PageNotFound.tsx
│ ├── PageTop.tsx
│ ├── Project.tsx
│ ├── QRCodeContainer.tsx
│ ├── SVG/
│ │ ├── Ditto.tsx
│ │ ├── Flameshot.tsx
│ │ ├── Flux.tsx
│ │ ├── Logo.tsx
│ │ ├── MicrosoftToDo.tsx
│ │ ├── RainDrop.tsx
│ │ ├── ShareX.tsx
│ │ ├── UPI.tsx
│ │ ├── Zip7.tsx
│ │ └── index.tsx
│ ├── ScrollProgressBar.tsx
│ ├── ScrollToTopButton.tsx
│ ├── ShareOnSocialMedia.tsx
│ ├── SnippetCard.tsx
│ ├── SnowfallCanvas.tsx
│ ├── StaticPage.tsx
│ ├── Stats/
│ │ ├── Artist.tsx
│ │ ├── MonkeyTypeStats.tsx
│ │ ├── StatsCard.tsx
│ │ └── Track.tsx
│ ├── Support.tsx
│ ├── TableOfContents.tsx
│ └── TopNavbar.tsx
├── content/
│ ├── FramerMotionVariants.ts
│ ├── meta.ts
│ ├── siteConfig.ts
│ ├── skillsData.ts
│ ├── socialMedia.ts
│ ├── support.ts
│ ├── user.ts
│ └── utilitiesData.ts
├── context/
│ └── darkModeContext.tsx
├── docs/
│ └── sanity-deploy.md
├── hooks/
│ ├── useBookmarkBlogs.ts
│ ├── useDebounce.ts
│ ├── useFetchWithSWR.ts
│ ├── useScrollPercentage.ts
│ ├── useShare.ts
│ ├── useWindowLocation.ts
│ └── useWindowSize.ts
├── layout/
│ ├── BlogLayout.tsx
│ ├── Layout.tsx
│ └── SnippetLayout.tsx
├── lib/
│ ├── MDXContent.ts
│ ├── devto.ts
│ ├── fetcher.ts
│ ├── generateRSS.ts
│ ├── github.ts
│ ├── hardcover.ts
│ ├── instaposts.ts
│ ├── interface/
│ │ └── sanity.ts
│ ├── interface.ts
│ ├── sanityClient.ts
│ ├── sanityContent.ts
│ ├── sitemap.ts
│ ├── spotify.ts
│ ├── supabase.ts
│ ├── tmdb.ts
│ ├── toc.ts
│ ├── types.ts
│ └── windowsAnimation.ts
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages/
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── about.tsx
│ ├── api/
│ │ ├── books.ts
│ │ ├── ga.ts
│ │ ├── now-playing.ts
│ │ ├── posts/
│ │ │ └── insta.ts
│ │ ├── revalidate.ts
│ │ ├── stats/
│ │ │ ├── artists.ts
│ │ │ ├── devto.ts
│ │ │ ├── github-contribution.ts
│ │ │ ├── github.ts
│ │ │ ├── monkeytype.ts
│ │ │ └── tracks.ts
│ │ ├── validate/
│ │ │ └── email.ts
│ │ └── views/
│ │ ├── [slug].ts
│ │ └── index.ts
│ ├── blogs/
│ │ ├── [slug].tsx
│ │ ├── bookmark.tsx
│ │ └── index.tsx
│ ├── books.tsx
│ ├── certificates.tsx
│ ├── epigraphs.tsx
│ ├── index.tsx
│ ├── privacy.tsx
│ ├── projects.tsx
│ ├── snippets/
│ │ ├── [slug].tsx
│ │ └── index.tsx
│ ├── stats.tsx
│ ├── tet.json
│ └── utilities.tsx
├── postcss.config.js
├── public/
│ ├── manifest.json
│ └── robots.txt
├── sanity/
│ ├── .eslintrc
│ ├── .gitignore
│ ├── README.md
│ ├── gist.md.ndjson
│ ├── package.json
│ ├── sanity.cli.ts
│ ├── sanity.config.ts
│ ├── schemas/
│ │ ├── author.ts
│ │ ├── blockContent.ts
│ │ ├── category.ts
│ │ ├── epigraph.ts
│ │ ├── index.ts
│ │ ├── language.ts
│ │ ├── organization.ts
│ │ ├── post.ts
│ │ ├── snippet.ts
│ │ └── static_page.ts
│ ├── static/
│ │ └── .gitkeep
│ ├── tailwind.config.js
│ └── tsconfig.json
├── sanity.cli.ts
├── styles/
│ └── globals.css
├── tailwind.config.js
├── tsconfig.json
├── utils/
│ ├── date.ts
│ ├── functions.ts
│ └── utils.ts
└── vercel.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": 0,
"@next/next/no-img-element": "off"
}
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# private markdown
meta.md
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# rss
/public/rss
/public/feed.xml
public/sitemap.xml
# pwa
/public/sw.js
/public/workbox-*.js
/public/worker-*.js
/public/sw.js.map
/public/workbox-*.js.map
/public/worker-*.js.map
/store
tsconfig.tsbuildinfo
.env*.local
.vscode/
================================================
FILE: .vercelignore
================================================
/sanity
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Jatin Sharma
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================





## Tools Used
- **Framework**: [Next.js](https://nextjs.org/)
- **Styling**: [Tailwind CSS](https://tailwindcss.com/)
- **Content**: [MDX](https://github.com/mdx-js/mdx)
- **Database**: [Supabase](https://supabase.com/)
- **Animations**: [Framer Motion](https://framer.com/motion)
- **Deployment**: [Vercel](https://vercel.com)
- **Icons**: [React Icons](https://react-icons.github.io/react-icons/)
- **Plugins**: [rehype](https://github.com/rehypejs/rehype)
- **Analytics**: [Google Analytics](https://analytics.google.com/analytics/web/)
- [SWR](https://swr.vercel.app/)
- [Email.js](https://www.emailjs.com/)
- [React Toastify](https://github.com/fkhadra/react-toastify)
## Run Locally
Clone the project:
```bash
git clone https://github.com/j471n/j471n.in.git
```
Go to the project directory:
```bash
cd j471n.in
```
Install dependencies
```bash
yarn
# or
npm install
```
Start the server:
```bash
yarn dev
# or
npm run dev
```
After that server should be running on [localhost:3000](http://localhost:3000)
> I am using [yarn](https://yarnpkg.com/) you can use [pnpm](https://pnpm.io/) or [npm](https://www.npmjs.com/)
> Warning: You could run into errors if you don't populate the `.env.local` with the correct values
## Setting up the Environment
Rename [`.env.example`](/.env.example) to `.env.local` and then you need to populate that with the respective values.
### Email Service Integration
- `NEXT_PUBLIC_YOUR_SERVICE_ID`: Go to the [Admin Panel](https://dashboard.emailjs.com/admin) of [emailjs.com](https://emailjs.com). If you haven't already added a service then Click on the **Add Service** Button as shown in the image

Then choose any method you want I am using **Gmail**

- Then first click on the **Connect Account and log** in with your Gmail account that you want to use to get the emails from.
- In the second step click on **Create Service** and then copy the **Service ID** and add this ID to `NEXT_PUBLIC_YOUR_SERVICE_ID` in `.env.local`

- `NEXT_PUBLIC_YOUR_TEMPLATE_ID`: To get the Template ID visit the [Email Templates](https://dashboard.emailjs.com/admin/templates) section and click on **Create New Template**.

And then you will see a window where you can edit your email template after you are satisfied with your template then click on the Save button in the top right corner.

After that you will have your Template ID as shown in the image below:

- `NEXT_PUBLIC_YOUR_USER_ID`: To get your User ID, Go to [Account](https://dashboard.emailjs.com/admin/account) and then you will be able to see it:

### dev\.to Integration
- `NEXT_PUBLIC_BLOGS_API`: I am using [Dev.to API](https://developers.forem.com/api) to fetch all the blog stats. You can get this API at the bottom of the [Extensions](https://dev.to/settings/extensions) section.

### Google Analytics
- `NEXT_PUBLIC_GA_MEASUREMENT_ID`: You can follow this [guide](https://support.google.com/analytics/answer/9539598?hl=en) to get your Google Analytics ID and then you will be able to use Google Analytics in this project.
- [**Google Analytics Data API**](https://developers.google.com/analytics/devguides/reporting/data/v1): I am using this API to get the analytics of this website so that I can show how many user visit this site in the last 7 days. In this you will need the value of the following properties:
- `GA_PROPERTY_ID`
- `GA_CLIENT_EMAIL`
- `GA_PRIVATE_KEY`
I have written a [blog](https://j471n.in/blogs/google-analytics-data-api) that shows how you can get these properties and guides to use them.
### Spotify Integration
I have used [Spotify API](https://developer.spotify.com/documentation/web-api/). So, you need three Environment Variable values-
- `SPOTIFY_CLIENT_ID`
- `SPOTIFY_CLIENT_SECRET`
- `SPOTIFY_REFRESH_TOKEN`
You need to follow this [blog](https://j471n.in/blogs/spotify-api-nextjs) to get these variables' values.
### Supabase Integration
I am using [Supabase](https://supabase.com/) with ISR to store all my projects and certificates for now. It provides an API that helps you to access the data. To access that data you need two things:
- `SUPABASE_URL`: Database URL.
- `SUPABASE_KEY`: It is safe to be used in a browser context.
**Steps-**
- To get these go to [Supabase](https://app.supabase.com/sign-in) and log in with your account.
- Click on **New Project** and fill all the fields.
- Click on **Create New Project**.
- Go to the [Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard.
- Click **API** in the sidebar.
- Find your API **URL** and **anon** key on this page.
- Now you can [Create table](https://app.supabase.com/project/_/editor) and start using it.
But before you use this there was one issue I had when I was using this it was returning the empty array ([]). It was because of project policies. By default, no-one has access to the data. To fix that you can do the following:
- Go to [Policies](https://app.supabase.com/project/_/auth/policies).
- Select your Project.
- Click on **New Policy**.

- You will be presented with two options. You can choose either one. I chose the 1st option:

- After that, you will have four options as shown in the following image. You can choose according to your need. I only need the read access so I went with 1st option.

- Click on **Use this template**.
- Click on **Review**.
- Click on **Save Policy**
After that, you will be able to access the data using [@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js). Install it and you just set up your project with Supabase.
- `REVALIDATE_SECRET`: As I am using [Supabase](https://supabase.com/), It has a feature called [webhooks](https://supabase.com/docs/guides/database/webhooks) which allow you to send real-time data from your database to another system whenever a table event occurs. So I am using it to revalidate my `projects` and `certificates` page. For that I am providing a custom secret value to verify that request is coming from authenticated source. Let's create webhook:
- Go to [webhooks](https://app.supabase.com/project/_/database/hooks) page.
- Click on **Create a new hook**
- Enter the name of the function hook (example: `update_projects`)

- Choose your table from the dropdown list

- Select events which will trigger this function hook

- Now Choose POST method and enter the revalidate URL (request will be sent to this URL)

- Then add two HTTP Params `secret` and `revalidateUrl`

- Now add this secret to your `env.local` and it will update the page when you made some changes to your supabase database.
- `pages/api/revalidate.ts` is using `revalidateUrl` to update the page with new data.
### GitHub Integration
To get `GITHUB_TOKEN` Follow these steps to generate a GitHub token that I am using fetch my GitHub details:
**Step 1: Accessing GitHub Developer Settings**
- Log in to your GitHub account.
- Click on your profile picture in the top-right corner of the page.
- From the drop-down menu, select Settings.

**Step 2: Navigating to Developer Settings**
In the left sidebar, scroll down and click on Developer settings.

**Step 3: Creating a New Personal Access Token**
- In the Developer settings page, click on Personal access tokens and then Click on Tokens (Classic).

- Next, click on the Generate new token button.

- After selecting the necessary permissions, click on the Generate token button at the bottom of the page.
- GitHub will generate a new token for you. Make sure to copy the token value.
- **Important**: Treat this token like a password and keep it secure. Do not share it publicly or commit it to a version control repository.
### Email Validation Integration
To get `EMAIL_VALIDATION_API` follow the following steps to get the `API_KEY` to validate the email address for the newsletter:
- You need to have an account on [RapidAPI](https://rapidapi.com/).
- If you have an account then you can just [subscribe](https://rapidapi.com/Top-Rated/api/e-mail-check-invalid-or-disposable-domain/pricing) the free version of [E-mail Check Invalid or Disposable Domain](https://rapidapi.com/Top-Rated/api/e-mail-check-invalid-or-disposable-domain/). Which will give you the 1000 request/month.

- Then you'll get the `API_KEY`, which you can store in your `.env.local`.

### Sanity Integration
- `SANITY_PROJECT_ID`:
- Go to the [Sanity.io](<(https://www.sanity.io/)>) website using your web browser.
- Login with you account/Create a new account.
- After logging in, you'll be redirected to the Sanity.io dashboard.
- If you have an existing project, you'll see it listed on the dashboard. Click on the project's name to access it.
- Once you're inside the project, look at the browser's address bar. The URL should look something like this: `https://www.sanity.io/manage/project/your-project-id`
- The your-project-id in the URL is your Sanity project ID. It's a unique identifier for your specific project.
That's it! You've now obtained your Sanity project ID, which you can use for interacting with your Sanity project via its API or other integrations.
### TMDB Integration
- `TMDB_ACCOUNT_ID` and `TMDB_ACCESS_TOKEN`: To enable seamless integration of movie and TV show data, we will use the TMDB API, which offers comprehensive information about media content. The following steps will guide you:
**1. Overview of TMDB Integration**
Previously, movie and TV show data were manually stored using Supabase, requiring tedious manual work. To streamline the process and automatically update ratings, we have switched to TMDB (The Movie Database).
**2. Creating or Logging into Your TMDB Account**
If you already have a TMDB account, log in with your existing credentials. Otherwise, visit TMDB's website and create a new account.
**3. Generating API Key**
After logging in, navigate to the API section in your account settings. Here, you can generate a new API key to access TMDB's data and services.

**Completing the API Key Request Form**
Fill in all the required details in the API key request form, and make sure to accept the terms and conditions.

**Obtaining API Key and Access Token**
Once you have completed the application registration, you will receive an API key and an access token. Assign the access token to the `TMDB_ACCESS_TOKEN` variable.

**Finding Your TMDB Account ID**
To get the `TMDB_ACCOUNT_ID`, log in to the TMDB system and visit the developer website. There, you will find your account ID associated with your account.

With the `TMDB_ACCOUNT_ID` and `TMDB_ACCESS_TOKEN` acquired from the steps above, you can now seamlessly access and update movie and TV show data through TMDB's API, automating the process and making it significantly more efficient. Enjoy your improved movie and TV show list management experience!
## Supabase Database Schema
### Table: certificates
| Column Name | Data Type | Constraints |
| ------------- | --------------------------- | ------------------------------------------------- |
| `id` | UUID | NOT NULL, PRIMARY KEY, DEFAULT uuid_generate_v4() |
| `title` | TEXT | NOT NULL |
| `issued_date` | DATE | - |
| `org_name` | TEXT | - |
| `org_logo` | TEXT | - |
| `url` | TEXT | - |
| `pinned` | BOOLEAN | - |
| `created_at` | TIMESTAMP WITHOUT TIME ZONE | NOT NULL, DEFAULT now() |
### Table: movies
| Column Name | Data Type | Constraints |
| ------------ | ------------------------ | ------------------------------------------------------- |
| `id` | BIGINT | GENERATED BY DEFAULT AS IDENTITY, NOT NULL, PRIMARY KEY |
| `created_at` | TIMESTAMP WITH TIME ZONE | NOT NULL, DEFAULT (now() AT TIME ZONE 'utc'::text) |
| `name` | TEXT | - |
| `image` | TEXT | - |
| `url` | TEXT | - |
| `year` | INTEGER | - |
| `watched` | BOOLEAN | DEFAULT true |
| `rating` | SMALLINT | - |
### Table: projects
| Column Name | Data Type | Constraints |
| ------------- | --------------------------- | ------------------------------------------------- |
| `id` | UUID | NOT NULL, PRIMARY KEY, DEFAULT uuid_generate_v4() |
| `created_at` | TIMESTAMP WITHOUT TIME ZONE | - |
| `name` | TEXT | - |
| `description` | TEXT | - |
| `githubURL` | TEXT | - |
| `previewURL` | TEXT | - |
| `tools` | TEXT[] | - |
| `pinned` | BOOLEAN | DEFAULT false |
| `coverImage` | TEXT | - |
### Table: views
| Column Name | Data Type | Constraints |
| ----------- | --------- | --------------------- |
| `slug` | TEXT | NOT NULL, PRIMARY KEY |
| `views` | BIGINT | - |
### Table: user_data
| Column Name | Data Type | Constraints |
| ------------ | ------------------------ | ------------------------------------------------ |
| `id` | UUID | NOT NULL, PRIMARY KEY, DEFAULT gen_random_uuid() |
| `key` | TEXT | - |
| `value` | TEXT | - |
| `created_at` | TIMESTAMP WITH TIME ZONE | DEFAULT now() |
This table is unique because it accommodates various types of miscellaneous data. I'm sharing the current keys in my database, which are utilized in my project. This will guide you on the types of data you need to add to avoid errors during implementation."
| Keys | Sample Data |
| ---------------------- | ---------------------------------------- |
| `linkedin` | [Sample JSON](https://traff.co/K6wi1Q3p) |
| `instagram_user_token` | [Sample TEXT](https://traff.co/mgFnU8vN) |
| `devto_stats` | [Sample JSON](https://traff.co/Ff99Gv2h) |
## Documentation
I have written an in-depth blog on [How I Made My Portfolio with Next.js](https://dev.to/j471n/how-i-made-my-portfolio-with-nextjs-2mn3). You can visit there to look at the detailed guide about this portfolio.
================================================
FILE: components/Blog.tsx
================================================
import { BlogPost } from "@lib/interface/sanity";
import Image from "next/image";
import Link from "next/link";
import { getFormattedDate } from "@utils/date";
import { motion } from "framer-motion";
import { FiArrowRight } from "react-icons/fi";
const cardVariants = {
hidden: { opacity: 0, y: 14 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 120, damping: 20 },
},
};
export default function Blog({
blog,
animate = false,
index,
}: {
blog: BlogPost;
animate?: boolean;
index?: number;
}) {
return (
{/* Left accent bar */}
{/* Article index */}
{index !== undefined && (
{String(index + 1).padStart(2, "0")}
)}
{/* Content */}
{blog.title}
{blog.excerpt}
{/* Meta row */}
{blog.author.name}
{blog.organization && (
<>
·
{blog.organization.name}
>
)}
·
{getFormattedDate(new Date(blog.publishedAt))}
{/* Thumbnail — grayscale at rest, color on hover */}
{/*
*/}
{/* Arrow */}
);
}
================================================
FILE: components/BookCard.tsx
================================================
import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";
import { HiOutlineExternalLink } from "react-icons/hi";
import { HardcoverBook } from "@lib/types";
const itemVariants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 260, damping: 24 },
},
};
const HARDCOVER_BOOK_URL = "https://hardcover.app/books/";
const FALLBACK_COVER = "https://imgur.com/5dYYce8.png";
const STAR_PATH =
"M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z";
function Stars({
rating,
size = "w-3 h-3",
}: {
rating: number;
size?: string;
}) {
const filled = Math.round(rating);
return (
<>
{[1, 2, 3, 4, 5].map((i) => (
))}
>
);
}
function formatFinishedDate(iso: string): string {
const d = new Date(iso);
return d.toLocaleDateString("en-US", { month: "short", year: "numeric" });
}
export default function BookCard({ book }: { book: HardcoverBook }) {
const href = book.slug
? `${HARDCOVER_BOOK_URL}${book.slug}`
: "https://hardcover.app";
const authorLine =
book.authors.length > 0 ? book.authors.join(", ") : "Unknown author";
const finishedDate =
book.statusId === 3
? book.finishedAt
? formatFinishedDate(book.finishedAt)
: book.updatedAt
? formatFinishedDate(book.updatedAt)
: null
: null;
const hasMyRating = book.userRating != null && book.userRating > 0;
const hasCommunityRating = book.rating != null && book.rating > 0;
return (
{/* External link icon */}
{/* Cover */}
{/* Details */}
{/* Author */}
{authorLine}
{/* Title */}
{book.title}
{/* Spacer */}
{/* Bottom meta */}
{/* Ratings row — personal stars + community avg on one line */}
{(hasMyRating || hasCommunityRating) && (
{hasMyRating && (
{book.userRating!.toFixed(1)}
)}
{hasMyRating && hasCommunityRating && (
·
)}
{hasCommunityRating && (
{book.rating!.toFixed(1)}
avg
)}
)}
{/* Year · pages */}
{book.releaseYear != null && {book.releaseYear} }
{book.releaseYear != null && book.pages != null && (
·
)}
{book.pages != null && (
{book.pages.toLocaleString()} pages
)}
{/* Finished date */}
{finishedDate && (
Finished
·
{finishedDate}
)}
);
}
================================================
FILE: components/Contact/Contact.tsx
================================================
import React from "react";
import { motion } from "framer-motion";
import ContactForm from "./ContactForm";
import siteConfig from "@content/siteConfig";
const infoRows = [
{ label: "Response Time", value: "< 24 hours" },
{ label: "Timezone", value: "IST · UTC +5:30" },
{ label: "Preferred", value: "Email / LinkedIn" },
{ label: "Work Type", value: "Remote / Contract" },
];
export default function Contact() {
const { contact } = siteConfig;
return (
);
}
================================================
FILE: components/Contact/ContactForm.tsx
================================================
import React, { useRef, useState } from "react";
import { ToastContainer, toast } from "react-toastify";
import { useDarkMode } from "@context/darkModeContext";
import emailjs from "@emailjs/browser";
import { motion, AnimatePresence } from "framer-motion";
import { FormInput } from "@lib/types";
import siteConfig from "@content/siteConfig";
import { FiSend } from "react-icons/fi";
const inputVariants = {
hidden: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
},
},
};
export default function Form() {
const { isDarkMode } = useDarkMode();
const sendButtonRef = useRef(null!);
const formRef = useRef(null!);
const [isSubmitting, setIsSubmitting] = useState(false);
const [focusedField, setFocusedField] = useState(null);
const { contact } = siteConfig;
const FailToastId: string = "failed";
function sendEmail(e: React.SyntheticEvent) {
e.preventDefault();
const target = e.target as typeof e.target & {
first_name: { value: string };
last_name: { value: string };
email: { value: string };
subject: { value: string };
message: { value: string };
};
const emailData = {
to_name: siteConfig.person.name,
first_name: target.first_name.value.trim(),
last_name: target.last_name.value.trim(),
email: target.email.value.trim(),
subject: target.subject.value.trim(),
message: target.message.value.trim(),
};
if (!validateForm(emailData) && !toast.isActive(FailToastId))
return toast.error("Please fill in all required fields", {
toastId: FailToastId,
});
setIsSubmitting(true);
sendButtonRef.current.setAttribute("disabled", "true");
const toastId = toast.loading("Sending your message...");
emailjs
.send(
process.env.NEXT_PUBLIC_YOUR_SERVICE_ID!,
process.env.NEXT_PUBLIC_YOUR_TEMPLATE_ID!,
emailData!,
process.env.NEXT_PUBLIC_YOUR_USER_ID,
)
.then(() => {
formRef.current.reset();
toast.update(toastId, {
render: "Message sent successfully!",
type: "success",
isLoading: false,
autoClose: 4000,
});
setIsSubmitting(false);
sendButtonRef.current.removeAttribute("disabled");
})
.catch(() => {
toast.update(toastId, {
render: "Failed to send message. Please try again.",
type: "error",
isLoading: false,
autoClose: 4000,
});
setIsSubmitting(false);
sendButtonRef.current.removeAttribute("disabled");
});
}
function validateForm(data: FormInput): boolean {
for (const key in data) {
if (data[key as keyof FormInput] === "") return false;
}
return true;
}
/* Shared field class — borderless top/sides, only bottom rule */
const fieldCls = (name: string) =>
`block w-full bg-transparent border-0 border-b py-3 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-600 focus:outline-none transition-colors duration-200 ${
focusedField === name
? "border-gray-900 dark:border-white"
: "border-gray-300 dark:border-gray-700"
}`;
return (
<>
{/* Name row */}
{/* First Name */}
First Name
setFocusedField("first_name")}
onBlur={() => setFocusedField(null)}
className={fieldCls("first_name")}
placeholder="John"
required
/>
{/* Last Name */}
Last Name
setFocusedField("last_name")}
onBlur={() => setFocusedField(null)}
className={fieldCls("last_name")}
placeholder="Doe"
required
/>
{/* Email */}
Email Address
setFocusedField("email")}
onBlur={() => setFocusedField(null)}
className={fieldCls("email")}
placeholder="john.doe@example.com"
required
/>
{/* Subject */}
Subject
setFocusedField("subject")}
onBlur={() => setFocusedField(null)}
className={fieldCls("subject")}
placeholder="Project Discussion"
required
/>
{/* Message */}
Message
{/* Submit */}
{/* fill layer */}
{isSubmitting ? (
Sending...
) : (
Send Message
)}
{/* Privacy note */}
{contact.privacyNote}
>
);
}
================================================
FILE: components/Contact/index.tsx
================================================
export { default } from "./Contact";
================================================
FILE: components/CreateAnIssue.tsx
================================================
import Link from "next/link";
import React from "react";
export default function CreateAnIssue() {
return (
Something went wrong. I know you don't know what's the problem. So Let
me know by{" "}
creating an issue
{" "}
on GitHub.
);
}
================================================
FILE: components/EpigraphCard.tsx
================================================
import { useState } from "react";
import { IEpigraph, EpigraphSourceType } from "@lib/interface/sanity";
import { motion } from "framer-motion";
import ReactMarkdown from "react-markdown";
import { FiCopy, FiCheck, FiSearch } from "react-icons/fi";
const SOURCE_LABELS: Record = {
book: "Book",
movie: "Film",
tvShow: "Series",
person: "Person",
song: "Song",
podcast: "Podcast",
other: "Other",
};
const rowVariants = {
hidden: { opacity: 0, y: 8 },
visible: { opacity: 1, y: 0, transition: { duration: 0.4, ease: "easeOut" } },
};
const quoteComponents = {
p: ({ children }: { children: React.ReactNode }) => (
{children}
),
strong: ({ children }: { children: React.ReactNode }) => (
{children}
),
em: ({ children }: { children: React.ReactNode }) => (
{children}
),
};
export default function EpigraphCard({
epigraph,
index,
}: {
epigraph: IEpigraph;
index: number;
}) {
const [copied, setCopied] = useState(false);
const sourceLabel = SOURCE_LABELS[epigraph.sourceType] ?? epigraph.sourceType;
const handleCopy = () => {
navigator.clipboard.writeText(epigraph.quote);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
{/* ── Mobile layout ── */}
{/* Header: index left, meta right */}
{String(index + 1).padStart(3, "0")}
{epigraph.year && (
{epigraph.year}
)}
{sourceLabel}
{/* Quote */}
{epigraph.quote}
{/* Attribution */}
{/* Footer: tags + copy */}
{/* ── Desktop layout: three-column ledger ── */}
{/* Left: index + rotated source + year */}
{String(index + 1).padStart(3, "0")}
{sourceLabel}
{epigraph.year && (
{epigraph.year}
)}
{/* Vertical rule */}
{/* Right: quote + attribution + footer */}
);
}
function Attribution({ epigraph }: { epigraph: IEpigraph }) {
return (
—
{epigraph.speaker && (
<>
{epigraph.speaker}
{(epigraph.sourceTitle || epigraph.sourceMeta) && (
,
)}
>
)}
{epigraph.sourceTitle}
{epigraph.sourceMeta && (
<>
/
{epigraph.sourceMeta}
>
)}
);
}
function Footer({
tags,
copied,
onCopy,
searchQuery,
}: {
tags?: string[];
copied: boolean;
onCopy: () => void;
searchQuery: string;
}) {
return (
{/* Copy button */}
{copied ? : }
{copied ? "Copied" : "Copy"}
{/* Google search */}
Search
{/* Tags */}
{tags?.map((tag) => (
#{tag}
))}
);
}
================================================
FILE: components/Footer.tsx
================================================
import Link from "next/link";
import Image from "next/image";
import socialMedia from "@content/socialMedia";
import siteConfig from "@content/siteConfig";
import { navigationRoutes } from "../utils/utils";
import { motion } from "framer-motion";
import { SiSpotify } from "react-icons/si";
import useSWR from "swr";
import fetcher from "../lib/fetcher";
import { HiOutlineQrcode } from "react-icons/hi";
import { Song } from "@lib/types";
export default function Footer({
setShowQR,
showQR,
}: {
setShowQR: (value: boolean) => void;
showQR: boolean;
}) {
const { data: currentSong } = useSWR("/api/now-playing", fetcher);
const { data: visitors } = useSWR("/api/ga", fetcher);
const navLinks = navigationRoutes.slice(0, 5);
const moreLinks = navigationRoutes.slice(5);
return (
);
}
function FooterLink({
href,
text,
isExternal,
}: {
href: string;
text: string;
isExternal: boolean;
}) {
return (
{text}
{isExternal ? "↗" : "→"}
);
}
function NotPlaying() {
return (
);
}
function WhenPlaying({ song }: { song: Song }) {
return (
{song.title}
{song.artist}
{/* Equaliser bars */}
{[3, 4, 2, 5].map((h, i) => (
))}
);
}
================================================
FILE: components/FramerMotion/AnimatedDiv.tsx
================================================
import { AnimatedTAGProps } from "@lib/types";
import { motion } from "framer-motion";
export default function AnimatedDiv({
variants,
className,
children,
infinity,
}: AnimatedTAGProps) {
return (
{children}
);
}
================================================
FILE: components/FramerMotion/AnimatedHeading.tsx
================================================
import { motion } from "framer-motion";
import { AnimatedTAGProps } from "@lib/types";
export default function AnimatedHeading({
variants,
className,
children,
infinity,
}: AnimatedTAGProps) {
return (
{children}
);
}
================================================
FILE: components/FramerMotion/AnimatedText.tsx
================================================
import { AnimatedTAGProps } from "@lib/types";
import { motion } from "framer-motion";
export default function AnimatedText({
variants,
className,
children,
infinity,
}: AnimatedTAGProps) {
return (
{children}
);
}
================================================
FILE: components/GitHubActivityGraph.tsx
================================================
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
TooltipProps,
XAxis,
YAxis,
} from "recharts";
import {
NameType,
ValueType,
} from "recharts/types/component/DefaultTooltipContent";
import { motion } from "framer-motion";
import React from "react";
import fetcher from "@lib/fetcher";
import { getFormattedDate } from "@utils/date";
import { useDarkMode } from "@context/darkModeContext";
import useSWR from "swr";
function ChartSectionHeading({
title,
description,
}: {
title: string;
description: string;
}) {
return (
Activity
{title}
{description}
);
}
export default function GitHubActivityGraph() {
const { isDarkMode } = useDarkMode();
const { data: githubActivity } = useSWR(
"/api/stats/github-contribution",
fetcher,
);
return (
{githubActivity?.contributions ? (
} />
) : (
)}
{githubActivity?.contributionCountByDayOfWeek ? (
} />
) : (
)}
);
}
const ContributionsToolTip = ({
active,
payload,
}: TooltipProps) => {
if (active && payload && payload.length) {
return (
Date : {" "}
{getFormattedDate(new Date(payload[0].payload.date))}
Commit Count : {payload[0].value}
);
}
return null;
};
const ContributionCountByDayOfWeekToolTip = ({
active,
payload,
}: TooltipProps) => {
if (active && payload && payload.length) {
return (
Day : {payload[0].payload.day}
Commit Count : {payload[0].value}
);
}
return null;
};
function LoadingBarChart() {
const { isDarkMode } = useDarkMode();
const barGraphLoadingData = [
{ day: "Monday", count: 3 },
{ day: "Tuesday", count: 5 },
{ day: "Wednesday", count: 7 },
{ day: "Thursday", count: 9 },
{ day: "Friday", count: 11 },
{ day: "Saturday", count: 13 },
{ day: "Sunday", count: 15 },
];
return (
);
}
function LoadingAreaChart() {
const { isDarkMode } = useDarkMode();
const areaChartLoadingData = [
{ shortDate: "09", contributionCount: 3 },
{ shortDate: "10", contributionCount: 5 },
{ shortDate: "11", contributionCount: 15 },
{ shortDate: "12", contributionCount: 9 },
{ shortDate: "13", contributionCount: 4 },
{ shortDate: "14", contributionCount: 13 },
{ shortDate: "15", contributionCount: 5 },
{ shortDate: "16", contributionCount: 4 },
{ shortDate: "17", contributionCount: 9 },
{ shortDate: "18", contributionCount: 2 },
{ shortDate: "19", contributionCount: 5 },
{ shortDate: "21", contributionCount: 6 },
{ shortDate: "22", contributionCount: 7 },
{ shortDate: "23", contributionCount: 3 },
{ shortDate: "24", contributionCount: 21 },
{ shortDate: "25", contributionCount: 4 },
{ shortDate: "26", contributionCount: 9 },
{ shortDate: "27", contributionCount: 2 },
];
return (
);
}
================================================
FILE: components/Home/BlogsSection.tsx
================================================
import Blog from "../Blog";
import { BlogPost } from "@lib/interface/sanity";
import Link from "next/link";
import { motion } from "framer-motion";
import { FiArrowRight } from "react-icons/fi";
import siteConfig from "@content/siteConfig";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.1 } },
};
export default function BlogsSection({
blogs,
totalBlogs,
}: {
blogs: BlogPost[];
totalBlogs: number;
}) {
const { blogsSection } = siteConfig.home;
return (
{/* Section number watermark */}
03
{/* ── Header ── */}
{/* Section label */}
{blogsSection.eyebrow}
{blogsSection.title}
{blogsSection.description}
{/* Article count + CTA */}
{totalBlogs}
Articles
{blogsSection.ctaLabel}
{/* ── Blog list ── */}
{blogs.map((blog, index) => (
))}
);
}
================================================
FILE: components/Home/EpigraphsSection.tsx
================================================
import { IEpigraph } from "@lib/interface/sanity";
import Link from "next/link";
import EpigraphCard from "@components/EpigraphCard";
import { motion } from "framer-motion";
import { FiArrowRight } from "react-icons/fi";
import siteConfig from "@content/siteConfig";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.08 } },
};
export default function EpigraphsSection({
epigraphs,
totalEpigraphs,
}: {
epigraphs: IEpigraph[];
totalEpigraphs: number;
}) {
const { epigraphsSection } = siteConfig.home;
return (
{/* Section number watermark */}
04
{/* ── Header ── */}
{/* Section label */}
{epigraphsSection.eyebrow}
{epigraphsSection.title}
{epigraphsSection.description}
{/* Count + CTA */}
{totalEpigraphs}
Epigraphs
{epigraphsSection.ctaLabel}
{/* ── Epigraphs list ── */}
{epigraphs.map((epigraph, i) => (
))}
);
}
================================================
FILE: components/Home/HeroSection.tsx
================================================
import React from "react";
import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";
import {
FiDownload,
FiArrowRight,
FiGithub,
FiLinkedin,
FiTwitter,
} from "react-icons/fi";
import { opacityVariant, popUp } from "@content/FramerMotionVariants";
import { featuredSocialLinks } from "@content/siteConfig";
import siteConfig from "@content/siteConfig";
const socialIconMap = {
github: FiGithub,
linkedin: FiLinkedin,
twitter: FiTwitter,
};
export default function HeroSection() {
const { hero } = siteConfig.home;
return (
{/* ── Dot grid ── */}
{/* ── Diagonal stripe — right half ── */}
{/* ── Bottom fade ── */}
{/* ── JS initials — gradient emboss ── */}
JS
{/* ── Corner cross-tick marks ── */}
{(
[
"top-0 left-0",
"top-0 right-0",
"bottom-8 left-0",
"bottom-8 right-0",
] as const
).map((pos) => (
))}
{/* ── Left content (3 cols) ── */}
{/* Availability badge — inverted */}
{hero.availabilityBadge}
{/* Name */}
{hero.greeting}
Jatin
Sharma
{/* Role line */}
{hero.rolePrefix}{" "}
{hero.companyName}
{/* Bio — left accent border */}
{hero.roleSuffix.replace(/^\.\s*/, "")}
{/* CTAs */}
{/* Primary — solid fill */}
{hero.primaryCta.label}
{/* Secondary — invert-fill on hover */}
{hero.secondaryCta.label}
{/* Social links */}
{hero.socialLabel}
{featuredSocialLinks.map((socialLink) => {
const Icon =
socialIconMap[socialLink.icon as keyof typeof socialIconMap];
if (!Icon) return null;
return (
);
})}
{/* ── Right image (2 cols) ── */}
{/* Diagonal stripe behind image */}
{/* Outer dashed ring +3° */}
{/* Inner solid ring -1° */}
{/* Profile image */}
{/* Floating card — years */}
{hero.experienceBadge.value}
{hero.experienceBadge.title}
{hero.experienceBadge.description}
{/* Floating card — projects */}
100+
Articles
{/* Scroll indicator */}
Scroll
);
}
================================================
FILE: components/Home/SkillSection.tsx
================================================
import { motion } from "framer-motion";
import siteConfig from "@content/siteConfig";
import skills from "@content/skillsData";
import React, { useState, useMemo } from "react";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.04 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 12, scale: 0.97 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: { type: "spring" as const, stiffness: 160, damping: 20 },
},
exit: { opacity: 0, y: 6, scale: 0.96, transition: { duration: 0.12 } },
};
export default function SkillSection() {
const [selectedCategory, setSelectedCategory] = useState("All");
const { skillsSection } = siteConfig.home;
const categories = useMemo(() => {
const set = new Set(skills.map((s) => s.category || "Other"));
return ["All", ...Array.from(set)];
}, []);
const filteredSkills = useMemo(
() =>
selectedCategory === "All"
? skills
: skills.filter((s) => s.category === selectedCategory),
[selectedCategory],
);
const categoryCounts = useMemo(() => {
const counts: Record = { All: skills.length };
for (const s of skills) {
const cat = s.category || "Other";
counts[cat] = (counts[cat] ?? 0) + 1;
}
return counts;
}, []);
return (
{/* Section number watermark */}
02
{/* ── Header ── */}
{/* Section label */}
{skillsSection.eyebrow}
{skillsSection.title}
{skillsSection.description}
{/* Tech count + category breakdown */}
{skills.length}
Technologies
{categories.slice(1).map((cat) => (
setSelectedCategory(cat)}
className="inline-flex items-center gap-1.5 text-[10px] font-mono uppercase tracking-[0.2em] text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
>
{cat}
({categoryCounts[cat]})
))}
{/* ── Marquee divider ── */}
{[...skills, ...skills].map((skill, i) => {
const Icon = skill.Icon;
return (
{/* @ts-ignore */}
{skill.name}
);
})}
{/* Side fades */}
{/* ── Category filter ── */}
{categories.map((cat) => (
setSelectedCategory(cat)}
className={`px-4 py-2 text-xs font-semibold font-mono uppercase tracking-wide transition-all duration-200 ${
selectedCategory === cat
? "bg-gray-900 dark:bg-white text-white dark:text-gray-900"
: "bg-transparent text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-gray-500 dark:hover:border-gray-500 hover:text-gray-900 dark:hover:text-white"
}`}
>
{cat}
{categoryCounts[cat]}
))}
{/* ── Skills grid — keyed so the whole grid stagger-enters on every filter change ── */}
{filteredSkills.map((skill) => {
const Icon = skill.Icon;
return (
{/* Hover background */}
{/* Left accent bar */}
{/* Icon */}
{/* @ts-ignore */}
{/* Name */}
{skill.name}
);
})}
);
}
================================================
FILE: components/Home/StatsSection.tsx
================================================
import { motion } from "framer-motion";
import { FiCode, FiUsers, FiCoffee, FiAward } from "react-icons/fi";
import siteConfig, { StatIconKey } from "@content/siteConfig";
const iconMap: Record = {
code: FiCode,
users: FiUsers,
coffee: FiCoffee,
award: FiAward,
};
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
},
},
};
export default function StatsSection() {
return (
{siteConfig.home.stats.map((stat) => {
const Icon = iconMap[stat.icon];
return (
{/* Background Gradient */}
{/* Content */}
{/* Icon */}
{/* Value */}
{stat.value}
{/* Label */}
{stat.label}
{stat.description}
);
})}
);
}
================================================
FILE: components/Instagram/InstagramPost.tsx
================================================
import { DetailedInstagramPost, MediaType } from "@lib/interface";
import { FaPlay } from "react-icons/fa";
import Link from "next/link";
import React from "react";
import { TbBoxMultiple } from "react-icons/tb";
export default function InstagramPost({
post,
}: {
post: DetailedInstagramPost;
}) {
const previewURL =
post.media_type === MediaType.Video ? post.thumbnail_url! : post.media_url;
return (
{post.media_type !== MediaType.Image && (
{post.media_type === MediaType.Video && (
)}
{post.media_type === MediaType.CarouselAlbum && (
)}
)}
);
}
================================================
FILE: components/Instagram/InstagramPostLoading.tsx
================================================
import React from "react";
export default function InstagramPostLoading({ count }: { count: number }) {
return (
<>
{Array.from(Array(count).keys()).map((item) => {
return (
);
})}
>
);
}
================================================
FILE: components/Instagram/InstagramSection.tsx
================================================
import { HomeHeading } from "pages";
import { InstagramData } from "@lib/interface";
import InstagramPost from "./InstagramPost";
import InstagramPostLoading from "./InstagramPostLoading";
import Link from "next/link";
import React from "react";
import fetcher from "@lib/fetcher";
import socialMedia from "@content/socialMedia";
import useSWR from "swr";
export default function InstagramSection() {
const { data: instaData } = useSWR(
"/api/posts/insta",
fetcher
);
return (
{instaData === undefined ? (
) : (
instaData?.data.slice(0, 9).map((post) => {
return ;
})
)}
item.title === "Instagram")?.url!}
className="flex items-center justify-center gap-1 font-medium transition border-transparent font-inter active:scale-95 active:border-black w-fit group"
>
View More on Instagram
);
}
================================================
FILE: components/MDXComponents/Code.tsx
================================================
type Props = {
children?: string | React.ReactNode;
};
export default function Code(props: Props) {
return (
<>
{typeof props.children === "string" ? (
{props.children}
) : (
{props.children}
)}
>
);
}
================================================
FILE: components/MDXComponents/CodeSandbox.tsx
================================================
export default function CodeSandbox({
id,
hideNavigation = true,
}: {
id: string;
hideNavigation: boolean;
}) {
return (
Code Sandbox
);
}
================================================
FILE: components/MDXComponents/CodeTitle.tsx
================================================
import { BsFileEarmarkCodeFill } from "react-icons/bs";
import {
SiCss3,
SiPython,
SiGnubash,
SiHtml5,
SiReact,
SiMarkdown,
SiNextdotjs,
SiVercel,
SiTypescript,
} from "react-icons/si";
import { VscJson } from "react-icons/vsc";
import { IoLogoJavascript } from "react-icons/io5";
import { AiOutlineFileText, AiOutlineFolderOpen } from "react-icons/ai";
type Props = {
title?: string;
lang: string;
};
export default function CodeTitle({ title, lang }: Props) {
let Icon;
switch (lang) {
case "html":
Icon = SiHtml5;
break;
case "css":
Icon = SiCss3;
break;
case "js":
Icon = IoLogoJavascript;
break;
case "bash":
Icon = SiGnubash;
break;
case "py":
Icon = SiPython;
break;
case "json":
Icon = VscJson;
break;
case "jsx":
Icon = SiReact;
break;
case "text":
Icon = AiOutlineFileText;
break;
case "md":
Icon = SiMarkdown;
break;
case "next":
Icon = SiNextdotjs;
break;
case "directory":
Icon = AiOutlineFolderOpen;
break;
case "vercel":
Icon = SiVercel;
break;
case "ts":
case "tsx":
Icon = SiTypescript;
break;
default:
Icon = BsFileEarmarkCodeFill;
break;
}
return (
);
}
================================================
FILE: components/MDXComponents/Codepen.tsx
================================================
export default function Codepen({ id }: { id: string }) {
return (
);
}
================================================
FILE: components/MDXComponents/Danger.tsx
================================================
type Props = { title?: string; text: string };
export default function Danger({ title, text }: Props) {
return (
);
}
================================================
FILE: components/MDXComponents/EmbedBlog.tsx
================================================
import Image from "next/image";
import Link from "next/link";
export default function EmbedBlog({
img,
text,
url,
}: {
img: string;
text: string;
url: string;
}) {
return (
{text}
);
}
================================================
FILE: components/MDXComponents/Figcaption.tsx
================================================
type Props = {
src: string;
caption?: string;
alt?: string;
};
export default function figcaption({ src, caption, alt }: Props) {
if (caption !== undefined) {
return (
{caption}
);
} else {
return ;
}
}
================================================
FILE: components/MDXComponents/LinkedInEmbed.tsx
================================================
export default function LinkedInEmbed({ id }: { id: string }) {
return (
);
}
================================================
FILE: components/MDXComponents/NextAndPreviousButton.tsx
================================================
import Link from "next/link";
import { IoArrowForwardSharp } from "react-icons/io5";
type Props = {
prevHref: string;
prevTitle: string;
nextHref: string;
nextTitle: string;
};
export default function NextAndPreviousButton({
prevHref,
prevTitle,
nextHref,
nextTitle,
}: Props) {
return (
{prevHref && prevTitle && (
)}
{nextHref && nextTitle && (
)}
);
}
function BlogPageButton({
href,
title,
type,
}: {
href: string;
title: string;
type: "previous" | "next";
}) {
return (
);
}
================================================
FILE: components/MDXComponents/Pre.tsx
================================================
import { ReactNode, useRef, useState } from "react";
import { MdCheck, MdContentCopy } from "react-icons/md";
const Pre = ({
children,
"data-theme": dataTheme,
}: {
children?: ReactNode;
"data-theme"?: string;
}) => {
const textInput = useRef(null);
const [copied, setCopied] = useState(false);
const onCopy = () => {
if (textInput.current !== null) {
setCopied(true);
navigator.clipboard.writeText(textInput.current.textContent!);
setTimeout(() => setCopied(false), 2000);
}
};
return (
{copied ? (
) : (
)}
{copied ? "Copied" : "Copy"}
{children}
);
};
export default Pre;
================================================
FILE: components/MDXComponents/Step.tsx
================================================
import React from "react";
export default function Step({
id,
children,
}: {
id: string;
children?: JSX.Element;
}) {
return (
);
}
================================================
FILE: components/MDXComponents/Tip.tsx
================================================
export default function Tip({
id,
children,
}: {
id: string;
children?: React.ReactNode;
}) {
return (
Tip {id && `#${id}`}
{children}
);
}
================================================
FILE: components/MDXComponents/UrlMetaInfo.tsx
================================================
import React, { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
interface MetaData {
title: string;
description: string;
image: string;
}
interface UrlMetaInfoProps {
url: string;
}
function UrlMetaInfo({ url }: UrlMetaInfoProps) {
const [metaData, setMetaData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.text();
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(data, "text/html");
const metaTags = htmlDoc.querySelectorAll("meta");
const metaInfo: MetaData = {
title: (htmlDoc.querySelector("title")?.textContent || "")
.replaceAll(" - Jatin Sharma", "")
.replaceAll(" - DEV Community", ""),
description: (
Array.from(metaTags)
.find((tag) => tag.getAttribute("name") === "description")
?.getAttribute("content") || ""
).split("...")[0],
image:
Array.from(metaTags)
.find((tag) => tag.getAttribute("property") === "og:image")
?.getAttribute("content") || "",
};
setMetaData(metaInfo);
} catch (error) {
console.error("Error fetching URL data:", error);
}
};
fetchData();
}, [url]);
if (!metaData) {
return Loading...
;
}
return (
{metaData.title}
{metaData.description}
);
}
export default UrlMetaInfo;
================================================
FILE: components/MDXComponents/Warning.tsx
================================================
type Props = {
text?: string;
title?: string;
children?: React.ReactNode;
};
export default function Warning({ text, title, children }: Props) {
return (
);
}
================================================
FILE: components/MDXComponents/YouTube.tsx
================================================
export default function YouTube({ id }: { id: string }) {
return (
VIDEO
);
}
================================================
FILE: components/MDXComponents/index.tsx
================================================
import Code from "./Code";
import CodeSandbox from "./CodeSandbox";
import CodeTitle from "./CodeTitle";
import Codepen from "./Codepen";
import Danger from "./Danger";
import EmbedBlog from "./EmbedBlog";
import Figcaption from "./Figcaption";
import LinkedInEmbed from "./LinkedInEmbed";
import NextAndPreviousButton from "./NextAndPreviousButton";
import Pre from "./Pre";
import Step from "./Step";
import Tip from "./Tip";
import UrlMetaInfo from "./UrlMetaInfo";
import Warning from "./Warning";
import YouTube from "./YouTube";
const MDXComponents = {
Codepen,
Figcaption,
Warning,
Danger,
CodeTitle,
Tip,
Step,
CodeSandbox,
NextAndPreviousButton,
YouTube,
EmbedBlog,
UrlMetaInfo,
LinkedInEmbed,
pre: Pre,
code: Code,
};
export default MDXComponents;
================================================
FILE: components/MetaData.tsx
================================================
import { useEffect, useState } from "react";
import { NextSeo } from "next-seo";
import useWindowLocation from "@hooks/useWindowLocation";
// import Head from "next/head";
type Props = {
title: string;
description: string;
previewImage?: string;
keywords?: string;
suffix?: string;
};
const getFaviconPath = (isDarkMode: boolean = true): string => {
return `/favicon-${isDarkMode ? "dark" : "light"}.ico`;
};
export default function MetaData({
title,
description,
previewImage,
keywords,
suffix,
}: Props) {
const { currentURL } = useWindowLocation();
const [faviconHref, setFaviconHref] = useState("/favicon-dark.ico");
useEffect(() => {
// Get current color scheme.
const matcher = window.matchMedia("(prefers-color-scheme: dark)");
// Set favicon initially.
setFaviconHref(getFaviconPath(matcher.matches));
// Change favicon if the color scheme changes.
matcher.onchange = () => setFaviconHref(getFaviconPath(matcher.matches));
}, [faviconHref]);
return (
//
//
//
//
//
// {title + (suffix ? ` - ${suffix}` : "")}
//
//
//
//
//
//
//
//
// {/* Og */}
//
//
//
//
//
// {/* Twitter */}
//
//
//
//
//
//
//
//
);
}
================================================
FILE: components/MovieCard.tsx
================================================
import React, { useState } from "react";
import { AiFillStar } from "react-icons/ai";
import { ITMDBData } from "@lib/interface";
import Image from "next/image";
import { motion } from "framer-motion";
const TMDB_IMAGE_PREFIX = "https://image.tmdb.org/t/p/w780";
const cardVariants = {
hidden: { opacity: 0, y: 12 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 160, damping: 22 },
},
};
export default function MovieCard({ movie }: { movie: ITMDBData }) {
const [loading, setLoading] = useState(true);
const title = movie?.original_title ?? movie?.original_name ?? "";
return (
{/* Poster */}
{loading && (
)}
setLoading(false)}
/>
{/* Info */}
);
}
function WatchStatus({ rating }: { rating?: number }) {
return (
{rating ? (
<>
Watched
{rating}
>
) : (
Watching
)}
);
}
================================================
FILE: components/Newsletter.tsx
================================================
import { useState } from "react";
import { ToastContainer, toast } from "react-toastify";
import { CgSpinnerTwo } from "react-icons/cg";
import { useDarkMode } from "@context/darkModeContext";
export default function Newsletter() {
const { isDarkMode } = useDarkMode();
const [email, setEmail] = useState("");
const [validationLoading, setValidationLoading] = useState(false);
async function subscribeNewsLetter(e: React.FormEvent) {
e.preventDefault();
setValidationLoading(true);
fetch("/api/validate/email", {
method: "POST",
body: JSON.stringify({ email }),
})
.then((res) => res.json())
.then((res) => {
if (res.status === "success" && res.valid === true) {
try {
fetch(process.env.NEXT_PUBLIC_EMAIL_LIST + email, {
mode: "no-cors",
});
} catch (error) {
console.error(error);
}
toast.success("You have been added to my mailing list.");
setEmail("");
} else {
toast.error("Please enter a valid email address.");
}
setValidationLoading(false);
})
.catch((error) => {
console.error(error);
setValidationLoading(false);
});
}
return (
<>
{/* Header */}
Newsletter
Monthly digest on web dev, tech, and productivity.
No spam. Unsubscribe any time.
{/* Form */}
>
);
}
================================================
FILE: components/OgImage.tsx
================================================
import Image from "next/image";
function OgImage({ src, alt }: { src: string; alt: string }) {
return (
);
}
export default OgImage;
================================================
FILE: components/PageHeader.tsx
================================================
import React from "react";
import { motion } from "framer-motion";
interface PageHeaderProps {
/** Large background watermark text, e.g. "about" or "/uses" */
watermark: string;
/** Mono eyebrow label above the title, e.g. "Profile — 001" */
eyebrow: string;
/** Page title rendered as h1 */
title: string;
/** Description rendered with left-border accent below the title */
description: string;
/** Content rendered inside the max-w-7xl container below the header */
children?: React.ReactNode;
/** Extra classes on the outermost wrapper — use for padding overrides */
className?: string;
}
export default function PageHeader({
watermark,
eyebrow,
title,
description,
children,
className = "",
}: PageHeaderProps) {
return (
{/* Watermark */}
{watermark}
{/* Header */}
{eyebrow}
{title}
{description}
{children}
);
}
================================================
FILE: components/PageNotFound.tsx
================================================
import Link from "next/link";
import MetaData from "@components/MetaData";
import { motion } from "framer-motion";
export default function PageNotFound() {
return (
<>
{/* Watermark */}
404
Error — 404
Page not found.
You didn't break the internet, but I can't find what
you're looking for. Visit the homepage to get back on track.
Take me home
>
);
}
================================================
FILE: components/PageTop.tsx
================================================
import {
fromLeftVariant,
opacityVariant,
} from "../content/FramerMotionVariants";
import AnimatedHeading from "./FramerMotion/AnimatedHeading";
import AnimatedText from "./FramerMotion/AnimatedText";
export default function PageTop({
pageTitle,
headingClass,
containerClass,
children,
}: {
pageTitle: string;
headingClass?: string;
containerClass?: string;
children?: React.ReactNode;
}) {
return (
);
}
================================================
FILE: components/Project.tsx
================================================
import { BsGithub } from "react-icons/bs";
import { MdOutlineLink } from "react-icons/md";
import Link from "next/link";
import Image from "next/image";
import { motion } from "framer-motion";
import { ProjectType } from "@lib/types";
const itemVariants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 260, damping: 24 },
},
};
export default function Project({
project,
featured = false,
}: {
project: ProjectType;
featured?: boolean;
}) {
return (
{/* Cover image */}
{project.coverImage && (
)}
{/* Content */}
{featured && (
Featured
)}
{project.name}
{project.description}
{/* Tech tags */}
{project.tools && project.tools.length > 0 && (
{(featured ? project.tools : project.tools.slice(0, 4)).map(
(tool, index) => (
{tool}
),
)}
)}
{/* Links */}
Code
{project.previewURL && (
Preview
)}
);
}
================================================
FILE: components/QRCodeContainer.tsx
================================================
import QRCode from "react-qr-code";
import useWindowLocation from "@hooks/useWindowLocation";
import { MdClose } from "react-icons/md";
import { AnimatePresence, motion } from "framer-motion";
import { useDarkMode } from "@context/darkModeContext";
export default function QRCodeContainer({
showQR,
setShowQR,
}: {
showQR: boolean;
setShowQR: (value: boolean) => void;
}) {
const { currentURL } = useWindowLocation();
const { isDarkMode } = useDarkMode();
function downloadQRCode() {
const svg = document.getElementById("QRCode");
const svgData = new XMLSerializer().serializeToString(svg!);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
const pngFile = canvas.toDataURL("image/png");
const downloadLink = document.createElement("a");
downloadLink.download = "QRCode";
downloadLink.href = `${pngFile}`;
downloadLink.click();
};
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`;
}
return (
{showQR && (
<>
{/* Backdrop */}
setShowQR(false)}
className="fixed inset-0 bg-black/50 z-[9999]"
/>
{/* Panel */}
{/* Header */}
Share
Scan QR Code
setShowQR(false)}
aria-label="Close"
className="w-8 h-8 flex items-center justify-center border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-900 dark:hover:border-white transition-colors"
>
{/* QR code */}
{/* URL + download */}
{currentURL}
Download PNG
>
)}
);
}
================================================
FILE: components/SVG/Ditto.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function Ditto() {
const { isDarkMode } = useDarkMode();
return (
);
}
================================================
FILE: components/SVG/Flameshot.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function Flameshot({ className }: { className?: string }) {
const { isDarkMode } = useDarkMode();
return (
);
}
================================================
FILE: components/SVG/Flux.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function Flux() {
const { isDarkMode } = useDarkMode();
return (
);
}
================================================
FILE: components/SVG/Logo.tsx
================================================
import { motion } from "framer-motion";
export default function Logo({ className }: { className: string }) {
return (
);
}
================================================
FILE: components/SVG/MicrosoftToDo.tsx
================================================
export default function MicrosoftToDo() {
return (
);
}
================================================
FILE: components/SVG/RainDrop.tsx
================================================
export default function RainDrop() {
return (
);
}
================================================
FILE: components/SVG/ShareX.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function ShareX() {
const { isDarkMode } = useDarkMode();
return (
);
}
================================================
FILE: components/SVG/UPI.tsx
================================================
import React from "react";
export default function UPI({ className }: { className: string }) {
return (
);
}
================================================
FILE: components/SVG/Zip7.tsx
================================================
export default function Zip7() {
return (
);
}
================================================
FILE: components/SVG/index.tsx
================================================
import Ditto from "./Ditto";
import Flux from "./Flux";
import MicrosoftToDo from "./MicrosoftToDo";
import RainDrop from "./RainDrop";
import ShareX from "./ShareX";
import Zip7 from "./Zip7";
import Flameshot from "./Flameshot";
const SVG = {
Ditto,
Flux,
MicrosoftToDo,
RainDrop,
ShareX,
Zip7,
Flameshot,
};
export default SVG;
================================================
FILE: components/ScrollProgressBar.tsx
================================================
import { useCallback, useEffect, useState } from "react";
export default function ScrollProgressBar() {
const [scroll, setScroll] = useState("0");
const progressBarHandler = useCallback(() => {
const totalScroll = document.documentElement.scrollTop;
const windowHeight =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const scroll = `${totalScroll / windowHeight}`;
setScroll(scroll);
}, []);
useEffect(() => {
window.addEventListener("scroll", progressBarHandler);
return () => window.removeEventListener("scroll", progressBarHandler);
}, [progressBarHandler]);
return (
);
}
================================================
FILE: components/ScrollToTopButton.tsx
================================================
import { IoIosArrowUp } from "react-icons/io";
import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import useScrollPercentage from "@hooks/useScrollPercentage";
export default function ScrollToTopButton() {
const [showButton, setShowButton] = useState(false);
const scrollPercentage = useScrollPercentage();
useEffect(() => {
setShowButton(scrollPercentage > 10 && scrollPercentage < 95);
}, [scrollPercentage]);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
{showButton && (
)}
);
}
================================================
FILE: components/ShareOnSocialMedia.tsx
================================================
import "react-toastify/dist/ReactToastify.css";
import {
FacebookShareButton,
LinkedinShareButton,
TwitterShareButton,
WhatsappShareButton,
} from "react-share";
import { FiCopy, FiLinkedin } from "react-icons/fi";
import { GrFacebookOption, GrTwitter } from "react-icons/gr";
import { BsThreeDots } from "react-icons/bs";
import { FaWhatsapp } from "react-icons/fa";
import { toast } from "react-toastify";
import useShare from "../hooks/useShare";
type Props = {
className?: string;
title: string;
url: string;
summary: string;
cover_image: string;
children?: React.ReactNode;
};
export default function ShareOnSocialMedia({
title,
url,
summary,
cover_image,
children,
}: Props) {
const { isShareSupported } = useShare();
async function handleShare() {
const blob = await fetch(cover_image).then((res) => res.blob());
const file = new File([blob], "image.png", { type: "image/png" });
if (window.navigator.share) {
window.navigator
.share({ title, text: summary, url, files: [file] })
.catch(console.error);
}
}
function copyTextToClipboard(text: string) {
if (!navigator.clipboard) {
toast.error("Clipboard not supported on this device.");
return;
}
navigator.clipboard.writeText(text).then(
() => toast.success("Link copied!"),
() => toast.error("Failed to copy link."),
);
}
const btnClass =
"flex items-center gap-1.5 font-mono text-[10px] tracking-[0.3em] uppercase text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors px-3 py-2 border-r border-gray-200 dark:border-neutral-700 last:border-r-0";
return (
Facebook
Twitter
LinkedIn
WhatsApp
copyTextToClipboard(url)} className={btnClass}>
Copy link
{isShareSupported && (
More
)}
{children}
);
}
================================================
FILE: components/SnippetCard.tsx
================================================
import { motion } from "framer-motion";
import { ISnippet } from "@lib/interface/sanity";
import Image from "next/image";
import Link from "next/link";
const itemVariants = {
hidden: { opacity: 0, y: 12 },
visible: { opacity: 1, y: 0, transition: { duration: 0.35 } },
};
export default function SnippetCard({ snippet }: { snippet: ISnippet }) {
return (
{/* Language icon */}
{/* Language label */}
{snippet.language.name}
{/* Title */}
{snippet.title}
{/* Excerpt */}
{snippet.excerpt}
);
}
================================================
FILE: components/SnowfallCanvas.tsx
================================================
// @ts-nocheck
import React, { useEffect, useRef } from "react";
import { useDarkMode } from "@context/darkModeContext";
import useWindowSize from "@hooks/useWindowSize";
const SnowfallCanvas: React.FC = () => {
const canvasRef = useRef(null);
const { width } = useWindowSize();
// useEffect(() => {
// if (canvasRef.current) {
// const c = canvasRef.current;
// const $ = c.getContext("2d");
// let w = window.innerWidth;
// let h = window.innerHeight;
// const Snowy = () => {
// let snow;
// const arr = [];
// const num =
// width > 1000 ? 1000 : width < 1000 && width > 500 ? 500 : 100,
// tsc = 1,
// sp = 1;
// const sc = 1.3,
// mv = 20,
// min = 1;
// const Flake = function () {
// this.draw = function () {
// this.g = $.createRadialGradient(
// this.x,
// this.y,
// 0,
// this.x,
// this.y,
// this.sz
// );
// this.g.addColorStop(0, "rgba(255,255,255,1)");
// this.g.addColorStop(1, "rgba(255,255,255,0)");
// $.moveTo(this.x, this.y);
// $.fillStyle = this.g;
// $.beginPath();
// $.arc(this.x, this.y, this.sz, 0, Math.PI * 2, true);
// $.fill();
// };
// };
// for (let i = 0; i < num; ++i) {
// snow = new Flake();
// snow.y = Math.random() * (h + 50);
// snow.x = Math.random() * w;
// snow.t = Math.random() * (Math.PI * 2);
// snow.sz = (100 / (10 + Math.random() * 100)) * sc;
// snow.sp = Math.pow(snow.sz * 0.8, 2) * 0.15 * sp;
// snow.sp = snow.sp < min ? min : snow.sp;
// arr.push(snow);
// }
// const go = () => {
// window.requestAnimationFrame(go);
// $.clearRect(0, 0, w, h);
// // $.fillStyle = "rgba(0,0,0, 1)"; // - add background color
// $.fillRect(0, 0, w, h);
// $.fill();
// for (let i = 0; i < arr.length; ++i) {
// const f = arr[i];
// f.t += 0.05;
// f.t = f.t >= Math.PI * 2 ? 0 : f.t;
// f.y += f.sp;
// f.x += Math.sin(f.t * tsc) * (f.sz * 0.3);
// if (f.y > h + 50) f.y = -10 - Math.random() * mv;
// if (f.x > w + mv) f.x = -mv;
// if (f.x < -mv) f.x = w + mv;
// f.draw();
// }
// };
// go();
// };
// window.addEventListener(
// "resize",
// () => {
// if (c) {
// w = window.innerWidth;
// h = window.innerHeight;
// c.width = w;
// c.height = h;
// }
// },
// false
// );
// Snowy();
// }
// }, []);
useEffect(() => {
let animationFrameId: number;
let ctx: CanvasRenderingContext2D | null;
const Snowy = () => {
if (canvasRef.current) {
ctx = canvasRef.current.getContext("2d");
if (!ctx) return;
const w = window.innerWidth;
const h = window.innerHeight;
canvasRef.current.width = w;
canvasRef.current.height = h;
let snow;
const arr: any[] = [];
const num =
width > 1000 ? 1000 : width < 1000 && width > 500 ? 500 : 100;
const tsc = 1;
const sp = 1;
const sc = 1.3,
mv = 20,
min = 1;
const Flake = function () {
this.draw = function () {
if (!ctx) return;
this.g = ctx.createRadialGradient(
this.x,
this.y,
0,
this.x,
this.y,
this.sz,
);
this.g.addColorStop(0, "hsla(255,255%,255%,1)");
this.g.addColorStop(1, "hsla(255,255%,255%,0)");
ctx.moveTo(this.x, this.y);
ctx.fillStyle = this.g;
ctx.beginPath();
ctx.arc(this.x, this.y, this.sz, 0, Math.PI * 2, true);
ctx.fill();
};
};
for (let i = 0; i < num; ++i) {
snow = new Flake();
snow.y = Math.random() * (h + 50);
snow.x = Math.random() * w;
snow.t = Math.random() * (Math.PI * 2);
snow.sz = (100 / (10 + Math.random() * 100)) * sc;
snow.sp = Math.pow(snow.sz * 0.8, 2) * 0.15 * sp;
snow.sp = snow.sp < min ? min : snow.sp;
arr.push(snow);
}
const go = () => {
animationFrameId = window.requestAnimationFrame(go);
if (!ctx) return;
ctx.clearRect(0, 0, w, h);
// ctx.fillStyle = "hsla(242, 95%, 3%, 1)";
ctx.fillRect(0, 0, w, h);
ctx.fill();
for (let i = 0; i < arr.length; ++i) {
const f = arr[i];
f.t += 0.05;
f.t = f.t >= Math.PI * 2 ? 0 : f.t;
f.y += f.sp;
f.x += Math.sin(f.t * tsc) * (f.sz * 0.3);
if (f.y > h + 50) f.y = -10 - Math.random() * mv;
if (f.x > w + mv) f.x = -mv;
if (f.x < -mv) f.x = w + mv;
f.draw();
}
};
go();
}
};
Snowy();
return () => {
window.cancelAnimationFrame(animationFrameId);
if (ctx && canvasRef.current) {
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
}
};
}, [width]);
const canvasStyles: React.CSSProperties = {
backgroundColor: "transparent",
margin: 0,
overflow: "hidden",
fontFamily: "'Molle', cursive",
position: "absolute",
top: "0",
left: "0",
zIndex: -1,
};
return (
);
};
export default SnowfallCanvas;
================================================
FILE: components/StaticPage.tsx
================================================
import { IStaticPage } from "@lib/interface/sanity";
import MDXComponents from "@components/MDXComponents";
import { MDXRemote } from "next-mdx-remote";
import MetaData from "@components/MetaData";
import { PageData } from "@lib/types";
import PageHeader from "@components/PageHeader";
export default function StaticPage({
metadata,
page,
showDescription = false,
}: {
metadata: PageData;
page: IStaticPage;
showDescription?: boolean;
}) {
return (
<>
>
);
}
================================================
FILE: components/Stats/Artist.tsx
================================================
import Image from "next/image";
import Link from "next/link";
type ArtistProps = {
name: string;
url: string;
coverImage: string;
popularity: number;
id: number;
};
export default function Artist({
name,
url,
coverImage,
popularity,
id,
}: ArtistProps) {
return (
{id + 1}
{name}
Popularity: {popularity}
);
}
================================================
FILE: components/Stats/MonkeyTypeStats.tsx
================================================
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
TooltipProps,
XAxis,
YAxis,
} from "recharts";
import {
NameType,
ValueType,
} from "recharts/types/component/DefaultTooltipContent";
import { motion } from "framer-motion";
import React from "react";
import fetcher from "@lib/fetcher";
import { useDarkMode } from "@context/darkModeContext";
import useSWR from "swr";
/* ── Types ── */
interface PersonalBest {
wpm: number;
acc: number;
consistency: number;
raw: number;
timestamp: number;
language: string;
difficulty: string;
}
interface TrendPoint {
index: number;
wpm: number;
raw: number;
acc: number;
mode: string;
timestamp: number;
}
interface MonkeyTypeData {
personalBests: {
time15: PersonalBest | null;
time30: PersonalBest | null;
time60: PersonalBest | null;
};
stats: {
completedTests: number;
startedTests: number;
timeTyping: number;
};
streak: {
length: number;
maxLength: number;
};
lastResult: any | null;
averages: {
wpm: number;
acc: number;
consistency: number;
} | null;
trendResults: TrendPoint[];
leaderboardRank: number | null;
}
/* ── Helpers ── */
function formatTime(seconds: number): string {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
if (hrs > 0) return `${hrs}h ${mins}m`;
return `${mins}m`;
}
function formatDate(ts: number): string {
return new Date(ts).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
}
/* ── Animation variants ── */
const itemVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 160, damping: 22 },
},
};
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.06 } },
};
/* ── Stat Card ── */
function MonkeyStatsCard({
title,
value,
}: {
title: string;
value: string | null | undefined;
}) {
return (
{title}
);
}
/* ── Section heading ── */
function SectionHeading({
eyebrow,
title,
description,
}: {
eyebrow: string;
title: string;
description?: string;
}) {
return (
{eyebrow}
{title}
{description && (
{description}
)}
);
}
/* ── Chart section heading ── */
function ChartSectionHeading({
title,
description,
}: {
title: string;
description: string;
}) {
return (
Typing
{title}
{description}
);
}
/* ── Personal best detail card ── */
function PersonalBestCard({
mode,
pb,
}: {
mode: string;
pb: PersonalBest | null | undefined;
}) {
if (!pb) {
return (
{mode}
{Array.from({ length: 4 }, (_, i) => (
))}
);
}
const rows = [
{ label: "Raw WPM", value: Math.round(pb.raw) },
{ label: "Accuracy", value: `${Math.round(pb.acc * 100) / 100}%` },
{
label: "Consistency",
value: `${Math.round(pb.consistency * 100) / 100}%`,
},
{ label: "Language", value: pb.language },
{ label: "Date", value: formatDate(pb.timestamp) },
];
return (
{mode}
{Math.round(pb.wpm)}
wpm
{rows.map((row) => (
{row.label}
{row.value}
))}
);
}
/* ── Tooltips ── */
const WpmTooltip = ({ active, payload }: TooltipProps) => {
if (active && payload && payload.length) {
return (
Mode : {payload[0].payload.mode}
WPM : {payload[0].value}
Raw : {payload[0].payload.raw}
);
}
return null;
};
const AccuracyTooltip = ({
active,
payload,
}: TooltipProps) => {
if (active && payload && payload.length) {
return (
Mode : {payload[0].payload.mode}
Accuracy : {" "}
{payload[0].payload.acc}%
Consistency : {" "}
{payload[0].payload.consistency}%
);
}
return null;
};
const TrendTooltip = ({
active,
payload,
}: TooltipProps) => {
if (active && payload && payload.length) {
return (
Test : #
{payload[0].payload.index}
WPM : {payload[0].payload.wpm}
Raw : {payload[0].payload.raw}
Acc : {payload[0].payload.acc}%
Mode : {payload[0].payload.mode}
);
}
return null;
};
/* ── Loading charts ── */
function LoadingBarChart({
data,
}: {
data: { mode: string; value: number }[];
}) {
const { isDarkMode } = useDarkMode();
return (
);
}
function LoadingAreaChart() {
const { isDarkMode } = useDarkMode();
const placeholderData = Array.from({ length: 10 }, (_, i) => ({
index: i + 1,
wpm: 60 + Math.sin(i) * 20,
}));
return (
);
}
/* ── Main Component ── */
export default function MonkeyTypeStats() {
const { isDarkMode } = useDarkMode();
const { data } = useSWR("/api/stats/monkeytype", fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 86400000,
});
const pb = data?.personalBests;
const stats = data?.stats;
const trendResults = data?.trendResults;
const allPbs = [pb?.time15, pb?.time30, pb?.time60].filter(
Boolean,
) as PersonalBest[];
const bestWpm = allPbs.length
? Math.round(Math.max(...allPbs.map((p) => p.wpm)))
: null;
const bestAcc = allPbs.length
? Math.round(Math.max(...allPbs.map((p) => p.acc)) * 100) / 100
: null;
const completionRate =
stats && stats.startedTests > 0
? Math.round((stats.completedTests / stats.startedTests) * 100)
: null;
const overviewStats: { title: string; value: string | null }[] = [
{ title: "Best WPM", value: bestWpm != null ? `${bestWpm}` : null },
{ title: "Best Accuracy", value: bestAcc != null ? `${bestAcc}%` : null },
{
title: "Tests Completed",
value: stats?.completedTests?.toLocaleString() ?? null,
},
{
title: "Completion Rate",
value: completionRate != null ? `${completionRate}%` : null,
},
{
title: "Time Typing",
value: stats ? formatTime(stats.timeTyping) : null,
},
];
// Chart data
const wpmChartData = pb
? [
{
mode: "15 seconds",
wpm: Math.round(pb.time15?.wpm ?? 0),
raw: Math.round(pb.time15?.raw ?? 0),
},
{
mode: "30 seconds",
wpm: Math.round(pb.time30?.wpm ?? 0),
raw: Math.round(pb.time30?.raw ?? 0),
},
{
mode: "60 seconds",
wpm: Math.round(pb.time60?.wpm ?? 0),
raw: Math.round(pb.time60?.raw ?? 0),
},
]
: null;
const accChartData = pb
? [
{
mode: "15 seconds",
acc: Math.round((pb.time15?.acc ?? 0) * 100) / 100,
consistency: Math.round((pb.time15?.consistency ?? 0) * 100) / 100,
},
{
mode: "30 seconds",
acc: Math.round((pb.time30?.acc ?? 0) * 100) / 100,
consistency: Math.round((pb.time30?.consistency ?? 0) * 100) / 100,
},
{
mode: "60 seconds",
acc: Math.round((pb.time60?.acc ?? 0) * 100) / 100,
consistency: Math.round((pb.time60?.consistency ?? 0) * 100) / 100,
},
]
: null;
const loadingData = [
{ mode: "15 seconds", value: 80 },
{ mode: "30 seconds", value: 100 },
{ mode: "60 seconds", value: 120 },
];
return (
<>
{/* ══════════════════════════════════════════════ */}
{/* Section 1 — Overview Stats Grid */}
{/* ══════════════════════════════════════════════ */}
{overviewStats.map((stat, index) => (
))}
{/* ══════════════════════════════════════════════ */}
{/* Section 2 — Personal Bests Detail Cards */}
{/* ══════════════════════════════════════════════ */}
{/* ══════════════════════════════════════════════ */}
{/* Section 3 — WPM Trend (full-width area chart) */}
{/* ══════════════════════════════════════════════ */}
{trendResults && trendResults.length > 0 ? (
}
cursor={{
stroke: isDarkMode ? "#ffffff30" : "#00000020",
strokeWidth: 1,
}}
/>
) : (
)}
{/* ══════════════════════════════════════════════ */}
{/* Section 4 — PB Charts (2-col grid) */}
{/* ══════════════════════════════════════════════ */}
{wpmChartData ? (
}
cursor={{ fill: "transparent" }}
/>
) : (
)}
{accChartData ? (
}
cursor={{ fill: "transparent" }}
/>
) : (
)}
>
);
}
================================================
FILE: components/Stats/StatsCard.tsx
================================================
import { motion } from "framer-motion";
const itemVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 160, damping: 22 },
},
};
export default function StatsCard({
title,
value,
}: {
title: string;
value: string;
}) {
return (
{title}
);
}
================================================
FILE: components/Stats/Track.tsx
================================================
import Image from "next/image";
import Link from "next/link";
type TrackProps = {
url: string;
title: string;
artist: string;
coverImage: string;
id: number;
};
export default function Track({
url,
title,
artist,
coverImage,
id,
}: TrackProps) {
return (
{id + 1}
);
}
================================================
FILE: components/Support.tsx
================================================
import UPI from "@components/SVG/UPI";
import {
FadeContainer,
fromTopVariant,
popUp,
} from "@content/FramerMotionVariants";
import support from "@content/support";
import Link from "next/link";
import React, { FormEvent, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useDarkMode } from "@context/darkModeContext";
import QRCode from "react-qr-code";
import { BiRupee } from "react-icons/bi";
import { IoMdArrowRoundBack } from "react-icons/io";
import { FiInfo } from "react-icons/fi";
import { lockScroll, removeScrollLock } from "@utils/functions";
import AnimatedDiv from "@components/FramerMotion/AnimatedDiv";
export default function Support() {
const [showUPIForm, setShowUPIForm] = useState(false);
return (
Support me 💪
{support.map((paymentMethod) => {
return (
);
})}
{
setShowUPIForm(!showUPIForm);
lockScroll();
}}
className="grid p-5 duration-200 bg-white shadow text-darkSecondary dark:bg-darkSecondary dark:text-gray-300 place-items-center group rounded-xl hover:ring-1 ring-gray-500 active:ring"
>
{showUPIForm && (
{
setShowUPIForm(false);
removeScrollLock();
}}
/>
)}
);
}
function UPIPaymentForm({ close }: { close: () => void }) {
const [amount, setAmount] = useState(0);
const [qrValue, setQrValue] = useState("");
const { isDarkMode } = useDarkMode();
const generatePaymentQR = (e: FormEvent) => {
e.preventDefault();
setQrValue(
`upi://pay?pa=${process.env.NEXT_PUBLIC_UPI}&pn=Jatin%20Sharma&am=${amount}&purpose=nothing&cu=INR`
);
};
return (
{!qrValue ? (
<>
{amount >= 100 && (
Pay{" "}
{amount && (
₹ {amount}
)}
)}
>
) : (
Scan the QR code via any UPI app
)}
);
}
================================================
FILE: components/TableOfContents.tsx
================================================
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";
import Link from "next/link";
import { BsListUl } from "react-icons/bs";
import { MdClose } from "react-icons/md";
import { CgSearch } from "react-icons/cg";
import { TableOfContents as TableOfContentType } from "@lib/types";
import { stringToSlug } from "@lib/toc";
export default function TableOfContents({
tableOfContents,
}: {
tableOfContents: TableOfContentType[];
}) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
if (!tableOfContents.length) return null;
const filtered = search.trim()
? tableOfContents.filter((t) =>
t.heading.toLowerCase().includes(search.trim().toLowerCase()),
)
: tableOfContents;
return (
<>
{/* FAB — flat, no shadow */}
setOpen((o) => !o)}
aria-label="Toggle Table of Contents"
className="fixed bottom-6 left-6 z-40 flex items-center gap-2 px-3 h-9 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-mono text-[10px] tracking-[0.35em] uppercase print:hidden"
>
Contents
{/* Backdrop */}
{open && (
setOpen(false)}
className="fixed inset-0 z-40 bg-black/40 print:hidden"
/>
)}
{/* Right drawer */}
{open && (
{/* Top accent bar */}
{/* Drawer header */}
Navigation
Table of Contents
setOpen(false)}
aria-label="Close"
className="w-8 h-8 flex items-center justify-center border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-900 dark:hover:border-white transition-colors"
>
{/* Search */}
{/* TOC items */}
{filtered.length === 0 ? (
No results
) : (
filtered.map((item) => (
{
setOpen(false);
setSearch("");
}}
style={{ paddingLeft: `${(item.level - 1) * 14 + 20}px` }}
className={`flex items-start gap-2.5 py-3 pr-6 text-sm border-l-2 transition-all group ${
item.level === 1
? "border-transparent hover:border-gray-900 dark:hover:border-white text-gray-900 dark:text-white font-medium hover:bg-gray-50 dark:hover:bg-white/5"
: "border-transparent hover:border-gray-400 dark:hover:border-gray-600 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-white/5"
}`}
>
{item.level > 1 && (
)}
{item.heading}
))
)}
{/* Footer */}
{filtered.length} section{filtered.length !== 1 ? "s" : ""}
{search && (
setSearch("")}
className="font-mono text-[10px] tracking-[0.3em] uppercase text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
>
Clear
)}
)}
>
);
}
================================================
FILE: components/TopNavbar.tsx
================================================
/* Importing Modules */
import React, { useEffect, useState, useRef, useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { motion, useAnimation, AnimatePresence } from "framer-motion";
import {
FadeContainer,
hamFastFadeContainer,
mobileNavItemSideways,
popUp,
} from "../content/FramerMotionVariants";
import { useDarkMode } from "../context/darkModeContext";
import { navigationRoutes } from "../utils/utils";
import Logo from "./SVG/Logo";
import { DarkModeSwitch } from "react-toggle-dark-mode";
/* TopNavbar Component */
export default function TopNavbar() {
const navRef = useRef(null);
const control = useAnimation();
const [navOpen, setNavOpen] = useState(false);
const { isDarkMode, changeDarkMode } = useDarkMode();
const addShadowToNavbar = useCallback(() => {
if (window.pageYOffset > 10) {
navRef.current!.classList.add(
...[
"border-b",
"border-gray-200",
"dark:border-neutral-700",
"backdrop-blur-xl",
"bg-white/80",
"dark:bg-darkPrimary/90",
],
);
control.start("visible");
} else {
navRef.current!.classList.remove(
...[
"border-b",
"border-gray-200",
"dark:border-neutral-700",
"backdrop-blur-xl",
"bg-white/80",
"dark:bg-darkPrimary/90",
],
);
control.start("hidden");
}
}, [control]);
useEffect(() => {
window.addEventListener("scroll", addShadowToNavbar);
return () => window.removeEventListener("scroll", addShadowToNavbar);
}, [addShadowToNavbar]);
function lockScroll() {
document.getElementsByTagName("html")[0].classList.toggle("lock-scroll");
}
function handleClick() {
lockScroll();
setNavOpen(!navOpen);
}
return (
<>
{/* Logo / name */}
Jatin Sharma
{/* Desktop nav */}
{navigationRoutes.slice(0, 8).map((link, i) => (
))}
{/* Right — dark mode + hamburger */}
{/* Hamburger — mobile only */}
{/* Mobile menu — rendered outside navRef to avoid backdrop-filter
creating a new fixed containing block that clips the overlay */}
{navOpen && (
)}
>
);
}
// NavItem
function NavItem({ href, text }: { href: string; text: string }) {
const router = useRouter();
const isActive = router.asPath === (href === "/home" ? "/" : href);
return (
{text}
{/* active underline */}
{isActive && (
)}
);
}
// Hamburger — sm:hidden
function HamBurger({
open,
handleClick,
}: {
open: boolean;
handleClick: () => void;
}) {
return (
);
}
// Mobile menu — full-screen
const MobileMenu = ({
links,
handleClick,
}: {
links: string[];
handleClick: () => void;
}) => {
return (
{/* Watermark */}
MENU
{/* mono label */}
Navigation
{links.slice(0, 8).map((link, index) => {
const href =
link.toLowerCase() === "home" ? "/" : `/${link.toLowerCase()}`;
const label = link === "rss" ? "RSS" : link;
return (
{label}
{String(index + 1).padStart(2, "0")}
);
})}
);
};
================================================
FILE: content/FramerMotionVariants.ts
================================================
import { Variants } from "framer-motion";
export const popUp: Variants = {
hidden: { scale: 0, opacity: 0 },
visible: {
opacity: 1,
scale: 1,
transition: {
type: "spring",
},
},
};
export const popUpFromBottomForText: Variants = {
hidden: { opacity: 0, y: 40 },
visible: {
opacity: 1,
y: 0,
transition: {
type: "spring",
stiffness: 60,
},
},
};
export const headingFromLeft: Variants = {
hidden: { x: -200, opacity: 0 },
visible: {
x: 0,
opacity: 1,
transition: {
duration: 0.1,
type: "spring",
stiffness: 70,
},
},
};
export const fromLeftVariant: Variants = {
hidden: { x: -100, opacity: 0 },
visible: {
x: 0,
opacity: 1,
transition: {
duration: 0.1,
type: "spring",
stiffness: 100,
},
},
};
export const fromLeftChildren: Variants = {
hidden: { x: -100, opacity: 0 },
visible: {
x: 0,
opacity: 1,
transition: {
duration: 0.1,
},
},
};
export const fromTopVariant: Variants = {
hidden: { y: -100, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: {
duration: 0.1,
type: "spring",
stiffness: 100,
},
},
};
export const opacityVariant: Variants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { delay: 0.2 } },
};
export const hamFastFadeContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
delayChildren: 0,
staggerChildren: 0.1,
},
},
};
export const mobileNavItemSideways: Variants = {
hidden: { x: -40, opacity: 0 },
visible: {
x: 0,
opacity: 1,
},
};
export const FadeContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { delayChildren: 0, staggerChildren: 0.1 },
},
};
export const svgVariant: Variants = {
hidden: {
pathLength: 0,
},
visible: {
pathLength: 1,
transition: {
duration: 4,
ease: "easeInOut",
},
},
};
export const searchBarSlideAnimation: Variants = {
hidden: {
width: 0,
opacity: 0,
},
visible: {
width: "100%",
opacity: 1,
transition: {
type: "linear",
duration: 1,
},
},
};
export const BlogCardAnimation: Variants = {
hidden: {
y: 50,
opacity: 0,
},
visible: {
y: 0,
opacity: 1,
transition: {
delay: 0.2,
},
},
};
================================================
FILE: content/meta.ts
================================================
import { PageMeta } from "@lib/types";
const pageMeta: PageMeta = {
home: {
title: "",
description:
"Hey, I am Jatin Sharma. A Front-end Developer/React Developer from India who loves to design and code. I use React.js or Next.js to build the web application interfaces and the functionalities. At the moment, I am pursuing my Bachelor's degree in Computer Science.",
image: "https://imgur.com/KeJgIVl.png",
keywords: "portfolio jatin, portfolio j471n, jatin blogs",
},
stats: {
title: "Statistics",
description:
"These are my personal statistics about me. It includes My Blogs and github Stats and top music stats.",
image: "https://imgur.com/9scFfW5.png",
keywords: "stats, Statistics",
},
utilities: {
title: "Utilities",
description:
"In case you are wondering What tech I use, Here's the list of what tech I'm currently using for coding on the daily basis. This list is always changing.",
image: "https://imgur.com/MpfymCd.png",
keywords: "Utilities, what i use?, utils, setup, uses,",
},
blogs: {
title: "Blogs",
description:
"I've been writing online since 2021, mostly about web development and tech careers. In total, I've written more than 50 articles till now.",
image: "https://imgur.com/nbNLLZk.png",
keywords: "j471n blog, blog, webdev, react",
},
bookmark: {
title: "Bookmarked Blogs",
description: "Bookmarked Blogs of Jatin Sharma's blogs by you",
image: "https://imgur.com/5XkrVPq.png",
keywords: "bookmark, blogs, ",
},
certificates: {
title: "Certificates",
description:
"I've participated in many contests, courses and test and get certified in many skills. You can find the certificates below.",
image: "https://imgur.com/J0q1OdT.png",
keywords: "Certificates, verified",
},
projects: {
title: "Projects",
description:
"I've been making various types of projects some of them were basics and some of them were complicated.",
image: "https://imgur.com/XJqiuNK.png",
keywords: "projects, work, side project,",
},
about: {
title: "About",
description:
"Hey, I am Jatin Sharma. A Front-end Developer/React Developer from India who loves to design and code. I use React.js or Next.js to build the web application interfaces and the functionalities. At the moment, I am pursuing my Bachelor's degree in Computer Science.",
image: "https://imgur.com/b0HRaPv.png",
keywords: "about, about me, ",
},
privacy: {
title: "Privacy Policy",
description:
"Privacy is an important factor for everyone. Following is the privacy policies of j471n.in",
image: "https://imgur.com/ghlRutT.png",
keywords: "Privacy, Privacy Policies, ",
},
snippets: {
title: "Code Snippets",
description:
"These are a collection of code snippets I've used in the past and saved. These could be useful to you as well.",
image: "https://imgur.com/KeJgIVl.png",
keywords: "Code, Code Snippets, Snippets",
},
books: {
title: "Books",
description:
"Books I've read, am currently reading, or want to read. My personal bookshelf tracked on Hardcover.",
image: "https://imgur.com/KeJgIVl.png",
keywords: "books, reading, bookshelf, hardcover",
},
epigraphs: {
title: "Epigraphs",
description:
"A personal collection of quotes, passages, and stanzas from books, movies, people, and more that left a mark on me.",
image: "https://imgur.com/KeJgIVl.png",
keywords: "quotes, epigraphs, passages, stanzas, books, movies",
},
};
export default pageMeta;
================================================
FILE: content/siteConfig.ts
================================================
import { homeProfileImage } from "@utils/utils";
export type SocialIconKey =
| "twitter"
| "linkedin"
| "github"
| "instagram"
| "devto"
| "codepen"
| "facebook"
| "mail";
export type StatIconKey = "code" | "users" | "coffee" | "award";
const siteConfig = {
person: {
name: "Jatin Sharma",
profileImage: homeProfileImage,
email: "work.j471n@gmail.com",
location: "Based in India",
availability: "Open to new projects",
},
socialLinks: [
{
title: "Twitter",
icon: "twitter" as SocialIconKey,
url: "https://twitter.com/intent/follow?screen_name=j471n_",
featured: true,
},
{
title: "LinkedIn",
icon: "linkedin" as SocialIconKey,
url: "https://www.linkedin.com/in/j471n/",
featured: true,
},
{
title: "Github",
icon: "github" as SocialIconKey,
url: "https://github.com/j471n",
featured: true,
},
{
title: "Instagram",
icon: "instagram" as SocialIconKey,
url: "https://www.instagram.com/j471n_",
featured: false,
},
{
title: "Dev.to",
icon: "devto" as SocialIconKey,
url: "https://dev.to/j471n",
featured: false,
},
{
title: "Codepen",
icon: "codepen" as SocialIconKey,
url: "https://codepen.io/j471n",
featured: false,
},
{
title: "Facebook",
icon: "facebook" as SocialIconKey,
url: "https://www.facebook.com/ja7in/",
featured: false,
},
{
title: "Mail",
icon: "mail" as SocialIconKey,
url: "mailto:work.j471n@gmail.com",
featured: false,
},
],
home: {
hero: {
availabilityBadge: "Building Something Amazing",
greeting: "Hi, I'm",
nameHighlight: "Jatin Sharma",
rolePrefix: "Tech Lead at",
companyName: "KonnectNXT",
companyUrl: "https://www.linkedin.com/company/konnectnxt/",
roleSuffix: ". Turning dreams into reality with modern technologies.",
primaryCta: {
label: "Download Resume",
url: "https://bit.ly/j471nCV",
},
secondaryCta: {
label: "Get in Touch",
url: "#contact",
},
socialLabel: "Connect:",
experienceBadge: {
value: "3+",
title: "Years",
description: "Industry Experience",
},
},
stats: [
{
icon: "code" as StatIconKey,
value: "50+",
label: "Projects Completed",
description: "Successful deliveries",
},
{
icon: "users" as StatIconKey,
value: "20+",
label: "Happy Clients",
description: "Worldwide satisfaction",
},
{
icon: "coffee" as StatIconKey,
value: "1000+",
label: "Cups of Coffee",
description: "Fueling innovation",
},
{
icon: "award" as StatIconKey,
value: "5+",
label: "Years Experience",
description: "In web development",
},
],
skillsSection: {
eyebrow: "Technical Expertise",
title: "Skills & Technologies",
description:
"A comprehensive toolkit of modern technologies I work with to build exceptional digital experiences",
},
blogsSection: {
eyebrow: "Latest Writing",
title: "Featured Articles",
description:
"Thoughts on web development, technology trends, and software engineering best practices",
ctaLabel: "View All",
},
epigraphsSection: {
eyebrow: "Collected Thoughts",
title: "Epigraphs",
description:
"Quotes, passages, and stanzas from books, movies, and life that left a mark",
ctaLabel: "View All",
},
},
contact: {
eyebrow: "Let's Work Together",
title: "Get in Touch",
description:
"Have a project in mind or just want to chat? I'm always open to discussing new opportunities, creative ideas, or partnerships.",
email: {
title: "Email",
responseTime: "Response within 24 hours",
},
location: {
title: "Location",
value: "Based in India",
},
availability: {
title: "Availability",
value: "Open to new projects",
},
socialTitle: "Connect",
servicesTitle: "Services",
services: [
"Web Development",
"Technical Consulting",
"Code Review",
"Mentorship",
],
privacyNote:
"Your information is safe and will never be shared with third parties.",
},
footer: {
description:
"Full-stack developer passionate about creating beautiful and functional web experiences.",
},
} as const;
export const featuredSocialLinks = siteConfig.socialLinks.filter(
(socialLink) => socialLink.featured,
);
export default siteConfig;
================================================
FILE: content/skillsData.ts
================================================
import { SkillType } from "@lib/types";
import {
SiHtml5,
SiCss3,
SiJavascript,
SiNextdotjs,
SiTailwindcss,
SiPython,
SiGit,
SiMysql,
SiFirebase,
SiTypescript,
SiReact,
SiC,
SiCplusplus,
SiFigma,
SiSupabase,
SiNodedotjs,
SiDjango,
SiRedux,
SiDocker,
SiVercel,
} from "react-icons/si";
const skills: SkillType[] = [
{
name: "HTML",
Icon: SiHtml5,
category: "Frontend",
},
{
name: "CSS",
Icon: SiCss3,
category: "Frontend",
},
{
name: "Javascript",
Icon: SiJavascript,
category: "Frontend",
},
{
name: "Typescript",
Icon: SiTypescript,
category: "Frontend",
},
{
name: "React.js",
Icon: SiReact,
category: "Frontend",
},
{
name: "Next.js",
Icon: SiNextdotjs,
category: "Frontend",
},
{
name: "Tailwind CSS",
Icon: SiTailwindcss,
category: "Frontend",
},
{
name: "Node.js",
Icon: SiNodedotjs,
category: "Backend",
},
{
name: "Python",
Icon: SiPython,
category: "Backend",
},
{
name: "Django",
Icon: SiDjango,
category: "Backend",
},
{
name: "C Programming",
Icon: SiC,
category: "Programming",
},
{
name: "C++",
Icon: SiCplusplus,
category: "Programming",
},
{
name: "MySQL",
Icon: SiMysql,
category: "Database",
},
{
name: "Firebase",
Icon: SiFirebase,
category: "Database",
},
{
name: "Supabase",
Icon: SiSupabase,
category: "Database",
},
{
name: "Git",
Icon: SiGit,
category: "Tools",
},
{
name: "Figma",
Icon: SiFigma,
category: "Tools",
},
{
name: "Redux",
Icon: SiRedux,
category: "Frontend",
},
{
name: "Docker",
Icon: SiDocker,
category: "Tools",
},
{
name: "Vercel",
Icon: SiVercel,
category: "Tools",
},
];
export default skills;
================================================
FILE: content/socialMedia.ts
================================================
import { SocialPlatform } from "@lib/types";
import { AiOutlineInstagram, AiOutlineTwitter } from "react-icons/ai";
import { BsFacebook, BsGithub, BsLinkedin } from "react-icons/bs";
import { FaDev } from "react-icons/fa";
import { HiMail } from "react-icons/hi";
import { SiCodepen } from "react-icons/si";
import siteConfig, { SocialIconKey } from "./siteConfig";
const iconMap: Record = {
twitter: AiOutlineTwitter,
linkedin: BsLinkedin,
github: BsGithub,
instagram: AiOutlineInstagram,
devto: FaDev,
codepen: SiCodepen,
facebook: BsFacebook,
mail: HiMail,
};
const socialMedia: SocialPlatform[] = siteConfig.socialLinks.map(
(socialLink) => ({
title: socialLink.title,
Icon: iconMap[socialLink.icon],
url: socialLink.url,
}),
);
export default socialMedia;
================================================
FILE: content/support.ts
================================================
import { SiBuymeacoffee } from "react-icons/si";
import { BsPaypal } from "react-icons/bs";
import { SupportMe } from "@lib/types";
const supportOptions: SupportMe[] = [
{
name: "Buy Me a Coffee",
url: "https://buymeacoffee.com/j471n",
Icon: SiBuymeacoffee,
},
{
name: "PayPal",
url: "https://paypal.me/j47in",
Icon: BsPaypal,
},
];
export default supportOptions;
================================================
FILE: content/user.ts
================================================
import siteConfig from "./siteConfig";
type AuthorInfo = {
name: string;
image: string;
org: string | null;
org_logo: string | null;
org_url: string | null;
};
export function getAuthorData(org: string | null = null): AuthorInfo {
switch (org) {
case "documatic":
return {
name: siteConfig.person.name,
image: siteConfig.person.profileImage,
org: "Documatic",
org_logo: "https://i.imgur.com/ZqBFtg1.png",
org_url: "https://www.documatic.com/",
};
default:
return {
name: siteConfig.person.name,
image: siteConfig.person.profileImage,
org: null,
org_logo: null,
org_url: null,
};
}
}
================================================
FILE: content/utilitiesData.ts
================================================
import {
SiMacos,
SiHomebrew,
SiIterm2,
SiWarp,
SiArc,
SiGooglechrome,
SiVisualstudiocode,
SiXcode,
SiDocker,
SiInsomnia,
SiPostman,
SiPrettier,
SiPnpm,
SiBun,
SiNodedotjs,
SiFigma,
SiCanva,
SiObsstudio,
SiNotion,
SiObsidian,
SiLinear,
SiGrammarly,
SiSpotify,
SiBitwarden,
SiVercel,
SiNetlify,
SiSupabase,
SiSlack,
SiDiscord,
SiZoom,
SiTelegram,
SiGithub,
SiTailwindcss,
SiTypescript,
} from "react-icons/si";
import {
BsFillTerminalFill,
BsApple,
BsGithub,
BsDatabase,
BsCommand,
BsWindowStack,
} from "react-icons/bs";
import { FaGitAlt } from "react-icons/fa";
import SVG from "@components/SVG";
import { Utilities } from "@lib/types";
const utilities: Utilities = {
title: "Utilities",
description:
"The tools, apps, and services I use daily for development, design, and productivity on macOS. This list is always evolving.",
lastUpdate: "April 11, 2026",
data: [
{
title: "System & OS",
data: [
{
name: "macOS",
description: "Primary operating system",
Icon: SiMacos,
link: "https://www.apple.com/macos/",
},
{
name: "Homebrew",
description: "Package manager for macOS",
Icon: SiHomebrew,
link: "https://brew.sh/",
},
{
name: "Raycast",
description: "Spotlight replacement & launcher",
Icon: BsCommand,
link: "https://www.raycast.com/",
},
{
name: "Rectangle",
description: "Window management with keyboard shortcuts",
Icon: BsWindowStack,
link: "https://rectangleapp.com/",
},
{
name: "Bitwarden",
description: "Open-source password manager",
Icon: SiBitwarden,
link: "https://bitwarden.com/",
},
{
name: "Raindrop.io",
description: "Bookmark & read-later manager",
Icon: SVG.RainDrop,
link: "https://raindrop.io/",
},
],
},
{
title: "Terminal & CLI",
data: [
{
name: "iTerm2",
description: "Feature-rich terminal emulator",
Icon: SiIterm2,
link: "https://iterm2.com/",
},
{
name: "Warp",
description: "AI-powered modern terminal",
Icon: SiWarp,
link: "https://www.warp.dev/",
},
{
name: "Oh My Zsh",
description: "Zsh configuration framework",
Icon: BsFillTerminalFill,
link: "https://ohmyz.sh/",
},
{
name: "Homebrew",
description: "Package manager for macOS & Linux",
Icon: SiHomebrew,
link: "https://brew.sh/",
},
{
name: "Git",
description: "Version control system",
Icon: FaGitAlt,
link: "https://git-scm.com/",
},
{
name: "GitHub CLI",
description: "GitHub from the command line",
Icon: BsGithub,
link: "https://cli.github.com/",
},
],
},
{
title: "Development",
data: [
{
name: "VSCode",
description: "Primary code editor",
Icon: SiVisualstudiocode,
link: "https://code.visualstudio.com/",
},
{
name: "Xcode",
description: "Apple's IDE for native development",
Icon: SiXcode,
link: "https://developer.apple.com/xcode/",
},
{
name: "Docker",
description: "Containerisation platform",
Icon: SiDocker,
link: "https://www.docker.com/products/docker-desktop/",
},
{
name: "TablePlus",
description: "GUI for relational databases",
Icon: BsDatabase,
link: "https://tableplus.com/",
},
{
name: "Insomnia",
description: "REST & GraphQL API client",
Icon: SiInsomnia,
link: "https://insomnia.rest/",
},
{
name: "Postman",
description: "API platform for building & testing",
Icon: SiPostman,
link: "https://www.postman.com/",
},
{
name: "GitHub Desktop",
description: "Visual Git client",
Icon: SiGithub,
link: "https://desktop.github.com/",
},
{
name: "Prettier",
description: "Opinionated code formatter",
Icon: SiPrettier,
link: "https://prettier.io/",
},
{
name: "pnpm",
description: "Fast, disk-efficient package manager",
Icon: SiPnpm,
link: "https://pnpm.io/",
},
{
name: "Bun",
description: "All-in-one JS runtime & package manager",
Icon: SiBun,
link: "https://bun.sh/",
},
{
name: "Node.js",
description: "JavaScript runtime environment",
Icon: SiNodedotjs,
link: "https://nodejs.org/",
},
{
name: "TypeScript",
description: "Typed superset of JavaScript",
Icon: SiTypescript,
link: "https://www.typescriptlang.org/",
},
{
name: "Tailwind CSS",
description: "Utility-first CSS framework",
Icon: SiTailwindcss,
link: "https://tailwindcss.com/",
},
{
name: "Supabase",
description: "Open-source Firebase alternative",
Icon: SiSupabase,
link: "https://supabase.com/",
},
{
name: "Vercel",
description: "Frontend cloud deployment platform",
Icon: SiVercel,
link: "https://vercel.com/",
},
{
name: "Netlify",
description: "Web hosting & serverless platform",
Icon: SiNetlify,
link: "https://www.netlify.com/",
},
],
},
{
title: "Design & Creativity",
data: [
{
name: "Figma",
description: "Collaborative UI design tool",
Icon: SiFigma,
link: "https://www.figma.com/",
},
{
name: "Canva",
description: "Quick graphics & social media design",
Icon: SiCanva,
link: "https://www.canva.com/",
},
{
name: "OBS Studio",
description: "Screen recording & live streaming",
Icon: SiObsstudio,
link: "https://obsproject.com/",
},
{
name: "CleanShot X",
description: "Screenshot & screen recording for Mac",
Icon: BsApple,
link: "https://cleanshot.com/",
},
],
},
{
title: "Productivity",
data: [
{
name: "Notion",
description: "All-in-one workspace for notes & docs",
Icon: SiNotion,
link: "https://www.notion.so/",
},
{
name: "Obsidian",
description: "Local-first Markdown knowledge base",
Icon: SiObsidian,
link: "https://obsidian.md/",
},
{
name: "Linear",
description: "Issue tracking & project management",
Icon: SiLinear,
link: "https://linear.app/",
},
{
name: "Grammarly",
description: "AI writing assistant & grammar checker",
Icon: SiGrammarly,
link: "https://www.grammarly.com/",
},
{
name: "Spotify",
description: "Music & podcast streaming",
Icon: SiSpotify,
link: "https://www.spotify.com/",
},
{
name: "Keka",
description: "macOS file archiver",
Icon: BsApple,
link: "https://www.keka.io/",
},
{
name: "f.lux",
description: "Blue light filter & display warmth",
Icon: SVG.Flux,
link: "https://justgetflux.com/",
},
],
},
{
title: "Browsers",
data: [
{
name: "Arc",
description: "Primary browser with workspace tabs",
Icon: SiArc,
link: "https://arc.net/",
},
{
name: "Chrome",
description: "Secondary browser for testing",
Icon: SiGooglechrome,
link: "https://www.google.com/chrome/",
},
],
},
{
title: "Communication",
data: [
{
name: "Slack",
description: "Team messaging & collaboration",
Icon: SiSlack,
link: "https://slack.com/",
},
{
name: "Discord",
description: "Developer communities & friends",
Icon: SiDiscord,
link: "https://discord.com/",
},
{
name: "Zoom",
description: "Video meetings",
Icon: SiZoom,
link: "https://zoom.us/",
},
{
name: "Telegram",
description: "Fast & private messaging",
Icon: SiTelegram,
link: "https://telegram.org/",
},
],
},
],
};
export default utilities;
================================================
FILE: context/darkModeContext.tsx
================================================
import React, { useState, useContext, useEffect, createContext } from "react";
export interface DarkModeContextType {
isDarkMode: boolean;
changeDarkMode(value: boolean): void;
}
const DarkModeContext = createContext(null);
export function DarkModeProvider({ children }: { children: React.ReactNode }) {
const [isDarkMode, setDarkMode] = useState(false);
function updateTheme() {
const currentTheme = localStorage.getItem("isDarkMode") || "false";
if (currentTheme === "true") {
document.body.classList.add("dark");
setDarkMode(true);
} else {
document.body.classList.remove("dark");
setDarkMode(false);
}
}
useEffect(() => {
updateTheme();
}, []);
function changeDarkMode(value: boolean) {
localStorage.setItem("isDarkMode", value.toString());
updateTheme();
}
const contextValue: DarkModeContextType = {
isDarkMode,
changeDarkMode,
};
return (
{children}
);
}
export const useDarkMode = (): DarkModeContextType => {
const context = useContext(DarkModeContext);
return context!;
};
================================================
FILE: docs/sanity-deploy.md
================================================
# Deploying Sanity Studio
Studio is live at **https://j471n-blog.sanity.studio/**
---
## First-time setup
Install the Sanity CLI globally (only needed once):
```bash
npm i @sanity/cli -g
```
Log in with your Google account:
```bash
sanity login
```
---
## Deploy
Run this from the `sanity/` folder whenever you make schema changes:
```bash
cd sanity
sanity deploy
```
---
## Local dev
```bash
cd sanity
sanity dev
```
Opens the studio at `http://localhost:3333`.
---
## Notes
- **Schema changes** (adding/editing fields) → `sanity deploy`
- **Content changes** (adding/editing entries) → just hit **Publish** in the studio, no deploy needed
- **Site updates automatically** — Next.js picks up new content within 5 minutes (ISR)
================================================
FILE: hooks/useBookmarkBlogs.ts
================================================
import { useEffect, useState } from "react";
import { BlogPost } from "@lib/interface/sanity";
import { FrontMatter } from "@lib/types";
const useBookmarkBlogs = (key: string, defaultValue: []) => {
const [bookmarkedBlogs, setBookmarkedBlogs] = useState((): BlogPost[] => {
let currentValue: BlogPost[] = [];
try {
currentValue = JSON.parse(localStorage.getItem(key)!);
} catch (error) {
currentValue = defaultValue;
}
return currentValue;
});
function getValue() {
var data = JSON.parse(localStorage.getItem(key)!);
if (data === null) {
localStorage.setItem(key, JSON.stringify([]));
return JSON.parse(localStorage.getItem(key)!);
}
return data;
}
function addToBookmark(blogToBookmark: FrontMatter) {
var data = getValue();
if (!data.includes(blogToBookmark)) {
data.unshift(blogToBookmark); // add blog to the starting of the array
setBookmarkedBlogs(data);
}
}
function removeFromBookmark(blogToRemove: string) {
var data = getValue();
setBookmarkedBlogs(
data.filter((blog: FrontMatter) => blog.slug != blogToRemove)
);
}
function isAlreadyBookmarked(searchBySlug: string) {
return bookmarkedBlogs
?.map(
(bookmarkedBlog: BlogPost) =>
bookmarkedBlog.slug.current === searchBySlug
)
.includes(true);
}
useEffect(() => {
localStorage.setItem(key, JSON.stringify(bookmarkedBlogs));
}, [bookmarkedBlogs, key]);
return {
bookmarkedBlogs,
addToBookmark,
removeFromBookmark,
isAlreadyBookmarked,
};
};
export default useBookmarkBlogs;
================================================
FILE: hooks/useDebounce.ts
================================================
import { useState, useEffect } from "react";
export function useDebounce(value: T, delay = 300): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
================================================
FILE: hooks/useFetchWithSWR.ts
================================================
import useSWR from "swr";
// fetcher to fetch the data
// const fetcher = (...args) => fetch(...args).then((res) => res.json());
import fetcher from "@lib/fetcher";
export default function useFetchWithSWR(url: string) {
const { data, error } = useSWR(url, fetcher);
return {
data,
isLoading: !error && !data,
isError: error,
} as {
data: any;
isLoading: boolean;
isError: any;
};
}
================================================
FILE: hooks/useScrollPercentage.ts
================================================
import { useEffect, useState, useCallback } from "react";
export default function useScrollPercentage() {
// fifteen
const [scrollPercentage, setScrollPercentage] = useState(0);
function getScrollPercent(): number {
var h = document.documentElement;
var b = document.body;
return (
((h.scrollTop || b.scrollTop) /
((h.scrollHeight || b.scrollHeight) - h.clientHeight)) *
100
);
}
const scrollEvent = useCallback(() => {
setScrollPercentage(getScrollPercent());
}, []);
useEffect(() => {
window.addEventListener("scroll", scrollEvent);
return () => {
window.removeEventListener("scroll", scrollEvent);
};
}, [scrollEvent]);
return scrollPercentage;
}
================================================
FILE: hooks/useShare.ts
================================================
import { useEffect, useState } from "react";
function useShare() {
// state for share supports
const [isShareSupported, setIsShareSupported] = useState(false);
// checking if that exist or not
useEffect(() => {
setIsShareSupported(() => ("share" in navigator ? true : false));
}, []);
return { isShareSupported };
}
export default useShare;
================================================
FILE: hooks/useWindowLocation.ts
================================================
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
type URL = string;
export default function useWindowLocation() {
const [currentURL, setCurrentURL] = useState("");
const router = useRouter();
useEffect(() => {
setCurrentURL(window.location.href);
}, [router.asPath]);
return { currentURL };
}
================================================
FILE: hooks/useWindowSize.ts
================================================
import { useState, useEffect } from "react";
export default function useWindowSize() {
// Initialize state with undefined width/height so server and client renders match
const [windowSize, setWindowSize] = useState({
width: 0,
height: 0,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
================================================
FILE: layout/BlogLayout.tsx
================================================
import { BlogPost } from "@lib/interface/sanity";
import { FiPrinter } from "react-icons/fi";
import Image from "next/image";
import Link from "next/link";
import ScrollProgressBar from "@components/ScrollProgressBar";
import TableOfContents from "@components/TableOfContents";
import { getFormattedDate } from "@utils/date";
import { motion } from "framer-motion";
export default function BlogLayout({
post,
children,
}: {
post: BlogPost;
children: JSX.Element | string;
}) {
return (
{/* Page wrapper */}
{/* Article header */}
{/* MDX body */}
{children}
);
}
================================================
FILE: layout/Layout.tsx
================================================
import React, { useState } from "react";
import Footer from "../components/Footer";
import QRCodeContainer from "@components/QRCodeContainer";
import ScrollToTopButton from "../components/ScrollToTopButton";
import SnowfallCanvas from "@components/SnowfallCanvas";
import TopNavbar from "../components/TopNavbar";
import { useDarkMode } from "@context/darkModeContext";
export default function Layout({ children }: { children: React.ReactNode }) {
const { isDarkMode } = useDarkMode();
const [showQR, setShowQR] = useState(false);
return (
<>
{(new Date().getMonth() >= 11 || new Date().getMonth() <= 1) &&
isDarkMode && }
{children}
>
);
}
================================================
FILE: layout/SnippetLayout.tsx
================================================
import { ISnippet } from "@lib/interface/sanity";
import Image from "next/image";
import { getFormattedDate } from "@utils/date";
import { motion } from "framer-motion";
import ScrollProgressBar from "@components/ScrollProgressBar";
export default function SnippetLayout({
snippet,
children,
}: {
snippet: ISnippet;
children: JSX.Element;
}) {
return (
{/* Page wrapper */}
{/* Header */}
{/* Language badge */}
{/* Title */}
{snippet.title}
{/* Excerpt */}
{snippet.excerpt}
{/* Meta row */}
{getFormattedDate(new Date(snippet.publishedAt))}
·
{snippet.readingTime.text}
{/* MDX Content */}
{children}
);
}
================================================
FILE: lib/MDXContent.ts
================================================
import { FrontMatter } from "./types";
import matter from "gray-matter";
import path from "path";
import { readFileSync } from "fs";
import readTime from "reading-time";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import { serialize } from "next-mdx-remote/serialize";
import { sync } from "glob";
const rehypePlugins = [
rehypeSlug,
[rehypeAutolinkHeadings, { behaviour: "wrap" }],
[
rehypePrettyCode,
{
theme: {
dark: "andromeeda",
light: "catppuccin-latte",
},
onVisitLine(node: any) {
if (node.children.length === 0) {
node.children = [{ type: "text", value: " " }];
}
},
onVisitHighlightedLine(node: any) {
node.properties.className.push("highlighted");
},
onVisitHighlightedWord(node: any) {
node.properties.className = ["word"];
},
},
],
] as any;
export default class MDXContent {
private POST_PATH: string;
constructor(folderName: string) {
this.POST_PATH = path.join(process.cwd(), folderName);
}
getSlugs() {
const paths = sync(`${this.POST_PATH}/*.mdx`);
return paths.map((path) => {
const parts = path.split("/");
const fileName = parts[parts.length - 1];
const [slug, _ext] = fileName.split(".");
return slug;
});
}
getFrontMatter(slug: string): FrontMatter | null {
const postPath = path.join(this.POST_PATH, `${slug}.mdx`);
const source = readFileSync(postPath);
const { content, data } = matter(source);
const readingTime = readTime(content);
if (!data.published) return null;
return {
slug,
readingTime,
excerpt: data.excerpt ?? "",
title: data.title ?? slug,
date: (data.date ?? new Date()).toString(),
keywords: data.keywords ?? "",
image: data.image ?? "https://imgur.com/aNqa9cE.png",
org: data.org ?? null,
};
}
async getPostFromSlug(slug: string, force: boolean = false) {
const postPath = path.join(this.POST_PATH, `${slug}.mdx`);
const source = readFileSync(postPath);
const { content, data } = matter(source);
if (!data.published && !force) return { post: null };
const frontMatter = this.getFrontMatter(slug);
const mdxSource = await serialize(content, {
mdxOptions: {
rehypePlugins,
},
});
return {
post: {
source: mdxSource,
tableOfContents: this.getTableOfContents(content),
meta: frontMatter,
},
};
}
getAllPosts(length?: number | undefined) {
const allPosts = this.getSlugs()
.map((slug) => {
return this.getFrontMatter(slug);
})
.filter((post) => post !== null) // Filter post if it is not published
.sort((a, b) => {
if (new Date(a!.date) > new Date(b!.date)) return -1;
if (new Date(a!.date) < new Date(b!.date)) return 1;
return 0;
});
return length === undefined ? allPosts : allPosts.slice(0, length);
}
getTableOfContents(markdown: string) {
const regXHeader = /#{2,6}.+/g;
const headingArray = markdown.match(regXHeader)
? markdown.match(regXHeader)
: [];
return headingArray?.map((heading) => {
return {
level: heading.split("#").length - 1 - 2, // we starts from the 2nd heading that's why we subtract 2 and 1 is extra heading text
heading: heading.replace(/#{2,6}/, "").trim(),
};
});
}
}
================================================
FILE: lib/devto.ts
================================================
const PER_PAGE: number = 1000;
const DEV_API = process.env.NEXT_PUBLIC_BLOGS_API;
/**
* Makes a request to the DEV API to retrieve a specific page of followers for the user.
*/
const getPageOfFollowers = async (page: number) => {
// Make a request to the DEV API to retrieve a specific page of followers
const perPageFollowers = await fetch(
`https://dev.to/api/followers/users?per_page=${PER_PAGE}&page=${page}`,
{
headers: {
api_key: DEV_API!,
},
},
)
.then((response) => response.json())
.catch((err) => console.error(err));
return perPageFollowers.length;
};
/**
* Makes multiple requests to the DEV API to retrieve all of the user's followers.
*/
export const allFollowers = async () => {
let numReturned = PER_PAGE;
let page = 1;
var totalFollowers = 0;
// Continue making requests to the DEV API until all followers have been retrieved
while (numReturned === PER_PAGE) {
const followers = await getPageOfFollowers(page);
totalFollowers += followers;
numReturned = followers;
page++;
}
return totalFollowers;
};
/**
* Makes a request to the DEV API to retrieve a specific page of posts for the user.
*/
const getPageOfPosts = async (page: number) => {
// Make a request to the DEV API to retrieve a specific page of posts
const perPagePosts = await fetch(
`https://dev.to/api/articles/me?per_page=${PER_PAGE}&page=${page}`,
{
headers: {
api_key: DEV_API!,
},
},
)
.then((response) => response.json())
.catch((err) => console.error(err));
return perPagePosts;
};
/**
* Makes multiple requests to the DEV API to retrieve all of the user's posts.
*/
export const allPosts = async () => {
let numReturned = PER_PAGE;
let page = 1;
var totalPosts = [];
// Continue making requests to the DEV API until all posts have been retrieved
while (numReturned === PER_PAGE) {
const posts = await getPageOfPosts(page);
totalPosts.push(...posts);
numReturned = posts.length;
page++;
}
return totalPosts;
};
================================================
FILE: lib/fetcher.ts
================================================
/**
* Makes a request to the specified URL and returns the response as JSON.
*/
export default async function fetcher(url: string) {
return fetch(url).then((r) => r.json());
}
================================================
FILE: lib/generateRSS.ts
================================================
import RSS from "rss";
import { getAllPostsMeta } from "./sanityContent";
import { writeFileSync } from "fs";
export default async function getRSS() {
const siteURL = "https://j471n.in";
const allBlogs = await getAllPostsMeta();
// Create a new RSS object
const feed = new RSS({
title: "Jatin Sharma",
description: `I've been writing online since 2021, mostly about web development
and tech careers. In total, I've written ${allBlogs.length} articles
till now.`,
site_url: siteURL,
feed_url: `${siteURL}/feed.xml`,
language: "en",
pubDate: new Date(),
copyright: `All rights reserved ${new Date().getFullYear()}, Jatin Sharma`,
});
// Add all blog posts to the RSS feed
allBlogs?.map((post) => {
feed.item({
title: post.title,
url: `${siteURL}/blogs/${post.slug.current}`,
date: post.publishedAt,
description: post!.excerpt,
});
});
// Write the RSS feed to a file
writeFileSync("./public/feed.xml", feed.xml({ indent: true }));
}
================================================
FILE: lib/github.ts
================================================
import {
IContributionCalendar,
IContributionCountByDay,
IContributionDay,
IGitHubProfileResponse,
IGitHubRepositoriesAPIResponse,
IUserContributionDetails,
IWeek,
} from "./interface";
import { GithubRepo } from "./types";
import moment from "moment";
const headers = new Headers({
Authorization: `token ${process.env.GITHUB_TOKEN}`,
});
// its for /api/stats/github
export async function fetchGithub(): Promise {
const requestOptions: RequestInit = {
method: "GET",
headers,
};
try {
const response = await fetch(
"https://api.github.com/users/j471n",
requestOptions
);
if (!response.ok) {
throw new Error("Error fetching GitHub data: " + response.statusText);
}
const data = await response.json(); // Await the JSON promise
return data as IGitHubProfileResponse;
} catch (error) {
console.error(error);
throw error;
}
}
/* Retrieves the number of stars and forks for the user's repositories on GitHub. */
export async function getGithubStarsAndForks() {
try {
// Fetch user's repositories from the GitHub API
const res = await fetch(
"https://api.github.com/users/j471n/repos?per_page=100",
{ headers }
);
const userRepos: IGitHubRepositoriesAPIResponse[] = await res.json();
// filter those repos that are not forked
const mineRepos: GithubRepo[] = userRepos.filter(
(repo: GithubRepo) => !repo.fork
);
// Calculate the total number of stars for the user's repositories
const githubStars = mineRepos.reduce(
(accumulator: number, repository: GithubRepo) => {
return accumulator + repository["stargazers_count"];
},
0
);
// Calculate the total number of forks for the user's repositories
const forks = mineRepos.reduce(
(accumulator: number, repository: GithubRepo) => {
return accumulator + repository["forks_count"];
},
0
);
return { githubStars, forks };
} catch (error) {
console.error(error);
throw error;
}
}
export async function getGithubContribution() {
const now = moment();
const from = moment(now).subtract(30, "days").utc().toISOString();
// also include the next day in case our server is behind in time with respect to GitHub
const to = moment(now).add(1, "days").utc().toISOString();
const q = {
query: `
query userInfo($LOGIN: String!, $FROM: DateTime!, $TO: DateTime!) {
user(login: $LOGIN) {
name
contributionsCollection(from: $FROM, to: $TO) {
contributionCalendar {
weeks {
contributionDays {
contributionCount
date
}
}
}
}
}
}
`,
variables: {
LOGIN: "j471n",
FROM: from,
TO: to,
},
};
const response = await fetch("https://api.github.com/graphql", {
method: "POST",
body: JSON.stringify(q),
headers,
});
const apiResponse = await response.json();
const userData: IUserContributionDetails = {
contributions: [],
name: apiResponse.data.user.name,
};
const weeks =
apiResponse.data.user.contributionsCollection.contributionCalendar.weeks;
weeks.map((week: IWeek) =>
week.contributionDays.map((contributionDay: IContributionDay) => {
contributionDay.shortDate = moment(contributionDay.date, moment.ISO_8601)
.date()
.toString();
userData.contributions.push(contributionDay);
})
);
const contributionCountByDayOfWeek = calculateMostProductiveDayOfWeek(
apiResponse.data.user.contributionsCollection.contributionCalendar
);
return { ...userData, contributionCountByDayOfWeek };
}
// Function to calculate the productive data by days
function calculateMostProductiveDayOfWeek(
contributionCalendar: IContributionCalendar
): { day: string; count: number }[] {
const daysOfWeek = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
const contributionCountByDayOfWeek: IContributionCountByDay = {
Sunday: 0,
Monday: 0,
Tuesday: 0,
Wednesday: 0,
Thursday: 0,
Friday: 0,
Saturday: 0,
};
for (const week of contributionCalendar.weeks) {
for (const day of week.contributionDays) {
const date = new Date(day.date);
const dayOfWeek = daysOfWeek[date.getUTCDay()];
contributionCountByDayOfWeek[dayOfWeek] += day.contributionCount;
}
}
const sortedData = Object.entries(contributionCountByDayOfWeek)
.sort((a, b) => daysOfWeek.indexOf(a[0]) - daysOfWeek.indexOf(b[0]))
.map(([day, count]) => ({ day, count }));
const sunday = sortedData.shift();
if (sunday) {
sortedData.push(sunday);
}
return sortedData;
}
================================================
FILE: lib/hardcover.ts
================================================
import { HardcoverBook, BookStatusId, HardcoverProfile } from "./types";
const HARDCOVER_GRAPHQL_URL = "https://api.hardcover.app/v1/graphql";
/* ---------- internal GraphQL helper ---------- */
async function hardcoverQuery(
query: string,
variables?: Record,
): Promise {
const res = await fetch(HARDCOVER_GRAPHQL_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
authorization: `Bearer ${process.env.HARDCOVER_API_KEY}`,
},
body: JSON.stringify({ query, variables }),
});
if (!res.ok) {
throw new Error(`Hardcover API responded with status ${res.status}`);
}
const json = await res.json();
if (json.errors?.length) {
throw new Error(json.errors[0].message);
}
return json.data as T;
}
/* ---------- helpers ---------- */
async function getMyUserId(): Promise {
const data = await hardcoverQuery<{
me: Array<{ id: number }> | { id: number };
}>(`query { me { id } }`);
const me = data.me;
return Array.isArray(me) ? me[0].id : (me as { id: number }).id;
}
/* ---------- raw response shapes ---------- */
type RawUserBook = {
status_id: number;
updated_at: string | null;
rating: number | null;
dates_read: Array<{ finished_at: string | null }>;
book: {
id: number;
title: string | null;
subtitle: string | null;
slug: string | null;
pages: number | null;
release_year: number | null;
rating: number | null;
image: { url: string } | null;
contributions: Array<{ author: { name: string } }>;
};
};
/* ---------- public API ---------- */
export async function getMyBooks(): Promise {
const userId = await getMyUserId();
const data = await hardcoverQuery<{ user_books: RawUserBook[] }>(
`query GetMyBooks($userId: Int!) {
user_books(
where: {
user_id: { _eq: $userId }
status_id: { _in: [1, 2, 3] }
}
order_by: { updated_at: desc }
) {
status_id
updated_at
rating
book {
id
title
subtitle
slug
pages
release_year
rating
image { url }
contributions {
author { name }
}
}
}
}`,
{ userId },
);
return data.user_books.map((ub) => ({
statusId: ub.status_id as BookStatusId,
id: ub.book.id,
title: ub.book.title ?? "",
subtitle: ub.book.subtitle ?? null,
slug: ub.book.slug ?? "",
pages: ub.book.pages ?? null,
releaseYear: ub.book.release_year ?? null,
rating: ub.book.rating ?? null,
coverUrl: ub.book.image?.url ?? null,
authors: ub.book.contributions.map((c) => c.author.name),
userRating: ub.rating ?? null,
updatedAt: ub.updated_at ?? null,
finishedAt: ub.dates_read?.[0]?.finished_at ?? null,
}));
}
/* ---------- profile + stats ---------- */
type RawGoal = {
goal: number;
progress: number;
metric: string;
state: string;
start_date: string;
end_date: string;
};
type RawProfile = {
me: Array<{
username: string;
name: string | null;
books_count: number;
goals: RawGoal[];
}>;
};
export async function getMyProfile(): Promise {
const currentYear = new Date().getFullYear();
const data = await hardcoverQuery(
`query GetMyProfile {
me {
username
name
books_count
goals(
where: {
metric: { _eq: "books" }
start_date: { _gte: "${currentYear}-01-01" }
end_date: { _lte: "${currentYear}-12-31" }
}
order_by: { start_date: desc }
limit: 1
) {
goal
progress
metric
state
start_date
end_date
}
}
}`,
);
const me = data.me[0];
const goal = me.goals[0] ?? null;
return {
username: me.username,
name: me.name ?? me.username,
booksCount: me.books_count,
currentYearGoal: goal
? {
target: goal.goal,
progress: Math.round(goal.progress),
metric: goal.metric,
state: goal.state,
year: currentYear,
}
: null,
};
}
================================================
FILE: lib/instaposts.ts
================================================
import { getUserDataValue, setUserDataValue } from "./supabase";
import { InstagramData } from "./interface";
import { generateUrl } from "@utils/functions";
export async function generateNewAccessTokenInstagram(
token: string
): Promise {
const requestURL = generateUrl(
"https://graph.instagram.com/refresh_access_token",
{
grant_type: "ig_refresh_token",
access_token: token,
}
);
try {
const response = await fetch(requestURL);
if (response.ok) {
const result = await response.json();
const newToken = result?.access_token;
if (newToken) {
await setUserDataValue("instagram_user_token", newToken);
return newToken;
}
} else {
return token;
}
} catch (e) {
console.error("Error : ", e);
return token;
}
}
export async function getInstagramPosts(
additionalParams = {}
): Promise {
const { data: token } = await getUserDataValue("instagram_user_token");
let outputData: InstagramData[] = [];
let requestParameters: any = {
fields: "id,permalink,caption,media_url,thumbnail_url,media_type,timestamp",
limit: 9,
access_token: token,
...additionalParams,
};
const requestURL = generateUrl(
"https://graph.instagram.com/me/media",
requestParameters
);
try {
const response = await fetch(requestURL);
if (response.ok) {
const result = (await response.json()) as InstagramData[];
outputData = result;
}
} catch (e) {
console.error("error", e);
const newToken = await generateNewAccessTokenInstagram(token);
requestParameters.access_token = newToken;
const requestURL = generateUrl(
"https://graph.instagram.com/me/media",
requestParameters
);
const response = await fetch(requestURL);
if (response.ok) {
const result = (await response.json()) as InstagramData[];
outputData = result;
}
}
return outputData;
}
================================================
FILE: lib/interface/sanity.ts
================================================
import { ReadTimeResults } from "reading-time";
import { SanityDocument } from "@sanity/types";
import { TableOfContents as TableOfContentsType } from "@lib/types";
export interface ISanityImage {
asset: {
_ref: string;
_type: "reference";
url: "string";
};
}
export interface BlogPost extends SanityDocument {
_id: string;
title: string;
slug: {
current: string;
};
keywords: string;
excerpt: string;
image_url: string;
mainImage: ISanityImage;
publishedAt: string;
author: {
name: string;
image: ISanityImage;
};
organization: {
name: string;
image: ISanityImage;
website: string;
};
content?: any;
readingTime: ReadTimeResults;
tableOfContents: TableOfContentsType[];
}
export interface ISnippet extends SanityDocument {
_id: string;
title: string;
slug: {
current: string;
};
publishedAt: string;
excerpt: string;
language: {
name: string;
image: ISanityImage;
};
content?: any;
readingTime: ReadTimeResults;
tableOfContents: {
level: number;
heading: string;
}[];
}
export interface IStaticPage extends SanityDocument {
_id: string;
title: string;
slug: {
current: string;
};
keywords: string;
excerpt: string;
mainImage: ISanityImage;
publishedAt: string;
content?: any;
}
export type EpigraphSourceType =
| "book"
| "movie"
| "tvShow"
| "person"
| "song"
| "podcast"
| "other";
export interface IEpigraph extends SanityDocument {
_id: string;
quote: string;
sourceType: EpigraphSourceType;
sourceTitle: string;
sourceMeta?: string;
speaker?: string;
year?: number;
tags?: string[];
addedAt: string;
}
/*
{
publishedAt: '2023-07-04T21:15:26.932Z',
_id: 'eb0cb39f-1d0a-4285-a3af-7357968bb1d6',
title: 'DarkMode Context API',
slug: { current: 'darkmode-context-api', _type: 'slug' },
excerpt: 'It enables to add dark mode switch.',
language: { name: 'React', image: [Object] }
}
*/
================================================
FILE: lib/interface.ts
================================================
export interface IExternalUrls {
spotify: string;
}
export interface IFollowers {
href?: null;
total: number;
}
export interface IImagesEntity {
height: number;
url: string;
width: number;
}
export interface IArtistsAPIResponse {
external_urls: IExternalUrls;
followers: IFollowers;
genres?: string[] | null;
href: string;
id: string;
images?: IImagesEntity[] | null;
name: string;
popularity: number;
type: string;
uri: string;
}
export interface ISpotifyArtist {
external_urls: IExternalUrls;
href: string;
id: string;
name: string;
type: string;
uri: string;
}
export interface ISpotifyAlbum {
album_type: string;
artists: ISpotifyAlbum[];
available_markets: string[];
external_urls: IExternalUrls;
href: string;
id: string;
images: IImagesEntity[];
name: string;
release_date: string;
release_date_precision: string;
total_tracks: number;
type: string;
uri: string;
}
export interface ITracksAPIResponse {
album: ISpotifyAlbum;
artists: ISpotifyAlbum[];
available_markets: string[];
disc_number: number;
duration_ms: number;
explicit: boolean;
external_urls: IExternalUrls;
href: string;
id: string;
is_local: boolean;
name: string;
popularity: number;
preview_url?: string;
track_number: number;
type: string;
uri: string;
}
export interface IGitHubProfileResponse {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
name: string;
company: any;
blog: string;
location: string;
email: any;
hireable: boolean;
bio: string;
twitter_username: string;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: string;
updated_at: string;
}
export interface IGitHubOwner {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface IGitHubLicense {
key: string;
name: string;
spdx_id: string;
url: string;
node_id: string;
}
export interface IGitHubRepositoriesAPIResponse {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: IGitHubOwner;
html_url: string;
description?: string;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
created_at: string;
updated_at: string;
pushed_at: string;
git_url: string;
ssh_url: string;
clone_url: string;
svn_url: string;
homepage?: string;
size: number;
stargazers_count: number;
watchers_count: number;
language?: string;
has_issues: boolean;
has_projects: boolean;
has_downloads: boolean;
has_wiki: boolean;
has_pages: boolean;
has_discussions: boolean;
forks_count: number;
mirror_url: any;
archived: boolean;
disabled: boolean;
open_issues_count: number;
license?: IGitHubLicense;
allow_forking: boolean;
is_template: boolean;
web_commit_signoff_required: boolean;
topics: string[];
visibility: string;
forks: number;
open_issues: number;
watchers: number;
default_branch: string;
}
export interface IContributionDay {
contributionCount: number;
date: string;
shortDate: string;
}
export interface IWeek {
contributionDays: IContributionDay[];
}
export interface IUserContributionDetails {
contributions: IContributionDay[];
name: string;
}
export interface IContributionCalendar {
weeks: IWeek[];
}
export interface IContributionCountByDay {
[day: string]: number;
}
export interface IEmailValidation {
valid: boolean;
block: boolean;
disposable: boolean;
domain: string;
text: string;
reason: string;
risk: number;
mx_host: string;
possible_typo: any[];
mx_ip: string;
mx_info: string;
last_changed_at: string;
}
/* Linkedin Interface */
export interface ILinkedinResponse {
public_identifier: string;
profile_pic_url: string;
background_cover_image_url: string;
first_name: string;
last_name: string;
full_name: string;
follower_count: number;
occupation: string;
headline: string;
summary: string;
country: string;
country_full_name: string;
city: string;
state: string;
experiences: ILinkedInExperience[];
education: ILinkedInEducation[];
languages: string[];
accomplishment_organisations: any[];
accomplishment_publications: any[];
accomplishment_honors_awards: any[];
accomplishment_patents: any[];
accomplishment_courses: any[];
accomplishment_projects: any[];
accomplishment_test_scores: any[];
volunteer_work: ILinkedInVolunteerWork[];
certifications: ILinkedInCertification[];
connections: any;
recommendations: any[];
activities: any[];
articles: any[];
groups: any[];
phone_numbers: any[];
social_networking_services: any[];
skills: string[];
gender: any;
birth_date: any;
industry: any;
interests: any[];
personal_emails: any[];
personal_numbers: any[];
}
export interface ILinkedInExperience {
company: string;
company_linkedin_profile_url: string;
logo_url: string;
job_titles: {
starts_at: ILinkedInStartsAt;
ends_at?: ILinkedInEndsAt;
title: string;
description?: string;
location?: string;
}[];
}
export interface ILinkedInStartsAt {
day: number;
month: number;
year: number;
}
export interface ILinkedInEndsAt {
day: number;
month: number;
year: number;
}
export interface ILinkedInEducation {
starts_at: ILinkedInStartsAt;
ends_at: ILinkedInEndsAt;
field_of_study: string;
degree_name: string;
school: string;
school_linkedin_profile_url: any;
description: any;
logo_url: string;
grade: any;
activities_and_societies: any;
}
export interface ILinkedInVolunteerWork {
starts_at: ILinkedInStartsAt;
ends_at: ILinkedInEndsAt;
title: string;
cause: string;
company: string;
company_linkedin_profile_url: string;
description?: string;
logo_url: string;
}
export interface ILinkedInCertification {
starts_at: ILinkedInStartsAt;
ends_at: ILinkedInEndsAt;
name: string;
license_number?: string;
display_source: string;
authority: string;
url: string;
}
export interface ITMDBData {
adult: boolean;
backdrop_path: string;
genre_ids: number[];
id: number;
original_language: string;
original_title?: string;
original_name?: string;
overview: string;
popularity: number;
poster_path: string;
release_date?: string;
first_air_date?: string;
title: string;
name?: string;
video: boolean;
vote_average: number;
vote_count: number;
rating?: number;
}
/* ----------------------------------INSTAGRAM------------------------------ */
export enum MediaType {
CarouselAlbum = "CAROUSEL_ALBUM",
Image = "IMAGE",
Video = "VIDEO",
}
export interface DetailedInstagramPost {
id: string;
permalink: string;
caption: string;
media_url: string;
media_type: MediaType;
timestamp: string;
thumbnail_url?: string;
}
export interface Cursors {
before: string;
after: string;
}
export interface Paging {
cursors: Cursors;
next: string;
previous: string;
}
export interface InstagramData {
data: DetailedInstagramPost[];
paging: Paging;
}
================================================
FILE: lib/sanityClient.ts
================================================
import { createClient } from "@sanity/client";
const client = createClient({
projectId: process.env.SANITY_PROJECT_ID, // you can find this in sanity.json
dataset: "production", // or the name you chose in step 1
useCdn: true, // `false` if you want to ensure fresh data
apiVersion: "2021-08-31",
});
export default client;
================================================
FILE: lib/sanityContent.ts
================================================
import { BlogPost, IEpigraph, ISnippet } from "./interface/sanity";
import groq from "groq";
import matter from "gray-matter";
import readTime from "reading-time";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import sanityClient from "@lib/sanityClient";
import { serialize } from "next-mdx-remote/serialize";
const rehypePlugins = [
rehypeSlug,
[rehypeAutolinkHeadings, { behaviour: "wrap" }],
[
rehypePrettyCode,
{
theme: {
dark: "github-dark",
light: "github-light",
},
onVisitLine(node: any) {
if (node.children.length === 0) {
node.children = [{ type: "text", value: " " }];
}
},
onVisitHighlightedLine(node: any) {
node.properties.className.push("highlighted");
},
onVisitHighlightedWord(node: any) {
node.properties.className = ["word"];
},
},
],
] as any;
export async function getPostCount(): Promise {
const query = groq`count(*[_type == "post"])`;
return sanityClient.fetch(query);
}
export async function getAllPostsMeta(limit?: number): Promise {
const query = groq`*[_type == "post"] | order(publishedAt desc)${
limit ? `[0..${limit - 1}]` : ""
} {
_id,
title,
slug,
keywords,
excerpt,
mainImage {
asset->{
_id,
url
}
},
publishedAt,
author->{name, image {asset -> {_id, url}}},
organization->{name, image {asset -> {_id, url}}, website},
}`;
const res = await sanityClient.fetch(query);
return res;
}
export async function getAllSnippetsMeta(limit?: number): Promise {
const query = groq`*[_type == "snippet"] | order(publishedAt desc)${
limit ? `[0..${limit - 1}]` : ""
} {
_id,
title,
slug,
excerpt,
language->{name, image {asset -> {_id, url}}},
publishedAt,
}`;
const res = await sanityClient.fetch(query);
return res;
}
export async function getAllSlugs({
type,
}: {
type: "post" | "snippet";
}): Promise {
const query = groq`*[_type == "${type}"] | order(publishedAt desc) {
slug {
current
}
}`;
const res_slugs = await sanityClient.fetch(query);
const slugs = res_slugs.map((item: any) => {
return item.slug.current;
});
return slugs;
}
export async function getPostFromSlug(slug: string) {
const query = groq`*[_type == "post" && slug.current == "${slug}"][0] {
_id,
title,
slug,
keywords,
excerpt,
image_url,
mainImage {
asset->{
_id,
url
}
},
_createdAt,
publishedAt,
author->{name, image {asset -> {_id, url}}},
organization->{name, image {asset -> {_id, url}}, website},
content
}`;
const post = await sanityClient.fetch(query);
const source = post.content;
const { content } = matter(source);
const readingTime = readTime(content);
const tableOfContents = getTableOfContents(content);
const mdxSource = await getMarkdownSource(content);
post["content"] = mdxSource;
post["tableOfContents"] = tableOfContents;
post["readingTime"] = readingTime;
return post;
}
export async function getSnippetFromSlug(slug: string) {
const query = groq`*[_type == "snippet" && slug.current == "${slug}"][0] {
_id,
title,
slug,
excerpt,
publishedAt,
language->{name, image {asset -> {_id, url}}},
content
}`;
const post = await sanityClient.fetch(query);
const source = post.content;
const { content } = matter(source);
const readingTime = readTime(content);
const tableOfContents = getTableOfContents(content);
const mdxSource = await getMarkdownSource(content);
post["content"] = mdxSource;
post["tableOfContents"] = tableOfContents;
post["readingTime"] = readingTime;
return post;
}
export async function getStaticPageFromSlug(slug: string) {
const query = groq`*[_type == "static_page" && slug.current == "${slug}"][0] {
_id,
title,
slug,
keywords,
excerpt,
mainImage {
asset->{
_id,
url
}
},
publishedAt,
content
}`;
const post = await sanityClient.fetch(query);
const source = post.content;
const { content } = matter(source);
const mdxSource = await getMarkdownSource(content);
post["content"] = mdxSource;
return post;
}
export function getTableOfContents(markdown: string) {
const regXHeader = /#{2,6}.+/g;
const headingArray = markdown.match(regXHeader)
? markdown.match(regXHeader)
: [];
const headingCounts = new Map();
return headingArray?.map((heading) => {
const cleanHeading = heading.replace(/#{2,6}/, "").trim();
let suffix = "";
if (headingCounts.has(cleanHeading)) {
const count = headingCounts.get(cleanHeading)! + 1;
headingCounts.set(cleanHeading, count);
suffix = `-${count}`;
} else {
headingCounts.set(cleanHeading, 0);
}
return {
level: heading.split("#").length - 1 - 2,
id: cleanHeading + suffix,
heading: heading.replace(/#{2,6}/, "").trim(),
};
});
}
export async function getMarkdownSource(content: string) {
const source = await serialize(content, {
mdxOptions: {
rehypePlugins,
},
});
return source;
}
export async function getEpigraphCount(): Promise {
const query = groq`count(*[_type == "epigraph"])`;
return sanityClient.fetch(query);
}
export async function getAllEpigraphs(limit?: number): Promise {
const query = groq`*[_type == "epigraph"] | order(addedAt desc)${
limit ? `[0..${limit - 1}]` : ""
} {
_id,
quote,
sourceType,
sourceTitle,
sourceMeta,
speaker,
year,
tags,
addedAt,
}`;
return sanityClient.fetch(query);
}
================================================
FILE: lib/sitemap.ts
================================================
import { getAllSlugs } from "./sanityContent";
import { globby } from "globby";
import { writeFileSync } from "fs";
export default async function generate() {
const pages = await globby([
"pages/*.tsx",
"!pages/_*.tsx",
"!pages/api",
"!pages/404.tsx",
]);
const postsSlugs = (await getAllSlugs({ type: "post" })).map(
(item) => `/blogs/${item}`
);
const snippetsSlugs = (await getAllSlugs({ type: "snippet" })).map(
(item) => `/snippets/${item}`
);
const pagesRoute = pages.map((page) => {
const path = page.replace("pages", "").replace(".tsx", "");
const route = path === "/index" ? "" : path;
return route;
});
const sitemap = `
${[...pagesRoute, ...postsSlugs, ...snippetsSlugs]
.map((route) => {
return `
${`https://j471n.in${route}`}
`;
})
.join("")}
`;
// eslint-disable-next-line no-sync
writeFileSync("public/sitemap.xml", sitemap);
}
================================================
FILE: lib/spotify.ts
================================================
import { IArtistsAPIResponse, ITracksAPIResponse } from "./interface";
import { SpotifyAccessToken } from "./types";
const client_id = process.env.SPOTIFY_CLIENT_ID;
const client_secret = process.env.SPOTIFY_CLIENT_SECRET;
const refresh_token = process.env.SPOTIFY_REFRESH_TOKEN;
/**
* Makes a request to the Spotify API to obtain a new access token using a refresh token.
*/
const getAccessToken = async (): Promise => {
// Make a POST request to the Spotify API to request a new access token
const response = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
// Set the Authorization header with the client ID and client secret encoded in base64
Authorization: `Basic ${Buffer.from(
`${client_id}:${client_secret}`
).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded",
},
// Set the body of the request to include the refresh token and grant type
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refresh_token!,
}),
});
// Return the JSON response from the API
return response.json();
};
/**
* Makes a request to the Spotify API to retrieve the user's top tracks.
*/
export const topTracks = async (): Promise => {
// Obtain an access token
const { access_token }: { access_token: string } = await getAccessToken();
// Make a request to the Spotify API to retrieve the user's top tracks in last 4 weeks
const response = await fetch(
"https://api.spotify.com/v1/me/top/tracks?limit=10&time_range=short_term",
{
headers: {
// Set the Authorization header with the access token
Authorization: `Bearer ${access_token}`,
},
}
);
// Handle the response and convert it to the expected type
if (!response.ok) {
throw new Error("Failed to fetch top artists.");
}
const data = await response.json();
return data.items as ITracksAPIResponse[];
};
/**
* Makes a request to the Spotify API to retrieve the user's top artists.
*/
export const topArtists = async (): Promise => {
// Obtain an access token
const { access_token } = await getAccessToken();
// Make a request to the Spotify API to retrieve the user's top artists in last 4 weeks
const response = await fetch(
"https://api.spotify.com/v1/me/top/artists?limit=5&time_range=short_term",
{
headers: {
// Set the Authorization header with the access token
Authorization: `Bearer ${access_token}`,
},
}
);
// Handle the response and convert it to the expected type
if (!response.ok) {
throw new Error("Failed to fetch top artists.");
}
const data = await response.json();
return data.items as IArtistsAPIResponse[];
};
/**
* Makes a request to the Spotify API to retrieve the currently playing song for the user.
*/
export const currentlyPlayingSong = async () => {
// Obtain an access token
const { access_token } = await getAccessToken();
// Make a request to the Spotify API to retrieve the currently playing song for the user
return fetch("https://api.spotify.com/v1/me/player/currently-playing", {
headers: {
// Set the Authorization header with the access token
Authorization: `Bearer ${access_token}`,
},
});
};
================================================
FILE: lib/supabase.ts
================================================
import { createClient } from "@supabase/supabase-js";
// A Supabase client object for making requests to a Supabase server.
export const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_KEY!,
);
/**
* Asynchronously fetches all projects from the database where the 'pinned' column is set to true.
* The results are sorted by the 'created_at' column in descending order.
*/
export async function getProjects() {
let { data: projects, error } = await supabase
.from("projects")
.select("*")
.eq("pinned", "true")
.order("created_at", { ascending: false });
return {
projects,
error: error !== null,
};
}
/**
* Asynchronously fetches all certificates from the database where the 'pinned' column is set to true.
* The results are sorted by the 'created_at' column in descending order.
*/
export async function getCertificates() {
let { data: certificates, error } = await supabase
.from("certificates")
.select("*")
.eq("pinned", "true")
.order("created_at", { ascending: false });
return {
certificates,
error: error !== null,
};
}
/**
* This function is used to add a view to the specified blog post. It first retrieves the blog post from the database
* by its slug value. If the post exists, it increments the view count by 1 and updates it in the database.
* If the post does not exist, it creates a new record with the slug and views set to 1.
*/
export async function addView(slug: string) {
try {
const blogSlug = await getViewBySlug(slug);
if (blogSlug !== undefined) {
return await supabase
.from("views")
.update({ views: blogSlug.views + 1 })
.eq("slug", slug);
} else {
return await supabase.from("views").insert({
slug: slug,
views: 1,
});
}
} catch (err) {
console.error(err);
}
}
/**
* This function is used to retrieve the view count of a specified blog post by its slug value.
* It queries the database and selects the "views" field for the record with a matching "slug" value.
*/
export async function getViewBySlug(slug: string) {
try {
const { data } = await supabase
.from("views")
.select("views")
.eq("slug", slug);
return data![0];
} catch (error) {
console.error(error);
}
}
/**
*
* This function is used to retrieve all the views count and all the blog post in the database.
* It first retrieves the total views count using a predefined function "views_sum" in supabase.
* Then it retrieves all the records from "views" table.
*/
export async function getAllViews() {
try {
// views_sum is defined in supabase
const { data: totalViews } = await supabase.rpc("views_sum");
const { data: posts } = await supabase.from("views").select("*");
return {
totalViews,
posts,
};
} catch (error) {
console.error(error);
}
}
/*
* This function will retrieve the individual custom data from the supabase such as linkedin data
*/
export async function getUserDataValue(key: string) {
let { data, error } = await supabase
.from("user_data")
.select("value")
.eq("key", key)
.limit(1)
.order("created_at", { ascending: false });
if (data?.length === 0) {
return {
data: null,
error: null,
};
}
return {
data: data![0].value,
error: error !== null,
};
}
export async function setUserDataValue(key: string, value1: any) {
const { data, error } = await supabase
.from("user_data")
.update({ value: value1 })
.eq("key", key)
.select();
if (data?.length === 0) {
return {
data: null,
error: null,
};
}
return {
data: data![0].value,
error: error !== null,
};
}
================================================
FILE: lib/tmdb.ts
================================================
import { ITMDBData } from "./interface";
const options: RequestInit = {
method: "GET",
headers: {
accept: "application/json",
Authorization: `Bearer ${process.env.TMDB_ACCESS_TOKEN}`,
},
};
async function fetchData(url: string): Promise {
try {
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(5000), // 5 second timeout (fast-fail)
});
if (!response.ok) {
throw new Error("Error while fetching TMDB data: " + response.statusText);
}
const data = await response.json();
return (data?.results ?? data?.items) as ITMDBData[];
} catch (error) {
console.error(error);
return [];
}
}
/**
* Fetch TMDB data from multiple endpoints and combine the results.
*/
export async function fetchTMDBData(): Promise {
try {
const accountId = process.env.TMDB_ACCOUNT_ID;
// Call all three APIs concurrently using Promise.all
const [recentRatedMovies, recentRatedTvShows, watchingData] =
await Promise.all([
fetchData(
`https://api.themoviedb.org/3/account/${accountId}/rated/movies?sort_by=created_at.desc`,
),
fetchData(
`https://api.themoviedb.org/3/account/${accountId}/rated/tv?sort_by=created_at.desc`,
),
fetchData(`https://api.themoviedb.org/3/list/8261150`),
]);
// Combine the results from the APIs
const combinedData = [
...recentRatedMovies.slice(0, 3),
...recentRatedTvShows.slice(0, 3),
];
return [...watchingData.reverse(), ...combinedData];
} catch (error) {
console.error(error);
return [];
}
}
================================================
FILE: lib/toc.ts
================================================
/**
* Converts a string to a slug by lowercasing it, trimming leading and trailing whitespace,
* replacing any non-word or non-space characters with an empty string, replacing all contiguous whitespace
* with a single hyphen, and removing any hyphens from the beginning or end of the resulting string.
*/
export function stringToSlug(str: string) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/[\s]+/g, "-")
.replace(/^-+|-+$/g, "");
}
================================================
FILE: lib/types.ts
================================================
import { IconType } from "react-icons/lib";
import { MDXRemoteSerializeResult } from "next-mdx-remote";
import React from "react";
import { ReadTimeResults } from "reading-time";
import { Variants } from "framer-motion";
/* Custom Animated Components types */
export type AnimatedTAGProps = {
variants: Variants;
className?: string;
children: React.ReactNode;
infinity?: boolean;
};
/* Spotify Track */
export type SpotifyTrack = {
id: number;
title: string;
url: string;
coverImage: {
url: string;
};
artist: string;
};
/* Spotify Artist */
export type SpotifyArtist = {
id: number;
name: string;
url: string;
coverImage: {
url: string;
};
popularity: number;
};
export type ProjectType = {
id: string;
name: string;
coverImage: string;
description: string;
githubURL: string;
previewURL?: string;
tools?: string[];
pinned?: boolean;
};
export type SkillType = {
name: string;
Icon: IconType;
level?: number; // Proficiency level 1-100
category?: string; // Frontend, Backend, Database, etc.
};
export type CertificateType = {
id: string;
title: string;
issuedDate: string;
orgName: string;
orgLogo: string;
url: string;
pinned: boolean;
};
export type SocialPlatform = {
title: string;
Icon: IconType;
url: string;
};
export type UtilityType = {
title: string;
data: {
name: string;
description: string;
Icon: IconType | JSX.Element;
link: string;
}[];
};
export type Utilities = {
title: string;
description: string;
lastUpdate: string;
data: UtilityType[];
};
export type FrontMatter = {
slug: string;
readingTime: ReadTimeResults;
excerpt: string;
title: string;
date: string;
keywords: string;
image: string;
org?: string | null;
};
export type PostType = {
meta: FrontMatter;
source: MDXRemoteSerializeResult;
tableOfContents: TableOfContents[];
};
export type TableOfContents = {
level: number;
id: string;
heading: string;
};
export type SupportMe = {
name: string;
url: string;
Icon: IconType;
};
export type Song = {
album: string;
artist: string;
albumImageUrl: string;
isPlaying: boolean;
songUrl: string;
title: string;
};
export type FormInput = {
to_name: string;
first_name: string;
last_name: string;
email: string;
subject: string;
message: string;
};
export type SpotifyAccessToken = {
access_token: string;
};
export type GithubRepo = {
stargazers_count: number;
fork: boolean;
forks_count: number;
};
export type PageData = {
title: string;
description: string;
image: string;
keywords: string;
};
export type PageMeta = {
home: PageData;
stats: PageData;
utilities: PageData;
blogs: PageData;
bookmark: PageData;
certificates: PageData;
projects: PageData;
about: PageData;
privacy: PageData;
snippets: PageData;
books: PageData;
epigraphs: PageData;
};
export type BookStatusId = 1 | 2 | 3;
export type HardcoverBook = {
id: number;
statusId: BookStatusId;
title: string;
subtitle: string | null;
slug: string;
pages: number | null;
releaseYear: number | null;
rating: number | null;
userRating: number | null;
coverUrl: string | null;
authors: string[];
updatedAt: string | null;
finishedAt: string | null;
};
export type HardcoverProfile = {
username: string;
name: string;
booksCount: number;
currentYearGoal: {
target: number;
progress: number;
metric: string;
state: string;
year: number;
} | null;
};
export type Snippet = {
slug: string;
title: string;
date: string;
excerpt: string;
image: string;
};
export type MovieType = {
id: number;
name: string;
image: string;
url: string;
year: number;
watched: boolean;
rating: number;
};
================================================
FILE: lib/windowsAnimation.ts
================================================
/* Adds a hover animation to an element by setting its background and border image properties. */
export function showHoverAnimation(e: any, isDarkMode: boolean) {
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left; // x position within the element.
const y = e.clientY - rect.top; // y position within the element.
if (isDarkMode) {
e.target.style.background = `radial-gradient(circle at ${x}px ${y}px , rgba(255,255,255,0.2),rgba(255,255,255,0) )`;
e.target.style.borderImage = `radial-gradient(20% 75% at ${x}px ${y}px ,rgba(255,255,255,0.7),rgba(255,255,255,0.1) ) 1 / 1px / 0px stretch `;
}
}
/* Removes the hover animation from an element by setting its background and border image properties to null. */
export function removeHoverAnimation(e: any) {
e.target.style.background = null;
e.target.style.borderImage = null;
}
================================================
FILE: next-env.d.ts
================================================
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
================================================
FILE: next.config.js
================================================
/**
* @type {import('next').NextConfig}
*/
const withPWA = require("next-pwa")({
dest: "public",
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === "development",
publicExcludes: ["!resume.pdf"],
});
module.exports = withPWA({
reactStrictMode: true,
images: {
domains: [
"ucarecdn.com",
"cdn.buymeacoffee.com",
"res.cloudinary.com",
"imgur.com",
"i.imgur.com",
"cutt.ly",
"activity-graph.herokuapp.com",
"i.scdn.co", // images from spotify
"images.unsplash.com",
"m.media-amazon.com", // for imdb images
"cdn.sanity.io", // sanity images
"image.tmdb.org", // tmdb images
"scontent.cdninstagram.com", // instagram media
"assets.hardcover.app",
],
},
typescript: {
ignoreBuildErrors: false,
},
});
================================================
FILE: package.json
================================================
{
"name": "portfolio-next",
"version": "0.1.0",
"private": true,
"license": "MIT",
"scripts": {
"dev": "next dev",
"tsc": "tsc",
"build": "next build",
"start": "next start",
"tsc-watch": "tsc --watch",
"lint": "next lint",
"find:unused": "next-unused"
},
"dependencies": {
"@emailjs/browser": "^3.6.2",
"@google-analytics/data": "^3.1.2",
"@portabletext/react": "^3.0.4",
"@portabletext/to-html": "^2.0.0",
"@sanity/cli": "^3.14.1",
"@sanity/client": "^6.1.3",
"@sanity/image-url": "^1.0.2",
"@sanity/types": "^3.12.2",
"@tailwindcss/line-clamp": "^0.4.0",
"classnames": "^2.3.2",
"framer-motion": "^6.3.3",
"globby": "^13.1.1",
"gray-matter": "^4.0.3",
"groq": "^3.12.2",
"moment": "^2.29.4",
"next": "^13.1.6",
"next-mdx-remote": "^6.0.0",
"next-pwa": "^5.6.0",
"next-seo": "^6.1.0",
"nextjs-google-analytics": "^1.2.0",
"nprogress": "^0.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-github-calendar": "^4.0.0",
"react-icons": "^5.0.1",
"react-markdown": "^8.0.7",
"react-qr-code": "^2.0.7",
"react-ripples": "^2.2.1",
"react-share": "^4.4.0",
"react-toastify": "^9.0.1",
"react-toggle-dark-mode": "^1.1.1",
"reading-time": "^1.5.0",
"recharts": "^2.7.1",
"rehype-autolink-headings": "^6.1.1",
"rehype-pretty-code": "^0.3.1",
"rehype-slug": "^5.0.1",
"rss": "^1.2.2",
"shiki": "^0.10.1",
"swr": "^1.3.0"
},
"devDependencies": {
"@supabase/supabase-js": "^2.2.3",
"@tailwindcss/typography": "^0.5.8",
"@types/next-pwa": "^5.6.0",
"@types/nprogress": "^0.2.0",
"@types/rss": "^0.0.29",
"autoprefixer": "^10.4.13",
"eslint": "8.14.0",
"eslint-config-next": "^13.1.6",
"next-unused": "^0.0.6",
"postcss": "^8.4.20",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.4"
}
}
================================================
FILE: pages/404.tsx
================================================
import PageNotFound from "@components/PageNotFound";
export default PageNotFound;
================================================
FILE: pages/_app.tsx
================================================
import "@styles/globals.css";
import "react-toastify/dist/ReactToastify.css";
import Layout from "@layout/Layout";
import { useEffect } from "react";
import { useRouter } from "next/router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { DarkModeProvider } from "@context/darkModeContext";
import { GoogleAnalytics } from "nextjs-google-analytics";
import { AppProps } from "next/app";
NProgress.configure({
easing: "ease",
speed: 800,
showSpinner: false,
});
function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
useEffect(() => {
const start = () => {
NProgress.start();
};
const end = () => {
NProgress.done();
};
router.events.on("routeChangeStart", start);
router.events.on("routeChangeComplete", end);
router.events.on("routeChangeError", end);
return () => {
router.events.off("routeChangeStart", start);
router.events.off("routeChangeComplete", end);
router.events.off("routeChangeError", end);
};
}, [router.events]);
return (
{process.env.NODE_ENV === "production" && (
)}
);
}
export default MyApp;
================================================
FILE: pages/_document.tsx
================================================
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
{/* Barlow */}
{/* Inter */}
{/* Sarina */}
);
}
================================================
FILE: pages/about.tsx
================================================
import { ILinkedinResponse, ITMDBData } from "@lib/interface";
import { IStaticPage } from "@lib/interface/sanity";
import Image from "next/image";
import MovieCard from "@components/MovieCard";
import MetaData from "@components/MetaData";
import MDXComponents from "@components/MDXComponents";
import PageHeader from "@components/PageHeader";
import { MDXRemote } from "next-mdx-remote";
import { fetchTMDBData } from "@lib/tmdb";
import { getStaticPageFromSlug } from "@lib/sanityContent";
import { getUserDataValue } from "@lib/supabase";
import { months } from "@utils/date";
import { motion } from "framer-motion";
import pageMeta from "@content/meta";
import { TIME_IN_SECONDS } from "@utils/utils";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.08 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 12 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 160, damping: 22 },
},
};
export default function About({
about,
movies,
linkedin,
}: {
about: IStaticPage;
movies: ITMDBData[];
linkedin: string;
}) {
const parsedLinkedIn: ILinkedinResponse = JSON.parse(linkedin);
return (
<>
{/* Prose content */}
{/* ── Experience ── */}
{/* Section header */}
{/* Experience cards */}
{parsedLinkedIn.experiences.map((experience) => (
{/* Logo */}
{/* Content */}
1 ? "ml-8" : ""
}`}
>
{experience.job_titles.length > 1 && (
{experience.company}
)}
{experience.job_titles.map((job, i) => (
{experience.job_titles.length > 1 && (
)}
{experience.job_titles.length > 1 && (
)}
{job.title}
{experience.job_titles.length === 1 && (
{experience.company}
)}
{job.location && (
{job.location}
)}
{months[job.starts_at.month - 1]} {job.starts_at.year}
{" — "}
{!job.ends_at
? "Present"
: `${months[job.ends_at.month - 1]} ${job.ends_at.year}`}
{job.description && (
{job.description}
)}
))}
))}
{/* ── Movies & TV ── */}
{/* Section header */}
Watching
Movies & TV Series
{/* Horizontal scroll strip */}
{movies.length !== 0 ? (
movies.map((movie) => )
) : (
)}
>
);
}
export async function getStaticProps() {
const about = await getStaticPageFromSlug("about");
const movies = await fetchTMDBData();
const { data: linkedin } = await getUserDataValue("linkedin");
return {
props: {
about,
movies,
linkedin,
},
revalidate: TIME_IN_SECONDS.ONE_DAY, // Revalidate every 24 hours
};
}
================================================
FILE: pages/api/books.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { getMyBooks } from "../../lib/hardcover";
import { HardcoverBook } from "../../lib/types";
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse<{ books: HardcoverBook[] } | { error: string }>,
) {
try {
const books = await getMyBooks();
// Revalidate once per day (86 400 s), serve stale for up to 12 h while revalidating
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=43200",
);
return res.status(200).json({ books });
} catch (error) {
const message =
error instanceof Error ? error.message : "Failed to fetch books";
return res.status(500).json({ error: message });
}
}
================================================
FILE: pages/api/ga.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { BetaAnalyticsDataClient } from "@google-analytics/data";
const propertyId = process.env.GA_PROPERTY_ID;
const DAYS = 7;
const analyticsDataClient = new BetaAnalyticsDataClient({
credentials: {
client_email: process.env.GA_CLIENT_EMAIL,
private_key: process.env.GA_PRIVATE_KEY?.replace(/\n/gm, '\n'),
},
});
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
const [response] = await analyticsDataClient.runReport({
property: `properties/${propertyId}`,
dateRanges: [
{
startDate: `${DAYS}daysAgo`,
endDate: "today",
},
],
dimensions: [
{
name: "year",
},
],
metrics: [
{
name: "activeUsers",
},
],
});
let totalVisitors = 0;
response.rows?.forEach((row: any) => {
totalVisitors += parseInt(row.metricValues[0].value);
});
res.setHeader(
"Cache-Control",
"public, s-maxage=43200, stale-while-revalidate=21600"
);
return res.status(200).json({
totalVisitors,
days: 7,
});
}
================================================
FILE: pages/api/now-playing.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { currentlyPlayingSong } from "../../lib/spotify";
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
const response = await currentlyPlayingSong();
if (response.status === 204 || response.status > 400) {
return res.status(200).json({ isPlaying: false });
}
const song = await response.json();
if (song.item === null) {
return res.status(200).json({ isPlaying: false });
}
const isPlaying = song.is_playing;
const title = song.item.name;
const artist = song.item.artists.map((artist: any) => artist.name).join(", ");
const album = song.item.album.name;
const albumImageUrl = song.item.album.images[0].url;
const songUrl = song.item.external_urls.spotify;
// res.setHeader(
// "Cache-Control",
// "public, s-maxage=60, stale-while-revalidate=10"
// );
return res.status(200).json({
album,
albumImageUrl,
artist,
isPlaying,
songUrl,
title,
});
}
================================================
FILE: pages/api/posts/insta.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { getInstagramPosts } from "@lib/instaposts";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const paginationCode = req.query?.code ?? null;
const mode = req.query?.mode ?? null;
const additionalParams = mode
? mode === "before"
? { before: paginationCode }
: { after: paginationCode }
: {};
const outputData = await getInstagramPosts(additionalParams);
res.setHeader(
"Cache-Control",
"public, s-maxage=3600, stale-while-revalidate=1800"
);
return res.status(200).json(outputData);
}
================================================
FILE: pages/api/revalidate.ts
================================================
import type { NextApiRequest, NextApiResponse } from "next";
interface ExtendedNextApiRequest extends NextApiRequest {
query: {
revalidateUrl: string;
secret: string;
};
}
export default async function handler(
req: ExtendedNextApiRequest,
res: NextApiResponse,
) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.REVALIDATE_SECRET) {
return res.status(401).json({
message:
"Invalid token alert! It looks like you're trying to sneak in without proper authorization. Please present a valid token or face rejection",
});
}
try {
// Regenerate the projects page
await res.revalidate(req.query.revalidateUrl);
return res.json({ revalidated: true });
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send("Error revalidating");
}
}
================================================
FILE: pages/api/stats/artists.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { topArtists } from "../../../lib/spotify";
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
const items = await topArtists();
const artists = items.map((artist) => ({
id: artist.id,
name: artist.name,
url: artist.external_urls.spotify,
popularity: artist.popularity,
coverImage: artist.images ? artist.images[1] : null,
}));
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=43200"
);
return res.status(200).json(artists);
}
================================================
FILE: pages/api/stats/devto.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { getUserDataValue } from "@lib/supabase";
export const config = {
runtime: "edge", // this is a pre-requisite
};
export default async function handler(_req: NextRequest) {
const { data } = await getUserDataValue("devto_stats");
return NextResponse.json(JSON.parse(data), {
headers: {
"Cache-Control": "public, s-maxage=86400, stale-while-revalidate=43200",
},
status: 200,
});
}
/*
Response of this API request is:
{"followers":18034,"likes":13603,"views":754641,"comments":958,"posts":119}
*/
================================================
FILE: pages/api/stats/github-contribution.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { getGithubContribution } from "../../../lib/github";
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
const data = await getGithubContribution();
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=43200"
);
return res.status(200).json(data);
}
================================================
FILE: pages/api/stats/github.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { fetchGithub, getGithubStarsAndForks } from "../../../lib/github";
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
const {
public_repos: repos,
public_gists: gists,
followers,
} = await fetchGithub();
const { githubStars, forks } = await getGithubStarsAndForks();
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=43200"
);
return res.status(200).json({
repos,
gists,
followers,
githubStars,
forks,
});
}
================================================
FILE: pages/api/stats/monkeytype.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
const BASE_URL = "https://api.monkeytype.com";
async function monkeyFetch(endpoint: string) {
const res = await fetch(`${BASE_URL}${endpoint}`, {
headers: {
Authorization: `ApeKey ${process.env.MONKEYTYPE_APE_KEY}`,
Accept: "application/json",
},
});
if (!res.ok) return null;
const json = await res.json();
return json.data;
}
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse,
) {
try {
const [pb15, pb30, pb60, stats, streak, results, rankData] =
await Promise.all([
monkeyFetch("/users/personalBests?mode=time&mode2=15"),
monkeyFetch("/users/personalBests?mode=time&mode2=30"),
monkeyFetch("/users/personalBests?mode=time&mode2=60"),
monkeyFetch("/users/stats"),
monkeyFetch("/users/streak"),
monkeyFetch("/results?limit=50"),
monkeyFetch("/leaderboards/rank?language=english&mode=time&mode2=60"),
]);
// personal bests come as arrays – pick the top entry (highest wpm)
const best = (arr: any[] | null) => {
if (!arr || arr.length === 0) return null;
return arr.reduce((a: any, b: any) => (a.wpm > b.wpm ? a : b));
};
const personalBests = {
time15: best(pb15),
time30: best(pb30),
time60: best(pb60),
};
// Process recent results
const recentResults: any[] = Array.isArray(results) ? results : [];
// WPM trend: last 30 results in chronological order
const trendResults = recentResults
.slice(0, 30)
.reverse()
.map((r: any, i: number) => ({
index: i + 1,
wpm: Math.round(r.wpm),
raw: Math.round(r.rawWpm ?? r.wpm),
acc: Math.round(r.acc * 100) / 100,
mode: r.mode2 ? `${r.mode} ${r.mode2}s` : r.mode,
timestamp: r.timestamp,
}));
const leaderboardRank: number | null = rankData?.rank ?? null;
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=43200",
);
return res.status(200).json({
personalBests,
stats: stats ?? { completedTests: 0, startedTests: 0, timeTyping: 0 },
streak: streak ?? { length: 0, maxLength: 0 },
trendResults,
leaderboardRank,
});
} catch (error) {
console.error("MonkeyType API error:", error);
return res.status(500).json({ error: "Failed to fetch MonkeyType data" });
}
}
================================================
FILE: pages/api/stats/tracks.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import { topTracks } from "../../../lib/spotify";
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
const items = await topTracks();
const tracks = items.map((track) => ({
title: track.name,
artist: track.artists.map((artist: any) => artist.name).join(", "),
url: track.external_urls.spotify,
coverImage: track.album.images[1],
}));
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=43200"
);
return res.status(200).json(tracks);
}
================================================
FILE: pages/api/validate/email.ts
================================================
import { IEmailValidation } from "@lib/interface";
import { NextApiRequest, NextApiResponse } from "next";
// Function to check if the host is valid
const isValidHost = (host: string) => {
if (process.env.NODE_ENV === "production")
return host.toLowerCase() === "j471n.in";
return true;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const origin = req.headers.origin;
const host = req.headers.host;
console.log({
origin,
host,
});
if (!isValidHost(host!)) {
return res.status(400).json({
status: "error",
message: "You are unauthorize to access this route.",
});
}
if (req.method !== "POST") {
return res.status(400).json({
status: "error",
message: "Invalid Method, use POST",
});
}
const { email } = JSON.parse(req.body);
const url = "https://mailcheck.p.rapidapi.com/?domain=" + email;
const options = {
method: "GET",
headers: {
"X-RapidAPI-Key": process.env.EMAIL_VALIDATION_API!,
"X-RapidAPI-Host": "mailcheck.p.rapidapi.com",
},
};
const response = await fetch(url, options);
if (response.status !== 200) {
return res.status(400).json({
status: "error",
message:
"Unable to process your request right now. Please try again later.",
});
}
const { valid, disposable } = (await response.json()) as IEmailValidation;
return res.status(200).json({
status: "success",
message: "Email is deliverable.",
valid: valid && !disposable,
});
}
================================================
FILE: pages/api/views/[slug].ts
================================================
import { addView, getViewBySlug } from "@lib/supabase";
import { NextApiRequest, NextApiResponse } from "next";
/* Extending API request because by default quey.slug return string | string[] and I only want string */
interface ExtendedNextApiRequest extends NextApiRequest {
query: {
slug: string;
};
}
/**
* This function handles HTTP requests made to a specific route. It takes in a "slug" parameter from the request query.
*
* If the request method is GET, it calls the getViewBySlug(slug) function, which retrieves the view count of the specified blog post by its slug value.
* If the view count is not found, it sends a response with a status code of 404 and a JSON object with a message of "Slug not found"
* If the view count is found, it sends a response with a status code of 200 and the data in JSON format.
*
* If the request method is POST and the app is running in production environment, it calls the addView(slug) function,
* which adds a view to the specified blog post. It sends a response with the status code and data returned from the addView function.
*
* If the request method is POST and the app is running in development environment, it sends a response with a status code of 401 and a JSON object with a message of "In Development, Can't add views"
*/
export default async function viewsSlug(
req: ExtendedNextApiRequest,
res: NextApiResponse
) {
const slug = req.query.slug;
if (req.method === "GET") {
const data = await getViewBySlug(slug);
if (data === undefined) {
return res
.status(404)
.json({
message:
"Sorry, the slug you're looking for has gone for a coffee break. Please try again later or make a cup of tea while you wait.",
});
} else {
return res.status(200).json(data);
}
}
// check if the app in the production and req method is post only then add the view to the database
if (req.method === "POST" && process.env.NODE_ENV === "production") {
const supabaseResponse = await addView(slug);
res.status(supabaseResponse?.status!).json(supabaseResponse);
} else {
return res.status(401).json({
message: "In Development, Can't add views",
});
}
}
================================================
FILE: pages/api/views/index.ts
================================================
import { NextRequest, NextResponse } from "next/server";
import { getAllViews } from "@lib/supabase";
export const config = {
runtime: "edge", // this is a pre-requisite
};
/**
* This function handles HTTP requests made to a specific route.
* If the request method is GET, it calls the getAllViews() function, which retrieves all views count and all blog post from the database
* and sends the result as a JSON object in the response with a status code of 200.
* If the request method is not GET, it sends a response with a status code of 405 and a JSON object with a message of "Invalid method use GET"
*/
export default async function views(req: NextRequest) {
if (req.method === "GET") {
return NextResponse.json(await getAllViews(), {
status: 200,
});
} else {
return NextResponse.json(
{
error:
"Invalid method detected! Please switch to GET before proceeding. Trust me, it's the way to go",
},
{
status: 405,
}
);
}
}
================================================
FILE: pages/blogs/[slug].tsx
================================================
import { getAllSlugs, getPostFromSlug } from "@lib/sanityContent";
import BlogLayout from "@layout/BlogLayout";
import { BlogPost } from "@lib/interface/sanity";
import { GetStaticPropsContext } from "next";
import MDXComponents from "@components/MDXComponents";
import { MDXRemote } from "next-mdx-remote";
import Metadata from "@components/MetaData";
import PageNotFound from "@components/PageNotFound";
import { useEffect } from "react";
import { TIME_IN_SECONDS } from "@utils/utils";
export default function Post({
post,
error,
}: {
post: BlogPost;
error: boolean;
}) {
// Adding Views to the supabase database
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${post.slug.current}`, {
method: "POST",
});
post != null && registerView();
}, [post]);
if (error) return ;
return (
<>
>
);
}
type StaticProps = GetStaticPropsContext & {
params: {
slug: string;
};
};
export async function getStaticProps({ params }: StaticProps) {
const { slug } = params;
const post = await getPostFromSlug(slug);
if (post != null) {
return {
props: {
error: false,
post,
},
revalidate: TIME_IN_SECONDS.SIX_HOURS,
};
} else {
return {
props: {
error: true,
post: null,
},
revalidate: TIME_IN_SECONDS.SIX_HOURS,
};
}
}
export async function getStaticPaths() {
const slugs = await getAllSlugs({
type: "post",
});
const paths = slugs.map((slug: any) => ({ params: { slug } }));
return {
paths,
fallback: "blocking",
};
}
================================================
FILE: pages/blogs/bookmark.tsx
================================================
import { motion } from "framer-motion";
import Blog from "@components/Blog";
import Metadata from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import useBookmarkBlogs from "@hooks/useBookmarkBlogs";
import pageMeta from "@content/meta";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.04 } },
};
export default function Blogs() {
const { bookmarkedBlogs } = useBookmarkBlogs("blogs", []);
return (
<>
{bookmarkedBlogs?.length > 0 ? (
{bookmarkedBlogs.map((blog, index) => (
))}
) : (
No bookmarks yet
)}
>
);
}
================================================
FILE: pages/blogs/index.tsx
================================================
import { AnimatePresence, motion } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { BiRss } from "react-icons/bi";
import Blog from "@components/Blog";
import { BlogPost } from "@lib/interface/sanity";
import { CgSearch } from "react-icons/cg";
import Link from "next/link";
import Metadata from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import { debounce } from "@utils/functions";
import { getAllPostsMeta } from "@lib/sanityContent";
import pageMeta from "@content/meta";
import { TIME_IN_SECONDS } from "@utils/utils";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.04 } },
};
export default function Blogs({ blogs }: { blogs: BlogPost[] }) {
const [filteredBlogs, setFilteredBlogs] = useState([...blogs]);
const [query, setQuery] = useState("");
const searchRef = useRef(null!);
const handleSearch = debounce((value: string) => {
setQuery(value);
setFilteredBlogs(
blogs.filter((post: BlogPost) =>
post.title.toLowerCase().includes(value.trim().toLowerCase()),
),
);
}, 300);
function handleAutoSearch(e: KeyboardEvent) {
if (e.code === "Slash" && e.ctrlKey) {
searchRef.current?.focus();
}
}
useEffect(() => {
document.addEventListener("keydown", handleAutoSearch);
return () => document.removeEventListener("keydown", handleAutoSearch);
}, []);
return (
<>
{/* Search bar */}
handleSearch(e.target.value)}
placeholder="Search articles… (Ctrl + /)"
className="w-full pl-9 pr-4 py-2.5 text-sm bg-white dark:bg-darkSecondary border border-gray-200 dark:border-neutral-700 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-600 outline-none focus:border-gray-400 dark:focus:border-gray-600 transition-colors font-mono"
/>
{/* Results header */}
{query
? `${filteredBlogs.length} result${filteredBlogs.length !== 1 ? "s" : ""} for "${query}"`
: `${blogs.length} articles`}
RSS
{/* Blog list */}
{filteredBlogs.length > 0 ? (
{filteredBlogs.map((blog, index) => (
))}
) : (
No results found
)}
>
);
}
export async function getStaticProps() {
const results = await getAllPostsMeta();
return {
props: { blogs: results },
revalidate: TIME_IN_SECONDS.TEN_MINUTES,
};
}
================================================
FILE: pages/books.tsx
================================================
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { CgSearch } from "react-icons/cg";
import MetaData from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import BookCard from "@components/BookCard";
import CreateAnIssue from "@components/CreateAnIssue";
import pageMeta from "@content/meta";
import { getMyBooks, getMyProfile } from "@lib/hardcover";
import { HardcoverBook, HardcoverProfile } from "@lib/types";
import { debounce } from "@utils/functions";
import { TIME_IN_SECONDS } from "@utils/utils";
const TABS = [
{ id: 3, label: "Read" },
{ id: 2, label: "Reading" },
{ id: 1, label: "Want to Read" },
] as const;
type TabId = (typeof TABS)[number]["id"];
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.05 } },
};
const fadeSlide = {
hidden: { opacity: 0, y: 8 },
visible: { opacity: 1, y: 0, transition: { duration: 0.25 } },
exit: { opacity: 0, y: -8, transition: { duration: 0.15 } },
};
const statItem = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 220, damping: 24 },
},
};
/* ── Stats section ── */
function BooksStats({
books,
profile,
}: {
books: HardcoverBook[];
profile: HardcoverProfile;
}) {
const readCount = books.filter((b) => b.statusId === 3).length;
const readingCount = books.filter((b) => b.statusId === 2).length;
const wantCount = books.filter((b) => b.statusId === 1).length;
const pagesRead = books
.filter((b) => b.statusId === 3)
.reduce((sum, b) => sum + (b.pages ?? 0), 0);
const stats = [
{ label: "Books Read", value: readCount.toString() },
{ label: "Currently Reading", value: readingCount.toString() },
{ label: "Want to Read", value: wantCount.toString() },
{
label: "Pages Read",
value: pagesRead > 0 ? pagesRead.toLocaleString() : "—",
},
];
const goal = profile.currentYearGoal;
const goalPct = goal
? Math.min(100, Math.round((goal.progress / goal.target) * 100))
: 0;
return (
{/* Eyebrow */}
Reading Stats
{/* Stat cards */}
{stats.map((s) => (
{s.label}
{s.value}
))}
{/* Reading goal */}
{goal && (
{goal.year} Reading Goal
{goal.progress}{" "}
/ {goal.target} books
{goalPct}%
{/* Progress bar */}
)}
);
}
const STATUS_LABEL: Record = {
1: "Want to Read",
2: "Reading",
3: "Read",
};
/* ── Page ── */
export default function BooksPage({
books,
profile,
error,
}: {
books: HardcoverBook[];
profile: HardcoverProfile;
error: boolean;
}) {
const [activeTab, setActiveTab] = useState(3);
const [query, setQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const handleSearch = debounce((value: string) => {
const trimmed = value.trim().toLowerCase();
setQuery(trimmed);
if (trimmed === "") {
setSearchResults([]);
return;
}
setSearchResults(
books.filter(
(b) =>
b.title.toLowerCase().includes(trimmed) ||
b.authors.some((a) => a.toLowerCase().includes(trimmed)),
),
);
}, 300);
const isSearching = query.length > 0;
if (error) return ;
const filtered = books.filter((b) => b.statusId === activeTab);
const countFor = (id: TabId) => books.filter((b) => b.statusId === id).length;
return (
<>
{/* Stats */}
{/* Divider */}
{/* Search */}
handleSearch(e.target.value)}
placeholder="Search books by title or author…"
className="w-full pl-9 pr-4 py-2.5 text-sm bg-white dark:bg-darkSecondary border border-gray-200 dark:border-neutral-700 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-600 outline-none focus:border-gray-400 dark:focus:border-neutral-600 transition-colors font-mono"
/>
{/* Tab bar — hidden while searching */}
{!isSearching && (
{TABS.map((tab) => {
const isActive = activeTab === tab.id;
const count = countFor(tab.id);
return (
setActiveTab(tab.id)}
className={`relative px-4 py-2.5 text-sm font-medium transition-colors ${
isActive
? "text-gray-900 dark:text-white"
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
}`}
>
{tab.label}
{count}
{isActive && (
)}
);
})}
)}
{/* Books grid — search results or tabbed view */}
{isSearching ? (
<>
{searchResults.length} result
{searchResults.length !== 1 ? "s" : ""} for “{query}”
{searchResults.length === 0 ? (
No books found.
) : (
{searchResults.map((book) => (
{STATUS_LABEL[book.statusId]}
))}
)}
>
) : (
{filtered.length === 0 ? (
Nothing here yet.
) : (
{filtered.map((book) => (
))}
)}
)}
>
);
}
export async function getStaticProps() {
try {
const [books, profile] = await Promise.all([getMyBooks(), getMyProfile()]);
return {
props: { books, profile, error: false },
revalidate: TIME_IN_SECONDS.ONE_DAY,
};
} catch {
return {
props: {
books: [],
profile: {
username: "",
name: "",
booksCount: 0,
currentYearGoal: null,
},
error: true,
},
revalidate: TIME_IN_SECONDS.ONE_DAY,
};
}
}
================================================
FILE: pages/certificates.tsx
================================================
import MetaData from "@components/MetaData";
import Image from "next/image";
import Link from "next/link";
import PageHeader from "@components/PageHeader";
import pageMeta from "@content/meta";
import { CertificateType } from "@lib/types";
import { getCertificates } from "@lib/supabase";
import CreateAnIssue from "@components/CreateAnIssue";
import { getFormattedDate } from "@utils/date";
import { motion } from "framer-motion";
import { TIME_IN_SECONDS } from "@utils/utils";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.04 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 12 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 260, damping: 24 },
},
};
export default function Certificates({
certificates,
error,
}: {
certificates: CertificateType[];
error: boolean;
}) {
if (error) return ;
return (
<>
{certificates.map((cer) => (
{/* Org logo */}
{/* Title + meta */}
{cer.title}
{cer.orgName}
{/* Issued date */}
{getFormattedDate(new Date(cer.issuedDate))}
))}
>
);
}
export async function getStaticProps() {
const { certificates, error } = await getCertificates();
return {
props: {
certificates,
error,
},
revalidate: TIME_IN_SECONDS.ONE_DAY, // Revalidate every 24 hours
};
}
================================================
FILE: pages/epigraphs.tsx
================================================
import { useState, useMemo } from "react";
import { useDebounce } from "@hooks/useDebounce";
import { IEpigraph, EpigraphSourceType } from "@lib/interface/sanity";
import Metadata from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import EpigraphCard from "@components/EpigraphCard";
import { getAllEpigraphs } from "@lib/sanityContent";
import pageMeta from "@content/meta";
import { motion } from "framer-motion";
import { TIME_IN_SECONDS } from "@utils/utils";
const SOURCE_TYPE_LABELS: Record = {
all: "All",
book: "Books",
movie: "Movies",
tvShow: "TV Shows",
person: "People",
song: "Songs",
podcast: "Podcasts",
other: "Other",
};
export default function Epigraphs({ epigraphs }: { epigraphs: IEpigraph[] }) {
const [activeType, setActiveType] = useState(
"all",
);
const [search, setSearch] = useState("");
const debouncedSearch = useDebounce(search, 300);
// Derive which source types actually exist in the data
const availableTypes = useMemo>(() => {
const types = new Set(epigraphs.map((e) => e.sourceType));
const ordered: Array = [
"all",
"book",
"movie",
"tvShow",
"person",
"song",
"podcast",
"other",
];
return ordered.filter(
(t) => t === "all" || types.has(t as EpigraphSourceType),
);
}, [epigraphs]);
const filtered = useMemo(() => {
const q = debouncedSearch.trim().toLowerCase();
return epigraphs.filter((m) => {
const typeMatch = activeType === "all" || m.sourceType === activeType;
if (!typeMatch) return false;
if (!q) return true;
return (
m.quote.toLowerCase().includes(q) ||
m.sourceTitle.toLowerCase().includes(q) ||
(m.sourceMeta?.toLowerCase().includes(q) ?? false) ||
(m.speaker?.toLowerCase().includes(q) ?? false) ||
(m.tags?.some((t) => t.toLowerCase().includes(q)) ?? false)
);
});
}, [epigraphs, activeType, debouncedSearch]);
return (
<>
{/* ── Filter Controls ── */}
{/* Search */}
setSearch(e.target.value)}
className="w-full bg-white dark:bg-darkSecondary border border-gray-200 dark:border-neutral-700 text-sm text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-600 px-4 py-2 focus:outline-none focus:border-gray-400 dark:focus:border-gray-500 transition-colors"
/>
{/* Source type filter pills */}
{availableTypes.map((type) => (
setActiveType(type)}
className={`font-mono text-[10px] tracking-[0.3em] uppercase px-3 py-1.5 border transition-colors ${
activeType === type
? "bg-gray-900 dark:bg-white text-white dark:text-gray-900 border-gray-900 dark:border-white"
: "bg-transparent text-gray-500 dark:text-gray-500 border-gray-200 dark:border-neutral-700 hover:border-gray-400 dark:hover:border-gray-500"
}`}
>
{SOURCE_TYPE_LABELS[type]}
))}
{/* Result count */}
{(debouncedSearch || activeType !== "all") && (
{filtered.length} result{filtered.length !== 1 ? "s" : ""}
)}
{/* ── List ── */}
{filtered.length > 0 ? (
{filtered.map((epigraph, i) => (
))}
) : (
“
No epigraphs match your filters.
{
setSearch("");
setActiveType("all");
}}
className="mt-4 font-mono text-[10px] tracking-widest uppercase text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
>
Clear filters
)}
>
);
}
export async function getStaticProps() {
const epigraphs = await getAllEpigraphs();
return {
props: { epigraphs },
revalidate: TIME_IN_SECONDS.ONE_DAY, // revalidate every 5 minutes
};
}
================================================
FILE: pages/index.tsx
================================================
// Page Components START----------
import { headingFromLeft } from "@content/FramerMotionVariants";
import AnimatedHeading from "@components/FramerMotion/AnimatedHeading";
import { BlogPost } from "@lib/interface/sanity";
import { IEpigraph } from "@lib/interface/sanity";
import BlogsSection from "@components/Home/BlogsSection";
import EpigraphsSection from "@components/Home/EpigraphsSection";
import Contact from "@components/Contact";
import HeroSection from "@components/Home/HeroSection";
import Metadata from "@components/MetaData";
import React from "react";
import SkillSection from "@components/Home/SkillSection";
import generateSitemap from "@lib/sitemap";
import {
getAllPostsMeta,
getPostCount,
getAllEpigraphs,
getEpigraphCount,
} from "@lib/sanityContent";
import getRSS from "@lib/generateRSS";
import pageMeta from "@content/meta";
import { TIME_IN_SECONDS } from "@utils/utils";
export default function Home({
blogs,
totalBlogs,
epigraphs,
totalEpigraphs,
}: {
blogs: BlogPost[];
totalBlogs: number;
epigraphs: IEpigraph[];
totalEpigraphs: number;
}) {
return (
<>
>
);
}
export function HomeHeading({ title }: { title: React.ReactNode | string }) {
return (
{title}
);
}
export async function getStaticProps() {
const [blogs, totalBlogs, epigraphs, totalEpigraphs] = await Promise.all([
getAllPostsMeta(3),
getPostCount(),
getAllEpigraphs(5),
getEpigraphCount(),
]);
// RSS and sitemap are generated at build time only.
// They are not regenerated on ISR revalidations because they write
// to the filesystem which is read-only on most hosting platforms after build.
if (
process.env.NODE_ENV === "production" &&
process.env.NEXT_PHASE === "phase-production-build"
) {
await getRSS();
await generateSitemap();
}
return {
props: { blogs, totalBlogs, epigraphs, totalEpigraphs },
revalidate: TIME_IN_SECONDS.TEN_MINUTES, // revalidate every 10 minutes
};
}
================================================
FILE: pages/privacy.tsx
================================================
import { IStaticPage } from "@lib/interface/sanity";
import StaticPage from "@components/StaticPage";
import { getStaticPageFromSlug } from "@lib/sanityContent";
import pageMeta from "@content/meta";
import { TIME_IN_SECONDS } from "@utils/utils";
export default function Privacy({
privacyPolicy,
}: {
privacyPolicy: IStaticPage;
}) {
return ;
}
export async function getStaticProps() {
const privacyPolicy = await getStaticPageFromSlug("privacy");
return {
props: {
privacyPolicy,
},
revalidate: TIME_IN_SECONDS.ONE_WEEK,
};
}
================================================
FILE: pages/projects.tsx
================================================
import React from "react";
import Project from "@components/Project";
import Metadata from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import pageMeta from "@content/meta";
import { getProjects } from "@lib/supabase";
import { ProjectType } from "@lib/types";
import CreateAnIssue from "@components/CreateAnIssue";
import { motion } from "framer-motion";
import { TIME_IN_SECONDS } from "@utils/utils";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.06 } },
};
export default function Projects({
projects,
error,
}: {
projects: ProjectType[];
error: boolean;
}) {
if (error) return ;
const visible = projects.filter(
(p) => !(p.name === "" && p.githubURL === ""),
);
return (
<>
{visible.map((project, index) => (
))}
>
);
}
export async function getStaticProps() {
const { projects, error } = await getProjects();
return {
props: {
projects,
error,
},
revalidate: TIME_IN_SECONDS.ONE_DAY,
};
}
================================================
FILE: pages/snippets/[slug].tsx
================================================
import { getAllSlugs, getSnippetFromSlug } from "@lib/sanityContent";
import { GetStaticPropsContext } from "next";
import { ISnippet } from "@lib/interface/sanity";
import MDXComponents from "@components/MDXComponents";
import { MDXRemote } from "next-mdx-remote";
import Metadata from "@components/MetaData";
import PageNotFound from "@components/PageNotFound";
import SnippetLayout from "@layout/SnippetLayout";
import pageMeta from "@content/meta";
import { TIME_IN_SECONDS } from "@utils/utils";
export default function SnippetPage({
snippet,
error,
}: {
snippet: ISnippet;
error: boolean;
}) {
if (error) return ;
return (
<>
>
);
}
type StaticProps = GetStaticPropsContext & {
params: {
slug: string;
};
};
export async function getStaticProps({ params }: StaticProps) {
const { slug } = params;
const snippet = await getSnippetFromSlug(slug);
if (snippet != null) {
return {
props: {
error: false,
snippet,
},
revalidate: TIME_IN_SECONDS.ONE_DAY,
};
} else {
return {
props: {
error: true,
snippet: null,
},
revalidate: TIME_IN_SECONDS.ONE_DAY,
};
}
}
export async function getStaticPaths() {
const slugs = await getAllSlugs({
type: "snippet",
});
const paths = slugs.map((slug: any) => ({ params: { slug } }));
return {
paths,
fallback: "blocking",
};
}
================================================
FILE: pages/snippets/index.tsx
================================================
import { motion } from "framer-motion";
import { ISnippet } from "@lib/interface/sanity";
import Metadata from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import SnippetCard from "@components/SnippetCard";
import { getAllSnippetsMeta } from "@lib/sanityContent";
import pageMeta from "@content/meta";
import { TIME_IN_SECONDS } from "@utils/utils";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.05 } },
};
export default function Snippets({ snippets }: { snippets: ISnippet[] }) {
return (
<>
{snippets.map((snippet, index) => (
))}
>
);
}
export async function getStaticProps() {
const snippets = await getAllSnippetsMeta();
return {
props: { snippets },
revalidate: TIME_IN_SECONDS.ONE_DAY, // Revalidate every 24 hours
};
}
================================================
FILE: pages/stats.tsx
================================================
// import { SpotifyArtist, SpotifyTrack } from "@lib/types";
// import Artist from "@components/Stats/Artist";
import GitHubActivityGraph from "@components/GitHubActivityGraph";
import GitHubCalendar from "react-github-calendar";
import MetaData from "@components/MetaData";
import MonkeyTypeStats from "@components/Stats/MonkeyTypeStats";
import PageHeader from "@components/PageHeader";
import React from "react";
import StatsCard from "@components/Stats/StatsCard";
console.log("Stats page rendered");
// import Track from "@components/Stats/Track";
import fetcher from "@lib/fetcher";
import pageMeta from "@content/meta";
import { motion } from "framer-motion";
import { useDarkMode } from "@context/darkModeContext";
import useSWR from "swr";
type Stats = {
title: string;
value: string;
};
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.06 } },
};
/* Shared section-header used by each sub-section */
function SectionHeading({
eyebrow,
title,
description,
}: {
eyebrow: string;
title: string;
description?: React.ReactNode;
}) {
return (
{eyebrow}
{title}
{description && (
{description}
)}
);
}
export default function Stats() {
const { isDarkMode } = useDarkMode();
// const { data: topTracks } = useSWR("/api/stats/tracks", fetcher);
// const { data: artists } = useSWR("/api/stats/artists", fetcher);
const { data: devto } = useSWR("/api/stats/devto", fetcher);
const { data: github } = useSWR("/api/stats/github", fetcher);
const devtoStats: Stats[] = [
{ title: "Total Posts", value: devto?.posts.toLocaleString() },
{ title: "Followers", value: devto?.followers.toLocaleString() },
{ title: "Reactions", value: devto?.likes.toLocaleString() },
{ title: "Views", value: devto?.views.toLocaleString() },
{ title: "Comments", value: devto?.comments.toLocaleString() },
];
const githubStats: Stats[] = [
{ title: "Repos", value: github?.repos },
{ title: "Gists", value: github?.gists },
{ title: "Followers", value: github?.followers },
{ title: "Stars", value: github?.githubStars },
{ title: "Forked", value: github?.forks },
];
return (
<>
{/* ═══════════════════════════════════════════ */}
{/* Dev.to Section */}
{/* ═══════════════════════════════════════════ */}
{devtoStats.map((stat, index) => (
))}
{/* ═══════════════════════════════════════════ */}
{/* GitHub Section */}
{/* ═══════════════════════════════════════════ */}
{githubStats.map((stat, index) => (
))}
{/* Contribution calendar */}
{/* GitHub charts — 2 col grid */}
{/* ═══════════════════════════════════════════ */}
{/* MonkeyType Section */}
{/* ═══════════════════════════════════════════ */}
{/* ── Top Tracks ── */}
{/*
{topTracks ? (
<>
{topTracks?.[0]?.title}
{" is my most streamed track in the last 4 weeks."}
>
) : (
"My top tracks on Spotify, updated daily."
)}
>
}
/>
{topTracks ? (
topTracks.map((track: SpotifyTrack, index: number) => (
))
) : (
)}
*/}
{/* ── Top Artists ── */}
{/*
{artists ? (
<>
{"My most listened artist is "}
{artists?.[0]?.name}
{" in the last 4 weeks."}
>
) : (
"My most listened artists on Spotify in the last 4 weeks."
)}
>
}
/>
{artists ? (
artists.length === 0 ? (
Not enough data to show.
) : (
artists.map((artist: SpotifyArtist, index: number) => (
))
)
) : (
)}
*/}
>
);
}
// function LoadingSongs() {
// return (
// <>
// {Array.from({ length: 10 }, (_, i) => (
//
//
// {i + 1}
//
//
//
//
// ))}
// >
// );
// }
// function LoadingArtists() {
// return (
// <>
// {Array.from({ length: 5 }, (_, i) => (
//
//
// {i + 1}
//
//
//
//
// ))}
// >
// );
// }
================================================
FILE: pages/tet.json
================================================
{
"public_identifier": "j471n",
"profile_pic_url": "https://i.imgur.com/RF2bEls.jpg",
"background_cover_image_url": null,
"first_name": "Jatin",
"last_name": "Sharma",
"full_name": "Jatin Sharma",
"follower_count": 843,
"occupation": "Trusted Member at Forem",
"headline": "React Developer | Next.js | Blogger @DEV Community",
"summary": "A front-end developer who specializes in React.js and Next.js. I have experience building and maintaining web applications. I build web applications using React, Next.js and TailwindCSS. I am always looking for new challenges and ways to work more efficiently. I am also familiar with Python & C++.\n\nThe one things I like the most is blogging. I post blogs on dev.to and share my experience and skills with others. \n\nWhen I am not working, you can find me exploring the frictional world through movies and web series.",
"country": "IN",
"country_full_name": "India",
"city": "Bareilly",
"state": "Uttar Pradesh",
"experiences": [
{
"company": "Forem",
"company_linkedin_profile_url": "https://www.linkedin.com/company/thepracticaldev/",
"logo_url": "https://i.imgur.com/ONNBTJH.jpg",
"job_titles": [
{
"starts_at": {
"day": 1,
"month": 10,
"year": 2021
},
"ends_at": null,
"title": "Blogger @DEV Community",
"description": "I research and study a subject, and then I compose an article about it. I introduce new and different topics and provide an in-depth explanation that is accessible to those who have no prior technical understanding of the subject matter. I ensure that the information is presented in a clear and in very simple language, making it accessible to readers of all skill levels even for those who may not have a strong command of the English language.\n\n\u2605 7.5K+ Followers\n\u2605 520K+ Post Views\n\u2605 Top 7 Author\n\u2605 100+ Posts\n\u2605 12K+ Reactions",
"location": "India"
},
{
"starts_at": {
"day": 1,
"month": 5,
"year": 2023
},
"location": "Remote",
"ends_at": null,
"title": "Trusted Member",
"description": "As a Trusted Member, I have been granted basic moderation abilities to help \ud835\uddd7\ud835\uddf2\ud835\ude03 \ud835\uddd6\ud835\uddfc\ud835\uddfa\ud835\uddfa\ud835\ude02\ud835\uddfb\ud835\uddf6\ud835\ude01\ud835\ude06 by moderate discussions and ensure positive interactions among community members. This includes assisting with reporting problematic content and potentially harmful behavior. Being designated as a Trusted Member is an honor and a great responsibility, and it reinforces the importance of maintaining a respectful and inclusive community."
},
{
"starts_at": {
"day": 1,
"month": 9,
"year": 2022
},
"ends_at": {
"day": 1,
"month": 9,
"year": 2022
},
"title": "Featured in Top 7 authors of the week",
"description": "I have been featured in Top 7 authors of the week twice.",
"location": null
}
]
},
{
"company": "KonnectNXT",
"company_linkedin_profile_url": "https://www.linkedin.com/company/konnectnxt/",
"logo_url": "https://i.imgur.com/TaEWZcc.jpg",
"job_titles": [
{
"title": "Software Developer",
"description": null,
"location": "Hyderabad, Telangana, India",
"starts_at": {
"day": 1,
"month": 2,
"year": 2023
},
"ends_at": null
}
]
},
{
"company": "Documatic",
"company_linkedin_profile_url": "https://www.linkedin.com/company/trydocumatic/",
"logo_url": "https://i.imgur.com/4ogaHWS.jpg",
"job_titles": [
{
"starts_at": {
"day": 1,
"month": 2,
"year": 2023
},
"ends_at": null,
"title": "Technical Writer",
"description": null,
"location": "United Kingdom"
}
]
},
{
"company": "Substack",
"company_linkedin_profile_url": "https://www.linkedin.com/company/substack/",
"logo_url": "https://i.imgur.com/HgVfweU.jpg",
"job_titles": [
{
"starts_at": {
"day": 1,
"month": 5,
"year": 2022
},
"ends_at": null,
"title": "Writer",
"description": "As a writer, my focus is primarily on the topics of web development and productivity chrome extensions. Through my writing, I aim to provide detailed and comprehensive coverage of the latest developments and trends in the field of web development, as well as share tips and tricks for using productivity chrome extensions to increase efficiency and productivity. I also ensure that they never miss any of my blog article. If they do then this is the reminder for them.",
"location": "India"
}
]
},
{
"company": "Hashnode",
"company_linkedin_profile_url": "https://www.linkedin.com/company/hashnode/",
"logo_url": "https://i.imgur.com/vwegEfc.jpg",
"job_titles": [
{
"starts_at": {
"day": 1,
"month": 11,
"year": 2021
},
"ends_at": null,
"title": "Blogger",
"description": "I am keeping a record of my programming journey to monitor my growth and evaluate the difficulties and successes I encounter.",
"location": "India"
}
]
},
{
"company": "Medium",
"company_linkedin_profile_url": "https://www.linkedin.com/company/medium-com/",
"logo_url": "https://i.imgur.com/hT7ajHV.jpg",
"job_titles": [
{
"starts_at": {
"day": 1,
"month": 10,
"year": 2020
},
"ends_at": null,
"title": "Blogger",
"description": "I am an author who focuses on web development and offers a wide variety of beginner-friendly tutorials. Through my writings, I aim to pass on my knowledge and experience to others who share my interest in all aspects of programming.",
"location": "India"
}
]
}
],
"education": [
{
"starts_at": {
"day": 1,
"month": 1,
"year": 2019
},
"ends_at": {
"day": 1,
"month": 1,
"year": 2023
},
"field_of_study": "Computer Science",
"degree_name": "Bachelor of Technology - B.Tech",
"school": "Dr. A.P.J. Abdul Kalam Technical University",
"school_linkedin_profile_url": null,
"description": null,
"logo_url": "https://i.imgur.com/faTfpD7.jpg",
"grade": null,
"activities_and_societies": null
}
],
"languages": [
"English",
"Hindi"
],
"accomplishment_organisations": [],
"accomplishment_publications": [],
"accomplishment_honors_awards": [],
"accomplishment_patents": [],
"accomplishment_courses": [],
"accomplishment_projects": [],
"accomplishment_test_scores": [],
"volunteer_work": [
{
"starts_at": {
"day": 1,
"month": 1,
"year": 2023
},
"ends_at": null,
"title": "Member",
"cause": "EDUCATION",
"company": "Google Developer Student Clubs",
"company_linkedin_profile_url": "https://www.linkedin.com/company/dscjscoe/",
"description": null,
"logo_url": "https://media.licdn.com/dms/image/C4E0BAQE_1tNZKj3sNQ/company-logo_400_400/0/1617607397103?e=1695254400&v=beta&t=mRveYFhesakeGFzPNIgcgszUrPZ_oo_f-Sl4Kh8VcE0"
},
{
"starts_at": {
"day": 1,
"month": 12,
"year": 2022
},
"ends_at": null,
"title": "Say Yes to Life, No to Drugs",
"cause": "HEALTH",
"company": "Ministry Of Home Affairs (mha), GOI",
"company_linkedin_profile_url": "https://www.linkedin.com/company/ministry-of-home-affairs-mha-/",
"description": "\ud835\uddd6\ud835\uddf2\ud835\uddff\ud835\ude01\ud835\uddf6\ud835\uddf3\ud835\uddf6\ud835\uddf0\ud835\uddee\ud835\ude01\ud835\uddf6\ud835\uddfc\ud835\uddfb: https://bit.ly/PledgeJatin",
"logo_url": "https://media.licdn.com/dms/image/C4D0BAQE7EcnynUNfOg/company-logo_400_400/0/1659293307400?e=1695254400&v=beta&t=Amf-ZxlrbOJLRO76l0YbenhVzusXhgd6FU0fb-no0FQ"
},
{
"starts_at": {
"day": 1,
"month": 10,
"year": 2021
},
"ends_at": null,
"title": "Member",
"cause": "EDUCATION",
"company": "daily.dev",
"company_linkedin_profile_url": "https://www.linkedin.com/company/dailydotdev/",
"description": null,
"logo_url": "https://media.licdn.com/dms/image/C4E0BAQHPne7VGe_P3A/company-logo_400_400/0/1603704711534?e=1695254400&v=beta&t=e5Gp0lxkjTugd0WkELbkdJmW9VtluwrsnuFZ0EX15Ts"
}
],
"certifications": [
{
"starts_at": {
"day": 1,
"month": 1,
"year": 2023
},
"ends_at": null,
"name": "API Fundamentals Student Expert",
"license_number": "63c1c78cdf42054887f80410",
"display_source": "badgr.io",
"authority": "Postman",
"url": "https://api.badgr.io/public/assertions/ORpisSFbRQWJvkAKJEr8vA"
},
{
"starts_at": {
"day": 1,
"month": 1,
"year": 2023
},
"ends_at": null,
"name": "Back End Development and APIs",
"license_number": null,
"display_source": "freecodecamp.org",
"authority": "freeCodeCamp",
"url": "https://freecodecamp.org/certification/j471n/back-end-development-and-apis"
},
{
"starts_at": {
"day": 1,
"month": 1,
"year": 2023
},
"ends_at": null,
"name": "Digital Skills: Artificial Intelligence",
"license_number": null,
"display_source": "futurelearn.com",
"authority": "Accenture",
"url": "https://www.futurelearn.com/certificates/07o1i90"
},
{
"starts_at": {
"day": 1,
"month": 8,
"year": 2022
},
"ends_at": null,
"name": "Developer & Technology Accenture Developer Program",
"license_number": "GXL4zXeSS3kzy7TQR",
"display_source": "google.com",
"authority": "Accenture",
"url": "https://drive.google.com/file/d/1RvReVd4YmRpT2LsnHIX5MWIqxlFTj4bl/view?usp=share_link"
},
{
"starts_at": {
"day": 1,
"month": 4,
"year": 2022
},
"ends_at": null,
"name": "JavaScript (Intermediate)",
"license_number": "fde0087a00a7",
"display_source": "hackerrank.com",
"authority": "HackerRank",
"url": "https://www.hackerrank.com/certificates/fde0087a00a7"
},
{
"starts_at": {
"day": 1,
"month": 12,
"year": 2021
},
"ends_at": null,
"name": "JavaScript Algorithms and Data Structures",
"license_number": null,
"display_source": "freecodecamp.org",
"authority": "freeCodeCamp",
"url": "https://freecodecamp.org/certification/j471n/javascript-algorithms-and-data-structures"
},
{
"starts_at": {
"day": 1,
"month": 4,
"year": 2021
},
"ends_at": null,
"name": "Python 3 Master Course for 2021",
"license_number": "UC-44d68c58-0203-4835-813a-50b893e01630",
"display_source": "udemy.com",
"authority": "Udemy",
"url": "https://www.udemy.com/certificate/UC-44d68c58-0203-4835-813a-50b893e01630/"
},
{
"starts_at": {
"day": 1,
"month": 4,
"year": 2021
},
"ends_at": null,
"name": "Scientific Computing with Python",
"license_number": null,
"display_source": "freecodecamp.org",
"authority": "freeCodeCamp",
"url": "https://freecodecamp.org/certification/j471n/scientific-computing-with-python-v7"
},
{
"starts_at": {
"day": 1,
"month": 12,
"year": 2020
},
"ends_at": null,
"name": "JavaScript (Basic)",
"license_number": "18FF8A46B713",
"display_source": "hackerrank.com",
"authority": "HackerRank",
"url": "https://www.hackerrank.com/certificates/18ff8a46b713"
},
{
"starts_at": {
"day": 1,
"month": 11,
"year": 2020
},
"ends_at": null,
"name": "AWSOME DAY Online Conference",
"license_number": null,
"display_source": "google.com",
"authority": "Amazon Web Services (AWS)",
"url": "https://drive.google.com/file/d/1ApnzbHVMQbLVDZfjZP5u05xjHDw4NDZA/view?usp=shari"
},
{
"starts_at": {
"day": 1,
"month": 11,
"year": 2020
},
"ends_at": null,
"name": "Microsoft Al Classroom Series",
"license_number": null,
"display_source": "google.com",
"authority": "Microsoft",
"url": "https://drive.google.com/file/d/1A8-2thKHG4xenkJzu5Uia6kVPnE0HiAa/view?usp=drivesdk"
},
{
"starts_at": {
"day": 1,
"month": 10,
"year": 2020
},
"ends_at": null,
"name": "Become a Software Developer - Learning Path",
"license_number": "AbxzbR6VbLGjkllLsxi8knM-Ypaq",
"display_source": "google.com",
"authority": "LinkedIn",
"url": "https://drive.google.com/file/d/1MXTze2mXB7b8Kod7Pk6Q1BTNb1l0OYn3/view?usp=drivesdk"
},
{
"starts_at": {
"day": 1,
"month": 10,
"year": 2020
},
"ends_at": null,
"name": "Learn React Course",
"license_number": null,
"display_source": "google.com",
"authority": "Codecademy",
"url": "https://drive.google.com/file/d/1TAxq-5pQLUxyW1W9V4ovK53JQL7wtQFf/view?usp=share_link"
},
{
"starts_at": {
"day": 1,
"month": 10,
"year": 2020
},
"ends_at": null,
"name": "Web Development",
"license_number": null,
"display_source": "google.com",
"authority": "Mimo",
"url": "https://drive.google.com/file/d/1HziWP3mLgDozfzXjXcs-qLvTzajU1x4j/view?usp=drivesdk"
},
{
"starts_at": {
"day": 1,
"month": 9,
"year": 2020
},
"ends_at": null,
"name": "Learn Python 3 Course",
"license_number": null,
"display_source": "google.com",
"authority": "Codecademy",
"url": "https://drive.google.com/file/d/1H2znARDQD91Mt8IAC3RPIuuv9oMGkAyB/view?usp=share_link"
},
{
"starts_at": {
"day": 1,
"month": 9,
"year": 2020
},
"ends_at": null,
"name": "Learn SQL Course",
"license_number": null,
"display_source": "google.com",
"authority": "Codecademy",
"url": "https://drive.google.com/file/d/18Eh2IGzdlOJJCAmqaOABAMzzTM8Q3z7F/view?usp=share_link"
},
{
"starts_at": {
"day": 1,
"month": 8,
"year": 2020
},
"ends_at": null,
"name": "Learning SQL Programming",
"license_number": "AUu5AHwuiRCKambPS4TfA2L24X0b",
"display_source": "google.com",
"authority": "LinkedIn",
"url": "https://drive.google.com/file/d/1lRinmNHufEOdJc5nJrzyDyrrTJYTKIC4/view?usp=sharing"
},
{
"starts_at": {
"day": 1,
"month": 8,
"year": 2020
},
"ends_at": null,
"name": "Python (Basic) ",
"license_number": "830BC394DFCA",
"display_source": "hackerrank.com",
"authority": "HackerRank",
"url": "https://www.hackerrank.com/certificates/830bc394dfca"
},
{
"starts_at": {
"day": 1,
"month": 7,
"year": 2020
},
"ends_at": null,
"name": "Complete Responsive Web Development: 4 courses in 1\n",
"license_number": null,
"display_source": "udemy.com",
"authority": "Udemy",
"url": "https://www.udemy.com/certificate/UC-8e18f7bf-1da1-4e29-b61c-2e75ed88aa29/"
},
{
"starts_at": {
"day": 1,
"month": 9,
"year": 2019
},
"ends_at": null,
"name": "C Programming Made Easy",
"license_number": null,
"display_source": "udemy.com",
"authority": "Udemy",
"url": "https://www.udemy.com/certificate/UC-S7KXIVWQ/"
}
],
"connections": null,
"people_also_viewed": [
{
"link": "https://www.linkedin.com/in/aayushi-rai-2580351a8",
"name": "Aayushi Rai",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/iamshadmirza",
"name": "Shad Mirza",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/ebenezhar-selvakumar-059559136",
"name": "Ebenezhar Selvakumar",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/fazlerocks",
"name": "Syed Fazle Rahman",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/radha-dadhich-9284b322a",
"name": "Radha Dadhich",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/savitasri",
"name": "Savita S.",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/kush-munot",
"name": "Kush Munot",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/prasad-karri-005257210",
"name": "Prasad Karri",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/vani-m-kokila-53462315",
"name": "Vani M Kokila",
"summary": null,
"location": null
},
{
"link": "https://www.linkedin.com/in/vaibhav-pansambal-66545a57",
"name": "Vaibhav Pansambal",
"summary": null,
"location": null
}
],
"recommendations": [],
"activities": [],
"similarly_named_profiles": [
{
"name": "Jatin Sharma",
"link": "https://in.linkedin.com/in/jatin-sharma-6aa64445",
"summary": "Lead Operation & Process design safety",
"location": "Ahmedabad"
},
{
"name": "jatin sharma",
"link": "https://in.linkedin.com/in/jatin-sharma-420318100",
"summary": "Technical Lead",
"location": "West Delhi"
},
{
"name": "Jatin Sharma",
"link": "https://in.linkedin.com/in/jatin-sharma-a6b59267",
"summary": "Application Development Team Lead at Accenture",
"location": "Shimla"
},
{
"name": "Jatin Sharma",
"link": "https://in.linkedin.com/in/jatsharma",
"summary": "Software Engineer III @ Stackera | Python | Blockchain | DeFi",
"location": "Greater Delhi Area"
},
{
"name": "Jatin Sharma",
"link": "https://jp.linkedin.com/in/jatin-sharma-91162090",
"summary": "Data Engineer at Rakuten | Azure Certified DP-203 | (Azure Data Engineer | Azure Data Factory | Databricks | Synapse | Informatica | ETL | Python | Data Engineer | Pyspark | Spark | Hive | Tableau ) | ETL developer",
"location": "Tokyo, Japan"
}
],
"articles": [],
"groups": [],
"phone_numbers": [],
"social_networking_services": [],
"skills": [
"Django",
"Technical Writing",
"Next.js",
"Python (Programming Language)",
"Firebase",
"React.js",
"Email Marketing",
"E-newsletter",
"Web Content Writing",
"API Development",
"API Management",
"Postman API",
"Content Writting",
"Copywriting",
"SEO Copywriting",
"JavaScript",
"TypeScript",
"Cloud Firestore",
"C (Programming Language)",
"Front-End Development"
],
"inferred_salary": {
"min": null,
"max": null
},
"gender": null,
"birth_date": null,
"industry": null,
"extra": {
"github_profile_id": null,
"twitter_profile_id": null,
"facebook_profile_id": null
},
"interests": [],
"personal_emails": [],
"personal_numbers": []
}
================================================
FILE: pages/utilities.tsx
================================================
import React from "react";
import Link from "next/link";
import { motion } from "framer-motion";
import { FiExternalLink } from "react-icons/fi";
import MetaData from "@components/MetaData";
import PageHeader from "@components/PageHeader";
import utilities from "@content/utilitiesData";
import pageMeta from "@content/meta";
import { UtilityType } from "@lib/types";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.05 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 160, damping: 22 },
},
};
export default function Utilities() {
return (
<>
{/* ── Sections ── */}
{utilities.data.map((utility, index) => (
))}
{/* ── Last updated ── */}
Last updated — {utilities.lastUpdate}
>
);
}
function UtilitySection({
utility,
index,
}: {
utility: UtilityType;
index: number;
}) {
const num = String(index + 1).padStart(2, "0");
return (
{/* Category header */}
{num}
{utility.title}
{/* Tool cards grid */}
{utility.data.map((item) => {
const Icon = item.Icon;
return (
{/* @ts-ignore */}
{item.name}
{item.description}
);
})}
);
}
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
================================================
FILE: public/manifest.json
================================================
{
"theme_color": "#000",
"background_color": "#fff",
"display": "standalone",
"scope": "/",
"start_url": "/",
"name": "Jatin Sharma",
"short_name": "Jatin Sharma",
"description": "I am currently perusing my Bachelor Degree in Computer Science. I can code in Python, C, C++, etc. I also work on React & Next.js. This is my portfolio which you can install as Progressive Web app.",
"icons": [
{
"src": "icons/maskable_icon_x192.png",
"sizes": "192x192",
"type": "image/x-icon",
"purpose": "maskable"
},
{
"src": "icons/icon-48x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "screenshots/home.png",
"sizes": "411x821",
"type": "image/gif"
},
{
"src": "screenshots/blogs.png",
"sizes": "411x821",
"type": "image/gif"
},
{
"src": "screenshots/projects.png",
"sizes": "411x821",
"type": "image/gif"
},
{
"src": "screenshots/stats.png",
"sizes": "411x821",
"type": "image/gif"
},
{
"src": "screenshots/utilities.png",
"sizes": "411x821",
"type": "image/gif"
},
{
"src": "screenshots/contact.png",
"sizes": "411x821",
"type": "image/gif"
}
],
"shortcuts": [
{
"name": "Blogs",
"url": "/blogs",
"icons": [
{
"src": "shortcuts/blog.png",
"sizes": "202x202",
"type": "image/png",
"purpose": "any"
}
]
},
{
"name": "About me",
"url": "/about",
"icons": [
{
"src": "shortcuts/about.png",
"sizes": "202x202",
"type": "image/png",
"purpose": "any"
}
]
},
{
"name": "Newsletter",
"url": "/newsletter",
"icons": [
{
"src": "shortcuts/newsletter.png",
"sizes": "202x202",
"type": "image/png",
"purpose": "any"
}
]
}
]
}
================================================
FILE: public/robots.txt
================================================
User-agent: *
Sitemap: https://jatin.vercel.app/sitemap.xml
================================================
FILE: sanity/.eslintrc
================================================
{
"extends": "@sanity/eslint-config-studio"
}
================================================
FILE: sanity/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Dependencies
/node_modules
/.pnp
.pnp.js
# Compiled Sanity Studio
/dist
# Temporary Sanity runtime, generated by the CLI on every dev server start
/.sanity
# Logs
/logs
*.log
# Coverage directory used by testing tools
/coverage
# Misc
.DS_Store
*.pem
# Typescript
*.tsbuildinfo
# Dotenv and similar local-only files
*.local
================================================
FILE: sanity/README.md
================================================
# Sanity Blogging Content Studio
Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend.
Now you can do the following things:
- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
- Check out the example frontend: [React/Next.js](https://github.com/sanity-io/tutorial-sanity-blog-react-next)
- [Read the blog post about this template](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=readme)
- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
================================================
FILE: sanity/gist.md.ndjson
================================================
{"_type":"post", "title":"ruby rails","_createdAt":"2023-05-26T21:06:53Z","publishedAt":"2023-05-26T21:06:53Z","body":[{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Ruby is a programming language that is often praised for its simplicity, elegance, and expressiveness. Just like any other programming language, it's important to focus on writing good quality code to ensure that the applications we build are reliable, scalable, and easy to maintain. Writing high-quality, maintainable Ruby code is essential for developing applications. Performing regular code reviews and using code quality tools can help catch problems early and improve your code over time. This article explores some of the best options for Ruby code reviews and quality analysis."}]},{"_type":"block","markDefs":[],"style":"h2","children":[{"_type":"span","marks":[],"text":"Introduction to Ruby on Rails"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://aldibs.com/wp-content/uploads/2020/01/ruby-on-rails-development.jpg","alt":"Differences between Ruby and Ruby on Rails - Aldibs Software Solutions"}}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Ruby on Rails, or simply Rails, is an open-source web application framework written in Ruby. It is designed to make programming web applications easier and faster."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"The main benefits of using Rails for building web applications are:"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":["strong"],"text":"Productivity"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails make developers very productive through conventions, generators, migrations and other tools. Features can be built quickly."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":["strong"],"text":"Convention over configuration"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails has opinions about the best way to do things and configure itself by default. This means less time spent configuring and more time writing code."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":["strong"],"text":"Elegant design"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails enforces an elegant object-oriented design that minimizes repetition and coupling through the use of DRY principles."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":["strong"],"text":"Simple syntax"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Ruby's syntax is clean and straightforward, making Rails code readable and enjoyable to write."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":["strong"],"text":"MVC architecture"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails embraces and implements the MVC pattern cleanly, separating application concerns into models, views and controllers."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":["strong"],"text":"Support for testing"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails makes it easy to write automated tests, allowing developers to refactor code quickly and safely."}]},{"_type":"block","markDefs":[],"style":"h2","children":[{"_type":"span","marks":[],"text":"Importance of Code Review in Ruby on Rails Development"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Code reviews are an important practice in software development that can:"}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Maintain code quality"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"By having another set of eyes review the code, issues like inefficient algorithms, poor variable names, unnecessary complexity, and bad practices can be caught and corrected before the code is merged. This helps maintain a consistent code style and quality standard."}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Improve collaboration"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"The code review process involves a discussion between reviewers and authors, sharing knowledge and best practices. This fosters communication and collaboration between team members."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://mobisoftinfotech.com/resources/wp-content/uploads/2022/01/og-code-quality.png","alt":"Best Practices to Ensure Better Code Quality of Your Software in 2023"}}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Reduce bugs"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Catching bugs early during code reviews is much cheaper and faster than fixing them later after the code has been merged and released. This significantly improves software reliability."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://www.bigscal.com/wp-content/uploads/2022/08/6-Proven-Tips-To-Prevent-Software-Bugs-For-Developers.png","alt":"6 Proven Tips To Prevent Software Bugs For Developers"}}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Opportunities for refactoring"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Reviewers may spot opportunities to refactor or restructure code in a more maintainable way. This can improve the design and architecture of the codebase over time."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://media.geeksforgeeks.org/wp-content/cdn-uploads/20200922214720/Red-Green-Refactoring.png","alt":"7 Code Refactoring Techniques in Software Engineering - GeeksforGeeks"}}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Share knowledge"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Junior developers can learn good coding practices and design patterns by reviewing the code of more experienced developers, and vice versa."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://asphn.org/wp-content/uploads/2019/12/encouraging-teamwork-through-knowledge-sharing.jpg","alt":"Encouraging Teamwork Through Knowledge Sharing - ASPHN"}}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Performance issues are identified"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Reviewers may spot performance bottlenecks, inefficient algorithms or resource usage issues that impact performance. These can be addressed before the code is merged."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://www.searchenginejournal.com/wp-content/uploads/2022/10/website-performance-and-health-monitoring-635945792a855-sej.png","alt":"Website Performance & Health Monitoring: Tips & Best Practices"}}]},{"_type":"block","markDefs":[],"style":"h2","children":[{"_type":"span","marks":[],"text":"Common Challenges in Ruby on Rails Code"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"When working with Ruby on Rails code, developers often face various challenges that can impact the overall quality of their applications. These challenges can range from performance bottlenecks and scalability concerns to security vulnerabilities. Addressing these common challenges is crucial."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Here are some common challenges developers face when working with Ruby on Rails code:"}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Performance bottlenecks"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Performance bottlenecks in Ruby on Rails occur when certain parts of the code or system architecture significantly slow down the application's performance. Let's consider an example to illustrate this:"}]},{"_type":"code","code":"# Slow query due to inefficient use of ActiveRecord @users = User.all @users.each do |user| user.orders.each do |order| # Perform some calculations or operations on each order end end # Optimized query @users = User.includes(:orders) @users.each do |user| user.orders.each do |order| # Perform some calculations or operations on each order end end ","language":"ruby"},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"The above-unoptimized code, makes a separate database query to fetch each user's orders, resulting in N + 1 queries total where N is the number of users."}]},{"_type":"block","markDefs":[{"_key":"6629c84eb71f","_type":"link","href":"https://www.allerin.com/blog/eager-loading-inwith-rails"}],"style":"normal","children":[{"_type":"span","marks":[],"text":"The optimized code uses "},{"_type":"span","marks":["6629c84eb71f"],"text":"eager loading"},{"_type":"span","marks":[],"text":" to fetch all user data and all order data in 2 queries. This reduces the number of queries from N + 1 to a constant number, improving performance."}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Scalability concerns"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"As traffic and data volumes increase, Rails applications can struggle to scale vertically on a single server. Scaling horizontally across multiple servers and databases requires additional configuration and tooling."}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Security vulnerabilities"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Like any framework, Rails has had security vulnerabilities over the years that require patching. Developers must keep dependencies up to date and follow secure coding practices."}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Code readability problems"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails aims for convention over configuration, but this can result in \"magic\" that makes the code less readable for new developers. Over time, the codebase can become cluttered and difficult to navigate."}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Dependency management"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails has a large number of dependencies that must be managed and kept up to date. Outdated dependencies can introduce security vulnerabilities or cause compatibility issues."}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Database migration issues"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"Rails uses Migrations to manage database schema changes, but over time these can become complex and difficult to maintain. Schema refactoring may be required."}]},{"_type":"block","markDefs":[],"style":"h2","children":[{"_type":"span","marks":[],"text":"Tools for Ruby on Rails Code Review"}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"RuboCop"}]},{"_type":"block","markDefs":[{"_key":"74f88b388894","_type":"link","href":"https://docs.rubocop.org/"},{"_key":"54532bf690d6","_type":"link","href":"https://rubystyle.guide/"}],"style":"normal","children":[{"_type":"span","marks":["74f88b388894"],"text":"RuboCop"},{"_type":"span","marks":[],"text":" is a Ruby static code analyzer (a.k.a. "},{"_type":"span","marks":["code"],"text":"linter"},{"_type":"span","marks":[],"text":") and code formatter. Out of the box it will enforce many of the guidelines outlined in the community "},{"_type":"span","marks":["54532bf690d6"],"text":"Ruby Style Guide"},{"_type":"span","marks":[],"text":". Apart from reporting the problems discovered in your code, RuboCop can also automatically fix many of them for you."}]},{"_type":"code","code":"rubocop # Check entire project rubocop --auto-correct # Automatically correct offenses rubocop --only Rails/ActionFilter # Check specific cop ","language":"bash"},{"_type":"block","markDefs":[{"_key":"89af21b60def","_type":"link","href":"https://www.youtube.com/watch?v=sfOGjcMVQ9U"}],"style":"normal","children":[{"_type":"span","marks":[],"text":"{% youtube "},{"_type":"span","marks":["89af21b60def"],"text":"https://www.youtube.com/watch?v=sfOGjcMVQ9U"},{"_type":"span","marks":[],"text":" %}"}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Reek"}]},{"_type":"block","markDefs":[{"_key":"4263f2a002fc","_type":"link","href":"https://github.com/troessner/reek"}],"style":"normal","children":[{"_type":"span","marks":["4263f2a002fc"],"text":"Reek"},{"_type":"span","marks":[],"text":" is a code smell detection tool for Ruby that helps identify potential design issues. It analyzes your codebase and provides feedback on areas that might benefit from refactoring or improvement. Here's an overview of what Reek is and how to use it:"}]},{"_type":"code","code":"reek app/ # Check whole app directory reek -c reek.yml app/models/*.rb # Check models with config ","language":"bash"},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://opengraph.githubassets.com/187baac8e516274a2e32a4628ec25f8c7dc2a032e5148d702dfb3032f15fb15e/troessner/reek","alt":"GitHub - troessner/reek: Code smell detector for Ruby"}}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Brakeman"}]},{"_type":"block","markDefs":[{"_key":"e991fa49d846","_type":"link","href":"https://brakemanscanner.org/"}],"style":"normal","children":[{"_type":"span","marks":["e991fa49d846"],"text":"Brakeman"},{"_type":"span","marks":[],"text":" is a static analysis security vulnerability scanner for Ruby on Rails applications. It finds potential security issues in Rails applications by examining the Ruby code. Brakeman helps find and fix security holes before deploying your Rails app."}]},{"_type":"code","code":"brakeman # To run brakeman brakeman your_rails_app # Add path of the folder brakeman -A # To run all the checks brakeman -q # To suppress informational warning ","language":"bash"},{"_type":"block","markDefs":[{"_key":"7687fc3fab29","_type":"link","href":"https://youtu.be/DHHHnPwSY5I"}],"style":"normal","children":[{"_type":"span","marks":[],"text":"{% youtube "},{"_type":"span","marks":["7687fc3fab29"],"text":"https://youtu.be/DHHHnPwSY5I"},{"_type":"span","marks":[],"text":" %}"}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"RSpec"}]},{"_type":"block","markDefs":[{"_key":"73d2397ddd27","_type":"link","href":"https://rspec.info/"},{"_key":"2da17a72c7d0","_type":"link","href":"https://en.wikipedia.org/wiki/Behavior-driven_development"}],"style":"normal","children":[{"_type":"span","marks":["73d2397ddd27"],"text":"RSpec"},{"_type":"span","marks":[],"text":" is a testing framework for Ruby that is widely used in the Ruby on Rails community. It allows developers to write and execute automated tests. RSpec promotes "},{"_type":"span","marks":["2da17a72c7d0"],"text":"behavior-driven development"},{"_type":"span","marks":[],"text":" (BDD) by providing a readable syntax for describing the expected behavior of the application."}]},{"_type":"code","code":"bundle exec rspec # Run all spec files bundle exec rspec -fd # Run specs with failures/pending bundle exec rspec path/to/spec.rb # Run specific spec ","language":"bash"},{"_type":"block","markDefs":[{"_key":"cfa7efa76f0d","_type":"link","href":"https://www.youtube.com/watch?v=-uhFA74eBG0"}],"style":"normal","children":[{"_type":"span","marks":[],"text":"{% youtube "},{"_type":"span","marks":["cfa7efa76f0d"],"text":"https://www.youtube.com/watch?v=-uhFA74eBG0"},{"_type":"span","marks":[],"text":" %}"}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Flay"}]},{"_type":"block","markDefs":[{"_key":"5cd38947563e","_type":"link","href":"https://github.com/seattlerb/flay"}],"style":"normal","children":[{"_type":"span","marks":["5cd38947563e"],"text":"Flay"},{"_type":"span","marks":[],"text":" analyzes ruby code for structural similarities. Differences in literal values, names, whitespace, and programming style are all ignored. Flay helps reduce code duplication and keep your code DRY (Don't Repeat Yourself)."}]},{"_type":"code","code":"flay . # Analyzes the code within the current directory flay --diff . # Shows a side-by-side diff of the duplicated code ","language":"bash"},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"image","asset":{"src":"https://ik.imagekit.io/analysistools/flay_https_3A_2F_2Fruby.sadi.st_2FFlay.html_rS3Inq4vnl.jpg","alt":"flay, a linter for Ruby - Rating And 47 Alternatives | Analysis Tools"}}]},{"_type":"block","markDefs":[],"style":"h3","children":[{"_type":"span","marks":[],"text":"Wrapping up"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"In conclusion, code reviews are essential for high-quality Ruby on Rails applications. They catch issues early, improve consistency and transfer knowledge between developers. Though time-consuming initially, code reviews save much more time by reducing bugs and improving maintainability. Start small with partial code reviews and expand coverage over time as the team adapts. The benefits to code quality, productivity and sustainability make code reviews a best practice for any Rails development process. Implementing regular code reviews should be a top priority for any team-building."}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"If you want more articles on similar topics just let me know in the comments section. And don't forget to ❤️ the article. I'll see you in the next one. In the meantime you can follow me here:"}]},{"_type":"block","markDefs":[],"style":"normal","children":[{"_type":"span","marks":[],"text":"{% user j471n %}"}]},{"_type":"block","markDefs":[{"_key":"ce4c547cffd6","_type":"link","href":"https://res.cloudinary.com/practicaldev/image/fetch/s--haJKPmN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media0.giphy.com/media/65ODDoEMHM5wbGDHWT/giphy.gif%3Fcid%3Decf05e472rut0ytp29eznos4focko84kmq33u4btpu74cw1w%26rid%3Dgiphy.gif"}],"style":"normal","children":[{"_type":"span","marks":[],"text":""}]}]}
================================================
FILE: sanity/package.json
================================================
{
"name": "j471n-blog",
"private": true,
"version": "1.0.0",
"main": "package.json",
"license": "UNLICENSED",
"scripts": {
"dev": "sanity dev",
"start": "sanity start",
"build": "sanity build",
"deploy": "sanity deploy",
"deploy-graphql": "sanity graphql deploy"
},
"keywords": [
"sanity"
],
"dependencies": {
"@dnd-kit/sortable": "^7.0.2",
"@sanity/vision": "^3.12.2",
"dotenv": "^16.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"sanity": "^3.12.2",
"styled-components": "^5.3.9"
},
"devDependencies": {
"@sanity/eslint-config-studio": "^2.0.1",
"@types/react": "^18.0.25",
"@types/styled-components": "^5.1.26",
"eslint": "^8.6.0",
"prettier": "^2.8.8",
"typescript": "^4.9.5"
},
"prettier": {
"semi": false,
"printWidth": 100,
"bracketSpacing": false,
"singleQuote": true
}
}
================================================
FILE: sanity/sanity.cli.ts
================================================
import {defineCliConfig} from '@sanity/cli'
export default defineCliConfig({
api: {
projectId: '5ec749u0',
dataset: 'production',
},
})
================================================
FILE: sanity/sanity.config.ts
================================================
// import {defineConfig} from 'sanity/lib/exports' // ONLY USE WHILE BUILDING THE APP
import {defineConfig} from 'sanity' // <==== USE THIS FOR LOCAL
import {deskTool} from 'sanity/desk'
import {schemaTypes} from './schemas'
import {visionTool} from '@sanity/vision'
export default defineConfig({
name: 'default',
title: 'j471n-blog',
projectId: '5ec749u0',
dataset: 'production',
plugins: [deskTool(), visionTool()],
schema: {
types: schemaTypes,
},
})
================================================
FILE: sanity/schemas/author.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'author',
title: 'Author',
type: 'document',
fields: [
defineField({
name: 'name',
title: 'Name',
type: 'string',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'name',
maxLength: 96,
},
}),
defineField({
name: 'image',
title: 'Image',
type: 'image',
options: {
hotspot: true,
},
}),
defineField({
name: 'bio',
title: 'Bio',
type: 'array',
of: [
{
title: 'Block',
type: 'block',
styles: [{title: 'Normal', value: 'normal'}],
lists: [],
},
],
}),
],
preview: {
select: {
title: 'name',
media: 'image',
},
},
})
================================================
FILE: sanity/schemas/blockContent.ts
================================================
import {defineType, defineArrayMember} from 'sanity'
/**
* This is the schema definition for the rich text fields used for
* for this blog studio. When you import it in schemas.js it can be
* reused in other parts of the studio with:
* {
* name: 'someName',
* title: 'Some title',
* type: 'blockContent'
* }
*/
export default defineType({
title: 'Block Content',
name: 'blockContent',
type: 'array',
of: [
defineArrayMember({
title: 'Block',
type: 'block',
// Styles let you set what your user can mark up blocks with. These
// correspond with HTML tags, but you can set any title or value
// you want and decide how you want to deal with it where you want to
// use your content.
styles: [
{title: 'Normal', value: 'normal'},
{title: 'H1', value: 'h1'},
{title: 'H2', value: 'h2'},
{title: 'H3', value: 'h3'},
{title: 'H4', value: 'h4'},
{title: 'Quote', value: 'blockquote'},
],
lists: [{title: 'Bullet', value: 'bullet'}],
// Marks let you mark up inline text in the block editor.
marks: {
// Decorators usually describe a single property – e.g. a typographic
// preference or highlighting by editors.
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
],
// Annotations can be any object structure – e.g. a link or a footnote.
annotations: [
{
title: 'URL',
name: 'link',
type: 'object',
fields: [
{
title: 'URL',
name: 'href',
type: 'url',
},
],
},
],
},
}),
// You can add additional types here. Note that you can't use
// primitive types such as 'string' and 'number' in the same array
// as a block type.
defineArrayMember({
type: 'image',
options: {hotspot: true},
}),
],
})
================================================
FILE: sanity/schemas/category.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'category',
title: 'Category',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
defineField({
name: 'description',
title: 'Description',
type: 'text',
}),
],
})
================================================
FILE: sanity/schemas/epigraph.ts
================================================
import {defineField, defineType, defineArrayMember} from 'sanity'
const SOURCE_TYPES = [
{title: 'Book', value: 'book'},
{title: 'Movie', value: 'movie'},
{title: 'TV Show', value: 'tvShow'},
{title: 'Person', value: 'person'},
{title: 'Song / Lyrics', value: 'song'},
{title: 'Podcast', value: 'podcast'},
{title: 'Other', value: 'other'},
]
export default defineType({
name: 'epigraph',
title: 'Epigraph',
type: 'document',
fields: [
defineField({
name: 'quote',
title: 'Quote / Passage',
type: 'text',
description: 'The quote, stanza, or passage you want to preserve.',
validation: (Rule) => Rule.required(),
}),
defineField({
name: 'sourceType',
title: 'Source Type',
type: 'string',
options: {
list: SOURCE_TYPES,
layout: 'radio',
},
validation: (Rule) => Rule.required(),
}),
defineField({
name: 'sourceTitle',
title: 'Source Title',
type: 'string',
description: 'Book name, movie title, show name, song name, podcast name, or person name.',
validation: (Rule) => Rule.required(),
}),
defineField({
name: 'sourceMeta',
title: 'Source Meta',
type: 'string',
description:
'Author (book), artist (song), director (movie), host (podcast), context (person), etc.',
}),
defineField({
name: 'speaker',
title: 'Speaker / Character',
type: 'string',
description:
'Character or person who said it (e.g. character in a book/movie, the person themselves).',
}),
defineField({
name: 'year',
title: 'Year',
type: 'number',
description: 'Release or publication year (optional).',
}),
{
name: 'tags',
title: 'Tags',
type: 'array' as const,
of: [{type: 'string' as const}],
options: {
layout: 'tags',
},
description: 'e.g. "life", "motivation", "love", "philosophy"',
},
defineField({
name: 'addedAt',
title: 'Added At',
type: 'datetime',
initialValue: () => new Date().toISOString(),
}),
],
preview: {
select: {
title: 'quote',
subtitle: 'sourceTitle',
sourceType: 'sourceType',
},
prepare({title, subtitle, sourceType}) {
const label = SOURCE_TYPES.find((s) => s.value === sourceType)?.title ?? sourceType
return {
title: title ? (title.length > 80 ? title.slice(0, 80) + '…' : title) : 'Untitled',
subtitle: subtitle ? `${label} — ${subtitle}` : label,
}
},
},
})
================================================
FILE: sanity/schemas/index.ts
================================================
import author from './author'
import blockContent from './blockContent'
import category from './category'
import language from './language'
import epigraph from './epigraph'
import organization from './organization'
import post from './post'
import snippet from './snippet'
import static_page from './static_page'
export const schemaTypes = [
post,
author,
category,
blockContent,
organization,
snippet,
language,
static_page,
epigraph,
]
================================================
FILE: sanity/schemas/language.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'language',
title: 'Language',
type: 'document',
fields: [
defineField({
name: 'name',
title: 'Name',
type: 'string',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'name',
maxLength: 96,
},
}),
defineField({
name: 'image',
title: 'Image',
type: 'image',
options: {
hotspot: true,
},
}),
],
preview: {
select: {
title: 'name',
media: 'image',
},
},
})
================================================
FILE: sanity/schemas/organization.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'organization',
title: 'Organization',
type: 'document',
fields: [
defineField({
name: 'name',
title: 'Name',
type: 'string',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'name',
maxLength: 96,
},
}),
defineField({
name: 'website',
title: 'Website',
type: 'string',
}),
defineField({
name: 'image',
title: 'Image',
type: 'image',
options: {
hotspot: true,
},
}),
defineField({
name: 'bio',
title: 'Bio',
type: 'array',
of: [
{
title: 'Block',
type: 'block',
styles: [{title: 'Normal', value: 'normal'}],
lists: [],
},
],
}),
],
preview: {
select: {
title: 'name',
media: 'image',
},
},
})
================================================
FILE: sanity/schemas/post.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'post',
title: 'Post',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
}),
defineField({
name: 'keywords',
title: 'Keywords',
type: 'string',
}),
defineField({
name: 'excerpt',
title: 'Excerpt',
type: 'text',
}),
defineField({
name: 'author',
title: 'Author',
type: 'reference',
to: {type: 'author'},
}),
defineField({
name: 'organization',
title: 'Organization',
type: 'reference',
to: {type: 'organization'},
}),
defineField({
name: 'mainImage',
title: 'Main image',
type: 'image',
options: {
hotspot: true,
},
}),
defineField({
name: 'categories',
title: 'Categories',
type: 'array',
of: [{type: 'reference', to: {type: 'category'}}],
}),
defineField({
name: 'publishedAt',
title: 'Published at',
type: 'datetime',
}),
defineField({
name: 'content',
title: 'Content',
type: 'text',
}),
],
preview: {
select: {
title: 'title',
author: 'author.name',
media: 'mainImage',
},
prepare(selection) {
const {author} = selection
return {...selection, subtitle: author && `by ${author}`}
},
},
})
================================================
FILE: sanity/schemas/snippet.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'snippet',
title: 'Snippet',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
}),
defineField({
name: 'excerpt',
title: 'Excerpt',
type: 'text',
}),
defineField({
name: 'language',
title: 'Language',
type: 'reference',
to: {type: 'language'},
}),
defineField({
name: 'publishedAt',
title: 'Published at',
type: 'datetime',
}),
defineField({
name: 'content',
title: 'Content',
type: 'text',
}),
],
preview: {
select: {
title: 'title',
language: 'language.name',
media: 'language.image',
},
prepare(selection) {
const {language} = selection
return {...selection, subtitle: language}
},
},
})
================================================
FILE: sanity/schemas/static_page.ts
================================================
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'static_page',
title: 'Static Page',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
}),
defineField({
name: 'keywords',
title: 'Keywords',
type: 'string',
}),
defineField({
name: 'excerpt',
title: 'Excerpt',
type: 'text',
}),
defineField({
name: 'mainImage',
title: 'Main image',
type: 'image',
options: {
hotspot: true,
},
}),
defineField({
name: 'publishedAt',
title: 'Published at',
type: 'datetime',
}),
defineField({
name: 'content',
title: 'Content',
type: 'text',
}),
],
preview: {
select: {
title: 'title',
media: 'mainImage',
},
prepare(selection) {
return {...selection}
},
},
})
================================================
FILE: sanity/static/.gitkeep
================================================
Files placed here will be served by the Sanity server under the `/static`-prefix
================================================
FILE: sanity/tailwind.config.js
================================================
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}
================================================
FILE: sanity/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
================================================
FILE: sanity.cli.ts
================================================
import { defineCliConfig } from "@sanity/cli";
export default defineCliConfig({
api: {
projectId: "5ec749u0",
dataset: "production",
},
});
================================================
FILE: styles/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: "Barlow";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Barlow/Barlow-400.woff2) format("woff2");
}
@font-face {
font-family: "Barlow";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Barlow/Barlow-500.woff2) format("woff2");
}
@font-face {
font-family: "Barlow";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Barlow/Barlow-600.woff2) format("woff2");
}
@font-face {
font-family: "Barlow";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/Barlow/Barlow-700.woff2) format("woff2");
}
@font-face {
font-family: "Barlow";
font-style: normal;
font-weight: 800;
font-display: swap;
src: url(/fonts/Barlow/Barlow-800.woff2) format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url(/fonts/Inter-var.woff2) format("woff2");
}
@font-face {
font-family: "Sarina";
font-style: normal;
font-weight: normal;
font-display: swap;
src: url(/fonts/Sarina/Sarina-400.woff2) format("woff2");
}
body,
html {
overflow-x: hidden;
scroll-behavior: auto;
}
body::-webkit-scrollbar {
width: 6px;
}
/* Adding Scroll Margin for top */
* {
scroll-margin-top: 80px;
}
@media screen and (max-width: 640px) {
* {
scroll-margin-top: 60px;
}
body::-webkit-scrollbar {
width: 2px;
}
}
pre::-webkit-scrollbar {
display: none;
}
body.dark {
background-color: #181a1b;
}
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: #b3b3b3;
}
.dark::-webkit-scrollbar-thumb {
background-color: #393e41;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.lock-scroll {
overflow: hidden !important;
}
.horizontal-scrollbar::-webkit-scrollbar {
height: 3px;
border-redius: 999px;
}
/* For preventing the blue highlight color box on tap(click) */
* {
-webkit-tap-highlight-color: transparent;
}
.auto-row {
-webkit-margin-before: auto;
margin-block-start: auto;
}
/* Code Line Highlighting START */
code {
counter-reset: line;
}
code span.line {
padding: 0px 12px;
border-left: 4px solid transparent;
}
span.line > span {
padding: 2px 0;
width: 100%;
}
span.line > span:last-child {
padding-right: 50px;
}
code > .line::before {
counter-increment: line;
content: counter(line);
/* Other styling */
display: inline-block;
width: 1rem;
margin-right: 1rem;
text-align: right;
color: gray;
font-weight: 500;
border-right: 4px solid transparent;
}
.highlighted {
background: rgba(200, 200, 255, 0.1);
border-left: 4px solid #3777de !important;
filter: saturate(1.5);
}
/* Code Line Highlighting ENDS */
/* Nprogress bar Custom Styling (force) : STARTS */
#nprogress .bar {
background-color: rgba(0, 89, 255, 0.7) !important;
height: 3px !important;
}
.dark #nprogress .bar {
background: #fff !important;
}
#nprogress .peg {
box-shadow: none !important;
}
/* Nprogress bar Custom Styling (force) : ENDS */
.blogGrid {
display: grid;
grid-template-columns: 300px 1fr;
grid-template-rows: 1fr;
}
.blog-pre {
border-radius: 0;
}
.blog-pre > code {
width: 100%;
display: flex;
flex-direction: column;
}
/* Dual-theme switching: show light by default, dark when body.dark is set */
div[data-theme="dark"] {
display: none;
}
div[data-theme="light"] {
display: block;
}
.dark div[data-theme="dark"] {
display: block;
}
.dark div[data-theme="light"] {
display: none;
}
.react-activity-calendar__calendar {
width: 100%;
height: 100%;
}
.react-activity-calendar {
max-width: 100% !important;
}
@keyframes loadingAnimation {
0% {
left: -100%; /* Move the bar to the left of the container */
}
100% {
left: 100%; /* Move the bar to the right of the container */
}
}
@keyframes pulse-slow {
0%,
100% {
opacity: 0.2;
transform: scale(1);
}
50% {
opacity: 0.3;
transform: scale(1.05);
}
}
.animate-pulse-slow {
animation: pulse-slow 4s ease-in-out infinite;
}
/* Skills marquee scroll */
@keyframes marquee {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.animate-marquee {
animation: marquee 40s linear infinite;
will-change: transform;
}
/* Hero section diagonal stripe decorator */
.hero-stripe {
background-image: repeating-linear-gradient(
-45deg,
transparent,
transparent 16px,
rgba(0, 0, 0, 0.03) 16px,
rgba(0, 0, 0, 0.03) 17px
);
}
.dark .hero-stripe {
background-image: repeating-linear-gradient(
-45deg,
transparent,
transparent 16px,
rgba(255, 255, 255, 0.03) 16px,
rgba(255, 255, 255, 0.03) 17px
);
}
/* Layers Components or the custom class extends with tailwind */
@layer components {
.bottom_nav_icon {
@apply mb-[2px] text-2xl cursor-pointer;
}
.top-nav-link {
@apply list-none mx-1 px-3 py-1 border-black dark:border-white transition-all duration-200 hover:rounded-md hover:bg-gray-100 dark:hover:bg-darkSecondary cursor-pointer text-lg font-semibold select-none sm:text-sm md:text-base;
}
.contact_field {
@apply text-sm font-medium text-black dark:text-white w-full px-4 py-2 m-2 rounded-md border-none outline-none shadow-inner shadow-slate-200 dark:shadow-zinc-800 focus:ring-1 focus:ring-purple-500 dark:bg-darkPrimary dark:placeholder-gray-500;
}
.title_of_page {
@apply text-center text-xl font-bold dark:bg-darkPrimary dark:text-gray-100;
}
.icon {
@apply text-2xl sm:text-3xl m-1 transform duration-200 lg:hover:scale-150 text-zinc-500 hover:text-zinc-800 dark:hover:text-white cursor-pointer;
}
.page_container {
@apply p-5 md:px-24 pb-10 dark:bg-darkPrimary dark:text-gray-200 grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 3xl:grid-cols-5;
}
.blog_bottom_icon {
@apply text-3xl p-1 bg-gray-100 dark:bg-darkSecondary sm:bg-transparent ring-1 dark:ring-gray-500 ring-gray-300 sm:hover:bg-gray-100 rounded-md cursor-pointer ml-1;
}
.blog_bottom_button {
@apply block sm:hidden py-1 w-full lg:hover:bg-gray-300 cursor-pointer bg-gray-200 rounded-md transform duration-100 active:scale-90 select-none;
}
.user_reaction {
@apply flex font-semibold items-center cursor-pointer w-full justify-center sm:justify-start sm:w-auto space-x-1 text-base;
}
.project_link {
@apply text-center bg-gray-200 p-2 my-1 rounded-full dark:bg-darkSecondary dark:text-white cursor-pointer shadow dark:shadow-gray-500;
}
.clickable_button {
@apply transform duration-100 active:scale-90 lg:hover:scale-105;
}
.home-section-container {
@apply flex gap-2 overflow-x-scroll p-5 md:px-24 w-full min-h-[200px] select-none snap-x lg:snap-none;
}
.home-content-section {
@apply relative min-w-[250px] xl:min-w-[300px] break-words shadow shadow-black/20 dark:shadow-white/20 dark:bg-darkSecondary ring-gray-400 rounded-xl p-3 cursor-pointer select-none lg:hover:scale-105 scale-95 transition bg-white snap-center lg:snap-align-none md:first:ml-24 md:last:mr-24;
}
.blog-hover-button {
@apply flex items-center space-x-2 border-2 border-white dark:border-zinc-600 px-3 py-1 font-semibold w-min text-white dark:text-white hover:bg-white dark:hover:bg-zinc-600 hover:text-black;
}
.hover-slide-animation {
@apply relative overflow-hidden before:absolute before:h-full before:w-40 before:bg-stone-900 dark:before:bg-gray-50 before:opacity-10 dark:before:opacity-5 before:-right-10 before:-z-10 before:rotate-[20deg] before:scale-y-150 before:top-4 hover:before:scale-[7] before:duration-700;
}
.pageTop {
@apply mt-[44px] md:mt-[60px] max-w-4xl 2xl:max-w-5xl 3xl:max-w-7xl relative mx-auto p-4 mb-10 text-neutral-900 dark:text-neutral-200;
}
.utilities-svg {
@apply !pointer-events-none mb-0 w-8 h-8;
}
.card {
@apply bg-white dark:bg-darkSecondary p-5 sm:p-10 flex flex-col sm:flex-row gap-8 items-center max-w-2xl shadow-md rounded-lg mt-[30%] sm:mt-8 transition-all;
}
.blog-container {
@apply !w-full dark:text-neutral-400 my-5 font-medium;
}
.prose-typography {
@apply prose dark:prose-invert sm:prose-lg blog-container prose-pre:bg-white dark:prose-pre:bg-darkSecondary prose-pre:saturate-150 dark:prose-pre:saturate-100 marker:text-black dark:marker:text-white prose-img:mx-auto prose-img:rounded-md prose-headings:text-gray-900 dark:prose-headings:text-white prose-p:text-gray-700 dark:prose-p:text-gray-300 prose-a:text-gray-900 dark:prose-a:text-white prose-strong:text-gray-900 dark:prose-strong:text-white prose-code:text-black dark:prose-code:text-gray-200 prose-blockquote:border-gray-300 dark:prose-blockquote:border-gray-700 prose-blockquote:text-gray-600 dark:prose-blockquote:text-gray-400;
}
}
@layer base {
body {
@apply font-inter bg-darkWhite;
}
button {
@apply outline-none;
}
hr {
@apply !mx-auto !w-1/2 h-0.5 !bg-gray-700 dark:!bg-gray-300 border-0 !rounded-full;
}
table {
@apply !border-collapse text-left;
}
table thead tr > th,
table tbody tr > td {
@apply !p-2 border border-gray-400 align-middle;
}
table thead tr > th {
@apply text-black dark:text-white;
}
table thead tr {
@apply align-text-top;
}
table th {
@apply font-bold;
}
table a {
@apply !text-blue-500 dark:!text-blue-400;
}
strong {
@apply !text-black dark:!text-white !font-bold;
}
/* For Blog page to remove the underline */
h2 > a,
h3 > a,
h4 > a,
h5 > a,
h6 > a {
@apply !text-black dark:!text-white !font-bold !no-underline;
}
}
@layer utilities {
/* Hiding the arrows in the input number */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
================================================
FILE: tailwind.config.js
================================================
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
"./layout/*.{js,jsx,ts,tsx}",
],
darkMode: "class", // or 'media' or 'class'
theme: {
fontFamily: {
inter: ["Inter", "sans-serif"],
sarina: ["Sarina", "cursive"],
barlow: ["Barlow", "sans-serif"],
mono: ["monospace"],
},
extend: {
colors: {
darkPrimary: "#181A1B",
darkSecondary: "#25282A",
darkWhite: "#f2f5fa",
"dark-3": "#b8b8b8",
},
listStyleType: {
square: "square",
roman: "upper-roman",
},
animation: {
wiggle: "wiggle 1s ease-in-out infinite",
"photo-spin": "photo-spin 2s 1 linear forwards",
},
keyframes: {
wiggle: {
"0%, 100%": { transform: "rotate(-3deg)" },
"50%": { transform: "rotate(3deg)" },
},
"photo-spin": {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" },
},
},
screens: {
"3xl": "2000px",
xs: "480px",
},
},
},
plugins: [
require("@tailwindcss/line-clamp"),
require("@tailwindcss/typography"),
],
};
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"module": "commonjs", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */
"baseUrl": ".", /* Specify the base directory to resolve non-relative module names. */
"paths": {
"@components/*": [
"components/*"
],
"@lib/*": [
"lib/*"
],
"@utils/*": [
"utils/*"
],
"@content/*": [
"content/*"
],
"@styles/*": [
"styles/*"
],
"@context/*": [
"context/*"
],
"@layout/*": [
"layout/*"
],
"@hooks/*": [
"hooks/*"
]
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */
"strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"noEmit": true,
"incremental": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"pages/sitemap.xml.jsx"
],
"exclude": [
"node_modules",
"sanity/**/*"
]
}
================================================
FILE: utils/date.ts
================================================
/* Formats a date as a string in the format 'Month day, year'. */
export const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
export function getFormattedDate(date: Date): string {
return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
}
================================================
FILE: utils/functions.ts
================================================
/**
* Locks the scroll of the document by adding a 'lock-scroll' class to the html element.
* The 'lock-scroll' class should be defined in a global stylesheet and contain styles for disabling scrolling.
*/
export function lockScroll() {
const root = document.getElementsByTagName("html")[0];
root.classList.toggle("lock-scroll"); // class is define in the global.css
}
/**
* Removes the scroll lock from the document by removing the 'lock-scroll' class from the html element.
*/
export function removeScrollLock() {
const root = document.getElementsByTagName("html")[0];
root.classList.remove("lock-scroll"); // class is define in the global.css
}
/**
* Debounces a function by delaying its execution until a certain amount of time has passed
* since the last time it was invoked. Only the last invocation within the delay period will be executed.
*/
export function debounce(fn: Function, time: number = 300): Function {
let timeoutId: ReturnType;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, time);
};
}
interface QueryParams {
[key: string]: string | number;
}
export function generateUrl(baseUrl: string, queryParams: QueryParams): string {
const queryString = Object.entries(queryParams)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
)
.join("&");
return `${baseUrl}?${queryString}`;
}
================================================
FILE: utils/utils.ts
================================================
export const DEFAULT_IMAGE_URL: string = "https://imgur.com/5dYYce8.png";
export const AvatarImage: string = "https://imgur.com/VAXEwKT.png";
export const homeProfileImage: string = "https://i.imgur.com/fuciSoZ.jpeg";
export const navigationRoutes: string[] = [
"home",
"about",
"stats",
"utilities",
"blogs",
"books",
"certificates",
"projects",
"snippets",
"epigraphs",
"privacy",
"newsletter",
"rss",
];
export const snippetsImages: { [key: string]: string } = {
css: "https://imgur.com/ArD8JIg.png",
js: "https://imgur.com/lFKi8mB.png",
react: "https://imgur.com/m2jv6MK.png",
ts: "https://imgur.com/Ux6L5Uh.png",
supabase: "https://imgur.com/xgNKVQa.png",
};
export const TIME_IN_SECONDS = {
MINUTE: 60,
FIVE_MINUTES: 60 * 5,
TEN_MINUTES: 60 * 10,
FIFTEEN_MINUTES: 60 * 15,
THIRTY_MINUTES: 60 * 30,
ONE_HOUR: 60 * 60,
TWO_HOURS: 60 * 60 * 2,
THREE_HOURS: 60 * 60 * 3,
SIX_HOURS: 60 * 60 * 6,
TWELVE_HOURS: 60 * 60 * 12,
ONE_DAY: 60 * 60 * 24,
ONE_WEEK: 60 * 60 * 24 * 7,
} as const;
================================================
FILE: vercel.json
================================================
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"headers": [
{
"source": "/fonts/Barlow/Barlow-400.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/fonts/Barlow/Barlow-500.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/fonts/Barlow/Barlow-600.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/fonts/Barlow/Barlow-700.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/fonts/Barlow/Barlow-800.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/fonts/Inter-var.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/fonts/Sarina/Sarina-400.woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"redirects": [
{
"source": "/home",
"destination": "/"
},
{
"source": "/rss",
"destination": "/feed.xml"
},
{
"source": "/sitemap",
"destination": "/sitemap.xml"
},
{
"source": "/newsletter",
"destination": "https://j471n.substack.com/"
},
{
"source": "/cv",
"destination": "https://read.cv/j471n"
},
{
"source": "/social",
"destination": "https://linktr.ee/j471n"
}
]
}