├── .eslintrc.json ├── src ├── app │ ├── icon.png │ ├── favicon.ico │ ├── apple-icon.png │ ├── twitter-image.png │ ├── opengraph-image.png │ ├── robot.ts │ ├── (main) │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── blog │ │ │ ├── page.tsx │ │ │ └── [...slug] │ │ │ │ └── page.tsx │ │ ├── projects │ │ │ └── page.tsx │ │ └── tags │ │ │ ├── [tag] │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── sitemap.ts │ ├── feed.xml │ │ └── route.ts │ ├── layout.tsx │ ├── api │ │ ├── views │ │ │ └── [slug] │ │ │ │ └── route.ts │ │ └── contact │ │ │ └── route.ts │ ├── error.tsx │ └── not-found.tsx ├── assets │ ├── svg │ │ ├── index.ts │ │ ├── luma.tsx │ │ └── chain-gpt.tsx │ └── images │ │ ├── cover │ │ ├── luma.png │ │ ├── chain-gpt.png │ │ ├── mint-kuto.png │ │ ├── podportal.png │ │ ├── power-up.png │ │ └── world-ranks.png │ │ ├── mint-kuto.avif │ │ ├── nft-connect.jpg │ │ ├── power-up.webp │ │ ├── world-rank.png │ │ ├── nft-connect.webp │ │ └── f-dubai-police.webp ├── components │ ├── ui │ │ ├── dank-mono.otf │ │ ├── top-loader.tsx │ │ ├── content-not-found.tsx │ │ ├── rss.tsx │ │ ├── fonts.ts │ │ ├── callout.tsx │ │ ├── label.tsx │ │ ├── typograpghy.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── skip-content.tsx │ │ ├── tooltip.tsx │ │ ├── sonner.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── form.tsx │ │ ├── sheet.tsx │ │ └── dropdown-menu.tsx │ ├── mdx │ │ ├── index.ts │ │ ├── custom-image.tsx │ │ ├── mdx-content.tsx │ │ └── custom-link.tsx │ ├── project │ │ ├── index.ts │ │ ├── project-list.tsx │ │ ├── project-icons.tsx │ │ ├── project-item.tsx │ │ └── _project-mock.ts │ ├── post │ │ ├── index.ts │ │ ├── post-comments.tsx │ │ ├── post-list.tsx │ │ ├── post-views.tsx │ │ ├── post-json-schema.tsx │ │ ├── post-item.tsx │ │ ├── post-toc.tsx │ │ └── post-metadata.tsx │ ├── scroll-progress.tsx │ ├── layout │ │ ├── nav │ │ │ ├── index.tsx │ │ │ ├── logo.tsx │ │ │ ├── nav-list.tsx │ │ │ ├── _nav-mock.ts │ │ │ ├── mobile-nav.tsx │ │ │ └── nav-item.tsx │ │ └── footer.tsx │ ├── back-btn.tsx │ ├── tags.tsx │ ├── search-input.tsx │ ├── skills.tsx │ ├── support-btn.tsx │ ├── socials.tsx │ ├── about-section.tsx │ └── contact-us.tsx ├── content │ └── posts │ │ ├── images │ │ └── cover │ │ │ ├── og-modulo-operator.webp │ │ │ └── publishing-react-package.webp │ │ ├── publishing-react-package.mdx │ │ └── javascript-modulo-operator.mdx ├── hooks │ ├── index.ts │ ├── use-isomorphic.tsx │ ├── use-client.tsx │ └── use-media.tsx ├── styles │ ├── globals.css │ └── mdx.css ├── actions │ ├── queries.ts │ └── mutations.ts ├── types │ └── config.ts ├── providers │ ├── react-query.tsx │ └── index.tsx ├── server │ └── db.ts ├── lib │ ├── axios.ts │ ├── utils.ts │ ├── seo.tsx │ └── shadcn-ui.ts ├── schema.ts ├── constants │ ├── env.ts │ ├── anime.ts │ └── stack.tsx └── config.ts ├── public ├── images │ ├── logo.png │ ├── blog │ │ └── reduce-cover.webp │ └── chain-gpt-logo.svg ├── audio │ └── switch-on.mp3 └── static │ ├── og-modulo-operator-a63a03.webp │ └── publishing-react-package-7c738b.webp ├── postcss.config.mjs ├── prisma └── schema.prisma ├── tailwind.config.ts ├── components.json ├── .env.example ├── .github └── ISSUE_TEMPLATE │ └── feature_request.md ├── SECURITY.md ├── tsconfig.json ├── next.config.mjs ├── .gitignore ├── velite.config.ts ├── package.json ├── README.md └── CODE_OF_CONDUCT.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/app/icon.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/public/images/logo.png -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/app/apple-icon.png -------------------------------------------------------------------------------- /src/app/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/app/twitter-image.png -------------------------------------------------------------------------------- /public/audio/switch-on.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/public/audio/switch-on.mp3 -------------------------------------------------------------------------------- /src/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/app/opengraph-image.png -------------------------------------------------------------------------------- /src/assets/svg/index.ts: -------------------------------------------------------------------------------- 1 | export {default as LumaIcon} from './luma' 2 | export {default as ChainGpt} from './chain-gpt' 3 | -------------------------------------------------------------------------------- /src/assets/images/cover/luma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/cover/luma.png -------------------------------------------------------------------------------- /src/assets/images/mint-kuto.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/mint-kuto.avif -------------------------------------------------------------------------------- /src/assets/images/nft-connect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/nft-connect.jpg -------------------------------------------------------------------------------- /src/assets/images/power-up.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/power-up.webp -------------------------------------------------------------------------------- /src/assets/images/world-rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/world-rank.png -------------------------------------------------------------------------------- /src/components/ui/dank-mono.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/components/ui/dank-mono.otf -------------------------------------------------------------------------------- /src/assets/images/nft-connect.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/nft-connect.webp -------------------------------------------------------------------------------- /public/images/blog/reduce-cover.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/public/images/blog/reduce-cover.webp -------------------------------------------------------------------------------- /src/assets/images/cover/chain-gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/cover/chain-gpt.png -------------------------------------------------------------------------------- /src/assets/images/cover/mint-kuto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/cover/mint-kuto.png -------------------------------------------------------------------------------- /src/assets/images/cover/podportal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/cover/podportal.png -------------------------------------------------------------------------------- /src/assets/images/cover/power-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/cover/power-up.png -------------------------------------------------------------------------------- /src/assets/images/f-dubai-police.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/f-dubai-police.webp -------------------------------------------------------------------------------- /src/components/mdx/index.ts: -------------------------------------------------------------------------------- 1 | export {default as MDXContent} from './mdx-content' 2 | export {default as CustomLink} from './custom-link' 3 | -------------------------------------------------------------------------------- /src/assets/images/cover/world-ranks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/assets/images/cover/world-ranks.png -------------------------------------------------------------------------------- /src/components/project/index.ts: -------------------------------------------------------------------------------- 1 | export {default as ProjectList} from './project-list' 2 | export {default as projects} from './_project-mock' 3 | -------------------------------------------------------------------------------- /public/static/og-modulo-operator-a63a03.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/public/static/og-modulo-operator-a63a03.webp -------------------------------------------------------------------------------- /public/static/publishing-react-package-7c738b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/public/static/publishing-react-package-7c738b.webp -------------------------------------------------------------------------------- /src/content/posts/images/cover/og-modulo-operator.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/content/posts/images/cover/og-modulo-operator.webp -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/content/posts/images/cover/publishing-react-package.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/HEAD/src/content/posts/images/cover/publishing-react-package.webp -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {default as useMediaQuery} from './use-media' 2 | export {default as useIsomorphicLayoutEffect} from './use-isomorphic' 3 | export {default as useIsClient} from './use-client' 4 | -------------------------------------------------------------------------------- /src/hooks/use-isomorphic.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useLayoutEffect} from 'react' 2 | 3 | const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect 4 | 5 | export default useIsomorphicLayoutEffect 6 | -------------------------------------------------------------------------------- /src/components/ui/top-loader.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import {AppProgressBar as ProgressBar} from 'next-nprogress-bar' 3 | 4 | const TopLoader = () => { 5 | return 6 | } 7 | 8 | export default TopLoader 9 | -------------------------------------------------------------------------------- /src/hooks/use-client.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react' 2 | 3 | export default function useIsClient() { 4 | const [isClient, setClient] = useState(false) 5 | 6 | useEffect(() => { 7 | setClient(true) 8 | }, []) 9 | 10 | return isClient 11 | } 12 | -------------------------------------------------------------------------------- /src/components/post/index.ts: -------------------------------------------------------------------------------- 1 | export {default as PostList} from './post-list' 2 | export {default as PostComments} from './post-comments' 3 | export {default as TableOfContent} from './post-toc' 4 | export {default as PostMetadata} from './post-metadata' 5 | export {default as JsonSchemaLD} from './post-json-schema' 6 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("POSTGRES_PRISMA_URL") 8 | directUrl = env("POSTGRES_URL_NON_POOLING") 9 | } 10 | 11 | model Views { 12 | slug String @id 13 | count Int @default(0) 14 | } 15 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type {Config} from 'tailwindcss' 2 | import {shadcnPreset} from './src/lib/shadcn-ui' 3 | 4 | const config = { 5 | presets: [shadcnPreset], 6 | content: ['./app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}'], 7 | plugins: [require('@tailwindcss/typography')], 8 | } satisfies Config 9 | 10 | export default config 11 | -------------------------------------------------------------------------------- /src/app/robot.ts: -------------------------------------------------------------------------------- 1 | import {MetadataRoute} from 'next' 2 | import config from '~/config' 3 | 4 | export default function robots(): MetadataRoute.Robots { 5 | return { 6 | rules: { 7 | userAgent: '*', 8 | allow: '/', 9 | disallow: '/private/', 10 | }, 11 | sitemap: `https://${config.domainName}/sitemap.xml`, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .el-focus-styles { 7 | @apply ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2; 8 | } 9 | 10 | ::selection { 11 | @apply bg-ring text-white; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/actions/queries.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { fetchFunc } from '~/lib/axios' 3 | 4 | type TPostView = { views: { slug: string; count: number } } 5 | 6 | export const usePostViews = (slug: string) => 7 | useQuery({ 8 | queryKey: ['POST_VIEWS'], 9 | queryFn: () => fetchFunc(`/views/${slug}`, { method: 'GET' }), 10 | refetchOnWindowFocus: true, 11 | }) 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "~/components", 15 | "utils": "~/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # https://nocodeapi.com/ 2 | NOCODE_API_KEY="" 3 | NOCODE_TAB_ID="" 4 | 5 | # https://vercel.com/storage/postgres 6 | POSTGRES_URL="" 7 | POSTGRES_PRISMA_URL="" 8 | POSTGRES_URL_NO_SSL="" 9 | POSTGRES_URL_NON_POOLING="" 10 | POSTGRES_USER="" 11 | POSTGRES_HOST="" 12 | POSTGRES_PASSWORD="" 13 | POSTGRES_DATABASE="" 14 | 15 | # https://giscus.app/ 16 | NEXT_PUBLIC_GISCUS_REPO_ID="" 17 | NEXT_PUBLIC_GISCUS_CATEGORY_ID="" -------------------------------------------------------------------------------- /src/components/scroll-progress.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React from 'react' 3 | import {motion, useScroll} from 'framer-motion' 4 | 5 | const ScrollProgress = () => { 6 | const {scrollYProgress} = useScroll() 7 | return ( 8 | 12 | ) 13 | } 14 | 15 | export default ScrollProgress 16 | -------------------------------------------------------------------------------- /src/components/mdx/custom-image.tsx: -------------------------------------------------------------------------------- 1 | import Image, {ImageProps} from 'next/image' 2 | 3 | const CustomImage: React.FC = ({...props}) => { 4 | return ( 5 |
6 | {props.alt} 14 |
15 | ) 16 | } 17 | 18 | export default CustomImage 19 | -------------------------------------------------------------------------------- /src/components/ui/content-not-found.tsx: -------------------------------------------------------------------------------- 1 | import {Frown} from 'lucide-react' 2 | import React from 'react' 3 | 4 | const ContentNotFound = ({text}: {text: string}) => { 5 | return ( 6 |
  • 10 |
    11 | 12 |

    {text}

    13 |
    14 |
  • 15 | ) 16 | } 17 | 18 | export default ContentNotFound 19 | -------------------------------------------------------------------------------- /src/components/ui/rss.tsx: -------------------------------------------------------------------------------- 1 | import {Rss} from 'lucide-react' 2 | import React from 'react' 3 | 4 | const RssFeed = () => { 5 | return ( 6 | 12 |
    13 | 14 |
    15 | RSS Feed 16 |
    17 | ) 18 | } 19 | 20 | export default RssFeed 21 | -------------------------------------------------------------------------------- /src/types/config.ts: -------------------------------------------------------------------------------- 1 | export type Theme = 'light' | 'dark' 2 | 3 | export interface ConfigProps { 4 | appName: string 5 | appDescription: string 6 | appDesignation: string 7 | domainName: string 8 | 9 | social: { 10 | github: string 11 | linkedin: string 12 | instagram: string 13 | discord: string 14 | email: string 15 | phone: string 16 | } 17 | 18 | colors: { 19 | theme: Theme 20 | main: string 21 | } 22 | auth: { 23 | loginUrl: string 24 | callbackUrl: string 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/providers/react-query.tsx: -------------------------------------------------------------------------------- 1 | import {QueryClient, QueryClientProvider} from '@tanstack/react-query' 2 | import React from 'react' 3 | 4 | function ReactQueryProvider({children}: React.PropsWithChildren) { 5 | const [client] = React.useState( 6 | new QueryClient({ 7 | defaultOptions: { 8 | queries: {staleTime: 5000, refetchOnWindowFocus: false}, 9 | }, 10 | }), 11 | ) 12 | 13 | return {children} 14 | } 15 | 16 | export default ReactQueryProvider 17 | -------------------------------------------------------------------------------- /src/components/layout/nav/index.tsx: -------------------------------------------------------------------------------- 1 | import Logo from './logo' 2 | import MobileNav from './mobile-nav' 3 | import NavList from './nav-list' 4 | 5 | const Navbar = () => { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export default Navbar 22 | -------------------------------------------------------------------------------- /src/components/ui/fonts.ts: -------------------------------------------------------------------------------- 1 | import {Bai_Jamjuree, Ubuntu} from 'next/font/google' 2 | import localFont from 'next/font/local' 3 | 4 | export const dankMono = localFont({ 5 | src: './dank-mono.otf', 6 | display: 'swap', 7 | variable: '--font-dank', 8 | }) 9 | 10 | export const fontSans = Bai_Jamjuree({ 11 | subsets: ['latin'], 12 | weight: ['400', '500', '600', '700'], 13 | variable: '--font-sans', 14 | }) 15 | 16 | export const ubuntu = Ubuntu({ 17 | subsets: ['latin'], 18 | weight: ['400'], 19 | variable: '--font-ubuntu', 20 | }) 21 | -------------------------------------------------------------------------------- /src/server/db.ts: -------------------------------------------------------------------------------- 1 | import {PrismaClient} from '@prisma/client' 2 | import {env} from '~/constants/env' 3 | 4 | const createPrismaClient = () => 5 | new PrismaClient({ 6 | log: 7 | env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], 8 | }) 9 | 10 | const globalForPrisma = globalThis as unknown as { 11 | prisma: ReturnType | undefined 12 | } 13 | 14 | export const db = globalForPrisma.prisma ?? createPrismaClient() 15 | 16 | if (env.NODE_ENV !== 'production') globalForPrisma.prisma = db 17 | -------------------------------------------------------------------------------- /src/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import {ReactNode} from 'react' 2 | import Footer from '~/components/layout/footer' 3 | import Navbar from '~/components/layout/nav' 4 | import SkipContent from '~/components/ui/skip-content' 5 | 6 | const Layout = ({children}: {children: ReactNode}) => { 7 | return ( 8 |
    9 |
    10 | 11 | 12 | {children} 13 |
    14 |
    15 |
    16 | ) 17 | } 18 | 19 | export default Layout 20 | -------------------------------------------------------------------------------- /src/components/layout/nav/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import Link from 'next/link' 3 | import React from 'react' 4 | 5 | const Logo = () => { 6 | return ( 7 | 13 | personal avatar 20 | 21 | ) 22 | } 23 | 24 | export default Logo 25 | -------------------------------------------------------------------------------- /src/components/back-btn.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, {ReactNode} from 'react' 3 | import {Button} from './ui/button' 4 | import {MoveLeft} from 'lucide-react' 5 | import {useRouter} from 'next/navigation' 6 | 7 | const BackButton = ({children}: {children: ReactNode}) => { 8 | const router = useRouter() 9 | return ( 10 | 18 | ) 19 | } 20 | 21 | export default BackButton 22 | -------------------------------------------------------------------------------- /src/components/layout/nav/nav-list.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction } from 'react' 2 | import { navData } from './_nav-mock' 3 | import NavItem from './nav-item' 4 | 5 | interface NavProps { 6 | setOpen?: Dispatch> 7 | } 8 | 9 | const NavList: React.FC = ({ setOpen }) => { 10 | return ( 11 |
      15 | {navData.map(nav => ( 16 | 17 | ))} 18 |
    19 | ) 20 | } 21 | 22 | export default NavList 23 | -------------------------------------------------------------------------------- /src/assets/svg/luma.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LumaIcon = () => { 4 | return ( 5 |
    6 | 13 | 17 | 18 |
    19 | ) 20 | } 21 | 22 | export default LumaIcon 23 | -------------------------------------------------------------------------------- /src/components/ui/callout.tsx: -------------------------------------------------------------------------------- 1 | import {cn} from '~/lib/utils' 2 | import {ReactNode} from 'react' 3 | 4 | interface CalloutProps { 5 | children?: ReactNode 6 | type?: 'default' | 'warning' | 'danger' 7 | } 8 | 9 | export default function Callout({children, type = 'default', ...props}: CalloutProps) { 10 | return ( 11 |
    18 |
    {children}
    19 |
    20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/layout/nav/_nav-mock.ts: -------------------------------------------------------------------------------- 1 | import {createId} from '@paralleldrive/cuid2' 2 | 3 | export const navData = [ 4 | { 5 | id: createId(), 6 | label: 'Home', 7 | path: '/', 8 | }, 9 | { 10 | id: createId(), 11 | label: 'Projects', 12 | path: '/projects', 13 | }, 14 | { 15 | id: createId(), 16 | label: 'Blog', 17 | path: '/blog', 18 | }, 19 | 20 | // { 21 | // id: createId(), 22 | // label: 'Guests', 23 | // path: '/guests', 24 | // }, 25 | 26 | // { 27 | // id: createId(), 28 | // label: 'Contact', 29 | // path: '/contact', 30 | // }, 31 | ] 32 | 33 | export type NavType = typeof navData 34 | -------------------------------------------------------------------------------- /src/lib/axios.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' 2 | import config from '~/config' 3 | 4 | export const ORIGIN = 5 | process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : `https://${config.domainName}` 6 | 7 | export const fetchFunc = async (endpoint: string, options?: AxiosRequestConfig): Promise => { 8 | try { 9 | const res: AxiosResponse = await axios(`${ORIGIN}/api${endpoint}`, { 10 | ...options, 11 | }) 12 | 13 | return res.data 14 | } catch (error) { 15 | throw axios.isAxiosError(error) 16 | ? new Error(`API request failed: ${(error as AxiosError).message}`) 17 | : error 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import {z} from 'zod' 2 | 3 | const phoneRegex = new RegExp(/^([+]?[\s0-9]+)?(\d{3}|[(]?[0-9]+[)])?([-]?[\s]?[0-9])+$/) 4 | 5 | export const ContactSchema = z.object({ 6 | fullName: z.string().min(1, {message: 'Please enter your full name.'}), 7 | phone: z 8 | .string() 9 | .min(1, {message: 'Please enter your phone.'}) 10 | .regex(phoneRegex, 'Invalid phone number.'), 11 | email: z 12 | .string() 13 | .min(1, {message: 'Please enter a valid email address.'}) 14 | .email({message: 'Invalid email address.'}), 15 | message: z.string().min(1, {message: 'Please enter a message.'}), 16 | }) 17 | 18 | export type contactSchemaType = z.infer 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/providers/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { ReactNode } from 'react' 3 | 4 | import ScrollProgress from '~/components/scroll-progress' 5 | import { TooltipProvider } from '~/components/ui/tooltip' 6 | import TopLoader from '~/components/ui/top-loader' 7 | import ReactQueryProvider from './react-query' 8 | import { Toaster } from '~/components/ui/sonner' 9 | 10 | const RootProviders = ({ children }: { children: ReactNode }) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | {children} 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default RootProviders 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "~/*": ["./src/*"], 22 | "#site/content": ["./.velite"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | import {build} from 'velite' 2 | 3 | import {fileURLToPath} from 'node:url' 4 | import createJiti from 'jiti' 5 | const jiti = createJiti(fileURLToPath(import.meta.url)) 6 | 7 | jiti('./src/constants/env') 8 | 9 | // Note that this approach uses top-level await, so it only supports next.config.mjs or ESM enabled. 10 | const isDev = process.argv.indexOf('dev') !== -1 11 | const isBuild = process.argv.indexOf('build') !== -1 12 | 13 | if (!process.env.VELITE_STARTED && (isDev || isBuild)) { 14 | process.env.VELITE_STARTED = '1' 15 | const {build} = await import('velite') 16 | await build({watch: isDev, clean: !isDev}) 17 | } 18 | 19 | /** @type {import('next').NextConfig} */ 20 | 21 | const nextConfig = {} 22 | 23 | export default nextConfig 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | 10 | /prisma/db.sql 11 | /prisma/db.sqlite-journal 12 | .prisma 13 | javascript/**/migrations/ 14 | typescript/**/migrations/ 15 | /prisma/migrations 16 | 17 | # testing 18 | /coverage 19 | 20 | # next.js 21 | /.next/ 22 | /out/ 23 | 24 | # production 25 | /build 26 | 27 | # misc 28 | .DS_Store 29 | *.pem 30 | 31 | # debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # local env files 37 | .env*.local 38 | .env 39 | 40 | # vercel 41 | .vercel 42 | 43 | # typescript 44 | *.tsbuildinfo 45 | next-env.d.ts 46 | 47 | # velite files 48 | .velite 49 | .env -------------------------------------------------------------------------------- /src/app/(main)/page.tsx: -------------------------------------------------------------------------------- 1 | import {posts} from '#site/content' 2 | import AboutSection from '~/components/about-section' 3 | import {PostList} from '~/components/post' 4 | import {ProjectList, projects} from '~/components/project' 5 | import Skills from '~/components/skills' 6 | import {sortPosts} from '~/lib/utils' 7 | import ContactUs from '../../components/contact-us' 8 | 9 | const HomePage = () => { 10 | const sortedPosts = sortPosts(posts.filter(post => post.published)) 11 | return ( 12 |
    13 | 14 | 15 | 16 | 17 | 18 |
    19 | ) 20 | } 21 | 22 | export default HomePage 23 | -------------------------------------------------------------------------------- /src/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { posts } from '#site/content' 2 | import { MetadataRoute } from 'next' 3 | import { BasePath } from '~/lib/utils' 4 | 5 | export default async function sitemap(): Promise { 6 | const blogPosts = posts.map(post => ({ 7 | url: BasePath(`/blog/${post.slugAsParams.split('/')}`), 8 | lastModified: post.date, 9 | })) 10 | 11 | return [ 12 | { 13 | url: BasePath(''), 14 | lastModified: new Date(), 15 | }, 16 | 17 | { 18 | url: BasePath('/blog'), 19 | lastModified: new Date(), 20 | }, 21 | { 22 | url: BasePath('/projects'), 23 | lastModified: new Date(), 24 | }, 25 | { 26 | url: BasePath('/about'), 27 | lastModified: new Date(), 28 | }, 29 | 30 | ...blogPosts, 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/constants/env.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from '@t3-oss/env-nextjs' 2 | import { z } from 'zod' 3 | 4 | export const env = createEnv({ 5 | server: { 6 | NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), 7 | NOCODE_API_KEY: z.string().min(1), 8 | NOCODE_TAB_ID: z.string().min(1), 9 | }, 10 | client: { 11 | NEXT_PUBLIC_GISCUS_REPO_ID: z.string().min(1), 12 | NEXT_PUBLIC_GISCUS_CATEGORY_ID: z.string().min(1), 13 | }, 14 | runtimeEnv: { 15 | NODE_ENV: process.env.NODE_ENV, 16 | NOCODE_API_KEY: process.env.NOCODE_API_KEY, 17 | NOCODE_TAB_ID: process.env.NOCODE_TAB_ID, 18 | 19 | NEXT_PUBLIC_GISCUS_REPO_ID: process.env.NEXT_PUBLIC_GISCUS_REPO_ID, 20 | NEXT_PUBLIC_GISCUS_CATEGORY_ID: process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID, 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/post/post-comments.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React from 'react' 3 | import Giscus from '@giscus/react' 4 | import {env} from '~/constants/env' 5 | 6 | const PostComments = () => { 7 | return ( 8 |
    9 | 24 |
    25 | ) 26 | } 27 | 28 | export default PostComments 29 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "~/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/components/ui/typograpghy.tsx: -------------------------------------------------------------------------------- 1 | import { cva, type VariantProps } from 'class-variance-authority' 2 | import { cn } from '~/lib/utils' 3 | 4 | const typography = cva(['font-bold inline-block font-ubuntu'], { 5 | variants: { 6 | variant: { 7 | h2: 'text-lg decoration-ring inline-block underline-offset-8 decoration-wavy underline', 8 | paragraph: 'text-muted-foreground font-normal block text-base font-ubuntu', 9 | }, 10 | size: { 11 | sm: 'text-sm', 12 | }, 13 | font: { 14 | sans: 'font-sans', 15 | dank: 'font-dank', 16 | ubuntu: 'font-ubuntu', 17 | }, 18 | }, 19 | defaultVariants: { 20 | variant: 'h2', 21 | }, 22 | }) 23 | 24 | export interface ButtonVariants extends VariantProps {} 25 | 26 | export const typo = (variants: ButtonVariants) => cn(typography(variants)) 27 | -------------------------------------------------------------------------------- /src/components/mdx/mdx-content.tsx: -------------------------------------------------------------------------------- 1 | import * as runtime from 'react/jsx-runtime' 2 | import Callout from '~/components/ui/callout' 3 | import {YouTubeEmbed} from '@next/third-parties/google' 4 | import CustomImage from './custom-image' 5 | import CustomLink from './custom-link' 6 | 7 | const sharedComponents = { 8 | CustomImage, 9 | CustomLink, 10 | YouTubeEmbed, 11 | Callout, 12 | } 13 | 14 | const useMDXComponent = (code: string) => { 15 | const fn = new Function(code) 16 | return fn({...runtime}).default 17 | } 18 | 19 | interface MDXProps { 20 | code: string 21 | components?: Record 22 | } 23 | 24 | const MDXContent = ({code, components}: MDXProps) => { 25 | const Component = useMDXComponent(code) 26 | return 27 | } 28 | 29 | export default MDXContent 30 | -------------------------------------------------------------------------------- /src/components/layout/nav/mobile-nav.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Menu } from 'lucide-react' 3 | import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '~/components/ui/sheet' 4 | import Logo from './logo' 5 | import NavList from './nav-list' 6 | import { useState } from 'react' 7 | 8 | const MobileNav = () => { 9 | const [open, setOpen] = useState(false) 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | } 27 | 28 | export default MobileNav 29 | -------------------------------------------------------------------------------- /src/components/post/post-list.tsx: -------------------------------------------------------------------------------- 1 | import {Post} from '#site/content' 2 | import React from 'react' 3 | import ContentNotFound from '../ui/content-not-found' 4 | import {typo} from '../ui/typograpghy' 5 | import PostItem from './post-item' 6 | 7 | type PostListProps = { 8 | posts: Post[] 9 | showRss?: boolean 10 | } 11 | 12 | const PostList: React.FC = ({posts, showRss}) => { 13 | return ( 14 |
    15 | {showRss &&

    Most recent posts

    } 16 |
      17 | {posts.length > 0 ? ( 18 | posts.map(post => ) 19 | ) : ( 20 | 21 | )} 22 |
    23 |
    24 | ) 25 | } 26 | export default PostList 27 | -------------------------------------------------------------------------------- /src/actions/mutations.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQueryClient } from '@tanstack/react-query' 2 | import { toast } from 'sonner' 3 | import { fetchFunc } from '~/lib/axios' 4 | import { contactSchemaType } from '~/schema' 5 | 6 | export const useSendContactData = () => 7 | useMutation({ 8 | mutationFn: (data: contactSchemaType) => fetchFunc('/contact', { method: 'POST', data }), 9 | onError: error => toast.error(error.message), 10 | onSuccess: () => toast.success("I'll be in touch shortly."), 11 | }) 12 | 13 | type TViewCount = { message: string } 14 | 15 | export const useIncrementViewCount = () => { 16 | const queryClient = useQueryClient() 17 | 18 | return useMutation({ 19 | mutationFn: (slug: string) => fetchFunc(`/views/${slug}`, { method: 'POST' }), 20 | onSuccess: () => queryClient.invalidateQueries({ queryKey: ['POST_VIEWS'] }), 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/tags.tsx: -------------------------------------------------------------------------------- 1 | import {slug} from 'github-slugger' 2 | import Link from 'next/link' 3 | import {buttonVariants} from './ui/button' 4 | 5 | const Tags = ({tags}: {tags: string[]}) => { 6 | return ( 7 |
      8 | {tags.map((tag, index) => ( 9 | 10 | ))} 11 |
    12 | ) 13 | } 14 | 15 | export default Tags 16 | 17 | export const Tag = ({tag, count}: {tag: string; count?: number}) => { 18 | return ( 19 |
  • 20 | 28 | #{tag} {count && `(${count})`} 29 | 30 |
  • 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/post/post-views.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import {Eye} from 'lucide-react' 3 | import React, {useEffect} from 'react' 4 | import {useIncrementViewCount} from '~/actions/mutations' 5 | import {usePostViews} from '~/actions/queries' 6 | 7 | const PostViews = ({slug}: {slug: string}) => { 8 | const {data, isLoading} = usePostViews(slug) 9 | const incrCount = useIncrementViewCount() 10 | 11 | useEffect(() => { 12 | incrCount.mutate(slug) 13 | // eslint-disable-next-line react-hooks/exhaustive-deps 14 | }, []) 15 | 16 | return ( 17 |
    18 |
    Blog Post views
    19 |
    20 |
    23 |
    24 | ) 25 | } 26 | 27 | export default PostViews 28 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import {cn} from '~/lib/utils' 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({className, ...props}, ref) => { 10 | return ( 11 |