├── .env.example ├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── components.json ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── prisma └── schema.prisma ├── public ├── audio │ └── switch-on.mp3 ├── images │ ├── blog │ │ └── reduce-cover.webp │ ├── chain-gpt-logo.svg │ └── logo.png └── static │ ├── og-modulo-operator-a63a03.webp │ └── publishing-react-package-7c738b.webp ├── src ├── actions │ ├── mutations.ts │ └── queries.ts ├── app │ ├── (main) │ │ ├── blog │ │ │ ├── [...slug] │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── projects │ │ │ └── page.tsx │ │ └── tags │ │ │ ├── [tag] │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── api │ │ ├── contact │ │ │ └── route.ts │ │ └── views │ │ │ └── [slug] │ │ │ └── route.ts │ ├── apple-icon.png │ ├── error.tsx │ ├── favicon.ico │ ├── feed.xml │ │ └── route.ts │ ├── icon.png │ ├── layout.tsx │ ├── not-found.tsx │ ├── opengraph-image.png │ ├── robot.ts │ ├── sitemap.ts │ └── twitter-image.png ├── assets │ ├── images │ │ ├── cover │ │ │ ├── chain-gpt.png │ │ │ ├── luma.png │ │ │ ├── mint-kuto.png │ │ │ ├── podportal.png │ │ │ ├── power-up.png │ │ │ └── world-ranks.png │ │ ├── f-dubai-police.webp │ │ ├── mint-kuto.avif │ │ ├── nft-connect.jpg │ │ ├── nft-connect.webp │ │ ├── power-up.webp │ │ └── world-rank.png │ └── svg │ │ ├── chain-gpt.tsx │ │ ├── index.ts │ │ └── luma.tsx ├── components │ ├── about-section.tsx │ ├── back-btn.tsx │ ├── contact-us.tsx │ ├── layout │ │ ├── footer.tsx │ │ └── nav │ │ │ ├── _nav-mock.ts │ │ │ ├── index.tsx │ │ │ ├── logo.tsx │ │ │ ├── mobile-nav.tsx │ │ │ ├── nav-item.tsx │ │ │ └── nav-list.tsx │ ├── mdx │ │ ├── custom-image.tsx │ │ ├── custom-link.tsx │ │ ├── index.ts │ │ └── mdx-content.tsx │ ├── post │ │ ├── index.ts │ │ ├── post-comments.tsx │ │ ├── post-item.tsx │ │ ├── post-json-schema.tsx │ │ ├── post-list.tsx │ │ ├── post-metadata.tsx │ │ ├── post-toc.tsx │ │ └── post-views.tsx │ ├── project │ │ ├── _project-mock.ts │ │ ├── index.ts │ │ ├── project-icons.tsx │ │ ├── project-item.tsx │ │ └── project-list.tsx │ ├── scroll-progress.tsx │ ├── search-input.tsx │ ├── skills.tsx │ ├── socials.tsx │ ├── support-btn.tsx │ ├── tags.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── button.tsx │ │ ├── callout.tsx │ │ ├── card.tsx │ │ ├── content-not-found.tsx │ │ ├── dank-mono.otf │ │ ├── dropdown-menu.tsx │ │ ├── fonts.ts │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── rss.tsx │ │ ├── sheet.tsx │ │ ├── skip-content.tsx │ │ ├── sonner.tsx │ │ ├── textarea.tsx │ │ ├── tooltip.tsx │ │ ├── top-loader.tsx │ │ └── typograpghy.tsx ├── config.ts ├── constants │ ├── anime.ts │ ├── env.ts │ └── stack.tsx ├── content │ └── posts │ │ ├── images │ │ └── cover │ │ │ ├── og-modulo-operator.webp │ │ │ └── publishing-react-package.webp │ │ ├── javascript-modulo-operator.mdx │ │ └── publishing-react-package.mdx ├── hooks │ ├── index.ts │ ├── use-client.tsx │ ├── use-isomorphic.tsx │ └── use-media.tsx ├── lib │ ├── axios.ts │ ├── seo.tsx │ ├── shadcn-ui.ts │ └── utils.ts ├── providers │ ├── index.tsx │ └── react-query.tsx ├── schema.ts ├── server │ └── db.ts ├── styles │ ├── globals.css │ └── mdx.css └── types │ └── config.ts ├── tailwind.config.ts ├── tsconfig.json ├── velite.config.ts └── yarn.lock /.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="" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | faisal-dev 7 |

8 | 9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | 19 | Welcome to my personal website! where I share my thoughts, projects, insights & blogs. Feel free to 20 | explore and get inspired 21 | 22 | ## ✨ Features 23 | 24 | - ⚡️ Next.js 14 with App Router (Turbo) 25 | - 📝 MDX powered by velite 26 | - 🎨 Tailwind CSS - for styling 27 | - 🌈 Shadcn UI - accessible UI components 28 | - 🛡 Strict TypeScript and ESLint configuration 29 | - 📱 Responsive design 30 | - 📈 SEO optimized with meta tags and JSON-LD 31 | - 📰 RSS feed 32 | - 🗺 Sitemap 33 | - 📊 Vercel Analytics 34 | - 📝 Blog with comments, likes, and post views 35 | - 🔎 Blog post search 36 | - 📖 Table of contents for blog posts 37 | - 📝 Code syntax highlighting - using Shiki 38 | - 🎨 Animation - using Framer Motion 39 | - 🏠 LightHouse score of nearly 100 40 | - 💄 Prettier - code formatting 41 | - 〰️ Prisma & Vercel Postgres 42 | - 👷🏻‍♂️ t3-env - validate environment variables before building 43 | 44 | ## 🔨 Requirements 45 | 46 | - Node, recommended `20.x` 47 | - npm, recommended `10.5.0` 48 | - PostgreSQL, recommended `14.x` check 49 | [https://vercel.com/storage/postgres](https://vercel.com/storage/postgres) 50 | - Visual Studio Code [https://code.visualstudio.com/](https://code.visualstudio.com/) 51 | - For the contact section, I'm using NoCode API [https://nocodeapi.com/](https://nocodeapi.com/). 52 | Simply create an account here and under the marketplace, enable the Excel Sheet API. Once enabled, 53 | you will receive a tabId and an API key. 54 | - For the blog comments, I'm using the Giscus component. Set up your configuration 55 | [https://giscus.app/](https://giscus.app/). 56 | 57 | ## 👋 Getting Started 58 | 59 | Follow these steps to run the project locally on your machine: 60 | 61 | ```bash 62 | git clone https://github.com/BinarySenseiii/personal-website.git 63 | cd personal-website 64 | npm install 65 | ``` 66 | 67 | Create a `.env.local` file based on the provided `.env.example` file and fill in the necessary 68 | variables. 69 | 70 | OR you can skip this by modifying `src/constants/env.ts`: 71 | 72 | ```ts 73 | export const env = createEnv({ 74 | skipValidation: true, 75 | 76 | server: { 77 | // ... 78 | }, 79 | }) 80 | ``` 81 | 82 | It will skip the validation of environment variables. And you may notice that some functionalities 83 | will not work properly. But it's okay for learning. 84 | 85 | Then generate prisma client: 86 | 87 | ```bash 88 | npx prisma generate 89 | npx migrate dev --name any 90 | ``` 91 | 92 | To run the app in development mode: 93 | 94 | ```bash 95 | npm run dev 96 | ``` 97 | 98 | The app will be available at `localhost:3000`. 99 | 100 | ## ✈️ TODO 101 | 102 | - ESM import { build } from 'velite' may be got a 103 | [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] warning generated during the next 104 | build process, which has little impact, refer to https://github.com/webpack/webpack/pull/15688 105 | 106 | ## ✍🏻 Author 107 | 108 | - [@BinarySenseiii](https://github.com/BinarySenseiii) 109 | 110 | ## 🪪 License 111 | 112 | This project is open source and available under the [GPL3 License](LICENSE). 113 | 114 |
115 |

116 | Design & Developed by ❤️ 117 |

118 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "personal-website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev:content": "velite --watch", 7 | "build:content": "velite --clean", 8 | "dev:next": "next dev", 9 | "build:next": "next build", 10 | "dev": "run-p dev:*", 11 | "build": "run-s build:*", 12 | "start": "next start", 13 | "upgrade:latest": "yarn upgrade-interactive --latest" 14 | }, 15 | "dependencies": { 16 | "@giscus/react": "^3.0.0", 17 | "@hookform/resolvers": "^3.6.0", 18 | "@next/third-parties": "^14.2.14", 19 | "@paralleldrive/cuid2": "^2.2.2", 20 | "@prisma/client": "^5.20.0", 21 | "@radix-ui/react-accordion": "^1.2.1", 22 | "@radix-ui/react-dialog": "^1.1.2", 23 | "@radix-ui/react-dropdown-menu": "^2.1.2", 24 | "@radix-ui/react-label": "^2.0.2", 25 | "@radix-ui/react-slot": "^1.0.2", 26 | "@radix-ui/react-tooltip": "^1.1.3", 27 | "@t3-oss/env-nextjs": "^0.11.1", 28 | "@tanstack/react-query": "^5.59.0", 29 | "@theme-toggles/react": "^4.1.0", 30 | "@uidotdev/usehooks": "^2.4.1", 31 | "@vercel/analytics": "^1.3.1", 32 | "@vercel/speed-insights": "^1.0.11", 33 | "axios": "^1.7.7", 34 | "class-variance-authority": "^0.7.0", 35 | "clsx": "^2.1.1", 36 | "framer-motion": "^11.11.1", 37 | "jiti": "^2.2.1", 38 | "lucide-react": "^0.447.0", 39 | "next": "14.2.14", 40 | "next-nprogress-bar": "^2.3.12", 41 | "next-themes": "^0.3.0", 42 | "react": "^18", 43 | "react-dom": "^18", 44 | "react-fast-marquee": "^1.6.4", 45 | "react-hook-form": "^7.53.0", 46 | "react-icons": "^5.2.1", 47 | "rehype-autolink-headings": "^7.1.0", 48 | "rehype-code-titles": "^1.2.0", 49 | "rehype-external-links": "^3.0.0", 50 | "rehype-pretty-code": "^0.14.0", 51 | "rehype-slug": "^6.0.0", 52 | "rough-notation": "^0.5.1", 53 | "rss": "^1.2.2", 54 | "sharp": "0.33.5", 55 | "shiki": "^1.21.0", 56 | "sonner": "^1.5.0", 57 | "tailwind-merge": "^2.5.3", 58 | "tailwindcss-animate": "^1.0.7", 59 | "zod": "^3.23.8" 60 | }, 61 | "devDependencies": { 62 | "@tailwindcss/typography": "^0.5.15", 63 | "@types/node": "^22.7.4", 64 | "@types/react": "^18.3.11", 65 | "@types/react-dom": "^18", 66 | "@types/rss": "^0.0.32", 67 | "eslint": "^8", 68 | "eslint-config-next": "14.2.14", 69 | "npm-run-all": "^4.1.5", 70 | "postcss": "^8.4.47", 71 | "prisma": "^5.20.0", 72 | "tailwindcss": "^3.4.13", 73 | "typescript": "^5.6.2", 74 | "velite": "^0.1.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/audio/switch-on.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/public/audio/switch-on.mp3 -------------------------------------------------------------------------------- /public/images/blog/reduce-cover.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/public/images/blog/reduce-cover.webp -------------------------------------------------------------------------------- /public/images/chain-gpt-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/public/images/logo.png -------------------------------------------------------------------------------- /public/static/og-modulo-operator-a63a03.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/public/static/og-modulo-operator-a63a03.webp -------------------------------------------------------------------------------- /public/static/publishing-react-package-7c738b.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/public/static/publishing-react-package-7c738b.webp -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /src/app/(main)/blog/[...slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import {posts} from '#site/content' 2 | import Image from 'next/image' 3 | import {notFound} from 'next/navigation' 4 | import BackButton from '~/components/back-btn' 5 | import {MDXContent} from '~/components/mdx' 6 | import { 7 | JsonSchemaLD, 8 | PostComments, 9 | PostMetadata, 10 | TableOfContent, 11 | } from '~/components/post' 12 | import Tags from '~/components/tags' 13 | import {getSEOTags} from '~/lib/seo' 14 | import {cn} from '~/lib/utils' 15 | import '~/styles/mdx.css' 16 | 17 | interface BlogPostParams { 18 | params: { 19 | slug: string[] 20 | } 21 | } 22 | 23 | async function getPostFromParams(params: BlogPostParams['params']) { 24 | const slug = params?.slug?.join('/') 25 | const post = posts.find(post => post.slugAsParams === slug) 26 | 27 | if (post === undefined || !post.published) { 28 | return notFound() 29 | } 30 | 31 | return post 32 | } 33 | 34 | export async function generateStaticParams(): Promise< 35 | BlogPostParams['params'][] 36 | > { 37 | return posts.map(post => ({slug: post.slugAsParams.split('/')})) 38 | } 39 | 40 | export async function generateMetadata({params}: BlogPostParams) { 41 | const post = await getPostFromParams(params) 42 | 43 | return getSEOTags({ 44 | title: post.title, 45 | description: post.description, 46 | canonicalUrlRelative: `/blog/${post.slugAsParams.split('/')}`, 47 | extraTags: { 48 | openGraph: { 49 | title: post.title, 50 | description: post.description, 51 | url: `/blog/${post.slug.split('/')}`, 52 | images: [ 53 | { 54 | url: post.cover.src, 55 | width: 1200, 56 | height: 660, 57 | }, 58 | ], 59 | locale: 'en_US', 60 | type: 'website', 61 | }, 62 | }, 63 | }) 64 | } 65 | 66 | export default async function BlogDetail({params}: BlogPostParams) { 67 | const post = await getPostFromParams(params) 68 | 69 | return ( 70 | <> 71 | {/* SCHEMA JSON-LD MARKUP FOR GOOGLE */} 72 | 73 |
74 | Back to Posts 75 |
76 | 83 | 84 | 85 | 86 |
87 | {post.title} 96 |
97 |
98 | 99 |
103 | 104 |
105 | 106 |
107 |
108 |

Tags

109 | 110 |
111 | 112 | 113 |
114 | 115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /src/app/(main)/blog/page.tsx: -------------------------------------------------------------------------------- 1 | import { posts } from '#site/content' 2 | import { PostList } from '~/components/post' 3 | import SearchInput from '~/components/search-input' 4 | import config from '~/config' 5 | import { getSEOTags } from '~/lib/seo' 6 | 7 | export const metadata: ReturnType = getSEOTags({ 8 | title: `All Blogs - ${config.appName}`, 9 | description: 10 | "Welcome to my digital garden where I share what I'm learning about shipping great products, becoming a better developer and growing a career in tech.", 11 | canonicalUrlRelative: '/blogs', 12 | keywords: [ 13 | 'JavaScript', 14 | 'TypeScript', 15 | 'React', 16 | 'Testing', 17 | 'Career', 18 | 'Software Development', 19 | 'Faisal tariq Blog', 20 | ], 21 | }) 22 | 23 | const BlogPage = async ({ searchParams }: { searchParams: { search: string | undefined } }) => { 24 | const filteredPosts = posts.filter(post => 25 | post.title.toLowerCase().includes(decodeURIComponent(searchParams.search || '')), 26 | ) 27 | 28 | return ( 29 |
30 |
31 |

All Publications

32 | 33 |
34 | 35 | 36 |
37 | ) 38 | } 39 | 40 | export default BlogPage 41 | -------------------------------------------------------------------------------- /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/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/(main)/projects/page.tsx: -------------------------------------------------------------------------------- 1 | import { ProjectList, projects } from '~/components/project' 2 | import SearchInput from '~/components/search-input' 3 | import config from '~/config' 4 | import { getSEOTags } from '~/lib/seo' 5 | 6 | export const metadata: ReturnType = getSEOTags({ 7 | title: `All Projects - ${config.appName}`, 8 | description: 9 | 'Explore a digital garden of my projects, where I showcase insights on shipping exceptional products, advancing as a developer, and thriving in the tech industry', 10 | canonicalUrlRelative: '/projects', 11 | keywords: [ 12 | 'JavaScript', 13 | 'TypeScript', 14 | 'React', 15 | 'Testing', 16 | 'Career', 17 | 'Software Development', 18 | ], 19 | }) 20 | 21 | const ProjectsPage = ({ searchParams }: { searchParams: { search: string | undefined } }) => { 22 | const filteredProjects = projects.filter(project => 23 | project.title.toLowerCase().includes(decodeURIComponent(searchParams.search || '')), 24 | ) 25 | 26 | return ( 27 |
28 |
29 |

All Projects

30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 | ) 38 | } 39 | 40 | export default ProjectsPage 41 | -------------------------------------------------------------------------------- /src/app/(main)/tags/[tag]/page.tsx: -------------------------------------------------------------------------------- 1 | import {posts} from '#site/content' 2 | import {slug} from 'github-slugger' 3 | import React, {Fragment} from 'react' 4 | import {CustomLink} from '~/components/mdx' 5 | import {PostList} from '~/components/post' 6 | import SkipContent from '~/components/ui/skip-content' 7 | import config from '~/config' 8 | import {getSEOTags} from '~/lib/seo' 9 | import {getAllTags, getPostsByTagSlug} from '~/lib/utils' 10 | 11 | interface TagPageProps { 12 | params: { 13 | tag: string 14 | } 15 | } 16 | 17 | export async function generateMetadata({ 18 | params, 19 | }: TagPageProps): Promise> { 20 | const {tag} = params 21 | 22 | return getSEOTags({ 23 | title: `Tagged “${tag}” - ${config.appName}`, 24 | description: `Posts on the topic of ${tag}`, 25 | canonicalUrlRelative: `/tags/${slug(tag)}`, 26 | }) 27 | } 28 | 29 | export const generateStaticParams = () => { 30 | const tags = getAllTags(posts) 31 | const paths = Object.keys(tags).map(tag => ({tag: slug(tag)})) 32 | return paths 33 | } 34 | 35 | const TagDetailPage: React.FC = ({params}) => { 36 | const {tag} = params 37 | const title = tag.split('-').join(' ') 38 | 39 | const displayPosts = getPostsByTagSlug(posts, tag) 40 | 41 | return ( 42 |
43 |

44 | Tagged [ {title} ] 45 |

46 | 47 | 48 | 49 | 53 |
54 | ) 55 | } 56 | 57 | export default TagDetailPage 58 | -------------------------------------------------------------------------------- /src/app/(main)/tags/page.tsx: -------------------------------------------------------------------------------- 1 | import {Post, posts} from '#site/content' 2 | import {CustomLink} from '~/components/mdx' 3 | import {PostList} from '~/components/post' 4 | import {Tag} from '~/components/tags' 5 | import config from '~/config' 6 | import {getSEOTags} from '~/lib/seo' 7 | import {getAllTags, sortedTagsCount} from '~/lib/utils' 8 | 9 | export const metadata: ReturnType = getSEOTags({ 10 | title: `All Tags - ${config.appName}`, 11 | canonicalUrlRelative: '/tags', 12 | }) 13 | 14 | type OrganizedPost = Record 15 | 16 | const TagsPage = () => { 17 | const tags = getAllTags(posts) 18 | const sortedTags = sortedTagsCount(tags) 19 | 20 | function organizePostsByTag(posts: Post[]): OrganizedPost { 21 | const organizedPosts: {[key: string]: Post[]} = {} 22 | 23 | posts.forEach(post => { 24 | post.tags.forEach(tag => { 25 | if (!organizedPosts[tag]) { 26 | organizedPosts[tag] = [] 27 | } 28 | organizedPosts[tag].push(post) 29 | }) 30 | }) 31 | 32 | const sortedKeys = Object.keys(organizedPosts).sort() 33 | const result: OrganizedPost = {} 34 | 35 | sortedKeys.forEach(key => { 36 | result[key] = organizedPosts[key] 37 | }) 38 | 39 | return result 40 | } 41 | 42 | const result: OrganizedPost = organizePostsByTag(posts) 43 | 44 | return ( 45 |
46 |
47 |

Posts by Tag (A-Z)

48 | 49 | {Object.keys(result).map(tag => ( 50 |
55 |

56 | {tag} 57 |

58 | 59 |
    60 | {result[tag].map((post, index) => ( 61 |
  • 62 | 63 | {post.title} 64 | 65 |
  • 66 | ))} 67 |
68 |
69 | ))} 70 |
71 | 72 |
73 |

All Tags

74 |
    75 | {sortedTags.map((tag, index) => ( 76 | 77 | ))} 78 |
79 |
80 |
81 | ) 82 | } 83 | 84 | export default TagsPage 85 | -------------------------------------------------------------------------------- /src/app/api/contact/route.ts: -------------------------------------------------------------------------------- 1 | import { env } from '~/constants/env' 2 | import { ContactSchema, contactSchemaType } from '~/schema' 3 | 4 | async function sendDataToGoogleSheets(data: contactSchemaType): Promise { 5 | const requestOptions = { 6 | method: 'POST', 7 | headers: { 8 | 'Content-Type': 'application/json', 9 | }, 10 | body: JSON.stringify([Object.values(data)]), 11 | } 12 | 13 | const response = await fetch( 14 | `https://v1.nocodeapi.com/faisal_dev/google_sheets/${env.NOCODE_API_KEY}?tabId=${env.NOCODE_TAB_ID}`, 15 | requestOptions, 16 | ) 17 | 18 | if (!response.ok) { 19 | throw new Error('Failed to send data to Google Sheets') 20 | } 21 | 22 | return await response.json() 23 | } 24 | 25 | async function handlePostRequest(request: Request): Promise { 26 | try { 27 | const data = (await request.json()) as contactSchemaType 28 | const form = ContactSchema.safeParse(data) 29 | 30 | if (form.error) { 31 | return new Response(JSON.stringify(form.error.formErrors.fieldErrors), { 32 | status: 400, 33 | headers: { 34 | 'Content-Type': 'application/json', 35 | }, 36 | }) 37 | } 38 | 39 | const result = await sendDataToGoogleSheets(data) 40 | 41 | return new Response(JSON.stringify(result), { 42 | headers: { 43 | 'Content-Type': 'application/json', 44 | }, 45 | status: 201, 46 | }) 47 | } catch (error) { 48 | console.error('Error:', error) 49 | return new Response(JSON.stringify({ error: 'Internal Server Error' }), { 50 | status: 500, 51 | headers: { 52 | 'Content-Type': 'application/json', 53 | }, 54 | }) 55 | } 56 | } 57 | 58 | export async function POST(request: Request): Promise { 59 | return handlePostRequest(request) 60 | } 61 | -------------------------------------------------------------------------------- /src/app/api/views/[slug]/route.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | import { db } from '~/server/db' 3 | 4 | interface Options { 5 | params: { 6 | slug: string 7 | } 8 | } 9 | 10 | export async function GET(_: Request, { params }: Options) { 11 | try { 12 | const slug = z.string().parse(params.slug) 13 | const views = await db.views.findUnique({ where: { slug } }) 14 | 15 | return Response.json({ views }, { status: 200 }) 16 | } catch (error) { 17 | const message = error instanceof Error ? error.message : 'Unexpected error' 18 | return Response.json({ message }, { status: 400 }) 19 | } 20 | } 21 | 22 | export async function POST(_: Request, { params }: Options): Promise { 23 | try { 24 | const slug = z.string().parse(params.slug) 25 | 26 | // Check if the view already exists 27 | const existingView = await db.views.findUnique({ where: { slug } }) 28 | 29 | if (existingView) { 30 | // If the view exists, update the count by incrementing it by 1 31 | await db.views.update({ 32 | where: { slug }, 33 | data: { count: existingView.count + 1 }, 34 | }) 35 | } else { 36 | // If the view doesn't exist, create a new one with count 1 37 | await db.views.create({ 38 | data: { slug, count: 1 }, 39 | }) 40 | } 41 | 42 | return new Response(JSON.stringify({ message: 'Count incremented successfully' }), { 43 | status: 200, 44 | }) 45 | } catch (error) { 46 | console.error('An error occurred while incrementing count:', error) 47 | 48 | return new Response(JSON.stringify({ message: 'Internal server error' }), { 49 | status: 500, 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/app/apple-icon.png -------------------------------------------------------------------------------- /src/app/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {useEffect} from 'react' 4 | import SupportButton from '~/components/support-btn' 5 | 6 | export default function Error({ 7 | error, 8 | reset, 9 | }: { 10 | error: Error & {digest?: string} 11 | reset: () => void 12 | }) { 13 | useEffect(() => { 14 | console.error('error::: ', error) 15 | }, [error]) 16 | 17 | return ( 18 |
19 |
20 |
21 | 27 | 31 | 35 | 39 | 43 | 44 | 45 | 49 | 50 | 54 | 58 | 59 | 63 | 67 | 68 | 69 | 73 | 77 | 81 | 85 | 89 | 93 | 97 | 101 | 102 |
103 |

Internal Server Error

104 | 105 |

106 | {error?.message} 107 |

108 | 109 | 110 |
111 |
112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/feed.xml/route.ts: -------------------------------------------------------------------------------- 1 | import {posts} from '#site/content' 2 | import RSS from 'rss' 3 | import config from '~/config' 4 | import {BasePath} from '~/lib/utils' 5 | 6 | export async function GET() { 7 | const feed = new RSS({ 8 | title: `${config.appName} Personal Website`, 9 | generator: 'RSS for Personal Portfolio', 10 | feed_url: BasePath('/feed.xml'), 11 | site_url: BasePath('/'), 12 | managingEditor: `${config.social.email} (${config.appName})`, 13 | webMaster: `${config.social.email} (${config.appName})`, 14 | copyright: `Copyright ${new Date().getFullYear().toString()}, ${ 15 | config.appName 16 | }`, 17 | language: 'en-US', 18 | pubDate: new Date().toUTCString(), 19 | ttl: 60, 20 | }) 21 | 22 | posts.forEach(post => { 23 | feed.item({ 24 | title: post.title, 25 | url: BasePath(`/blog/${post.slugAsParams.split('/')}`), 26 | date: post.date, 27 | description: post.description, 28 | author: 'Faisal Tariq', 29 | }) 30 | }) 31 | 32 | return new Response(feed.xml(), { 33 | headers: { 34 | 'Content-Type': 'application/xml; charset=utf-8', 35 | }, 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/app/icon.png -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Analytics } from '@vercel/analytics/react' 2 | import { SpeedInsights } from '@vercel/speed-insights/next' 3 | import { dankMono, fontSans, ubuntu } from '~/components/ui/fonts' 4 | import { getSEOTags, renderSchemaTags } from '~/lib/seo' 5 | import { cn } from '~/lib/utils' 6 | import RootProviders from '~/providers' 7 | import '~/styles/globals.css' 8 | 9 | export const viewport = { 10 | viewportFit: 'cover', 11 | width: 'device-width', 12 | initialScale: 1, 13 | maximumScale: 3, 14 | userScalable: true, 15 | themeColor: [ 16 | { media: '(prefers-color-scheme: light)', color: 'white' }, 17 | { media: '(prefers-color-scheme: dark)', color: 'black' }, 18 | ], 19 | } 20 | 21 | export const metadata = getSEOTags() 22 | 23 | export default function RootLayout({ 24 | children, 25 | }: Readonly<{ 26 | children: React.ReactNode 27 | }>) { 28 | return ( 29 | 30 | 37 | {renderSchemaTags()} 38 | 39 | {children} 40 | 41 | {process.env.NODE_ENV === 'production' && ( 42 | <> 43 | 44 | 45 | 46 | )} 47 | 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import SupportButton from '~/components/support-btn' 2 | 3 | export default function Custom404() { 4 | return ( 5 |
6 |
7 |
8 | 13 | 17 | 21 | 25 | 29 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 75 | 76 | 77 | 78 | 79 | 83 | 84 | 88 | 89 | 90 | 94 | 95 |
96 |

97 | This page doesn't exist 98 |

99 | 100 |

101 | Oops! It seems like you've stumbled upon a page that doesn't 102 | exist Don't worry, even the best of us get lost sometimes. Feel 103 | free to navigate back to Home or contact us if you need help 104 |

105 | 106 | 107 |
108 |
109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /src/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/app/opengraph-image.png -------------------------------------------------------------------------------- /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/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/app/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/app/twitter-image.png -------------------------------------------------------------------------------- /src/assets/images/cover/chain-gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/cover/chain-gpt.png -------------------------------------------------------------------------------- /src/assets/images/cover/luma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/cover/luma.png -------------------------------------------------------------------------------- /src/assets/images/cover/mint-kuto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/cover/mint-kuto.png -------------------------------------------------------------------------------- /src/assets/images/cover/podportal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/cover/podportal.png -------------------------------------------------------------------------------- /src/assets/images/cover/power-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/cover/power-up.png -------------------------------------------------------------------------------- /src/assets/images/cover/world-ranks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/cover/world-ranks.png -------------------------------------------------------------------------------- /src/assets/images/f-dubai-police.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/f-dubai-police.webp -------------------------------------------------------------------------------- /src/assets/images/mint-kuto.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/mint-kuto.avif -------------------------------------------------------------------------------- /src/assets/images/nft-connect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/nft-connect.jpg -------------------------------------------------------------------------------- /src/assets/images/nft-connect.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/nft-connect.webp -------------------------------------------------------------------------------- /src/assets/images/power-up.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/power-up.webp -------------------------------------------------------------------------------- /src/assets/images/world-rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinarySenseiii/personal-website/6f4f0a97fbb86fa1060453b94a979e9023c0e0f4/src/assets/images/world-rank.png -------------------------------------------------------------------------------- /src/assets/svg/chain-gpt.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import {SVGProps} from 'react' 3 | interface SVGRProps { 4 | title?: string 5 | titleId?: string 6 | } 7 | const ChainGpt = ({title, titleId, ...props}: SVGProps & SVGRProps) => ( 8 |
9 | 17 | {title ? {title} : null} 18 | 22 | 26 | 30 | 34 | 38 | 39 | 47 | 48 | 49 | 50 | 58 | 59 | 60 | 61 | 69 | 70 | 71 | 72 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
87 | ) 88 | export default ChainGpt 89 | -------------------------------------------------------------------------------- /src/assets/svg/index.ts: -------------------------------------------------------------------------------- 1 | export {default as LumaIcon} from './luma' 2 | export {default as ChainGpt} from './chain-gpt' 3 | -------------------------------------------------------------------------------- /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/about-section.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import {useEffect, useRef} from 'react' 3 | import {annotate, annotationGroup} from 'rough-notation' 4 | import { 5 | RoughAnnotationConfig, 6 | RoughAnnotationGroup, 7 | } from 'rough-notation/lib/model' 8 | 9 | import config from '~/config' 10 | import {useMediaQuery} from '~/hooks' 11 | import {cn} from '~/lib/utils' 12 | import {typo} from './ui/typograpghy' 13 | import Image from 'next/image' 14 | import dubaiCon from '~/assets/images/f-dubai-police.webp' 15 | import {annotationsConfig} from '~/constants/anime' 16 | 17 | const AboutSection = () => { 18 | const isSmallDevice = useMediaQuery('(max-width: 500px)') 19 | const annotationRefs = annotationsConfig.map(() => 20 | // eslint-disable-next-line react-hooks/rules-of-hooks 21 | useRef(null), 22 | ) 23 | 24 | useEffect(() => { 25 | const annotations = annotationsConfig.map((config, index) => { 26 | const {ref, ...options} = config 27 | return annotate( 28 | annotationRefs[index]!.current!, 29 | options as RoughAnnotationConfig, 30 | ) 31 | }) 32 | 33 | const annotationGroupInstance: RoughAnnotationGroup = 34 | annotationGroup(annotations) 35 | 36 | if (!isSmallDevice) { 37 | annotationGroupInstance.show() 38 | } 39 | 40 | return () => annotationGroupInstance.hide() 41 | }, [annotationRefs, isSmallDevice]) 42 | 43 | return ( 44 |
45 |
46 |

47 | Hello , I'm Faisal from UAE. 48 |

49 | 50 |

51 | Experienced self-taught developer with over{' '} 52 | 53 | 5+ years 54 | {' '} 55 | of crafting advanced SaaS products and B2B solutions, specializing in 56 | transforming imaginative designs into robust, scalable web solutions 57 | that set new standards. 58 |

59 | 60 |

61 | I Love building tools that are{' '} 62 | 63 | user friendly, simple 64 | {' '} 65 | and{' '} 66 | 67 | delightful 68 | 69 | . 70 |

71 | 72 |

73 | Through these experiences, I had the opportunity to work with both 74 | small and large companies, as well as specialized and cross-functional 75 | teams across different time zones & developed a working style that 76 | prioritizes{' '} 77 | 78 | flexibility, clarity 79 | {' '} 80 | and{' '} 81 | 82 | collaboration. 83 | 84 |

85 | 86 |

89 | I'm currently looking for a new role as a developer.{' '} 90 | } 92 | href={`mailto:${config.social.email}`} 93 | aria-label="Hire me" 94 | className="text-ring el-focus-styles" 95 | > 96 | Hire me? 97 | 98 |

99 |
100 | 101 |
102 |
103 | Speaking on stage at Dubai police station during a presentation 110 |
111 |
112 | ) 113 | } 114 | 115 | export default AboutSection 116 | -------------------------------------------------------------------------------- /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/contact-us.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { zodResolver } from '@hookform/resolvers/zod' 3 | import { useForm } from 'react-hook-form' 4 | import { useSendContactData } from '~/actions/mutations' 5 | import { Form, FormControl, FormField, FormItem, FormMessage } from '~/components/ui/form' 6 | import config from '~/config' 7 | import { ContactSchema, contactSchemaType } from '~/schema' 8 | import { CustomLink } from './mdx' 9 | import Socials from './socials' 10 | import { Button } from './ui/button' 11 | import { Input } from './ui/input' 12 | import { Textarea } from './ui/textarea' 13 | import { typo } from './ui/typograpghy' 14 | 15 | const ContactUs = () => { 16 | const { mutate, isPending } = useSendContactData() 17 | const form = useForm({ 18 | resolver: zodResolver(ContactSchema), 19 | defaultValues: { 20 | fullName: '', 21 | phone: '', 22 | email: '', 23 | message: '', 24 | }, 25 | }) 26 | 27 | function onSubmit(data: contactSchemaType) { 28 | mutate(data, { 29 | onSuccess: () => form.reset(), 30 | }) 31 | } 32 | 33 | return ( 34 |
35 |
36 |
37 |

Get in Touch

38 |

39 | If you have any inquiries, please feel free to reach out. You can contact us via email 40 | at{' '} 41 | 42 | {config.social.email} 43 | {' '} 44 |

45 | 46 |
47 |

Follow me

48 | 49 |
50 |
51 | 52 |
53 | 57 |

Fill this form

58 |
59 | ( 63 | 64 | 65 | 66 | 67 | 68 | 69 | )} 70 | /> 71 | 72 | ( 76 | 77 | 78 | 79 | 80 | 81 | 82 | )} 83 | /> 84 |
85 | 86 | ( 90 | 91 | 92 | 93 | 94 | 95 | 96 | )} 97 | /> 98 | 99 | ( 103 | 104 | 105 |