Showing preview only (330K chars total). Download the full file or copy to clipboard to get everything.
Repository: rahuldkjain/github-profile-readme-generator
Branch: main
Commit: 5ae90bfcbd7a
Files: 87
Total size: 308.4 KB
Directory structure:
gitextract_r5j2l4sc/
├── .all-contributorsrc
├── .eslintignore
├── .eslintrc.json
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature-enhancement-request.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── config.yml
│ └── workflows/
│ └── deploy.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CHEATSHEET.md
├── CODE_OF_CONDUCT.md
├── CODE_STYLE_GUIDE.md
├── COMMIT_CONVENTION.md
├── CONTRIBUTING.md
├── DEPLOYMENT.md
├── LICENSE
├── README.md
├── env.example
├── eslint.config.mjs
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public/
│ ├── manifest.json
│ └── robots.txt
├── setupTests.js
├── src/
│ ├── app/
│ │ ├── about/
│ │ │ └── page.tsx
│ │ ├── addons/
│ │ │ └── page.tsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── robots.ts
│ │ ├── sitemap.ts
│ │ └── support/
│ │ └── page.tsx
│ ├── components/
│ │ ├── analytics/
│ │ │ └── conditional-analytics.tsx
│ │ ├── forms/
│ │ │ ├── __tests__/
│ │ │ │ └── form-input.test.tsx
│ │ │ ├── form-checkbox.tsx
│ │ │ ├── form-input.tsx
│ │ │ ├── form-select.tsx
│ │ │ ├── form-textarea.tsx
│ │ │ └── github-username-input.tsx
│ │ ├── layout/
│ │ │ ├── footer.tsx
│ │ │ ├── header.tsx
│ │ │ └── theme-provider.tsx
│ │ ├── sections/
│ │ │ ├── basic-info-section.tsx
│ │ │ ├── links-section.tsx
│ │ │ ├── skills-section.tsx
│ │ │ └── social-section.tsx
│ │ └── ui/
│ │ ├── accessibility-menu.tsx
│ │ ├── buy-me-coffee.tsx
│ │ ├── collapsible-section.tsx
│ │ ├── confirm-dialog.tsx
│ │ ├── cookie-consent.tsx
│ │ ├── github-stats.tsx
│ │ ├── markdown-preview.tsx
│ │ ├── select.tsx
│ │ ├── theme-toggle.tsx
│ │ └── toast.tsx
│ ├── constants/
│ │ ├── defaults.ts
│ │ └── skills.ts
│ ├── hooks/
│ │ ├── use-consent.ts
│ │ ├── use-local-storage.ts
│ │ └── use-theme.ts
│ ├── lib/
│ │ ├── analytics.ts
│ │ ├── asset-path.ts
│ │ ├── github-api.ts
│ │ ├── markdown-generator.ts
│ │ ├── storage.ts
│ │ ├── store.ts
│ │ └── validations.ts
│ ├── markdown-pages/
│ │ ├── about.md
│ │ ├── addons.md
│ │ └── support.md
│ ├── styles/
│ │ └── tailwind.css
│ ├── test/
│ │ └── setup.ts
│ └── types/
│ ├── profile.ts
│ ├── skills.ts
│ └── theme.ts
├── tailwind.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vitest.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"files": ["README.md"],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "sarbikbetal",
"name": "Sarbik Betal",
"avatar_url": "https://avatars2.githubusercontent.com/u/41508422?v=4",
"profile": "https://github.com/sarbikbetal",
"contributions": ["code"]
},
{
"login": "Hardik0307",
"name": "Hardik Bagada",
"avatar_url": "https://avatars3.githubusercontent.com/u/41434099?v=4",
"profile": "https://github.com/Hardik0307",
"contributions": ["code"]
},
{
"login": "antonkomarev",
"name": "Anton Komarev",
"avatar_url": "https://avatars0.githubusercontent.com/u/1849174?v=4",
"profile": "https://komarev.com",
"contributions": ["plugin"]
},
{
"login": "KKVANONYMOUS",
"name": "Kunal Kumar Verma",
"avatar_url": "https://avatars3.githubusercontent.com/u/58628586?v=4",
"profile": "https://kkvanonymous.github.io/",
"contributions": ["code"]
},
{
"login": "jaideepghosh",
"name": "Jaideep Ghosh",
"avatar_url": "https://avatars2.githubusercontent.com/u/3909648?v=4",
"profile": "http://jaideepghosh.blogspot.com",
"contributions": ["code"]
},
{
"login": "YashKandalkar",
"name": "yash",
"avatar_url": "https://avatars0.githubusercontent.com/u/35102959?v=4",
"profile": "http://yashkandalkar.github.io",
"contributions": ["code"]
},
{
"login": "abhijit-hota",
"name": "Abhijit Hota",
"avatar_url": "https://avatars0.githubusercontent.com/u/8116174?v=4",
"profile": "https://github.com/abhijit-hota",
"contributions": ["code", "test"]
},
{
"login": "Maddoxx88",
"name": "Sunit Shirke",
"avatar_url": "https://avatars1.githubusercontent.com/u/34238672?v=4",
"profile": "https://maddoxx88.github.io/",
"contributions": ["code"]
},
{
"login": "g-savitha",
"name": "Savitha Gollamudi",
"avatar_url": "https://avatars0.githubusercontent.com/u/31612459?v=4",
"profile": "https://www.gsavitha.in",
"contributions": ["code"]
}
],
"contributorsPerLine": 7,
"projectName": "github-profile-readme-generator",
"projectOwner": "rahuldkjain",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true
}
================================================
FILE: .eslintignore
================================================
node_modules/**
================================================
FILE: .eslintrc.json
================================================
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["plugin:react/recommended", "airbnb", "prettier"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react"],
"rules": {
"react/forbid-prop-types": 0
},
"ignorePatterns": ["**/*.test.js"]
}
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: rahuldkjain
patreon: # Replace with a single Patreon username
open_collective: github-profile-readme-generator
ko_fi: rahuldkjain
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://paypal.me/rahuldkjain', 'https://www.buymeacoffee.com/rahuldkjain']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: 🐛 Bug Report
about: Report a bug in GitHub Profile README Generator
title: '[Bug] '
labels: ['bug']
assignees: ''
---
## 🐛 Bug Description
A clear and concise description of what the bug is.
## 🔄 Steps to Reproduce
1. Go to [URL or page]
2. Click on [element]
3. Fill in [specific fields]
4. See error
## ✅ Expected Behavior
A clear description of what you expected to happen.
## 📸 Screenshots
If applicable, add screenshots to help explain your problem.
## 🖥️ Environment
**Desktop:**
- OS: [e.g. macOS, Windows, Linux]
- Browser: [e.g. Chrome 118, Safari 17, Firefox 119]
**Mobile:**
- Device: [e.g. iPhone 15, Samsung Galaxy S23]
- OS: [e.g. iOS 17.1, Android 14]
- Browser: [e.g. Safari, Chrome Mobile]
## 🔧 Additional Context
- Does this happen in incognito/private mode? [Yes/No]
- Console errors (if any): [Paste console output]
- Network connectivity: [Good/Slow/Offline]
**Note:** Please test at the current version: https://rahuldkjain.github.io/gh-profile-readme-generator
Join the **Discord Server** for further discussions.
<a href="https://discord.gg/HHMs7Eg">
<img src="https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg" alt="GPRG Discord Server Link" width="300px"/>
</a>
Server Link: https://discord.gg/HHMs7Eg
================================================
FILE: .github/ISSUE_TEMPLATE/feature-enhancement-request.md
================================================
---
name: Feature/Enhancement request
about: Suggest an idea for this project
title: ''
labels: enhancement, hacktoberfest
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
Join the **Discord Server** for further discussions.
<a href="https://discord.gg/HHMs7Eg">
<img src="https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg" alt="GPRG Discord Server Link" width="300px"/>
</a>
Server Link: https://discord.gg/HHMs7Eg
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: ✨ Feature Request
about: Suggest a new feature for GitHub Profile README Generator
title: '[Feature] '
labels: ['enhancement']
assignees: ''
---
## ✨ Feature Description
A clear and concise description of the feature you'd like to see.
## 🎯 Problem Statement
What problem does this feature solve? Is your feature request related to a problem?
## 💡 Proposed Solution
Describe the solution you'd like to see implemented.
## 🔄 User Flow
Describe how a user would interact with this feature:
1. User goes to...
2. User clicks/types...
3. System responds with...
## 🎨 Design Considerations
- UI/UX requirements
- Accessibility considerations
- Mobile responsiveness needs
- Theme compatibility (dark/light mode)
## 🔧 Technical Considerations
- Performance impact
- Browser compatibility requirements
- Dependencies needed
- Potential breaking changes
## 📋 Alternative Solutions
Describe alternatives you've considered.
## 📸 Mockups/Examples
If applicable, add mockups, sketches, or examples from other tools.
## 🎯 Priority
- [ ] Low - Nice to have
- [ ] Medium - Would improve UX significantly
- [ ] High - Critical for user workflow
- [ ] Critical - Blocking current functionality
## 📱 Target Platforms
- [ ] Desktop
- [ ] Mobile
- [ ] Tablet
- [ ] All platforms
---
💬 **Join our Discord** for feature discussions: https://discord.gg/HHMs7Eg
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
🚀 Thanks for contributing to GitHub Profile README Generator V2!
Before submitting your Pull Request, please ensure you've done the following:
📖 Read the Contributing Guide: https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/CONTRIBUTING.md
📖 Read the Code of Conduct: https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/CODE_OF_CONDUCT.md
🔄 Follow our Commit Convention: https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/COMMIT_CONVENTION.md
👷♀️ Create focused, single-purpose PRs
✅ Test your changes thoroughly
📝 Use conventional commit messages (feat:, fix:, docs:, etc.)
📗 Update documentation and add screenshots for UI changes
For Work In Progress PRs, please use the Draft PR feature.
Avoid force-pushing after receiving reviews unless requested.
-->
# 🔄 Pull Request
## 📋 **Type of Change** (check all applicable)
- [ ] 🐛 **Bug Fix** - Fixes an issue without breaking existing functionality
- [ ] ✨ **Feature** - Adds new functionality
- [ ] ⚡ **Performance** - Improves performance without changing functionality
- [ ] ♻️ **Refactor** - Code changes that neither fix bugs nor add features
- [ ] 📚 **Documentation** - Updates to documentation, comments, or README
- [ ] 🎨 **Style** - Code style changes (formatting, missing semi-colons, etc.)
- [ ] 🧪 **Test** - Adding or updating tests
- [ ] 🏗️ **Build** - Changes to build system or dependencies
- [ ] 👷 **CI/CD** - Changes to CI/CD workflows
- [ ] 🔒 **Security** - Security improvements or vulnerability fixes
- [ ] ♿ **Accessibility** - Improves accessibility compliance
- [ ] 📱 **Mobile** - Mobile-specific improvements
- [ ] 🌐 **i18n** - Internationalization changes
## 📖 **Description**
<!-- Provide a clear and concise description of what this PR does -->
### **What changed?**
<!-- Describe the changes made -->
### **Why was this change made?**
<!-- Explain the motivation behind this change -->
### **How does this change help users?**
<!-- Describe the user benefit -->
## 🔗 **Related Issues**
<!-- Link related issues using keywords: Closes #123, Fixes #456, Related to #789 -->
- Closes #
- Fixes #
- Related to #
## 🧪 **Testing & Quality Assurance**
### **Testing Done** (check all applicable)
- [ ] ✅ **Manual testing** - Tested functionality manually
- [ ] 🧪 **Unit tests** - Added/updated unit tests
- [ ] 🔄 **Integration tests** - Tested with other components
- [ ] 📱 **Mobile testing** - Tested on mobile devices
- [ ] ♿ **Accessibility testing** - Tested with screen readers/keyboard nav
- [ ] 🌐 **Cross-browser testing** - Tested in multiple browsers
- [ ] 🎨 **Visual testing** - Checked UI/UX in light/dark themes
### **Test Instructions**
<!-- Provide step-by-step instructions for reviewers to test your changes -->
1.
2.
3.
### **Expected Behavior**
<!-- Describe what should happen when testing -->
## 📸 **Screenshots/Recordings**
<!--
For UI changes, please include:
- Before/after screenshots
- Mobile screenshots
- Dark/light theme screenshots
- Screen recordings for complex interactions
-->
### **Before**
<!-- Screenshot/description of current state -->
### **After**
<!-- Screenshot/description of new state -->
## 📋 **Checklist**
### **Code Quality**
- [ ] 🔍 **TypeScript** - No TypeScript errors (`npm run type-check`)
- [ ] 🧹 **Linting** - No ESLint errors (`npm run lint`)
- [ ] 🎨 **Formatting** - Code is properly formatted (`npm run format`)
- [ ] 🏗️ **Build** - Production build succeeds (`npm run build`)
- [ ] ⚡ **Performance** - No performance regressions introduced
### **Accessibility**
- [ ] ♿ **WCAG Compliance** - Follows WCAG 2.1 AA guidelines
- [ ] ⌨️ **Keyboard Navigation** - All interactive elements are keyboard accessible
- [ ] 🔍 **Screen Reader** - Proper ARIA labels and semantic HTML
- [ ] 🎨 **Color Contrast** - Meets contrast requirements
- [ ] 🎯 **Focus Management** - Visible focus indicators
### **Mobile & Responsive**
- [ ] 📱 **Mobile Responsive** - Works on mobile devices (320px+)
- [ ] 🖥️ **Desktop** - Works on desktop (1024px+)
- [ ] 📐 **Tablet** - Works on tablet sizes (768px+)
- [ ] 🔄 **Orientation** - Works in portrait and landscape
### **Browser Compatibility**
- [ ] 🌐 **Chrome** - Latest version
- [ ] 🦊 **Firefox** - Latest version
- [ ] 🧭 **Safari** - Latest version
- [ ] 📱 **Mobile Safari** - iOS Safari
- [ ] 📱 **Chrome Mobile** - Android Chrome
### **Documentation**
- [ ] 📚 **Code Comments** - Added helpful comments for complex logic
- [ ] 📖 **Documentation** - Updated relevant documentation
- [ ] 📝 **README** - Updated README if needed
- [ ] 🔄 **Changelog** - Will be auto-generated from conventional commits
### **Security & Privacy**
- [ ] 🔒 **No Secrets** - No API keys, passwords, or sensitive data exposed
- [ ] 🛡️ **Input Validation** - Proper validation for user inputs
- [ ] 🔐 **XSS Prevention** - Protected against XSS attacks
- [ ] 🍪 **Privacy Compliant** - Follows GDPR/privacy requirements
## 🚀 **Deployment Notes**
<!-- Any special considerations for deployment -->
- [ ] **No breaking changes** - Backward compatible
- [ ] **Database changes** - N/A (static site)
- [ ] **Environment variables** - No new env vars needed
- [ ] **Third-party dependencies** - No new external dependencies
## 📝 **Additional Notes**
<!-- Any additional information, concerns, or context -->
## 👀 **Reviewers**
<!-- Tag specific reviewers if needed -->
<!-- @username for specific reviewers -->
---
**By submitting this PR, I confirm that:**
- ✅ I have read and agree to the [Code of Conduct](https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/CODE_OF_CONDUCT.md)
- ✅ I have followed the [Contributing Guidelines](https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/CONTRIBUTING.md)
- ✅ I have used [Conventional Commits](https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/COMMIT_CONVENTION.md) for my commit messages
- ✅ I have tested my changes thoroughly
- ✅ My code follows the project's coding standards
================================================
FILE: .github/config.yml
================================================
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Thanks for opening your first issue here! Your contribution means alot. 🙌 Join Discord Server (https://discord.gg/HHMs7Eg) for discussing issues, pull-requests, new features, etc.
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
Thanks for opening this pull request! Make sure you have assigned an issue to this respective PR 😇
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
firstPRMergeComment: >
Congrats on merging your first pull request🎉! Thanks alot for your contribution. 🙏
================================================
FILE: .github/workflows/deploy.yml
================================================
name: Build and Deploy
on:
push:
branches: [master, dev]
# Allow concurrent deployments for different environments
concurrency:
group: 'pages-${{ github.ref }}'
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run type check
run: npm run type-check
- name: Run linting
run: npm run lint
- name: Run tests
run: npm run test
- name: Build application
run: |
echo "Building for branch: ${{ github.ref_name }}"
echo "NODE_ENV: $NODE_ENV"
echo "SURGE_PREVIEW: $SURGE_PREVIEW"
npm run build
env:
NEXT_PUBLIC_GA_ID: ${{ secrets.NEXT_PUBLIC_GA_ID }}
NEXT_PUBLIC_REQUIRE_CONSENT: true
NEXT_PUBLIC_ANONYMIZE_IP: true
# Set SURGE_PREVIEW for non-master branches to disable basePath
SURGE_PREVIEW: ${{ github.ref != 'refs/heads/master' && 'true' || '' }}
- name: Upload Pages Artifact (Production)
if: github.ref == 'refs/heads/master'
uses: actions/upload-pages-artifact@v3
with:
path: ./out
- name: Upload Preview Artifact
if: github.ref != 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: preview-build-${{ github.run_number }}-${{ github.run_attempt }}
path: ./out
retention-days: 30
# Production deployment to main GitHub Pages
deploy-production:
needs: build
runs-on: ubuntu-latest
# Only deploy to production from master branch
if: github.ref == 'refs/heads/master'
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
# Preview deployment for development branches
deploy-preview:
needs: build
runs-on: ubuntu-latest
# Deploy preview for dev branches (not master, only on direct push)
if: github.ref != 'refs/heads/master' && github.event_name == 'push'
# Add deployment environment to show URL in GitHub UI
environment:
name: preview-${{ github.ref_name }}
steps:
- name: Calculate sanitized branch name
id: branch
run: |
SANITIZED_BRANCH=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9-]/-/g' | tr '[:upper:]' '[:lower:]')
echo "sanitized=$SANITIZED_BRANCH" >> $GITHUB_OUTPUT
echo "url=https://gprg-${SANITIZED_BRANCH}.surge.sh" >> $GITHUB_OUTPUT
- name: Debug deployment conditions
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "GitHub ref name: ${{ github.ref_name }}"
echo "Event name: ${{ github.event_name }}"
echo "Is master?: ${{ github.ref == 'refs/heads/master' }}"
echo "Is push?: ${{ github.event_name == 'push' }}"
echo "Should deploy?: ${{ github.ref != 'refs/heads/master' && github.event_name == 'push' }}"
- name: Download Preview Artifact
uses: actions/download-artifact@v4
with:
name: preview-build-${{ github.run_number }}-${{ github.run_attempt }}
path: ./preview-out
- name: Deploy to Surge.sh (Preview)
id: deploy
run: |
npm install -g surge
echo "Deploying preview for branch: ${{ github.ref_name }}"
echo "Sanitized branch name: ${{ steps.branch.outputs.sanitized }}"
echo "Preview URL: ${{ steps.branch.outputs.url }}"
surge ./preview-out ${{ steps.branch.outputs.url }} --token ${{ secrets.SURGE_TOKEN }}
echo "deployment_url=${{ steps.branch.outputs.url }}" >> $GITHUB_OUTPUT
env:
SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }}
- name: Comment PR with Preview URL
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const previewUrl = '${{ steps.deploy.outputs.deployment_url }}';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `🚀 **Preview Deployment Ready!**\n\n📱 Preview URL: ${previewUrl}\n\n*This preview will be available for 30 days.*`
});
- name: Create deployment status
uses: actions/github-script@v7
with:
script: |
const previewUrl = '${{ steps.deploy.outputs.deployment_url }}';
// Create a commit status with the deployment URL
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: 'success',
target_url: previewUrl,
description: `Preview deployed to ${previewUrl}`,
context: 'deployment/preview'
});
- name: Update Status Check
run: |
echo "🚀 Preview deployed successfully!"
echo "📱 Preview URL: ${{ steps.deploy.outputs.deployment_url }}"
echo ""
echo "You can find this URL in:"
echo "1. GitHub Actions > Environments tab"
echo "2. Commit status checks"
echo "3. This workflow run summary"
# Add to job summary for easy access
echo "## 🚀 Preview Deployment Complete!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Preview URL:** [${{ steps.deploy.outputs.deployment_url }}](${{ steps.deploy.outputs.deployment_url }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Where to find this URL:" >> $GITHUB_STEP_SUMMARY
echo "- **Environments tab:** Go to your repository → Environments → preview-${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit status:** Check the commit status checks for 'deployment/preview'" >> $GITHUB_STEP_SUMMARY
echo "- **This summary:** Bookmark this workflow run for easy access" >> $GITHUB_STEP_SUMMARY
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# Claude
.cursor/
.claude/
================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged
================================================
FILE: .prettierignore
================================================
.cache
package.json
package-lock.json
public
================================================
FILE: .prettierrc
================================================
{
"singleQuote": true,
"jsxSingleQuote": false,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "es5",
"semi": true,
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.0] - 2025-10-15
### ✨ Features
- **Complete rewrite**: Migrated to Next.js 15 with App Router and Turbopack
- **React 19**: Updated to latest React with concurrent features
- **TypeScript 5**: Full type safety with strict configuration
- **Modern UI**: Tailwind CSS 4 with design tokens and CSS variables
- **Accessibility**: WCAG 2.1 AA compliance with accessibility menu
- **Privacy**: GDPR-compliant analytics with opt-in consent
- **Performance**: 3x faster builds and 50% smaller bundle size
- **Auto-fill**: GitHub integration for automatic profile data
- **Export/Import**: JSON functionality for profile data
- **Enhanced UX**: Multi-step wizard with real-time validation
- **Responsive**: Mobile-first design with touch optimization
### 🐛 Bug Fixes
- Fixed skill selection persistence across sessions
- Resolved theme toggle flickering on page load
- Fixed social media icon alignment issues
- Corrected markdown preview rendering edge cases
### ⚡ Performance Improvements
- Implemented code splitting with lazy loading
- Optimized bundle size with Turbopack
- Added image optimization for better loading
- Reduced JavaScript bundle by 50%
### ♻️ Code Refactoring
- Migrated from Gatsby to Next.js 15
- Converted all components to TypeScript
- Implemented modern React patterns (hooks, context)
- Restructured project architecture for scalability
### 📚 Documentation
- Added comprehensive TypeScript documentation
- Created accessibility guidelines
- Updated deployment documentation
- Added contributing guidelines for V2
### 🏗️ Build System
- Migrated to Next.js build system
- Added Turbopack for development
- Implemented ESLint + Prettier configuration
- Added Vitest for testing
### 👷 Continuous Integration
- Enhanced GitHub Actions workflows
- Added preview deployments with environment tracking
- Implemented automated release management
- Added comprehensive testing pipeline
---
## Previous Versions (V1)
For changes in V1, see the [V1 Release Archive](https://github.com/rahuldkjain/github-profile-readme-generator/releases?q=v1&expanded=true).
### Migration from V1 to V2
V2 represents a complete rewrite with breaking changes:
- **Technology Stack**: Gatsby → Next.js 15
- **Styling**: CSS Modules → Tailwind CSS 4
- **State Management**: Local state → Zustand + localStorage
- **Build System**: Webpack → Turbopack
- **Type Safety**: JavaScript → TypeScript 5
All V1 functionality has been preserved and enhanced in V2. See [MIGRATION_STRATEGY.md](./MIGRATION_STRATEGY.md) for detailed migration information.
================================================
FILE: CHEATSHEET.md
================================================
# GitHub Profile Generator - Developer Cheatsheet
## 🚀 Quick Start
```bash
npm install && npm run dev
```
Visit: http://localhost:3000
---
## 📁 File Structure (Where to Edit)
| Task | File Location |
|------|--------------|
| Add form field | `src/components/sections/[section]-section.tsx` |
| Add validation | `src/lib/validations.ts` |
| Modify markdown output | `src/lib/markdown-generator.ts` |
| Add new section | Create in `src/components/sections/` + add to `src/app/page.tsx` |
| Create form component | `src/components/forms/` |
| Add skill | `src/constants/skills.ts` |
| Storage logic | `src/lib/storage.ts` |
| Theme colors | `src/styles/globals.css` |
| Header/Footer | `src/components/layout/` |
---
## 🎨 Theme Colors (Always Use These)
```typescript
// Backgrounds
bg-background // Main background
bg-card // Card background
bg-accent // Accent background
bg-muted // Muted background
bg-primary // Primary action background
// Text
text-foreground // Main text
text-muted-foreground // Secondary text
text-primary-foreground // Text on primary bg
text-destructive // Error text
// Borders & Effects
border-border // Border color
border-input // Input border
ring-ring // Focus ring
```
**❌ Never hardcode:** `bg-white`, `text-black`, `border-gray-300`
---
## 📝 Component Templates
### Form Component
```typescript
'use client';
import { forwardRef } from 'react';
import type { InputHTMLAttributes } from 'react';
export interface MyInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
label?: string;
error?: string;
}
export const MyInput = forwardRef<HTMLInputElement, MyInputProps>(
({ label, error, className = '', ...props }, ref) => {
return (
<div className="w-full space-y-1">
{label && <label htmlFor={props.id} className="text-foreground text-sm font-medium">{label}</label>}
<input
ref={ref}
className={`border-input bg-background text-foreground focus:ring-ring w-full rounded-lg border px-4 py-2 transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-50 ${error ? 'border-destructive' : ''} ${className}`}
aria-invalid={!!error}
{...props}
/>
{error && <p className="text-destructive text-sm" role="alert">{error}</p>}
</div>
);
}
);
MyInput.displayName = 'MyInput';
```
### Section Component
```typescript
'use client';
import { UseFormRegister, FieldErrors } from 'react-hook-form';
import { FormInput } from '@/components/forms/form-input';
import type { MyFormData } from '@/lib/validations';
interface MySectionProps {
register: UseFormRegister<MyFormData>;
errors: FieldErrors<MyFormData>;
}
export function MySection({ register, errors }: MySectionProps) {
return (
<div className="space-y-6">
<div className="border-b border-border pb-4">
<h2 className="text-2xl font-bold">Section Title</h2>
<p className="text-muted-foreground mt-1 text-sm">Description</p>
</div>
<div className="grid gap-4 md:grid-cols-2">
<FormInput
{...register('fieldName')}
id="fieldName"
label="Label"
error={errors.fieldName?.message}
/>
</div>
</div>
);
}
```
---
## 🔒 TypeScript Patterns
```typescript
// Component Props
interface ComponentProps {
title: string; // Required
count?: number; // Optional
onAction: () => void; // Required function
onChange?: (val: string) => void; // Optional function
}
// Zod Schema
const schema = z.object({
email: z.string().email(),
age: z.number().min(0).max(120),
url: z.string().url().optional(),
isActive: z.boolean().default(false),
});
type FormData = z.infer<typeof schema>;
// Form Hook
const { register, watch, setValue, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { isActive: false }
});
```
---
## ♿ Accessibility Checklist
```typescript
// ✅ Keyboard Navigation
<button
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
className="focus:ring-2 focus:ring-primary"
>
// ✅ ARIA Labels
<nav aria-label="Main navigation">
<button aria-label="Close dialog">
<input aria-describedby="help-text" aria-invalid={!!error} />
// ✅ Form Labels
<label htmlFor="email">Email</label>
<input id="email" />
// ✅ Error Messages
<p id="email-error" role="alert" className="text-destructive">
{error}
</p>
// ✅ Active States
<Link href="/" aria-current={isActive ? 'page' : undefined}>
// ✅ Alt Text
<img src={url} alt="Descriptive text" loading="lazy" />
```
---
## 📱 Responsive Patterns
```typescript
// Mobile First
className="text-sm md:text-base lg:text-lg"
// Grid Layouts
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
// Visibility
className="hidden md:block" // Desktop only
className="block md:hidden" // Mobile only
// Spacing
className="gap-2 md:gap-4 lg:gap-6"
className="px-4 md:px-6 lg:px-8"
// Breakpoints
// base: 0-639px
// sm: 640px+
// md: 768px+
// lg: 1024px+
// xl: 1280px+
```
---
## 🎯 Import Order
```typescript
// 1. React & Next.js
import { useState, useEffect } from 'react';
import Link from 'next/link';
// 2. Third-party
import { useForm } from 'react-hook-form';
import { z } from 'zod';
// 3. Internal utils
import { saveFormData } from '@/lib/storage';
import type { ProfileFormData } from '@/lib/validations';
// 4. Components
import { FormInput } from '@/components/forms/form-input';
// 5. Constants
import { skills } from '@/constants/skills';
```
---
## 🏎️ Performance
```typescript
// Memoize calculations
const count = useMemo(() =>
expensiveCalculation(data),
[data]
);
// Memoize callbacks
const handleClick = useCallback(() => {
doSomething();
}, []);
// Memoize components
const MemoizedComponent = React.memo(Component);
// Debounce auto-save
useEffect(() => {
const timer = setTimeout(() => {
saveData();
}, 1000);
return () => clearTimeout(timer);
}, [data]);
// Lazy load images
<img src={url} alt="..." loading="lazy" />
```
---
## 💾 Storage Pattern
```typescript
// Save
import { saveFormData } from '@/lib/storage';
saveFormData({
profile: getProfileValues(),
social: getSocialValues(),
// ...
skills: selectedSkills,
lastSaved: new Date().toISOString(),
});
// Load
import { loadFormData } from '@/lib/storage';
useEffect(() => {
const saved = loadFormData();
if (saved) {
// Restore data
setValue('title', saved.profile.title);
}
}, []);
// Clear
import { clearFormData } from '@/lib/storage';
clearFormData();
```
---
## 🐛 Common Errors & Fixes
| Error | Fix |
|-------|-----|
| "Type 'any' is not assignable" | Add explicit types, avoid `any` |
| "Property does not exist" | Add to interface, check spelling |
| "Cannot find module '@/...'" | Check path, restart TS server |
| "localStorage is not defined" | Wrap in `useEffect`, check 'use client' |
| "Hydration mismatch" | Ensure same HTML on server/client |
| TypeScript errors after install | Run `npm run type-check` |
---
## 🎨 Tailwind Class Order
```
Layout → Size → Spacing → Typography → Colors → Borders → Effects → States → Responsive
```
Example:
```typescript
className="flex items-center gap-2 w-full px-4 py-2 text-sm font-medium text-foreground bg-accent rounded-lg border border-border shadow-sm transition-colors hover:bg-accent/80 focus:ring-2 focus:ring-primary disabled:opacity-50 md:px-6 lg:text-base"
```
---
## 🔧 Useful Commands
```bash
# Development
npm run dev # Start dev server (Turbopack)
npm run build # Production build
npm run start # Start production server
npm run type-check # Check TypeScript
npm run lint # Run ESLint
# Git
git status # Check changes
git add . # Stage all changes
git commit -m "feat: add feature" # Commit
git push # Push to remote
```
---
## 🎯 Git Commit Types
```
feat(scope): # New feature
fix(scope): # Bug fix
refactor(scope): # Code restructure
style(scope): # Formatting
a11y(scope): # Accessibility
perf(scope): # Performance
docs(scope): # Documentation
test(scope): # Tests
chore(scope): # Maintenance
```
---
## 📚 Key Files to Read
1. **REVAMP_SUMMARY.md** - Full architecture
2. **.cursorrules** - Code conventions
3. **.cursorrules-quick** - Quick patterns
4. **DX_GUIDE.md** - Developer guide
5. **src/app/page.tsx** - Main wizard implementation
---
## 🤖 Cursor AI Quick Commands
```
# Include file in context
@filename
# Include folder
@folder
# Create component
@.cursorrules Create a form component for [x]
# Fix code
@.cursorrules Fix accessibility issues in [component]
# Review
@.cursorrules Review this code for [standards]
```
---
## 💡 Pro Tips
1. **Always use 'use client'** for components with state/events
2. **Never hardcode colors** - use CSS variables
3. **Type everything** - avoid `any`
4. **Think accessibility first** - keyboard + ARIA
5. **Mobile first** - base styles for mobile
6. **Test dark mode** - both themes
7. **Debounce saves** - 1 second delay
8. **Memoize when needed** - useMemo/useCallback
9. **Clean up effects** - return cleanup function
10. **Follow patterns** - check existing code first
---
## 🚨 Before Committing
- [ ] No TypeScript errors
- [ ] No ESLint warnings
- [ ] Works on mobile (375px)
- [ ] Works on desktop (1440px)
- [ ] Keyboard navigable
- [ ] Focus indicators visible
- [ ] Dark mode tested
- [ ] Auto-save works (if form)
- [ ] No console errors
- [ ] Follows .cursorrules patterns
---
## 📞 Help Resources
- **Internal**: REVAMP_SUMMARY.md, .cursorrules, DX_GUIDE.md
- **Next.js**: https://nextjs.org/docs
- **React**: https://react.dev
- **Tailwind**: https://tailwindcss.com/docs
- **React Hook Form**: https://react-hook-form.com
- **Zod**: https://zod.dev
- **WCAG**: https://www.w3.org/WAI/WCAG21/quickref/
---
**Print this and keep it handy! 📋**
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at rahuldkjain@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: CODE_STYLE_GUIDE.md
================================================
# Coding Style Guide
## Project Architecture
This project uses **Next.js 15** with **TypeScript** and **Tailwind CSS**. We follow modern React patterns with functional components and hooks.
## File Layout (`src/components/*.tsx`)
1. **Imports**
- React imports first
- Third-party library imports
- Internal component imports
- Type imports (using `import type`)
2. **Type Definitions**
- Interface definitions for props
- Type aliases if needed
3. **Reusable Components**
- Smaller components needed for the main component
- Place **above** the main component, **not** inside it
4. **Main Component**
- Main exported component (e.g., `SkillsSection` in `skills-section.tsx`)
5. **Export Statement**
- `export default MainComponent;` or named exports
## TypeScript Guidelines
### Component Props
- Use `interface` for component props with clear naming:
```tsx
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
onClick?: () => void;
children: React.ReactNode;
}
```
### Type Safety
- Avoid `any` types - use `unknown` or proper types
- Use strict TypeScript configuration
- Leverage type inference where possible
- Use `as const` for literal types
```tsx
// Good
const themes = ['light', 'dark'] as const;
type Theme = (typeof themes)[number];
// Bad
const themes: any = ['light', 'dark'];
```
## Component Patterns
### Functional Components
- Use **ES6 arrow functions** for all components
- Use `React.forwardRef` when ref forwarding is needed
- Prefer named exports for reusable components
```tsx
// Good
export const Button = ({ variant = 'primary', ...props }: ButtonProps) => {
return <button className={`btn btn-${variant}`} {...props} />;
};
// Also good for main components
const SkillsSection = ({ skills, onSkillToggle }: SkillsSectionProps) => {
// component logic
};
export default SkillsSection;
```
### Hooks Usage
- Use custom hooks for reusable logic
- Keep hooks at the top of components
- Use `useCallback` and `useMemo` for performance optimization
```tsx
const MyComponent = () => {
const [state, setState] = useState();
const { data, loading } = useCustomHook();
const memoizedValue = useMemo(() => expensiveCalculation(), [dependency]);
// component JSX
};
```
## Styling with Tailwind CSS
### Class Organization
- Use Tailwind utility classes
- Group related classes together
- Use responsive prefixes (`sm:`, `md:`, `lg:`)
```tsx
// Good
<div className="flex flex-col gap-4 rounded-lg border p-4 sm:flex-row sm:gap-6">
<button className="bg-primary text-primary-foreground hover:bg-primary/90 rounded px-4 py-2 transition-colors">
Click me
</button>
</div>
```
### CSS Variables
- Use CSS custom properties for theming
- Follow the design system color palette
- Prefer Tailwind classes over custom CSS
## File Naming Conventions
- **Components**: `kebab-case.tsx` (e.g., `skills-section.tsx`)
- **Hooks**: `use-hook-name.ts` (e.g., `use-local-storage.ts`)
- **Types**: `kebab-case.ts` (e.g., `profile-types.ts`)
- **Utils**: `kebab-case.ts` (e.g., `markdown-generator.ts`)
## Spacing & Formatting
### JavaScript/TypeScript
- Use **2 spaces** for indentation (not 4)
- Use spaces after `if`, `for`, `while`, `switch`
- No spaces after opening `(` and before closing `)`
- Use spaces around destructuring
```tsx
// Good
const { name, email } = user;
const handleClick = ({ target }: MouseEvent) => {
if (target instanceof HTMLElement) {
// logic
}
};
// Bad
const { name, email } = user;
const handleClick = ({ target }: MouseEvent) => {
if (target instanceof HTMLElement) {
// logic
}
};
```
### JSX Formatting
- Space before self-closing tag slash
- No spaces in JSX curly braces
- Use new lines for multiple props
```tsx
// Good
<Input
value={value}
onChange={handleChange}
placeholder="Enter text"
/>
<Icon className="h-4 w-4" />
// Bad
<Input value={value} onChange={handleChange} placeholder="Enter text"/>
<Icon className="h-4 w-4"/>
```
## Props & Event Handling
### Prop Naming
- Use `camelCase` for prop names
- Use `PascalCase` if prop value is a React component
- Use descriptive names with proper prefixes
```tsx
interface FormProps {
initialValues?: Record<string, unknown>;
onSubmit?: (data: FormData) => void;
isLoading?: boolean;
ErrorComponent?: React.ComponentType;
}
```
### Event Handlers
- Prefix with `handle` or `on`
- Use descriptive names
- Type event handlers properly
```tsx
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
// submit logic
};
```
## Best Practices
### Code Quality
- **Always** add semicolons
- Use meaningful variable and function names
- Keep components small and focused (< 200 lines)
- Extract complex logic into custom hooks
- Use TypeScript strict mode
### Accessibility
- Always add `alt` prop to `img` tags
- Use semantic HTML elements
- Add proper ARIA attributes
- Don't use `outline: none` without alternative focus styles
- Use proper heading hierarchy
### Performance
- Use `React.memo` for expensive components
- Implement proper dependency arrays for hooks
- Avoid inline objects and functions in JSX
- Use `useCallback` and `useMemo` appropriately
```tsx
// Good
const MemoizedComponent = React.memo(({ data }: Props) => {
const processedData = useMemo(() => processData(data), [data]);
const handleClick = useCallback(() => {
// click logic
}, []);
return <div>{/* JSX */}</div>;
});
```
### Error Handling
- Use error boundaries for component errors
- Handle async operations properly
- Provide fallback UI for loading states
```tsx
const AsyncComponent = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return <EmptyState />;
return <DataDisplay data={data} />;
};
```
## Dependencies & Imports
### Import Organization
```tsx
// 1. React imports
import { useState, useEffect, useCallback } from 'react';
import type { ReactNode } from 'react';
// 2. Third-party libraries
import { motion } from 'framer-motion';
import { ChevronDown } from 'lucide-react';
// 3. Internal imports
import { Button } from '@/components/ui/button';
import { useLocalStorage } from '@/hooks/use-local-storage';
import type { ProfileData } from '@/types/profile';
```
### Package Management
- Prefer stable, well-maintained packages
- Keep dependencies up to date
- Use exact versions for critical dependencies
- Document any custom modifications
## Testing Considerations
- Write testable components with clear props
- Avoid complex side effects in components
- Use dependency injection for external services
- Keep business logic separate from UI logic
## Further Reading
This guide is based on:
- [Next.js Best Practices](https://nextjs.org/docs/app/building-your-application/styling/tailwind-css)
- [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/)
- [Tailwind CSS Best Practices](https://tailwindcss.com/docs/reusing-styles)
For questions about code style, please discuss with maintainers on our [Discord community](https://discord.gg/HHMs7Eg).
================================================
FILE: COMMIT_CONVENTION.md
================================================
# 📝 Commit Message Convention
This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification for automated changelog generation and semantic versioning.
## Format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
## Types
| Type | Description | Version Bump |
| ---------- | ------------------------ | ------------ |
| `feat` | New feature | Minor |
| `fix` | Bug fix | Patch |
| `perf` | Performance improvement | Patch |
| `refactor` | Code refactoring | Patch |
| `docs` | Documentation changes | Patch |
| `style` | Code style changes | Patch |
| `test` | Adding or updating tests | Patch |
| `build` | Build system changes | Patch |
| `ci` | CI/CD changes | Patch |
| `chore` | Maintenance tasks | No bump |
## Breaking Changes
Add `BREAKING CHANGE:` in the footer or `!` after type to indicate breaking changes:
```bash
feat!: remove deprecated API endpoints
BREAKING CHANGE: The old API endpoints have been removed. Use the new v2 endpoints instead.
```
## Examples
### Features
```bash
feat: add GitHub auto-fill integration
feat(ui): implement dark mode toggle
feat!: migrate to Next.js 15 App Router
```
### Bug Fixes
```bash
fix: resolve skill selection persistence issue
fix(mobile): correct responsive navigation layout
fix(a11y): improve keyboard navigation for forms
```
### Performance
```bash
perf: optimize image loading with next/image
perf(build): reduce bundle size by 30%
```
### Documentation
```bash
docs: update installation instructions
docs(api): add TypeScript examples
docs(readme): fix broken demo links
```
### Refactoring
```bash
refactor: convert components to TypeScript
refactor(store): migrate to Zustand state management
```
## Scopes (Optional)
Use scopes to indicate the area of change:
- `ui` - User interface components
- `api` - API related changes
- `build` - Build system
- `ci` - Continuous integration
- `docs` - Documentation
- `test` - Testing
- `a11y` - Accessibility
- `perf` - Performance
- `mobile` - Mobile-specific changes
## Tools
### Commitizen (Recommended)
Install commitizen for interactive commit messages:
```bash
npm install -g commitizen cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
```
Use `git cz` instead of `git commit`:
```bash
git add .
git cz
```
### VS Code Extension
Install "Conventional Commits" extension for VS Code to get commit message templates.
## Automated Release Process
1. **Commit** using conventional format
2. **Push** to master branch
3. **Release Please** analyzes commits
4. **Creates PR** with changelog and version bump
5. **Merge PR** to trigger release and deployment
## Examples in Practice
```bash
# Adding new feature
git commit -m "feat(ui): add accessibility menu with font size controls"
# Fixing bug
git commit -m "fix(mobile): resolve navigation menu overflow on small screens"
# Breaking change
git commit -m "feat!: migrate to Next.js 15 App Router
BREAKING CHANGE: Pages directory structure has changed.
See migration guide for updating custom pages."
# Performance improvement
git commit -m "perf(build): implement code splitting for 50% bundle reduction"
# Documentation update
git commit -m "docs(contributing): add TypeScript coding standards"
```
## Benefits
- ✅ **Automated changelogs** - No manual changelog maintenance
- ✅ **Semantic versioning** - Automatic version bumps based on commit types
- ✅ **Release notes** - Rich, categorized release notes
- ✅ **Consistency** - Standardized commit history
- ✅ **Tooling integration** - Works with Release Please, semantic-release, etc.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to GitHub Profile README Generator
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
<a href="https://discord.gg/HHMs7Eg" target="blank">
<img src="https://img.shields.io/discord/735303195105951764?color=%23677BC4&label=Join%20Community&style=flat-square" alt="join discord community of github profile readme generator"/>
</a>
Please note we have a code of conduct, please follow it in all your interactions with the project.
## 🚀 Tech Stack
This project is built with modern web technologies:
- **Framework**: [Next.js 15](https://nextjs.org/) with App Router
- **Language**: [TypeScript](https://www.typescriptlang.org/) for type safety
- **Styling**: [Tailwind CSS](https://tailwindcss.com/) for utility-first styling
- **Icons**: [Lucide React](https://lucide.dev/) for consistent iconography
- **Animations**: [Framer Motion](https://www.framer.com/motion/) for smooth animations
- **Forms**: [React Hook Form](https://react-hook-form.com/) for form management
- **Analytics**: [Google Analytics 4](https://developers.google.com/analytics/devguides/collection/ga4) with privacy compliance
- **Testing**: [Vitest](https://vitest.dev/) for unit testing
- **Linting**: [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) for code quality
## 🛠️ Development Setup
### Prerequisites
- **Node.js**: Version 18.17 or higher
- **npm**: Version 9 or higher (comes with Node.js)
- **Git**: For version control
### Local Development
1. **Fork & Clone the repository**
```bash
# Fork the repo on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/github-profile-readme-generator.git
cd github-profile-readme-generator
```
2. **Install dependencies**
```bash
npm install
```
3. **Set up environment variables** (optional)
```bash
# Copy the example environment file
cp env.example .env.local
# Add your Google Analytics ID if you want to test analytics
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
```
4. **Start the development server**
```bash
npm run dev
```
The app will be available at `http://localhost:3000`
### Available Scripts
```bash
# Development
npm run dev # Start development server
npm run build # Build for production
npm run start # Start production server
npm run export # Export static site
# Code Quality
npm run lint # Run ESLint
npm run lint:fix # Fix ESLint issues automatically
npm run type-check # Run TypeScript type checking
# Testing
npm run test # Run tests
npm run test:watch # Run tests in watch mode
npm run test:ui # Run tests with UI
```
## 📝 Pull Request Process
### Before You Start
1. **Check existing issues** to see if your feature/bug is already being worked on
2. **Create an issue** if one doesn't exist for your contribution
3. **Join our Discord** to discuss your ideas with the community
4. **Read our [Code Style Guide](CODE_STYLE_GUIDE.md)** to understand our coding standards
### Making Changes
1. **Create a feature branch** from `main`
```bash
git checkout -b feature/your-feature-name
# or
git checkout -b fix/bug-description
```
2. **Follow our coding standards**
- Use TypeScript with strict mode
- Follow the existing code style (ESLint + Prettier)
- Write meaningful commit messages
- Add tests for new features
- Update documentation if needed
3. **Test your changes**
```bash
# Run all checks before submitting
npm run lint # Check code style
npm run type-check # Check TypeScript
npm run test # Run tests
npm run build # Ensure it builds successfully
```
4. **Commit your changes**
```bash
# Use conventional commit messages
git add .
git commit -m "feat: add new skill category filter"
# or
git commit -m "fix: resolve mobile navigation issue"
```
### Submitting Your PR
1. **Push your branch**
```bash
git push origin feature/your-feature-name
```
2. **Create a Pull Request** on GitHub with:
- Clear title describing the change
- Detailed description of what was changed and why
- Screenshots for UI changes
- Reference to any related issues
3. **PR Requirements**:
- ✅ All tests pass
- ✅ No TypeScript errors
- ✅ ESLint passes
- ✅ Builds successfully
- ✅ Follows our code style guide
- ✅ Includes tests for new features
- ✅ Updates documentation if needed
### Review Process
1. **Automated checks** will run on your PR
2. **Maintainers** will review your code
3. **Address feedback** by pushing new commits to your branch
4. **Merge** happens after approval from maintainers
## 🎯 Contribution Guidelines
### What We're Looking For
- **Bug fixes** with clear reproduction steps
- **New features** that align with the project's goals
- **Performance improvements** with benchmarks
- **Accessibility improvements** following WCAG guidelines
- **Documentation** improvements and translations
- **Test coverage** improvements
### Areas That Need Help
- 🌍 **Internationalization** (i18n) support
- 📱 **Mobile experience** improvements
- ♿ **Accessibility** enhancements
- 🎨 **UI/UX** improvements
- 🧪 **Test coverage** expansion
- 📚 **Documentation** improvements
- 🔧 **Developer experience** tools
### Component Development
When creating new components:
```tsx
// Follow this structure for new components
interface ComponentProps {
// Define clear prop types
title: string;
onAction?: () => void;
variant?: 'primary' | 'secondary';
}
export const Component = ({ title, onAction, variant = 'primary' }: ComponentProps) => {
// Component logic here
return <div className="component-styles">{/* JSX here */}</div>;
};
```
### File Organization
```
src/
├── app/ # Next.js app directory
├── components/ # Reusable UI components
│ ├── forms/ # Form-related components
│ ├── layout/ # Layout components
│ ├── sections/ # Page sections
│ └── ui/ # Basic UI components
├── hooks/ # Custom React hooks
├── lib/ # Utility functions and configurations
├── types/ # TypeScript type definitions
└── constants/ # Application constants
```
## 🐛 Reporting Bugs
When reporting bugs, please include:
1. **Steps to reproduce** the bug
2. **Expected behavior** vs actual behavior
3. **Screenshots** or screen recordings if applicable
4. **Browser/OS information**
5. **Console errors** if any
Use our [bug report template](https://github.com/rahuldkjain/github-profile-readme-generator/issues/new/choose) for consistency.
## 💡 Suggesting Features
For feature requests:
1. **Check existing issues** to avoid duplicates
2. **Describe the problem** you're trying to solve
3. **Propose a solution** with examples
4. **Consider the impact** on existing users
5. **Be open to discussion** and alternative approaches
## 🏷️ Issue Labels
We use labels to organize issues:
- `bug` - Something isn't working
- `enhancement` - New feature or request
- `good first issue` - Good for newcomers
- `help wanted` - Extra attention is needed
- `documentation` - Improvements to docs
- `accessibility` - A11y improvements
- `performance` - Performance improvements
## 📋 Code Review Checklist
Before requesting review, ensure:
- [ ] Code follows our style guide
- [ ] All tests pass locally
- [ ] TypeScript compiles without errors
- [ ] ESLint passes without warnings
- [ ] Component is accessible (proper ARIA labels, keyboard navigation)
- [ ] Mobile-responsive design
- [ ] Performance considerations addressed
- [ ] Documentation updated if needed
- [ ] Commit messages are clear and descriptive
## 🎉 Recognition
Contributors are recognized in:
- **README.md** contributors section
- **All Contributors** bot for automated recognition
- **Release notes** for significant contributions
- **Discord community** shoutouts
## 📞 Getting Help
- **Discord**: [Join our community](https://discord.gg/HHMs7Eg)
- **Issues**: [GitHub Issues](https://github.com/rahuldkjain/github-profile-readme-generator/issues)
- **Discussions**: [GitHub Discussions](https://github.com/rahuldkjain/github-profile-readme-generator/discussions)
## 📄 License
By contributing, you agree that your contributions will be licensed under the same license as the project (MIT License).
---
Thank you for contributing to GitHub Profile README Generator! 🚀
================================================
FILE: DEPLOYMENT.md
================================================
# 🚀 Production Deployment Guide
## Pre-Deployment Checklist
### ✅ SEO & Performance
- [x] **Meta Tags**: Complete Open Graph and Twitter Card metadata
- [x] **Structured Data**: JSON-LD schema for better search visibility
- [x] **Canonical URLs**: Proper canonical URLs for all pages
- [x] **Sitemap**: Auto-generated XML sitemap at `/sitemap.xml`
- [x] **Robots.txt**: SEO-friendly robots.txt configuration
- [x] **PWA Manifest**: Mobile app-like experience with manifest.json
### ✅ Assets & Performance
- [x] **Static Assets**: All assets properly placed in `/public` directory
- [x] **Image Optimization**: OG image and favicon configured
- [x] **Bundle Optimization**: Turbopack enabled for faster builds
- [x] **CSS Optimization**: Tailwind CSS optimized for production
- [x] **Font Loading**: Local fonts with proper fallbacks
### ✅ Analytics & Tracking
- [x] **Google Analytics**: GA4 integration with environment variable
- [x] **Buy Me Coffee**: Widget properly integrated
- [x] **Error Tracking**: Console error handling
## Environment Configuration
### 1. Create Environment File
```bash
cp .env.example .env.local
```
### 2. Configure Analytics & Privacy
```env
# Required for production analytics
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
# Optional: Google Search Console verification
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=your-verification-code
# Privacy & GDPR Compliance (recommended)
NEXT_PUBLIC_REQUIRE_CONSENT=true
NEXT_PUBLIC_ANONYMIZE_IP=true
```
**GA4 Setup Instructions:**
1. Create a GA4 property in Google Analytics
2. Copy your Measurement ID (format: G-XXXXXXXXXX)
3. Add it to your environment variables
4. The app includes GDPR-compliant consent management
5. Custom events track: GitHub auto-fill, README completion, file exports
## Build & Deploy
### GitHub Pages Deployment
```bash
# Build for production
npm run build
# The built files will be in the 'out' directory
# GitHub Pages will automatically serve from this directory
```
### Custom Domain Deployment
1. Update the base URL in `next.config.ts`
2. Update URLs in `src/app/layout.tsx` metadata
3. Update sitemap and robots.txt URLs
## Performance Metrics
### Bundle Analysis
- **Main Bundle**: ~282 kB (optimized)
- **First Load JS**: ~174 kB shared
- **Build Time**: ~3.2s with Turbopack
### SEO Score
- **Structured Data**: ✅ Complete
- **Meta Tags**: ✅ All pages covered
- **Performance**: ✅ Optimized bundles
- **Accessibility**: ✅ ARIA labels and semantic HTML
- **PWA**: ✅ Manifest and service worker ready
## Post-Deployment Verification
### 1. SEO Tools
- [ ] Test with [Google Rich Results Test](https://search.google.com/test/rich-results)
- [ ] Verify with [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/)
- [ ] Check with [Twitter Card Validator](https://cards-dev.twitter.com/validator)
### 2. Performance Testing
- [ ] Run [Google PageSpeed Insights](https://pagespeed.web.dev/)
- [ ] Test with [GTmetrix](https://gtmetrix.com/)
- [ ] Verify mobile responsiveness
### 3. Functionality Testing
- [ ] Test all form submissions
- [ ] Verify GitHub API integration
- [ ] Check markdown generation
- [ ] Test theme switching
- [ ] Verify analytics tracking
## Monitoring
### Analytics Setup
1. **Google Analytics**: Monitor user engagement and conversion
2. **Search Console**: Track search performance and indexing
3. **Error Monitoring**: Monitor console errors and user issues
### Key Metrics to Track
- **Page Load Speed**: < 3 seconds
- **Core Web Vitals**: LCP, FID, CLS scores
- **Conversion Rate**: README generation completion
- **User Engagement**: Time on site, bounce rate
## Troubleshooting
### Common Issues
1. **Build Failures**: Check Node.js version (18+)
2. **Asset Loading**: Verify all assets are in `/public`
3. **Analytics Not Working**: Check environment variables
4. **SEO Issues**: Validate structured data and meta tags
### Support
- **Issues**: [GitHub Issues](https://github.com/rahuldkjain/github-profile-readme-generator/issues)
- **Discussions**: [GitHub Discussions](https://github.com/rahuldkjain/github-profile-readme-generator/discussions)
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://rahuldkjain.github.io/github-profile-readme-generator">
<img alt="GitHub Profile Readme Generator" src="./src/images/mdg.png" width="60" />
</a>
</p>
<h1 align="center">
GitHub Profile README Generator
</h1>
<p align="center">
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/LICENSE" target="blank">
<img src="https://img.shields.io/github/license/rahuldkjain/github-profile-readme-generator?style=flat-square" alt="github-profile-readme-generator license" />
</a>
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/fork" target="blank">
<img src="https://img.shields.io/github/forks/rahuldkjain/github-profile-readme-generator?style=flat-square" alt="github-profile-readme-generator forks"/>
</a>
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/stargazers" target="blank">
<img src="https://img.shields.io/github/stars/rahuldkjain/github-profile-readme-generator?style=flat-square" alt="github-profile-readme-generator stars"/>
</a>
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/issues" target="blank">
<img src="https://img.shields.io/github/issues/rahuldkjain/github-profile-readme-generator?style=flat-square" alt="github-profile-readme-generator issues"/>
</a>
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/pulls" target="blank">
<img src="https://img.shields.io/github/issues-pr/rahuldkjain/github-profile-readme-generator?style=flat-square" alt="github-profile-readme-generator pull-requests"/>
</a>
<a href="https://discord.gg/HHMs7Eg" target="blank">
<img src="https://img.shields.io/discord/735303195105951764?label=Join%20Community&logo=discord&style=flat-square" alt="join discord community of github profile readme generator"/>
</a>
</p>
<p align="center"><img src="/public/demo.gif" alt="github-profile-readme-generator gif" /></p>
<p align="center">
<a href="https://rahuldkjain.github.io/github-profile-readme-generator" target="blank">View Demo</a>
·
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/issues/new/choose">Report Bug</a>
·
<a href="https://github.com/rahuldkjain/github-profile-readme-generator/issues/new/choose">Request Feature</a>
</p>
<p align="center">
<i>Loved the tool? Please consider <a href="https://paypal.me/rahuldkjain/10">donating</a> 💸 to help it improve!</i>
</p>
<p align="center">
<a href="https://www.paypal.me/rahuldkjain"><img src="https://img.shields.io/badge/support-PayPal-blue?logo=PayPal&style=flat-square&label=Donate" alt="sponsor github profile readme generator"/>
</a>
<a href='https://ko-fi.com/A0A81XXSX' target='_blank'><img height='23' width="100" src='https://cdn.ko-fi.com/cdn/kofi3.png?v=2' alt='Buy Coffee for rahuldkjain' />
</a>
<a href="https://www.buymeacoffee.com/rahuldkjain" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="23" width="100" style="border-radius:1px" />
</p>
#### Tired of editing GitHub Profile README with new features?
This tool provides an easy way to create a GitHub profile readme with the latest add-ons such as `visitors count`, `github stats`, etc.
## 🚀 Demo
<a href="https://rahuldkjain.github.io/github-profile-readme-generator" target="blank">
<img src="https://img.shields.io/website?url=https%3A%2F%2Frahuldkjain.github.io%2Fgithub-profile-readme-generator&logo=github&style=flat-square" />
</a>
Try the tool: [GitHub Profile README Generator](https://rahuldkjain.github.io/github-profile-readme-generator)
## 🧐 Features
Just fill in the details such as `Name`, `Tagline`, `Dev Platforms Username`, `Current Work`, `Portfolio`, `Blog`, etc. with a minimal UI.
- **Uniform Dev Icons**
- **Uniform Social Icons**
- **Visitors Counter Badge**
- **GitHub Profile Stats Card**
- **GitHub Top Skills**
- **GitHub Streak Stats**
- **Dynamic Dev(.)to Blogs** (GitHub Action)
- **Dynamic Medium Blogs** (GitHub Action)
- **Dynamic Personal Blogs from RSS Feed** (GitHub Action)
- **Wakatime Stats** [contribute](https://github.com/rahuldkjain/github-profile-readme-generator/issues/115)
- **Buy Me A Coffee button**
Click on `Generate README` to get your README in `markdown`.
You can preview the README too.
## 🛠️ Installation Steps
1. Clone the repository
```bash
git clone https://github.com/rahuldkjain/github-profile-readme-generator.git
```
2. Change the working directory
```bash
cd github-profile-readme-generator
```
3. Install dependencies
```bash
npm install
```
4. Run the app
```bash
npm run dev
```
🌟 You are all set!
## 🍰 Contributing
Please contribute using [GitHub Flow](https://guides.github.com/introduction/flow). Create a branch, add commits, and [open a pull request](https://github.com/rahuldkjain/github-profile-readme-generator/compare).
Please read [`CONTRIBUTING`](CONTRIBUTING.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md), and the process for submitting pull requests to us.
## 💻 Built with
- [Next.js 15](https://nextjs.org/) - React framework with App Router
- [TypeScript](https://www.typescriptlang.org/) - Type safety and better DX
- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework
- [Framer Motion](https://www.framer.com/motion/) - Production-ready motion library
- [Lucide React](https://lucide.dev/) - Beautiful & consistent icons
- [React Hook Form](https://react-hook-form.com/) - Performant forms with easy validation
## 🙇 Special Thanks
- [Anurag Hazra](https://github.com/anuraghazra) for amazing [github-readme-stats](https://github.com/anuraghazra/github-readme-stats)
- [Anton Komarev](https://github.com/antonkomarev) for super cool [github-profile-views-counter](https://github.com/antonkomarev/github-profile-views-counter)
- [Gautam Krishna R](https://github.com/gautamkrishnar) for the awesome [blog post workflow](https://github.com/gautamkrishnar/blog-post-workflow)
- [Jonah Lawrence](https://github.com/DenverCoder1) for the incredible [github-readme-streak-stats](https://github.com/DenverCoder1/github-readme-streak-stats)
- [Julien Monty](https://github.com/konpa) for super useful [devicon](https://github.com/konpa/devicon)
- [Eliot Sanford](https://github.com/techieeliot) for adding hashnode as a blog input
## 🙇 Sponsors
- [Scott C Wilson](https://github.com/scottcwilson) donated the first-ever grant to this tool. A big thanks to him.
- [Max Schmitt](https://github.com/mxschmitt) loved the tool and showed support with his donation. Thanks a lot.
- [Aadit Kamat](https://github.com/aaditkamat) find the tool useful and showed support with his donation. A big thanks to him.
- [Jean-Michel Fayard](https://github.com/jmfayard) used the generator to create his GitHub Profile README and he loved it. Thanks to him for showing support to the tool with the donation.
## 🔒 Privacy & Analytics
This tool includes privacy-friendly analytics to help improve the user experience:
- **Google Analytics 4** with GDPR-compliant consent management
- **IP anonymization** and privacy-first configuration
- **Custom events** tracking for GitHub auto-fill, README generation, and exports
- **Cookie consent banner** - users can opt-out anytime
- **No personal data** collection - only anonymous usage patterns
## 📄 Font Licensing
This project uses the **Wotfard** font family:
- **Font**: Wotfard Regular
- **Usage**: This font is used under fair use for open source projects
- **Source**: Downloaded from online typography resources
- **Note**: If you're the font creator and have concerns about usage, please [contact us](mailto:rahuldkjain@gmail.com)
For commercial use of this project, please verify font licensing requirements.
## 🙏 Support
<p align="left">
<a href="https://www.paypal.me/rahuldkjain/10"><img src="https://ionicabizau.github.io/badges/paypal.svg" alt="sponsor github profile readme generator"/>
</a>
<a href="https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Frahuldkjain.github.io%2Fgithub-profile-readme-generator">
<img src="https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Frahuldkjain.github.io%2Fgithub-profile-readme-generator" alt="tweet github profile readme generator"/>
</a>
</p>
<p align="left">
<a href='https://ko-fi.com/A0A81XXSX' target='_blank'><img height='23' width="100" src='https://cdn.ko-fi.com/cdn/kofi3.png?v=2' alt='Buy Coffee for rahuldkjain' />
</a>
<a href="https://www.buymeacoffee.com/rahuldkjain" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="23" width="100" style="border-radius:2px" />
</p>
## 🌟 Star History
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=rahuldkjain/github-profile-readme-generator&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=rahuldkjain/github-profile-readme-generator&type=Date" />
<img alt="GPRG Star History Chart" src="https://api.star-history.com/svg?repos=rahuldkjain/github-profile-readme-generator&type=Date" />
</picture>
<hr>
<p align="center">
Developed with ❤️ in India 🇮🇳
</p>
================================================
FILE: env.example
================================================
# GitHub Profile README Generator - Environment Configuration
# Google Analytics 4 Configuration
# Get your GA4 Measurement ID from Google Analytics
# Format: G-XXXXXXXXXX
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
# Optional: Google Search Console Verification
# Get this from Google Search Console -> Settings -> Ownership verification
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=your-verification-code
# Privacy & GDPR Compliance
# Set to 'true' to require explicit consent before loading analytics
# Set to 'false' to load analytics by default (not GDPR compliant)
NEXT_PUBLIC_REQUIRE_CONSENT=true
# Analytics Configuration
# Set to 'true' to anonymize IP addresses (recommended for privacy)
NEXT_PUBLIC_ANONYMIZE_IP=true
# Development Configuration
# Set to 'development' to disable analytics in development mode
NODE_ENV=development
# Optional: Custom Domain Configuration
# Update if deploying to a custom domain
NEXT_PUBLIC_SITE_URL=https://your-domain.com
================================================
FILE: eslint.config.mjs
================================================
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript", "prettier"),
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"dist/**",
"coverage/**",
"*.config.js",
"*.config.ts",
"next-env.d.ts",
"old-gatsby-backup/**",
],
},
{
rules: {
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-explicit-any": "warn",
"react/no-unescaped-entities": "off",
"react-hooks/exhaustive-deps": "warn",
},
},
];
export default eslintConfig;
================================================
FILE: next.config.ts
================================================
import type { NextConfig } from 'next';
import { PHASE_PRODUCTION_BUILD } from 'next/constants';
const nextConfig = (phase: string): NextConfig => {
// Determine if we should use basePath (production build, not Surge preview)
const isProductionBuild = phase === PHASE_PRODUCTION_BUILD;
const isSurgePreview = process.env.SURGE_PREVIEW === 'true';
const shouldUseBasePath = isProductionBuild && !isSurgePreview;
const basePath = shouldUseBasePath ? '/github-profile-readme-generator' : '';
return {
// Output as static site for GitHub Pages
output: 'export',
// Base path for GitHub Pages (only for production builds, not Surge previews)
basePath,
// Asset prefix to ensure all assets use the correct path
assetPrefix: shouldUseBasePath ? '/github-profile-readme-generator/' : '',
// Environment variables
env: {
NEXT_PUBLIC_BASE_PATH: basePath,
},
// Image optimization for static export
images: {
unoptimized: true, // Required for static export
},
// Trailing slashes for better compatibility
trailingSlash: true,
// Enable strict mode for better error catching
reactStrictMode: true,
// Enable experimental features for better performance
experimental: {
// Optimize CSS
optimizeCss: true,
// Enable optimized package imports for heavy libraries
optimizePackageImports: [
'framer-motion',
'@hookform/resolvers',
'react-markdown',
'remark-gfm',
'rehype-raw',
'rehype-sanitize',
'zod',
'zustand',
'lucide-react',
'@headlessui/react',
],
},
// Compiler options for better performance
compiler: {
// Remove console.log in production
removeConsole: isProductionBuild ? { exclude: ['error', 'warn'] } : false,
// Enable React compiler optimizations
reactRemoveProperties: isProductionBuild,
},
// Optimize transpilation
transpilePackages: ['react-markdown', 'remark-gfm', 'rehype-raw', 'rehype-sanitize'],
// Turbopack configuration (replaces webpack config)
turbopack: {
// Enable faster module resolution
resolveAlias: {
// Optimize common imports
'@': './src',
},
},
// Webpack optimizations for development (only when not using Turbopack)
webpack: (config, { dev, isServer }) => {
if (dev && !isServer && !process.env.TURBOPACK) {
// Optimize development builds
config.optimization = {
...config.optimization,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
},
markdown: {
test: /[\\/]node_modules[\\/](react-markdown|remark-|rehype-)/,
name: 'markdown',
chunks: 'all',
priority: 20,
},
},
},
};
// Enable faster rebuilds
config.cache = {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
};
}
return config;
},
};
};
export default nextConfig;
================================================
FILE: package.json
================================================
{
"name": "github-profile-readme-generator",
"version": "2.0.0",
"description": "Generate GitHub profile README easily with the latest add-ons like visitors count, GitHub stats, etc using minimal UI",
"private": true,
"author": "Rahul Jain <rahuldkjain@gmail.com>",
"scripts": {
"dev": "TURBOPACK=1 next dev --turbo",
"build": "next build --turbo",
"export": "next build --turbo",
"start": "next start",
"type-check": "tsc --noEmit",
"lint": "eslint",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
},
"dependencies": {
"@headlessui/react": "^2.2.9",
"@hookform/resolvers": "^5.2.2",
"@next/third-parties": "^15.5.4",
"@tailwindcss/typography": "^0.5.19",
"critters": "^0.0.23",
"framer-motion": "^12.23.24",
"lucide-react": "^0.545.0",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.65.0",
"react-markdown": "^10.1.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",
"zod": "^4.1.12",
"zustand": "^5.0.8"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^5.0.4",
"eslint": "^9",
"eslint-config-next": "15.5.4",
"eslint-config-prettier": "^10.1.8",
"jsdom": "^27.0.0",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwindcss": "^4",
"typescript": "^5",
"vitest": "^3.2.4"
}
}
================================================
FILE: postcss.config.mjs
================================================
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;
================================================
FILE: public/manifest.json
================================================
{
"name": "GitHub Profile README Generator",
"short_name": "GitHub README Gen",
"description": "Create amazing GitHub profile READMEs in seconds with customizable templates and easy-to-use interface",
"start_url": "./",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"orientation": "portrait-primary",
"icons": [
{
"src": "/mdg.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/mdg.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
],
"categories": ["developer", "productivity", "utilities"],
"lang": "en",
"dir": "ltr",
"scope": "./",
"prefer_related_applications": false
}
================================================
FILE: public/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Allow: /
# Host
Host: https://rahuldkjain.github.io
# Sitemaps
Sitemap: https://rahuldkjain.github.io/gh-profile-readme-generator/sitemap.xml
================================================
FILE: setupTests.js
================================================
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
================================================
FILE: src/app/about/page.tsx
================================================
import { Header } from '@/components/layout/header';
import { Footer } from '@/components/layout/footer';
import type { Metadata } from 'next';
import Image from 'next/image';
export const metadata: Metadata = {
title: 'About',
description:
'Learn about GitHub Profile README Generator - an open-source tool for creating awesome GitHub profile READMEs with customizable templates, skills showcase, and social links integration.',
alternates: {
canonical: '/about',
},
openGraph: {
title: 'About | GitHub Profile README Generator',
description:
'Learn about GitHub Profile README Generator - an open-source tool for creating awesome GitHub profile READMEs',
url: '/about',
},
};
export default function AboutPage() {
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="container mx-auto flex-1 px-4 py-12">
<div className="page-content mx-auto max-w-4xl">
<h1 className="mb-6 text-4xl font-bold">👨💻 About</h1>
<div className="mb-8 flex gap-2">
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/LICENSE"
target="_blank"
rel="noopener noreferrer"
>
<Image
src="https://img.shields.io/github/license/rahuldkjain/github-profile-readme-generator?style=flat-square"
alt="github-profile-readme-generator license"
width={100}
height={100}
/>
</a>
</div>
<p className="text-lg">
<strong>GitHub Profile README Generator</strong> is an OSS (Open Source Software) that
provides a cool interface to generate GitHub profile README in markdown.
</p>
<p>
The tool aims to provide hassle-free experience to add trending addons like profile{' '}
<strong>visitors count</strong>, <strong>github-stats</strong>,{' '}
<strong>dynamic blog posts</strong> etc.
</p>
<p>
The profile should be neat and minimal to give a clear overview of the work. Non-uniform
icons, too much content, too much images/gifs distracts visitors to see your actual
work.
</p>
<p>To solve this, GitHub Profile README Generator came into existence.</p>
<p>
So many developers contributed to the project and made it more awesome to use. You can
contribute too to make it grow even further.
</p>
<div className="my-6 flex gap-3">
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/issues"
target="_blank"
rel="noopener noreferrer"
>
<Image
src="https://img.shields.io/github/issues/rahuldkjain/github-profile-readme-generator?style=flat-square"
alt="github-profile-readme-generator issues"
width={100}
height={100}
/>
</a>
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/pulls"
target="_blank"
rel="noopener noreferrer"
>
<Image
src="https://img.shields.io/github/issues-pr/rahuldkjain/github-profile-readme-generator?style=flat-square"
alt="github-profile-readme-generator pull-requests"
width={130}
height={130}
/>
</a>
</div>
<h3 className="mt-8 mb-4 text-2xl font-bold">Contributors 🙏</h3>
<p>List of the developers who contributed to the project. A big shout out for them.</p>
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/graphs/contributors"
target="_blank"
rel="noopener noreferrer"
>
<Image
src="https://contributors-img.web.app/image?repo=rahuldkjain/github-profile-readme-generator"
alt="Contributors"
className="my-4"
width={600}
height={600}
/>
</a>
<hr className="my-8" />
<h2 className="mb-4 text-3xl font-bold">How do I create a profile README?</h2>
<p>
The profile README is created by creating a new repository that's the same name as your
username. For example, my GitHub username is <strong>rahuldkjain</strong> so I created a
new repository with the name <strong>rahuldkjain</strong>. Note: at the time of this
writing, in order to access the profile README feature, the letter-casing must match
your GitHub username.
</p>
<ol className="list-decimal space-y-3 pl-6">
<li>
Create a new repository with the same name (including casing) as your GitHub username:{' '}
<a
href="https://github.com/new"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
https://github.com/new
</a>
</li>
<li>
Create a README.md file inside the new repo with content (text, GIFs, images, emojis,
etc.)
</li>
<li>
Commit your fancy new README! If you're on GitHub's web interface you can choose to
commit directly to the repo's main branch (i.e., master or main) which will make it
immediately visible on your profile
</li>
<li>
Push changes to GitHub (if you made changes locally i.e., on your computer and not
github.com)
</li>
</ol>
<hr className="my-8" />
<h2 className="mb-4 text-3xl font-bold">How to use?</h2>
<p>
Tired of editing profile README(.md) to add new features like visitors-count badge,
github-stats etc?
</p>
<p>Don't worry. Keep calm, fill the form and let the tool do the work for you</p>
<Image
src="https://raw.githubusercontent.com/rahuldkjain/github-profile-readme-generator/master/src/images/github-profile-readme-generator.gif"
alt="github profile readme generator"
width="320"
height={100}
className="my-6"
/>
<hr className="my-8" />
<h2 className="mb-4 text-3xl font-bold">Why visitors count keeps on increasing?</h2>
<p>
So many users raised an issue that the counter keeps on increasing everytime the page
reloads.
</p>
<p>
Well it is visitors count not "unique" visitors count. The goal of the addon is to
provide a good stat of how well the github profile is doing.
</p>
<p>
Proper use or misuse of the addon is the sole responsibility of the user. The developer
of the addon is working on it to fix this issue.
</p>
</div>
</main>
<Footer />
</div>
);
}
================================================
FILE: src/app/addons/page.tsx
================================================
import { Header } from '@/components/layout/header';
import { Footer } from '@/components/layout/footer';
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Addons',
description:
'Discover the awesome open-source addons and tools used in GitHub Profile README Generator. Explore the technology stack and libraries that power this amazing tool.',
alternates: {
canonical: '/addons',
},
openGraph: {
title: 'Addons | GitHub Profile README Generator',
description:
'Discover the awesome open-source addons and tools used in GitHub Profile README Generator',
url: '/addons',
},
};
export default function AddonsPage() {
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="container mx-auto flex-1 px-4 py-12">
<div className="page-content mx-auto max-w-4xl">
<h1 className="mb-6 text-4xl font-bold">🚀 Addons</h1>
<p className="text-lg">
GitHub Profile README Generator tool uses few open-source addons developed by other
developers. Including such features makes the tool useful. The developers of this tool
is very grateful to use these awesome addons.
</p>
<hr className="my-8" />
<h2 className="text-3xl font-bold">
<a
href="https://github.com/anuraghazra/github-readme-stats"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
GitHub README Stats
</a>
</h2>
<p>⚡️ Dynamically generated stats for your github readmes</p>
<h4 className="mt-6 mb-3 text-xl font-semibold">GitHub Stats Card</h4>
<a href="https://github.com/rahuldkjain" target="_blank" rel="noopener noreferrer">
<img
src="https://github-readme-stats.vercel.app/api?username=rahuldkjain&show_icons=true"
width="320"
alt="Rahul's github stats"
/>
</a>
<h4 className="mt-6 mb-3 text-xl font-semibold">Top Skills Card</h4>
<a href="https://github.com/rahuldkjain" target="_blank" rel="noopener noreferrer">
<img
src="https://github-readme-stats.vercel.app/api/top-langs/?username=rahuldkjain&layout=compact&hide=html"
width="320"
alt="Rahul's github top skills"
/>
</a>
<p>
Developed by{' '}
<a
href="https://github.com/anuraghazra"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Anurag Hazra
</a>
.
</p>
<p>
You can customize the theme too. See how to customize yours{' '}
<a
href="https://github.com/anuraghazra/github-readme-stats"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
here
</a>
</p>
<hr className="my-8" />
<h2 className="text-3xl font-bold">
<a
href="https://github.com/DenverCoder1/github-readme-streak-stats"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
GitHub Readme Streak Stats
</a>
</h2>
<p>
Stay motivated while contributing to open source by displaying your current contribution
streak
</p>
<img
src="https://github-readme-streak-stats.herokuapp.com/?user=rahuldkjain"
alt="rahuldkjain"
className="my-4"
/>
<p>
Developed by{' '}
<a
href="https://github.com/DenverCoder1"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Jonah Lawrence
</a>
.
</p>
<p>
See how to customize the theme{' '}
<a
href="https://github.com/DenverCoder1/github-readme-streak-stats"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
here
</a>
</p>
<hr className="my-8" />
<h2 className="text-3xl font-bold">
<a
href="https://github.com/antonkomarev/github-profile-views-counter"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
GitHub Profile Views Counter
</a>
</h2>
<p>
It counts how many times your GitHub profile has been viewed. Free cloud micro-service.
</p>
<img
src="https://komarev.com/ghpvc/?username=rahuldkjain&style=flat-square"
alt="rahuldkjain"
className="my-4"
/>
<p>
Developed by{' '}
<a
href="https://github.com/antonkomarev"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Anton Komarev
</a>
.
</p>
<p>
You can customize the color, label and style too. See how to customize{' '}
<a
href="https://github.com/antonkomarev/github-profile-views-counter"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
here
</a>
</p>
<hr className="my-8" />
<h2 className="text-3xl font-bold">
<a
href="https://github.com/gautamkrishnar/blog-post-workflow"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Dynamic Latest Blog Posts
</a>
</h2>
<p>
Show your latest blog posts from any sources (like dev.to, medium etc) or StackOverflow
activity on your GitHub profile/project readme automatically using the RSS feed.
</p>
<img
src="https://user-images.githubusercontent.com/8397274/88047382-29b8b280-cb6f-11ea-9efb-2af2b10f3e0c.png"
width="320"
alt="dynamic latest blog example"
className="my-4"
/>
<p>
Developed by{' '}
<a
href="https://github.com/gautamkrishnar"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Gautam Krishna R
</a>
</p>
<h3 className="mt-6 mb-3 text-2xl font-semibold">How to use</h3>
<ul className="list-disc space-y-3 pl-6">
<li>Go to your repository</li>
<li>
Add the following section to your **README.md** file, you can give whatever title you
want. Just make sure that you use **<!-- BLOG-POST-LIST:START --><!--
BLOG-POST-LIST:END -->** in your readme. The workflow will replace this comment
with the actual blog post list:
</li>
</ul>
<pre className="my-4 overflow-x-auto rounded-lg bg-slate-800 p-4 text-sm text-white dark:bg-slate-900">
<code>{`# Blog posts
<!-- BLOG-POST-LIST:START -->
<!-- BLOG-POST-LIST:END -->`}</code>
</pre>
<ul className="list-disc space-y-3 pl-6">
<li>
Create a folder named <code>.github</code> and create <code>workflows</code> folder
inside it if it doesn't exist.
</li>
<li>
Create a new file named <code>blog-post-workflow.yml</code> with the following
contents inside the workflows folder:
</li>
</ul>
<pre className="my-4 overflow-x-auto rounded-lg bg-slate-800 p-4 text-sm text-white dark:bg-slate-900">
<code>{`name: Latest blog post workflow
on:
schedule:
# Runs every hour
- cron: '0 * * * *'
jobs:
update-readme-with-blog:
name: Update this repo's README with latest blog posts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gautamkrishnar/blog-post-workflow@master
with:
feed_list: 'https://dev.to/feed/rahuldkjain, https://medium.com/feed/@rahuldkjain'`}</code>
</pre>
<ul className="list-disc space-y-3 pl-6">
<li>Replace the above url list with your own rss feed urls.</li>
<li>Commit and wait for it to run</li>
</ul>
<p>
To know more, check out the{' '}
<a
href="https://github.com/gautamkrishnar/blog-post-workflow"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
official github repository
</a>
</p>
</div>
</main>
<Footer />
</div>
);
}
================================================
FILE: src/app/globals.css
================================================
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
/* Screen reader only utility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
:root {
/* Light mode colors */
--background: #ffffff;
--foreground: #171717;
--card: #f9fafb;
--card-foreground: #171717;
--primary: #2563eb;
--primary-foreground: #ffffff;
--secondary: #64748b;
--secondary-foreground: #ffffff;
--muted: #f1f5f9;
--muted-foreground: #64748b;
--accent: #f1f5f9;
--accent-foreground: #0f172a;
--destructive: #ef4444;
--destructive-foreground: #ffffff;
--border: #e2e8f0;
--input: #e2e8f0;
--ring: #2563eb;
--radius: 0.5rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode colors */
--background: #0a0a0a;
--foreground: #ededed;
--card: #171717;
--card-foreground: #ededed;
--primary: #3b82f6;
--primary-foreground: #ffffff;
--secondary: #94a3b8;
--secondary-foreground: #0f172a;
--muted: #1e293b;
--muted-foreground: #94a3b8;
--accent: #1e293b;
--accent-foreground: #f1f5f9;
--destructive: #dc2626;
--destructive-foreground: #ffffff;
--border: #1e293b;
--input: #1e293b;
--ring: #3b82f6;
}
}
/* Manual light mode class override (to override system preference) */
.light {
--background: #ffffff;
--foreground: #171717;
--card: #f9fafb;
--card-foreground: #171717;
--primary: #2563eb;
--primary-foreground: #ffffff;
--secondary: #64748b;
--secondary-foreground: #ffffff;
--muted: #f1f5f9;
--muted-foreground: #64748b;
--accent: #f1f5f9;
--accent-foreground: #0f172a;
--destructive: #ef4444;
--destructive-foreground: #ffffff;
--border: #e2e8f0;
--input: #e2e8f0;
--ring: #2563eb;
}
/* Manual dark mode class override */
.dark {
--background: #0a0a0a;
--foreground: #ededed;
--card: #171717;
--card-foreground: #ededed;
--primary: #3b82f6;
--primary-foreground: #ffffff;
--secondary: #94a3b8;
--secondary-foreground: #0f172a;
--muted: #1e293b;
--muted-foreground: #94a3b8;
--accent: #1e293b;
--accent-foreground: #f1f5f9;
--destructive: #dc2626;
--destructive-foreground: #ffffff;
--border: #1e293b;
--input: #1e293b;
--ring: #3b82f6;
}
* {
border-color: var(--border);
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans, Arial, Helvetica, sans-serif);
}
/* Optimized transitions for theme changes - only target necessary elements */
html {
transition: background-color 100ms ease-out;
}
body {
transition:
background-color 100ms ease-out,
color 100ms ease-out;
}
/* Target specific UI elements for faster transitions */
[class*='bg-'],
[class*='text-'],
[class*='border-'] {
transition:
background-color 100ms ease-out,
color 100ms ease-out,
border-color 100ms ease-out;
}
/* SVG icons transition */
svg {
transition:
fill 100ms ease-out,
stroke 100ms ease-out,
color 100ms ease-out;
}
/* Button and interactive elements */
button,
a,
input,
select,
textarea {
transition:
background-color 100ms ease-out,
color 100ms ease-out,
border-color 100ms ease-out,
opacity 100ms ease-out;
}
/* Cards and containers */
[class*='card'],
[class*='bg-card'] {
transition:
background-color 100ms ease-out,
border-color 100ms ease-out;
}
/* Optimize theme switching performance */
html.theme-switching * {
transition: none !important;
}
html.theme-switching {
transition: none !important;
}
/* Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
/* Reduced motion override via settings */
:root[style*='--motion-reduce'] * {
transition: none !important;
animation: none !important;
}
/* Markdown Preview Styles */
.markdown-preview {
color: var(--foreground);
}
.markdown-preview h1,
.markdown-preview h2,
.markdown-preview h3,
.markdown-preview h4,
.markdown-preview h5,
.markdown-preview h6 {
color: var(--foreground);
}
.markdown-preview p {
color: var(--foreground);
line-height: 1.6;
}
.markdown-preview strong,
.markdown-preview b {
color: var(--foreground);
font-weight: 600;
}
.markdown-preview img {
display: inline-block;
vertical-align: middle;
}
.markdown-preview a img {
margin: 0;
}
/* Custom page content styles to override prose */
.page-content {
color: var(--foreground);
}
.page-content h1,
.page-content h2,
.page-content h3,
.page-content h4,
.page-content h5,
.page-content h6 {
color: var(--foreground);
line-height: 1.2;
margin-bottom: 1rem;
}
.page-content h1 {
font-size: 2.25rem;
font-weight: 700;
}
.page-content h2 {
font-size: 1.875rem;
font-weight: 700;
}
.page-content h3 {
font-size: 1.5rem;
font-weight: 600;
}
.page-content h4 {
font-size: 1.25rem;
font-weight: 600;
}
.page-content p {
color: var(--foreground);
line-height: 1.6;
margin-bottom: 1rem;
}
.page-content strong,
.page-content b {
color: var(--foreground);
font-weight: 600;
}
.page-content a {
color: var(--primary);
text-decoration: underline;
}
.page-content a:hover {
text-decoration: none;
}
.page-content ul,
.page-content ol {
color: var(--foreground);
margin-bottom: 1rem;
padding-left: 1.5rem;
}
.page-content li {
margin-bottom: 0.5rem;
}
.page-content blockquote {
color: var(--foreground);
border-left: 4px solid var(--primary);
padding-left: 1rem;
margin: 1.5rem 0;
font-style: italic;
}
.page-content hr {
border-color: var(--border);
margin: 2rem 0;
}
.page-content pre {
background-color: var(--muted);
color: var(--foreground);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin: 1rem 0;
}
.page-content code {
background-color: var(--muted);
color: var(--foreground);
padding: 0.125rem 0.25rem;
border-radius: 0.25rem;
font-size: 0.875rem;
}
.page-content img {
max-width: 100%;
height: auto;
margin: 1rem 0;
}
/* High Contrast Mode */
.high-contrast {
--background: #000000;
--foreground: #ffffff;
--card: #000000;
--card-foreground: #ffffff;
--primary: #ffff00;
--primary-foreground: #000000;
--secondary: #00ffff;
--secondary-foreground: #000000;
--muted: #333333;
--muted-foreground: #ffffff;
--accent: #00ff00;
--accent-foreground: #000000;
--destructive: #ff0000;
--destructive-foreground: #ffffff;
--border: #ffffff;
--input: #ffffff;
--ring: #ffff00;
}
.high-contrast * {
border-width: 2px;
}
.high-contrast button,
.high-contrast a,
.high-contrast input,
.high-contrast textarea,
.high-contrast select {
outline: 2px solid var(--foreground);
outline-offset: 2px;
}
/* Form placeholder opacity - reduce to avoid looking like filled fields */
input::placeholder,
textarea::placeholder,
select::placeholder {
opacity: 0.4;
}
/* Font Size Variants */
.text-small {
font-size: 14px;
}
.text-small h1 {
font-size: 1.75rem;
}
.text-small h2 {
font-size: 1.5rem;
}
.text-small h3 {
font-size: 1.25rem;
}
.text-large {
font-size: 18px;
}
.text-large h1 {
font-size: 2.5rem;
}
.text-large h2 {
font-size: 2rem;
}
.text-large h3 {
font-size: 1.75rem;
}
================================================
FILE: src/app/layout.tsx
================================================
import type { Metadata } from 'next';
import { Roboto_Mono } from 'next/font/google';
import localFont from 'next/font/local';
import './globals.css';
import { ThemeProvider } from '@/components/layout/theme-provider';
import { ToastProvider } from '@/components/ui/toast';
import { BuyMeACoffeeWidget } from '@/components/ui/buy-me-coffee';
import { ConditionalAnalytics } from '@/components/analytics/conditional-analytics';
import { CookieConsent } from '@/components/ui/cookie-consent';
import { getAssetPath } from '@/lib/asset-path';
const robotoMono = Roboto_Mono({
variable: '--font-mono',
subsets: ['latin'],
weight: ['400', '500', '600', '700'],
});
const wotfard = localFont({
src: '../../public/fonts/wotfard/Wotfard-Regular.woff',
variable: '--font-sans',
weight: '400',
});
export const metadata: Metadata = {
title: {
default: 'GitHub Profile README Generator - Create Amazing Profile in Seconds',
template: '%s | GitHub Profile README Generator',
},
description:
'The best profile README generator to create an amazing GitHub profile in seconds. Customize your profile with skills, social links, stats, and more. Free, open-source, and easy to use.',
keywords: [
'github',
'profile',
'readme',
'generator',
'markdown',
'github profile',
'readme generator',
'github readme',
'profile generator',
'github stats',
'github badges',
'developer profile',
'github profile maker',
'readme maker',
],
authors: [{ name: 'Rahul Jain', url: 'https://github.com/rahuldkjain' }],
creator: 'Rahul Jain',
publisher: 'Rahul Jain',
formatDetection: {
email: false,
address: false,
telephone: false,
},
metadataBase: new URL('https://rahuldkjain.github.io/gh-profile-readme-generator/'),
alternates: {
canonical: '/',
},
openGraph: {
title: 'GitHub Profile README Generator - Create Amazing Profile in Seconds',
description:
'Create an amazing GitHub profile README in seconds with customizable templates and easy-to-use interface. Add skills, social links, GitHub stats, and more.',
url: 'https://rahuldkjain.github.io/gh-profile-readme-generator/',
siteName: 'GitHub Profile README Generator',
locale: 'en_US',
type: 'website',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'GitHub Profile README Generator - Create Amazing Profile in Seconds',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'GitHub Profile README Generator',
description:
'Create an amazing GitHub profile README in seconds with customizable templates. Free and easy to use.',
creator: '@rahuldkjain',
images: ['/og-image.png'],
},
icons: {
icon: [
{ url: '/favicon.ico', sizes: 'any' },
{ url: getAssetPath('/mdg.png'), type: 'image/png' },
],
apple: getAssetPath('/mdg.png'),
},
manifest: getAssetPath('/manifest.json'),
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
verification: {
google: 'google-site-verification-code', // User will need to add their code
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'WebApplication',
name: 'GitHub Profile README Generator',
description:
'Create an amazing GitHub profile README in seconds with customizable templates and easy-to-use interface.',
url: 'https://rahuldkjain.github.io/gh-profile-readme-generator/',
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Any',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
author: {
'@type': 'Person',
name: 'Rahul Jain',
url: 'https://github.com/rahuldkjain',
},
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '4.8',
ratingCount: '1000',
},
};
return (
<html lang="en" suppressHydrationWarning>
<head>
{/* Favicon and manifest are now handled by Next.js metadata API above */}
<meta name="theme-color" content="#000000" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="GitHub README Gen" />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body className={`${wotfard.variable} ${robotoMono.variable} font-sans antialiased`}>
<ThemeProvider>
<ToastProvider>{children}</ToastProvider>
</ThemeProvider>
<BuyMeACoffeeWidget />
<ConditionalAnalytics />
<CookieConsent />
</body>
</html>
);
}
================================================
FILE: src/app/page.tsx
================================================
'use client';
import { useState, useEffect, useMemo, lazy, Suspense, useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { motion } from 'framer-motion';
import { Download } from 'lucide-react';
import { profileSchema, linksSchema, socialSchema } from '@/lib/validations';
import { DEFAULT_DATA, DEFAULT_LINK, DEFAULT_SOCIAL } from '@/constants/defaults';
import { initialSkillState } from '@/constants/skills';
import { BasicInfoSection } from '@/components/sections/basic-info-section';
import { LinksSection } from '@/components/sections/links-section';
import { SocialSection } from '@/components/sections/social-section';
import { generateMarkdown } from '@/lib/markdown-generator';
import { saveFormData, loadFormData, clearFormData } from '@/lib/storage';
import type { ProfileFormData, LinksFormData, SocialFormData } from '@/lib/validations';
import { DEFAULT_SUPPORT } from '@/constants/defaults';
import { Header } from '@/components/layout/header';
import { Footer } from '@/components/layout/footer';
import { useErrorToast, useSuccessToast } from '@/components/ui/toast';
import { trackReadmeGenerated, trackFileExported } from '@/lib/analytics';
import { useConfirmDialog } from '@/components/ui/confirm-dialog';
// Lazy load heavy components
const SkillsSection = lazy(() =>
import('@/components/sections/skills-section').then((module) => ({
default: module.SkillsSection,
}))
);
const MarkdownPreview = lazy(() =>
import('@/components/ui/markdown-preview').then((module) => ({ default: module.MarkdownPreview }))
);
type Step = 'basic' | 'links' | 'social' | 'skills' | 'preview';
const steps: { id: Step; title: string; description: string }[] = [
{ id: 'basic', title: 'Basic Info', description: 'Tell us about yourself' },
{ id: 'links', title: 'Links', description: 'Portfolio, blog, resume' },
{ id: 'social', title: 'Social', description: 'Social media profiles' },
{ id: 'skills', title: 'Skills', description: 'Technologies you know' },
{ id: 'preview', title: 'Preview', description: 'Review and generate' },
];
export default function GeneratorPage() {
// Toast hooks
const showError = useErrorToast();
const showSuccess = useSuccessToast();
const { showConfirm, ConfirmDialog } = useConfirmDialog();
// Load saved data FIRST before any state initialization
const savedData = useMemo(() => {
if (typeof window === 'undefined') return null;
const data = loadFormData();
console.log('🎯 Initial load - Saved data:', data);
return data;
}, []); // Empty deps - only run once on mount
const [currentStep, setCurrentStep] = useState<Step>('basic');
const [skills, setSkills] = useState(() => {
// Lazy initialization - use saved skills if available
const initialSkills = savedData?.skills || initialSkillState;
console.log(
'🎯 Initial skills state:',
Object.values(initialSkills).filter(Boolean).length,
'selected'
);
return initialSkills;
});
const [lastSaved, setLastSaved] = useState<Date | null>(() => {
if (savedData?.lastSaved) {
return new Date(savedData.lastSaved);
}
return null;
});
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved'>('idle');
const [hasInitialized, setHasInitialized] = useState(false);
const {
register: registerProfile,
formState: { errors: profileErrors },
watch: watchProfile,
reset: resetProfile,
trigger: triggerProfile,
} = useForm<ProfileFormData>({
resolver: zodResolver(profileSchema),
defaultValues: savedData?.profile ? { ...DEFAULT_DATA, ...savedData.profile } : DEFAULT_DATA,
mode: 'onChange',
});
const {
register: registerLinks,
formState: { errors: linksErrors },
watch: watchLinks,
reset: resetLinks,
trigger: triggerLinks,
} = useForm<LinksFormData>({
resolver: zodResolver(linksSchema),
defaultValues: savedData?.links ? { ...DEFAULT_LINK, ...savedData.links } : DEFAULT_LINK,
mode: 'onChange',
});
const {
register: registerSocial,
formState: { errors: socialErrors },
watch: watchSocial,
reset: resetSocial,
trigger: triggerSocial,
} = useForm<SocialFormData>({
resolver: zodResolver(socialSchema),
defaultValues: savedData?.social ? { ...DEFAULT_SOCIAL, ...savedData.social } : DEFAULT_SOCIAL,
mode: 'onChange',
});
// Watch all form values for live preview
const profileData = watchProfile();
const linksData = watchLinks();
const socialData = watchSocial();
// Generate markdown with useMemo to prevent unnecessary recalculations
const markdown = useMemo(() => {
return generateMarkdown({
profile: profileData,
links: linksData,
social: socialData,
support: DEFAULT_SUPPORT,
skills,
});
}, [profileData, linksData, socialData, skills]);
// Mark as initialized after first render to enable auto-save
useEffect(() => {
console.log('🔍 Mount - Data already loaded in initialization');
if (savedData) {
console.log('✅ Mount - Restored from localStorage automatically');
} else {
console.log('🆕 Mount - Starting fresh (no saved data)');
}
// Set initialized to true after a brief delay to ensure forms are fully set up
const timer = setTimeout(() => {
console.log('🎬 Initialization complete - Auto-save now enabled');
setHasInitialized(true);
}, 100);
return () => clearTimeout(timer);
}, [savedData]);
// Auto-save form data - only after initialization complete
useEffect(() => {
// Skip until initialization is complete
if (!hasInitialized) {
console.log('⏭️ Auto-save - Waiting for initialization to complete');
return;
}
console.log('💾 Auto-save - Starting...');
console.log('📊 Auto-save - Profile data:', profileData);
console.log('📊 Auto-save - Links data:', linksData);
console.log('📊 Auto-save - Social data:', socialData);
console.log('📊 Auto-save - Skills selected:', Object.values(skills).filter(Boolean).length);
setSaveStatus('saving');
const timer = setTimeout(() => {
const now = new Date();
const dataToSave = {
profile: profileData,
links: linksData,
social: socialData,
support: DEFAULT_SUPPORT,
skills,
lastSaved: now.toISOString(),
};
console.log('💾 Auto-save - Saving to localStorage:', dataToSave);
saveFormData(dataToSave);
// Verify it was saved
const savedDataCheck = localStorage.getItem('github-profile-generator');
console.log('✅ Auto-save - Verified in localStorage:', savedDataCheck ? 'YES' : 'NO');
console.log('📏 Auto-save - Data size:', savedDataCheck?.length || 0, 'bytes');
setLastSaved(now);
setSaveStatus('saved');
// Reset to idle after animation
setTimeout(() => setSaveStatus('idle'), 2000);
}, 1000); // Save 1 second after last change
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
hasInitialized,
JSON.stringify(profileData),
JSON.stringify(linksData),
JSON.stringify(socialData),
JSON.stringify(skills),
]);
const handleSkillChange = (skill: string, checked: boolean) => {
setSkills((prev) => ({ ...prev, [skill]: checked }));
};
const handleGitHubAutoFill = (data: {
profile: Partial<ProfileFormData>;
links: Partial<LinksFormData>;
social: Partial<SocialFormData>;
skills: string[];
}) => {
// Update profile data
if (data.profile.title) {
resetProfile((prev) => ({ ...prev, ...data.profile }));
}
// Update links data
if (data.links.blog) {
resetLinks((prev) => ({ ...prev, ...data.links }));
}
// Update social data
if (data.social.github || data.social.twitter) {
resetSocial((prev) => ({ ...prev, ...data.social }));
}
// Update skills
if (data.skills.length > 0) {
const newSkills = { ...skills };
data.skills.forEach((skill) => {
if (skill in newSkills) {
newSkills[skill] = true;
}
});
setSkills(newSkills);
}
};
// Restore is now automatic on mount, but keep this for manual restore if needed
// This function is no longer needed but kept for backwards compatibility
// Check if there's any meaningful data to clear
const hasAnyData = useMemo(() => {
// Check profile data (excluding empty strings)
const hasProfileData = Object.entries(profileData).some(([key, value]) => {
if (key === 'subtitle' && value === '') return false; // Empty subtitle is now default
return typeof value === 'string' ? value.trim() !== '' : value !== false && value !== null;
});
// Check links data
const hasLinksData = Object.values(linksData).some((value) => value && value.trim() !== '');
// Check social data
const hasSocialData = Object.values(socialData).some((value) =>
typeof value === 'string' ? value.trim() !== '' : value === true
);
// Check skills data
const hasSkillsData = Object.values(skills).some((selected) => selected === true);
return hasProfileData || hasLinksData || hasSocialData || hasSkillsData;
}, [profileData, linksData, socialData, skills]);
const handleClearAll = useCallback(() => {
showConfirm({
title: 'Clear All Data',
message:
'Are you sure you want to clear all data? This will reset all form fields, skills, and settings. This action cannot be undone.',
confirmText: 'Clear All',
cancelText: 'Cancel',
variant: 'warning',
onConfirm: () => {
clearFormData();
resetProfile(DEFAULT_DATA);
resetLinks(DEFAULT_LINK);
resetSocial(DEFAULT_SOCIAL);
setSkills(initialSkillState);
setLastSaved(null);
setSaveStatus('idle');
showSuccess('All data cleared successfully', 'Form has been reset to default values');
},
});
}, [showConfirm, resetProfile, resetLinks, resetSocial, setSkills, showSuccess]);
const handleDownloadJSON = () => {
const data = {
version: '1.0.0',
exportedAt: new Date().toISOString(),
profile: profileData,
links: linksData,
social: socialData,
support: DEFAULT_SUPPORT,
skills: Object.entries(skills)
.filter(([_, selected]) => selected)
.map(([skill]) => skill),
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `github-profile-${new Date().getTime()}.json`;
document.body.appendChild(a);
a.click();
// Track JSON export
trackFileExported('json_export', 'json');
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const handleImportJSON = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const imported = JSON.parse(e.target?.result as string);
// Validate and import data
if (imported.profile) {
resetProfile({ ...DEFAULT_DATA, ...imported.profile } as ProfileFormData);
}
if (imported.links) {
resetLinks({ ...DEFAULT_LINK, ...imported.links } as LinksFormData);
}
if (imported.social) {
resetSocial({ ...DEFAULT_SOCIAL, ...imported.social } as SocialFormData);
}
if (imported.skills && Array.isArray(imported.skills)) {
const newSkills = { ...initialSkillState };
imported.skills.forEach((skill: string) => {
if (skill in newSkills) {
newSkills[skill] = true;
}
});
setSkills(newSkills);
}
alert('Profile data imported successfully!');
} catch (error) {
alert('Error importing JSON: ' + (error as Error).message);
}
};
reader.readAsText(file);
// Reset input
event.target.value = '';
};
const currentStepIndex = steps.findIndex((s) => s.id === currentStep);
// Validate current step before navigation
const validateCurrentStep = async (): Promise<boolean> => {
let isValid = true;
const errorMessages: string[] = [];
switch (currentStep) {
case 'basic':
const profileValid = await triggerProfile();
if (!profileValid) {
isValid = false;
// Get specific error messages
if (profileErrors.title) {
errorMessages.push(`Name: ${profileErrors.title.message}`);
}
// Add other field errors as needed
Object.entries(profileErrors).forEach(([field, error]) => {
if (field !== 'title' && error?.message) {
errorMessages.push(`${field}: ${error.message}`);
}
});
}
break;
case 'links':
const linksValid = await triggerLinks();
if (!linksValid) {
isValid = false;
Object.entries(linksErrors).forEach(([field, error]) => {
if (error?.message) {
errorMessages.push(`${field}: ${error.message}`);
}
});
}
break;
case 'social':
const socialValid = await triggerSocial();
if (!socialValid) {
isValid = false;
Object.entries(socialErrors).forEach(([field, error]) => {
if (error?.message) {
errorMessages.push(`${field}: ${error.message}`);
}
});
}
break;
case 'skills':
// Skills don't have validation requirements
break;
case 'preview':
// Preview doesn't need validation
break;
}
if (!isValid) {
const stepName = steps.find((s) => s.id === currentStep)?.title || 'current step';
showError(
`Please fix errors in ${stepName}`,
errorMessages.length > 0 ? errorMessages.join(', ') : 'Please check all required fields'
);
}
return isValid;
};
const goToNextStep = async () => {
// Validate current step before proceeding
const isValid = await validateCurrentStep();
if (!isValid) {
return; // Don't proceed if validation fails
}
const nextIndex = currentStepIndex + 1;
if (nextIndex < steps.length) {
setCurrentStep(steps[nextIndex].id);
// Show success message for completing a step
const currentStepName = steps[currentStepIndex].title;
showSuccess(`${currentStepName} completed!`, 'Moving to next step');
// Track README generation completion when reaching preview step
if (steps[nextIndex].id === 'preview') {
const socialData = watchSocial();
const linksData = watchLinks();
const selectedSkillsCount = Object.values(skills).filter(Boolean).length;
trackReadmeGenerated({
hasSkills: selectedSkillsCount > 0,
hasSocial: Object.values(socialData).some(
(value) => typeof value === 'string' && value.trim() !== ''
),
hasLinks: Object.values(linksData).some(
(value) => typeof value === 'string' && value.trim() !== ''
),
stepCount: currentStepIndex + 1,
});
}
}
};
const goToPrevStep = () => {
const prevIndex = currentStepIndex - 1;
if (prevIndex >= 0) {
setCurrentStep(steps[prevIndex].id);
}
};
return (
<div className="flex min-h-screen flex-col">
{/* Header with Save Status */}
<Header saveStatus={saveStatus} lastSaved={lastSaved} />
<main className="container mx-auto flex-1 px-4 py-8">
<div className="mx-auto max-w-6xl">
{/* Progress Steps - Responsive */}
<nav aria-label="Form progress" className="mb-8">
<div className="flex items-center justify-center overflow-x-auto px-4">
<div className="flex min-w-max items-center">
{steps.map((step, index) => (
<div key={step.id} className="flex items-center">
<button
onClick={() => setCurrentStep(step.id)}
className={`flex flex-col items-center gap-1 px-2 py-1 ${
currentStep === step.id
? 'text-primary'
: index < currentStepIndex
? 'text-foreground'
: 'text-muted-foreground'
}`}
aria-label={`Step ${index + 1}: ${step.title}`}
aria-current={currentStep === step.id ? 'step' : undefined}
>
<div
className={`flex h-8 w-8 items-center justify-center rounded-full border-2 transition-colors sm:h-10 sm:w-10 ${
currentStep === step.id
? 'border-primary bg-primary text-primary-foreground'
: index < currentStepIndex
? 'border-primary bg-primary/20'
: 'border-border'
}`}
>
<span className="text-xs font-medium sm:text-sm">{index + 1}</span>
</div>
<div className="hidden text-center sm:block">
<p className="text-xs font-medium whitespace-nowrap">{step.title}</p>
<p className="text-muted-foreground text-xs whitespace-nowrap">
{step.description}
</p>
</div>
</button>
{index < steps.length - 1 && (
<div
className={`mx-2 h-0.5 w-8 sm:mx-4 sm:w-12 ${
index < currentStepIndex ? 'bg-primary' : 'bg-border'
}`}
/>
)}
</div>
))}
</div>
</div>
{/* Screen reader announcement for step changes */}
<div className="sr-only" aria-live="polite" aria-atomic="true">
Current step: {steps[currentStepIndex].title} - {steps[currentStepIndex].description}
</div>
</nav>
{/* Form Content */}
<motion.div
key={currentStep}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
className="border-border bg-card rounded-lg border p-6 shadow-sm md:p-8"
>
{currentStep === 'basic' && (
<BasicInfoSection
register={registerProfile}
errors={profileErrors}
socialRegister={registerSocial}
watchSocial={watchSocial}
onGitHubAutoFill={handleGitHubAutoFill}
onImportJSON={handleImportJSON}
onClearAll={handleClearAll}
hasClearableData={hasAnyData}
/>
)}
{currentStep === 'links' && (
<LinksSection register={registerLinks} errors={linksErrors} />
)}
{currentStep === 'social' && (
<SocialSection register={registerSocial} errors={socialErrors} watch={watchSocial} />
)}
{currentStep === 'skills' && (
<Suspense
fallback={
<div className="animate-pulse space-y-4">
<div className="h-8 rounded bg-gray-200"></div>
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
{Array.from({ length: 12 }).map((_, i) => (
<div key={i} className="h-12 rounded bg-gray-200"></div>
))}
</div>
</div>
}
>
<SkillsSection
selectedSkills={skills}
onSkillChange={handleSkillChange}
registerProfile={registerProfile}
/>
</Suspense>
)}
{currentStep === 'preview' && (
<div className="space-y-6">
<div className="border-border border-b pb-4">
{/* Mobile: Stack vertically, Desktop: Side by side */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-xl font-bold sm:text-2xl">Preview & Generate</h2>
<p className="text-muted-foreground mt-1 text-sm">
Your README is ready! Copy or download it below.
</p>
</div>
{/* Export Button - With text */}
<button
onClick={handleDownloadJSON}
className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors"
title="Export profile data as JSON"
aria-label="Export profile data as JSON"
>
<Download className="h-4 w-4" />
<span>Export</span>
</button>
</div>
</div>
<Suspense
fallback={
<div className="animate-pulse space-y-4">
<div className="h-8 rounded bg-gray-200"></div>
<div className="h-96 rounded bg-gray-200"></div>
</div>
}
>
<MarkdownPreview markdown={markdown} title="Your GitHub Profile README" />
</Suspense>
</div>
)}
</motion.div>
{/* Navigation Buttons */}
<nav className="mt-6 flex justify-between" aria-label="Form navigation">
<button
onClick={goToPrevStep}
disabled={currentStepIndex === 0}
className="border-border hover:bg-accent rounded-lg border px-6 py-2 font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
aria-label={`Go to previous step${currentStepIndex > 0 ? `: ${steps[currentStepIndex - 1].title}` : ''}`}
>
Previous
</button>
{/* Hide Next button at preview step since we're already at the end */}
{currentStepIndex < steps.length - 1 && (
<button
onClick={goToNextStep}
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-lg px-6 py-2 font-medium transition-colors"
aria-label={`Go to next step: ${steps[currentStepIndex + 1].title}`}
>
Next
</button>
)}
</nav>
</div>
</main>
<Footer />
{/* Confirmation Dialog */}
<ConfirmDialog />
</div>
);
}
================================================
FILE: src/app/robots.ts
================================================
import { MetadataRoute } from 'next';
export const dynamic = 'force-static';
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://rahuldkjain.github.io/gh-profile-readme-generator';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [],
},
],
sitemap: `${baseUrl}/sitemap.xml`,
};
}
================================================
FILE: src/app/sitemap.ts
================================================
import { MetadataRoute } from 'next';
export const dynamic = 'force-static';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://rahuldkjain.github.io/gh-profile-readme-generator';
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1.0,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.7,
},
{
url: `${baseUrl}/addons`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.7,
},
{
url: `${baseUrl}/support`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.6,
},
];
}
================================================
FILE: src/app/support/page.tsx
================================================
import { Header } from '@/components/layout/header';
import { Footer } from '@/components/layout/footer';
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Support',
description:
'Support the development of GitHub Profile README Generator and help make it better. Learn how to contribute, report issues, and sponsor the project.',
alternates: {
canonical: '/support',
},
openGraph: {
title: 'Support | GitHub Profile README Generator',
description:
'Support the development of GitHub Profile README Generator and help make it better',
url: '/support',
},
};
export default function SupportPage() {
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="container mx-auto flex-1 px-4 py-12">
<div className="page-content mx-auto max-w-4xl">
<h1 className="mb-6 text-4xl font-bold">💵 Support OSS</h1>
<blockquote className="border-primary border-l-4 pl-4 italic">
Think of giving not as a duty but as a privilege - John D. Rockefeller Jr.
</blockquote>
<p className="text-lg">
🚀 GitHub Profile README Generator tool is free and will always be free. Numerous
developers has put their time and efforts to make this tool more powerful. However,
these developers are doing their full time job along with open-source contributions.
</p>
<p>
You can come forward to support the developers by making small donations. You will never
know what this support mean to them. If you find the tool really helpful, then it will
be very grateful to support the tool 🙇.
</p>
<div className="my-6 flex flex-wrap gap-3">
<a
href="https://www.paypal.me/rahuldkjain/10"
target="_blank"
rel="noopener noreferrer"
>
<img src="https://ionicabizau.github.io/badges/paypal.svg" alt="PayPal" />
</a>
<a href="https://ko-fi.com/A0A81XXSX" target="_blank" rel="noopener noreferrer">
<img
height="23"
width="100"
src="https://cdn.ko-fi.com/cdn/kofi3.png?v=2"
alt="Buy Coffee for rahuldkjain"
/>
</a>
<a
href="https://www.buymeacoffee.com/rahuldkjain"
target="_blank"
rel="noopener noreferrer"
>
<img
src="https://cdn.buymeacoffee.com/buttons/default-orange.png"
alt="Buy Me A Coffee"
height="23"
width="100"
style={{ borderRadius: '2px' }}
/>
</a>
</div>
<hr className="my-8" />
<h2 className="mb-4 text-3xl font-bold">Social Support 🤝</h2>
<a
href="https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Frahuldkjain.github.io%2Fgithub-profile-readme-generator"
target="_blank"
rel="noopener noreferrer"
>
<img
src="https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Frahuldkjain.github.io%2Fgithub-profile-readme-generator"
alt="tweet github profile readme generator"
/>
</a>
<p>Let the world know how you feel using this tool. Share with others on twitter.</p>
<hr className="my-8" />
<h2 className="mb-4 text-3xl font-bold">Sponsors 🙏</h2>
<ul className="space-y-3">
<li>
<a
href="https://github.com/scottcwilson"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Scott C Wilson
</a>{' '}
donated the first ever grant to this tool. A big thanks to him.
</li>
<li>
<a
href="https://github.com/mxschmitt"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Max Schmitt
</a>{' '}
loved the tool and showed the support with his donation. Thanks a lot.
</li>
</ul>
<hr className="my-8" />
<div className="bg-primary/5 border-primary/20 rounded-lg border p-6">
<h3 className="mb-3 text-xl font-semibold">Other Ways to Support</h3>
<ul className="space-y-2">
<li>⭐ Star the project on GitHub</li>
<li>🐛 Report bugs and issues</li>
<li>💡 Suggest new features</li>
<li>🔧 Contribute code via pull requests</li>
<li>📢 Share the tool with your network</li>
<li>📝 Write articles or tutorials about the tool</li>
</ul>
</div>
</div>
</main>
<Footer />
</div>
);
}
================================================
FILE: src/components/analytics/conditional-analytics.tsx
================================================
'use client';
import { useEffect } from 'react';
import { GoogleAnalytics } from '@next/third-parties/google';
import { useConsent } from '@/hooks/use-consent';
import { initializeAnalytics } from '@/lib/analytics';
/**
* Conditionally loads Google Analytics based on user consent
*/
export function ConditionalAnalytics() {
const { status } = useConsent();
const gaId = process.env.NEXT_PUBLIC_GA_ID;
// Initialize analytics when consent is accepted
useEffect(() => {
if (status === 'accepted' && gaId) {
// Small delay to ensure GA4 is loaded
const timer = setTimeout(() => {
initializeAnalytics();
}, 1000);
return () => clearTimeout(timer);
}
}, [status, gaId]);
// Only render GoogleAnalytics if consent is accepted and GA ID exists
if (status !== 'accepted' || !gaId) {
return null;
}
return <GoogleAnalytics gaId={gaId} />;
}
================================================
FILE: src/components/forms/__tests__/form-input.test.tsx
================================================
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { FormInput } from '../form-input';
describe('FormInput', () => {
it('renders basic input with label', () => {
render(<FormInput id="test-input" label="Test Label" />);
expect(screen.getByLabelText('Test Label')).toBeInTheDocument();
expect(screen.getByRole('textbox')).toBeInTheDocument();
});
it('renders without label when not provided', () => {
render(<FormInput id="test-input" placeholder="Enter text" />);
expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument();
expect(screen.queryByRole('label')).not.toBeInTheDocument();
});
it('shows required asterisk when required prop is true', () => {
render(<FormInput id="test-input" label="Required Field" required />);
expect(screen.getByText('*')).toBeInTheDocument();
expect(screen.getByText('*')).toHaveClass('text-destructive');
});
it('displays error message when error prop is provided', () => {
const errorMessage = 'This field is required';
render(<FormInput id="test-input" label="Test Field" error={errorMessage} />);
const errorElement = screen.getByRole('alert');
expect(errorElement).toBeInTheDocument();
expect(errorElement).toHaveTextContent(errorMessage);
expect(errorElement).toHaveClass('text-destructive');
});
it('displays helper text when provided and no error', () => {
const helperText = 'This is helpful information';
render(<FormInput id="test-input" label="Test Field" helperText={helperText} />);
expect(screen.getByText(helperText)).toBeInTheDocument();
expect(screen.getByText(helperText)).toHaveClass('text-muted-foreground');
});
it('hides helper text when error is present', () => {
const helperText = 'This is helpful information';
const errorMessage = 'Error occurred';
render(
<FormInput id="test-input" label="Test Field" helperText={helperText} error={errorMessage} />
);
expect(screen.getByText(errorMessage)).toBeInTheDocument();
expect(screen.queryByText(helperText)).not.toBeInTheDocument();
});
it('applies error styling when error is present', () => {
render(<FormInput id="test-input" label="Test Field" error="Error message" />);
const input = screen.getByRole('textbox');
expect(input).toHaveClass('border-destructive', 'focus:ring-destructive');
});
it('applies custom className', () => {
const customClass = 'custom-input-class';
render(<FormInput id="test-input" className={customClass} />);
const input = screen.getByRole('textbox');
expect(input).toHaveClass(customClass);
});
it('forwards ref correctly', () => {
const ref = vi.fn();
render(<FormInput ref={ref} id="test-input" />);
expect(ref).toHaveBeenCalledWith(expect.any(HTMLInputElement));
});
it('handles user input correctly', async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(<FormInput id="test-input" onChange={handleChange} />);
const input = screen.getByRole('textbox');
await user.type(input, 'Hello World');
expect(input).toHaveValue('Hello World');
expect(handleChange).toHaveBeenCalled();
});
it('passes through all HTML input attributes', () => {
render(
<FormInput
id="test-input"
type="email"
placeholder="Enter email"
disabled
maxLength={50}
data-testid="email-input"
/>
);
const input = screen.getByTestId('email-input');
expect(input).toHaveAttribute('type', 'email');
expect(input).toHaveAttribute('placeholder', 'Enter email');
expect(input).toBeDisabled();
expect(input).toHaveAttribute('maxLength', '50');
});
it('associates label with input using htmlFor and id', () => {
render(<FormInput id="test-input" label="Test Label" />);
const label = screen.getByText('Test Label');
const input = screen.getByRole('textbox');
expect(label).toHaveAttribute('for', 'test-input');
expect(input).toHaveAttribute('id', 'test-input');
});
it('has proper accessibility attributes for error state', () => {
render(<FormInput id="test-input" label="Test Field" error="Error message" />);
const errorElement = screen.getByRole('alert');
expect(errorElement).toHaveAttribute('role', 'alert');
});
});
================================================
FILE: src/components/forms/form-checkbox.tsx
================================================
'use client';
import { forwardRef } from 'react';
import type { InputHTMLAttributes } from 'react';
export interface FormCheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
label?: string;
error?: string;
}
export const FormCheckbox = forwardRef<HTMLInputElement, FormCheckboxProps>(
({ label, error, className = '', ...props }, ref) => {
return (
<div className="w-full space-y-1">
<div className="flex items-center gap-2">
<input
ref={ref}
type="checkbox"
className={`border-input bg-background text-primary focus:ring-ring h-4 w-4 rounded transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
error ? 'border-destructive' : ''
} ${className}`}
{...props}
/>
{label && (
<label htmlFor={props.id} className="text-foreground text-sm font-medium">
{label}
</label>
)}
</div>
{error && (
<p className="text-destructive text-sm" role="alert">
{error}
</p>
)}
</div>
);
}
);
FormCheckbox.displayName = 'FormCheckbox';
================================================
FILE: src/components/forms/form-input.tsx
================================================
'use client';
import { forwardRef } from 'react';
import type { InputHTMLAttributes } from 'react';
export interface FormInputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: string;
}
export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(
({ label, error, helperText, className = '', ...props }, ref) => {
return (
<div className="w-full space-y-2">
{label && (
<label htmlFor={props.id} className="text-foreground block text-sm font-medium">
{label}
{props.required && <span className="text-destructive ml-1">*</span>}
</label>
)}
<input
ref={ref}
className={`border-input bg-background text-foreground placeholder:text-muted-foreground focus:ring-ring w-full rounded-lg border px-4 py-2 transition-colors focus:ring-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
error ? 'border-destructive focus:ring-destructive' : ''
} ${className}`}
{...props}
/>
{error && (
<p className="text-destructive text-sm" role="alert">
{error}
</p>
)}
{helperText && !error && <p className="text-muted-foreground text-sm">{helperText}</p>}
</div>
);
}
);
FormInput.displayName = 'FormInput';
================================================
FILE: src/components/forms/form-select.tsx
================================================
'use client';
import { forwardRef } from 'react';
import { Select, type SelectOption } from '@/components/ui/select';
export interface FormSelectProps {
label?: string;
error?: string;
helperText?: string;
placeholder?: string;
options: SelectOption[];
value?: string;
onChange?: (value: string) => void;
disabled?: boolean;
required?: boolean;
id?: string;
className?: string;
}
export const FormSelect = forwardRef<HTMLButtonElement, FormSelectProps>((props, ref) => {
return <Select ref={ref} {...props} />;
});
FormSelect.displayName = 'FormSelect';
================================================
FILE: src/components/forms/form-textarea.tsx
================================================
'use client';
import { forwardRef } from 'react';
import type { TextareaHTMLAttributes } from 'react';
export interface FormTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string;
error?: string;
helperText?: string;
}
export const FormTextarea = forwardRef<HTMLTextAreaElement, FormTextareaProps>(
({ label, error, helperText, className = '', ...props }, ref) => {
return (
<div className="w-full space-y-2">
{label && (
<label htmlFor={props.id} className="text-foreground block text-sm font-medium">
{label}
{props.required && <span className="text-destructive ml-1">*</span>}
</label>
)}
<textarea
ref={ref}
className={`border-input bg-background text-foreground placeholder:text-muted-foreground focus:ring-ring w-full rounded-lg border px-4 py-2 transition-colors focus:ring-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
error ? 'border-destructive focus:ring-destructive' : ''
} ${className}`}
{...props}
/>
{error && (
<p className="text-destructive text-sm" role="alert">
{error}
</p>
)}
{helperText && !error && <p className="text-muted-foreground text-sm">{helperText}</p>}
</div>
);
}
);
FormTextarea.displayName = 'FormTextarea';
================================================
FILE: src/components/forms/github-username-input.tsx
================================================
'use client';
import { forwardRef } from 'react';
import {
fetchGitHubUser,
mapLanguageToSkills,
generateSmartSubtitle,
type GitHubApiError,
} from '@/lib/github-api';
import { useErrorToast, useToast } from '@/components/ui/toast';
import { trackGitHubAutofill } from '@/lib/analytics';
import type { ProfileFormData, LinksFormData, SocialFormData } from '@/lib/validations';
interface GitHubUsernameInputProps {
value: string;
name?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
onDataFetched?: (data: {
profile: Partial<ProfileFormData>;
links: Partial<LinksFormData>;
social: Partial<SocialFormData>;
skills: string[];
}) => void;
}
export const GitHubUsernameInput = forwardRef<HTMLInputElement, GitHubUsernameInputProps>(
function GitHubUsernameInput({ value, name, onChange, onBlur, onDataFetched }, ref) {
const errorToast = useErrorToast();
const { promise } = useToast();
const handleFetch = async () => {
if (!value.trim()) {
errorToast('Please enter a GitHub username');
return;
}
// Track GitHub auto-fill usage
trackGitHubAutofill(value.trim());
try {
const userData = await promise(fetchGitHubUser(value.trim()), {
loading: `Fetching data for ${value.trim()}...`,
success: (data) => `Successfully loaded ${data?.name || value.trim()}'s profile!`,
error: (error: GitHubApiError) => error.message,
});
if (!userData) {
errorToast('Unable to fetch user data', 'Please check the username and try again.');
return;
}
// Map GitHub data to form data
const suggestedSkills: string[] = [];
userData.topLanguages.forEach((lang) => {
suggestedSkills.push(...mapLanguageToSkills(lang));
});
if (onDataFetched) {
onDataFetched({
profile: {
title: userData.name,
subtitle: generateSmartSubtitle(userData),
},
links: {
blog: userData.blog,
},
social: {
github: userData.username,
twitter: userData.twitter,
},
skills: [...new Set(suggestedSkills)], // Remove duplicates
});
}
} catch (error) {
const apiError = error as GitHubApiError;
// Handle rate limit errors with retry action
if (apiError.type === 'rate_limit') {
errorToast(
'GitHub API Rate Limit Exceeded',
apiError.message,
apiError.retryAfter
? {
label: `Retry in ${apiError.retryAfter}m`,
onClick: () => {
setTimeout(() => handleFetch(), apiError.retryAfter! * 60 * 1000);
},
}
: undefined
);
} else {
// For other errors, show retry action
errorToast('Failed to fetch GitHub data', apiError.message, {
label: 'Retry',
onClick: handleFetch,
});
}
}
};
return (
<div className="space-y-3">
{/* Mobile: Stack vertically, Desktop: Side by side */}
<div className="flex flex-col gap-2 sm:flex-row">
<input
ref={ref}
type="text"
name={name}
value={value}
onChange={onChange}
onBlur={onBlur}
placeholder="Enter GitHub username"
className="border-border bg-input focus:border-ring focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none sm:flex-1"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleFetch();
}
}}
/>
<button
onClick={handleFetch}
className="bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-lg px-4 py-2 text-sm font-medium transition-colors sm:w-auto sm:whitespace-nowrap"
aria-label="Auto-fill from GitHub profile"
>
✨ Auto-fill
</button>
</div>
<p className="text-muted-foreground text-xs">
Enter your GitHub username and click "Auto-fill" to populate fields with your profile data
and suggest relevant skills.
</p>
</div>
);
}
);
================================================
FILE: src/components/layout/footer.tsx
================================================
import Link from 'next/link';
import Image from 'next/image';
import { getAssetPath } from '@/lib/asset-path';
export function Footer() {
return (
<footer className="border-border bg-card border-t py-8">
<div className="container mx-auto px-4">
{/* Logo Section */}
<div className="mb-8 flex items-center justify-center gap-3">
<Image
src={getAssetPath('/mdg.png')}
alt="GitHub Profile README Generator Logo"
width={48}
height={48}
className="h-12 w-12"
unoptimized
/>
<span className="text-xl font-bold">GitHub Profile README Generator</span>
</div>
<div className="grid gap-8 md:grid-cols-4">
{/* About */}
<div>
<h3 className="mb-3 font-semibold">About</h3>
<p className="text-muted-foreground text-sm">
Create an awesome GitHub profile README with ease. Made with ❤️ for the developer
community.
</p>
</div>
{/* Quick Links */}
<div>
<h3 className="mb-3 font-semibold">Quick Links</h3>
<ul className="space-y-2 text-sm">
<li>
<Link
href="/"
className="text-muted-foreground hover:text-primary transition-colors"
>
Generator
</Link>
</li>
<li>
<Link
href="/addons"
className="text-muted-foreground hover:text-primary transition-colors"
>
Addons
</Link>
</li>
<li>
<Link
href="/about"
className="text-muted-foreground hover:text-primary transition-colors"
>
About
</Link>
</li>
<li>
<Link
href="/support"
className="text-muted-foreground hover:text-primary transition-colors"
>
Support
</Link>
</li>
</ul>
</div>
{/* Resources */}
<div>
<h3 className="mb-3 font-semibold">Resources</h3>
<ul className="space-y-2 text-sm">
<li>
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator"
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
GitHub Repository
</a>
</li>
<li>
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/issues"
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
Report Issues
</a>
</li>
<li>
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/LICENSE"
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
License
</a>
</li>
</ul>
</div>
{/* Connect */}
<div>
<h3 className="mb-3 font-semibold">Connect</h3>
<ul className="space-y-2 text-sm">
<li>
<a
href="https://github.com/rahuldkjain"
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
</li>
<li>
<a
href="https://twitter.com/rahuldkjain"
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
Twitter
</a>
</li>
<li>
<a
href="https://linkedin.com/in/rahuldkjain"
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
LinkedIn
</a>
</li>
</ul>
</div>
</div>
<div className="text-muted-foreground border-border mt-8 border-t pt-6 text-center text-sm">
<p>
© {new Date().getFullYear()} GitHub Profile README Generator. Made with ❤️ by{' '}
<a
href="https://github.com/rahuldkjain"
className="text-primary font-medium hover:underline"
target="_blank"
rel="noopener noreferrer"
>
Rahul Jain
</a>
</p>
<p className="mt-2">
Open source under the{' '}
<a
href="https://github.com/rahuldkjain/github-profile-readme-generator/blob/master/LICENSE"
className="text-primary hover:underline"
target="_blank"
rel="noopener noreferrer"
>
Apache License 2.0
</a>
</p>
</div>
</div>
</footer>
);
}
================================================
FILE: src/components/layout/header.tsx
================================================
'use client';
import Link from 'next/link';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { ThemeToggle } from '@/components/ui/theme-toggle';
import { AccessibilityMenu } from '@/components/ui/accessibility-menu';
import { GitHubStats } from '@/components/ui/github-stats';
import { getAssetPath } from '@/lib/asset-path';
const navigation = [
{ name: 'Generator', href: '/' },
{ name: 'Addons', href: '/addons' },
{ name: 'About', href: '/about' },
{ name: 'Support', href: '/support' },
];
interface HeaderProps {
saveStatus?: 'idle' | 'saving' | 'saved';
lastSaved?: Date | null;
}
export function Header({}: HeaderProps = {}) {
const pathname = usePathname();
return (
<header className="border-border bg-card sticky top-0 z-50 border-b">
<div className="container mx-auto px-4 py-4">
<div className="flex items-center justify-between gap-4">
{/* Logo, Title, and GitHub Stats */}
<div className="flex items-center gap-4">
<Link href="/" prefetch={true} className="flex items-center gap-3 hover:opacity-80">
<Image
src={getAssetPath('/mdg.png')}
alt="GitHub Profile README Generator Logo"
width={40}
height={40}
className="h-10 w-10"
priority
unoptimized
/>
<span className="hidden text-xl font-bold sm:inline-block lg:text-2xl">
GitHub Profile README Generator
</span>
</Link>
<GitHubStats />
</div>
{/* Right side content */}
<div className="flex items-center gap-3">
<nav className="hidden lg:block" aria-label="Main navigation">
<ul className="flex gap-4">
{navigation.map((item) => {
// Normalize paths for comparison (remove trailing slashes)
const normalizedPathname = pathname.replace(/\/$/, '') || '/';
const normalizedHref = item.href.replace(/\/$/, '') || '/';
const isActive = normalizedPathname === normalizedHref;
return (
<li key={item.name}>
<Link
href={item.href}
prefetch={true}
className={`hover:text-primary text-sm font-medium transition-colors ${
isActive ? 'text-primary font-semibold' : 'text-muted-foreground'
}`}
aria-current={isActive ? 'page' : undefined}
>
{item.name}
</Link>
</li>
);
})}
</ul>
</nav>
<div className="flex items-center gap-2">
<AccessibilityMenu />
<ThemeToggle />
</div>
</div>
</div>
{/* Mobile Navigation */}
<nav className="mt-4 lg:hidden" aria-label="Mobile navigation">
<ul className="flex gap-4 overflow-x-auto">
{navigation.map((item) => {
// Normalize paths for comparison (remove trailing slashes)
const normalizedPathname = pathname.replace(/\/$/, '') || '/';
const normalizedHref = item.href.replace(/\/$/, '') || '/';
const isActive = normalizedPathname === normalizedHref;
return (
<li key={item.name}>
<Link
href={item.href}
prefetch={true}
className={`hover:text-primary text-sm font-medium whitespace-nowrap transition-colors ${
isActive ? 'text-primary font-semibold' : 'text-muted-foreground'
}`}
aria-current={isActive ? 'page' : undefined}
>
{item.name}
</Link>
</li>
);
})}
</ul>
</nav>
</div>
</header>
);
}
================================================
FILE: src/components/layout/theme-provider.tsx
================================================
'use client';
import { useEffect, useState } from 'react';
import { useTheme } from '@/hooks/use-theme';
import { useThemeStore } from '@/lib/store';
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false);
const { accessibility } = useThemeStore();
useTheme(); // Initialize theme
useEffect(() => {
setMounted(true);
}, []);
// Apply accessibility settings to document
useEffect(() => {
if (!mounted) return;
const root = document.documentElement;
// High contrast mode
if (accessibility.highContrast) {
root.classList.add('high-contrast');
} else {
root.classList.remove('high-contrast');
}
// Font size
root.classList.remove('text-small', 'text-large');
if (accessibility.fontSize === 'small') {
root.classList.add('text-small');
} else if (accessibility.fontSize === 'large') {
root.classList.add('text-large');
}
// Reduced motion (already handled by CSS)
if (accessibility.reducedMotion) {
root.style.setProperty('--motion-reduce', '1');
} else {
root.style.removeProperty('--motion-reduce');
}
}, [mounted, accessibility]);
// Prevent flash of unstyled content
if (!mounted) {
return <div style={{ visibility: 'hidden' }}>{children}</div>;
}
return <>{children}</>;
}
================================================
FILE: src/components/sections/basic-info-section.tsx
================================================
'use client';
import { Upload } from 'lucide-react';
import { useState, useEffect } from 'react';
import { UseFormRegister, FieldErrors, UseFormWatch } from 'react-hook-form';
import { FormInput } from '@/components/forms/form-input';
import { FormTextarea } from '@/components/forms/form-textarea';
import { FormCheckbox } from '@/components/forms/form-checkbox';
import { GitHubUsernameInput } from '@/components/forms/github-username-input';
import { CollapsibleSection } from '@/components/ui/collapsible-section';
import type { ProfileFormData, LinksFormData, SocialFormData } from '@/lib/validations';
interface BasicInfoSectionProps {
register: UseFormRegister<ProfileFormData>;
errors: FieldErrors<ProfileFormData>;
socialRegister: UseFormRegister<SocialFormData>;
watchSocial: UseFormWatch<SocialFormData>;
onGitHubAutoFill?: (data: {
profile: Partial<ProfileFormData>;
links: Partial<LinksFormData>;
social: Partial<SocialFormData>;
skills: string[];
}) => void;
onImportJSON?: (event: React.ChangeEvent<HTMLInputElement>) => void;
onClearAll?: () => void;
hasClearableData?: boolean;
}
export function BasicInfoSection({
register,
errors,
socialRegister,
watchSocial,
onGitHubAutoFill,
onImportJSON,
onClearAll,
hasClearableData = true,
}: BasicInfoSectionProps) {
const githubUsername = watchSocial('github') || '';
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
return (
<div className="space-y-6">
<div className="border-border border-b pb-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold">Basic Information</h2>
<p className="text-muted-foreground mt-1 text-sm">
Tell us about yourself and what you do
</p>
</div>
{/* Action Buttons */}
<div className="flex items-center gap-2">
{/* Clear All Button */}
{onClearAll && (
<button
onClick={onClearAll}
disabled={!hasClearableData}
className={`flex items-center justify-center rounded-lg border p-3 transition-colors ${
hasClearableData
? 'bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground border-border'
: 'bg-muted text-muted-foreground border-border cursor-not-allowed opacity-50'
}`}
title={hasClearableData ? 'Clear all data' : 'No data to clear'}
aria-label={hasClearableData ? 'Clear all data' : 'No data to clear'}
>
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
<span className="ml-2 text-sm">Clear All</span>
</button>
)}
{/* Import JSON Button */}
{onImportJSON && (
<label className="bg-secondary text-secondary-foreground hover:bg-secondary/90 flex cursor-pointer items-center justify-center rounded-lg p-3 transition-colors">
<Upload className="h-5 w-5" />
<input
type="file"
accept=".json"
onChange={onImportJSON}
className="hidden"
title="Import profile data from JSON"
aria-label="Import profile data from JSON"
/>
</label>
)}
</div>
</div>
</div>
{/* Quick Start: GitHub Auto-fill */}
{onGitHubAutoFill && (
<div className="border-primary/30 bg-primary/5 rounded-lg border-2 p-4">
<div className="mb-3 flex items-start gap-2">
<span className="text-2xl">🚀</span>
<div className="flex-1">
<h3 className="text-sm font-semibold">Quick Start with GitHub</h3>
<p className="text-muted-foreground mt-1 text-xs">
Enter your GitHub username to automatically populate your profile with smart
defaults
</p>
</div>
</div>
<GitHubUsernameInput
{...socialRegister('github')}
value={githubUsername}
onDataFetched={onGitHubAutoFill}
/>
</div>
)}
{/* Basic Fields - Always visible */}
<div className="grid gap-6 md:grid-cols-2">
<FormInput
{...register('title')}
id="title"
label="Your Name"
placeholder="John Doe"
error={errors.title?.message}
required
/>
<FormInput
{...register('subtitle')}
id="subtitle"
label="Subtitle"
placeholder="A passionate developer"
error={errors.subtitle?.message}
/>
</div>
{/* Additional Fields - Collapsible on mobile */}
{isMobile ? (
<div className="space-y-3">
<CollapsibleSection title="Current Work" icon="🔭" description="What you're working on">
<FormTextarea
{...register('currentWork')}
id="currentWork"
label="I'm currently working on"
placeholder="a MERN Stack project"
rows={2}
error={errors.currentWork?.message}
/>
</CollapsibleSection>
<CollapsibleSection title="Learning" icon="🌱" description="What you're learning">
<FormTextarea
{...register('currentLearn')}
id="currentLearn"
label="I'm currently learning"
placeholder="GraphQL and TypeScript"
rows={2}
error={errors.currentLearn?.message}
/>
</CollapsibleSection>
<CollapsibleSection
title="Collaboration"
icon="👯"
description="What you want to collaborate on"
>
<FormTextarea
{...register('collaborateOn')}
id="collaborateOn"
label="I'm looking to collaborate on"
placeholder="open source projects"
rows={2}
error={errors.collaborateOn?.message}
/>
</CollapsibleSection>
<CollapsibleSection title="Help Needed" icon="🤝" description="What you need help with">
<FormTextarea
{...register('helpWith')}
id="helpWith"
label="I'm looking for help with"
placeholder="learning system design"
rows={2}
error={errors.helpWith?.message}
/>
</CollapsibleSection>
<CollapsibleSection title="Ask Me About" icon="💬" description="Your areas of expertise">
<FormTextarea
{...register('ama')}
id="ama"
label="Ask me about"
placeholder="React, Node.js, and web development"
rows={2}
error={errors.ama?.message}
/>
</CollapsibleSection>
<CollapsibleSection title="Contact" icon="📫" description="How to reach you">
<FormInput
{...register('contact')}
id="contact"
label="How to reach me"
type="email"
placeholder="your.email@example.com"
error={errors.contact?.message}
/>
</CollapsibleSection>
<CollapsibleSection
title="Fun Fact"
icon="⚡"
description="Something interesting about you"
>
<FormTextarea
{...register('funFact')}
id="funFact"
label="Fun fact"
placeholder="I think I am funny"
rows={2}
error={errors.funFact?.message}
/>
</CollapsibleSection>
</div>
) : (
<>
<FormTextarea
{...register('currentWork')}
id="currentWork"
label="🔭 I'm currently working on"
placeholder="a MERN Stack project"
rows={2}
error={errors.currentWork?.message}
/>
<FormTextarea
{...register('currentLearn')}
id="currentLearn"
label="🌱 I'm currently learning"
placeholder="GraphQL and TypeScript"
rows={2}
error={errors.currentLearn?.message}
/>
<FormTextarea
{...register('collaborateOn')}
id="collaborateOn"
label="👯 I'm looking to collaborate on"
placeholder="open source projects"
rows={2}
error={errors.collaborateOn?.message}
/>
<FormTextarea
{...register('helpWith')}
id="helpWith"
label="🤝 I'm looking for help with"
placeholder="learning system design"
rows={2}
error={errors.helpWith?.message}
/>
<FormTextarea
{...register('ama')}
id="ama"
label="💬 Ask me about"
placeholder="React, Node.js, and web development"
rows={2}
error={errors.ama?.message}
/>
<FormInput
{...register('contact')}
id="contact"
label="📫 How to reach me"
type="email"
placeholder="your.email@example.com"
error={errors.contact?.message}
/>
<FormTextarea
{...register('funFact')}
id="funFact"
label="⚡ Fun fact"
placeholder="I think I am funny"
rows={2}
error={errors.funFact?.message}
/>
</>
)}
{/* Profile Badge Option */}
<div className="border-border mt-6 border-t pt-6">
<div className="bg-accent/50 rounded-lg p-4">
<h4 className="mb-2 flex items-center gap-2 text-sm font-semibold">
<span>📊</span>
<span>Profile Enhancement</span>
</h4>
<FormCheckbox
{...register('visitorsBadge')}
id="visitorsBadge"
label="Show profile visitors counter badge"
/>
</div>
</div>
</div>
);
}
================================================
FILE: src/components/sections/links-section.tsx
================================================
'use client';
import { UseFormRegister, FieldErrors } from 'react-hook-form';
import { FormInput } from '@/components/forms/form-input';
import type { LinksFormData } from '@/lib/validations';
interface LinksSectionProps {
register: UseFormRegister<LinksFormData>;
errors: FieldErrors<LinksFormData>;
}
export function LinksSection({ register, errors }: LinksSectionProps) {
return (
<div className="space-y-6">
<div className="border-b border-border pb-4">
<h2 className="text-2xl font-bold">Links</h2>
<p className="text-muted-foreground mt-1 text-sm">
Add links to your portfolio, blog, and resume
</p>
</div>
<FormInput
{...register('portfolio')}
id="portfolio"
label="👨💻 Portfolio"
type="url"
placeholder="https://yourportfolio.com"
error={errors.portfolio?.message}
helperText="Your personal website or portfolio"
/>
<FormInput
{...register('blog')}
id="blog"
label="📝 Blog"
type="url"
placeholder="https://yourblog.com"
error={errors.blog?.message}
helperText="Where you write articles"
/>
<FormInput
{...register('resume')}
id="resume"
label="📄 Resume/CV"
type="url"
placeholder="https://drive.google.com/your-resume"
error={errors.resume?.message}
helperText="Link to your resume or CV"
/>
</div>
);
}
================================================
FILE: src/components/sections/skills-section.tsx
================================================
'use client';
import { useState, useMemo, useEffect } from 'react';
import { Info } from 'lucide-react';
import { UseFormRegister } from 'react-hook-form';
import { FormCheckbox } from '@/components/forms/form-checkbox';
import { FormInput } from '@/components/forms/form-input';
import { Select } from '@/components/ui/select';
import { CollapsibleSection } from '@/components/ui/collapsible-section';
import { categorizedSkills, categories } from '@/constants/skills';
import { getSkillIconUrl } from '@/lib/markdown-generator';
import type { ProfileFormData } from '@/lib/validations';
interface SkillsSectionProps {
selectedSkills: Record<string, boolean>;
onSkillChange: (skill: string, checked: boolean) => void;
registerProfile: UseFormRegister<ProfileFormData>;
}
export function SkillsSection({
selectedSkills,
onSkillChange,
registerProfile,
}: SkillsSectionProps) {
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string>('all');
const [isMobile, setIsMobile] = useState(false);
// Check if we're on mobile for responsive behavior
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
const selectedCount = useMemo(() => {
return Object.values(selectedSkills).filter(Boolean).length;
}, [selectedSkills]);
const filteredCategories = useMemo(() => {
if (selectedCategory !== 'all') {
return [selectedCategory];
}
return categories;
}, [selectedCategory]);
const filterSkills = (skills: string[]) => {
if (!searchQuery) return skills;
return skills.filter((skill) => skill.toLowerCase().includes(searchQuery.toLowerCase()));
};
// Create options for the select component
const categoryOptions = [
{ value: 'all', label: 'All Categories' },
...categories.map((category) => ({
value: category,
label: categorizedSkills[category].title,
})),
];
return (
<div className="space-y-6">
<div className="border-border border-b pb-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold">Skills & Technologies</h2>
<p className="text-muted-foreground mt-1 text-sm">
Select the skills you want to showcase ({selectedCount} selected)
</p>
</div>
</div>
</div>
{/* Search and Filter - Stack on mobile */}
<div className="flex flex-col gap-4 sm:grid sm:grid-cols-2">
<FormInput
id="skill-search"
placeholder="Search skills..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<Select
value={selectedCategory}
onChange={setSelectedCategory}
options={categoryOptions}
placeholder="Select category"
/>
</div>
{/* Skills Grid - Responsive layout */}
<div className="space-y-6">
{filteredCategories.map((category) => {
const { title, skills } = categorizedSkills[category];
const filtered = filterSkills(skills);
if (filtered.length === 0) return null;
const skillsGrid = (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{filtered.map((skill) => {
const iconUrl = getSkillIconUrl(skill);
const isSelected = selectedSkills[skill] || false;
return (
<button
key={skill}
type="button"
onClick={() => onSkillChange(skill, !isSelected)}
className={`relative flex flex-col items-center gap-2 rounded-lg border-2 p-2 transition-all hover:scale-105 sm:p-3 ${
isSelected
? 'border-primary bg-primary/10'
: 'border-border hover:border-primary/50'
}`}
aria-pressed={isSelected}
>
<img
src={iconUrl}
alt={skill}
className="h-8 w-8 object-contain sm:h-10 sm:w-10"
loading="eager"
/>
<span className="text-center text-xs leading-tight capitalize">
{skill.replace(/_/g, ' ')}
</span>
{isSelected && (
<div className="bg-primary absolute top-1 right-1 h-2 w-2 rounded-full" />
)}
</button>
);
})}
</div>
);
// On mobile, use collapsible sections for better organization
if (isMobile && selecte
gitextract_r5j2l4sc/ ├── .all-contributorsrc ├── .eslintignore ├── .eslintrc.json ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature-enhancement-request.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── config.yml │ └── workflows/ │ └── deploy.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CHEATSHEET.md ├── CODE_OF_CONDUCT.md ├── CODE_STYLE_GUIDE.md ├── COMMIT_CONVENTION.md ├── CONTRIBUTING.md ├── DEPLOYMENT.md ├── LICENSE ├── README.md ├── env.example ├── eslint.config.mjs ├── next.config.ts ├── package.json ├── postcss.config.mjs ├── public/ │ ├── manifest.json │ └── robots.txt ├── setupTests.js ├── src/ │ ├── app/ │ │ ├── about/ │ │ │ └── page.tsx │ │ ├── addons/ │ │ │ └── page.tsx │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── robots.ts │ │ ├── sitemap.ts │ │ └── support/ │ │ └── page.tsx │ ├── components/ │ │ ├── analytics/ │ │ │ └── conditional-analytics.tsx │ │ ├── forms/ │ │ │ ├── __tests__/ │ │ │ │ └── form-input.test.tsx │ │ │ ├── form-checkbox.tsx │ │ │ ├── form-input.tsx │ │ │ ├── form-select.tsx │ │ │ ├── form-textarea.tsx │ │ │ └── github-username-input.tsx │ │ ├── layout/ │ │ │ ├── footer.tsx │ │ │ ├── header.tsx │ │ │ └── theme-provider.tsx │ │ ├── sections/ │ │ │ ├── basic-info-section.tsx │ │ │ ├── links-section.tsx │ │ │ ├── skills-section.tsx │ │ │ └── social-section.tsx │ │ └── ui/ │ │ ├── accessibility-menu.tsx │ │ ├── buy-me-coffee.tsx │ │ ├── collapsible-section.tsx │ │ ├── confirm-dialog.tsx │ │ ├── cookie-consent.tsx │ │ ├── github-stats.tsx │ │ ├── markdown-preview.tsx │ │ ├── select.tsx │ │ ├── theme-toggle.tsx │ │ └── toast.tsx │ ├── constants/ │ │ ├── defaults.ts │ │ └── skills.ts │ ├── hooks/ │ │ ├── use-consent.ts │ │ ├── use-local-storage.ts │ │ └── use-theme.ts │ ├── lib/ │ │ ├── analytics.ts │ │ ├── asset-path.ts │ │ ├── github-api.ts │ │ ├── markdown-generator.ts │ │ ├── storage.ts │ │ ├── store.ts │ │ └── validations.ts │ ├── markdown-pages/ │ │ ├── about.md │ │ ├── addons.md │ │ └── support.md │ ├── styles/ │ │ └── tailwind.css │ ├── test/ │ │ └── setup.ts │ └── types/ │ ├── profile.ts │ ├── skills.ts │ └── theme.ts ├── tailwind.config.js ├── tailwind.config.ts ├── tsconfig.json └── vitest.config.ts
SYMBOL INDEX (110 symbols across 44 files)
FILE: src/app/about/page.tsx
function AboutPage (line 21) | function AboutPage() {
FILE: src/app/addons/page.tsx
function AddonsPage (line 20) | function AddonsPage() {
FILE: src/app/layout.tsx
function RootLayout (line 108) | function RootLayout({
FILE: src/app/page.tsx
type Step (line 34) | type Step = 'basic' | 'links' | 'social' | 'skills' | 'preview';
function GeneratorPage (line 44) | function GeneratorPage() {
FILE: src/app/robots.ts
function robots (line 5) | function robots(): MetadataRoute.Robots {
FILE: src/app/sitemap.ts
function sitemap (line 5) | function sitemap(): MetadataRoute.Sitemap {
FILE: src/app/support/page.tsx
function SupportPage (line 20) | function SupportPage() {
FILE: src/components/analytics/conditional-analytics.tsx
function ConditionalAnalytics (line 11) | function ConditionalAnalytics() {
FILE: src/components/forms/form-checkbox.tsx
type FormCheckboxProps (line 6) | interface FormCheckboxProps extends Omit<InputHTMLAttributes<HTMLInputEl...
FILE: src/components/forms/form-input.tsx
type FormInputProps (line 6) | interface FormInputProps extends InputHTMLAttributes<HTMLInputElement> {
FILE: src/components/forms/form-select.tsx
type FormSelectProps (line 6) | interface FormSelectProps {
FILE: src/components/forms/form-textarea.tsx
type FormTextareaProps (line 6) | interface FormTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaE...
FILE: src/components/forms/github-username-input.tsx
type GitHubUsernameInputProps (line 14) | interface GitHubUsernameInputProps {
FILE: src/components/layout/footer.tsx
function Footer (line 5) | function Footer() {
FILE: src/components/layout/header.tsx
type HeaderProps (line 18) | interface HeaderProps {
function Header (line 23) | function Header({}: HeaderProps = {}) {
FILE: src/components/layout/theme-provider.tsx
function ThemeProvider (line 7) | function ThemeProvider({ children }: { children: React.ReactNode }) {
FILE: src/components/sections/basic-info-section.tsx
type BasicInfoSectionProps (line 14) | interface BasicInfoSectionProps {
function BasicInfoSection (line 30) | function BasicInfoSection({
FILE: src/components/sections/links-section.tsx
type LinksSectionProps (line 7) | interface LinksSectionProps {
function LinksSection (line 12) | function LinksSection({ register, errors }: LinksSectionProps) {
FILE: src/components/sections/skills-section.tsx
type SkillsSectionProps (line 14) | interface SkillsSectionProps {
function SkillsSection (line 20) | function SkillsSection({
FILE: src/components/sections/social-section.tsx
type SocialSectionProps (line 10) | interface SocialSectionProps {
function SocialSection (line 16) | function SocialSection({ register, errors, watch }: SocialSectionProps) {
FILE: src/components/ui/accessibility-menu.tsx
function AccessibilityMenu (line 8) | function AccessibilityMenu() {
FILE: src/components/ui/buy-me-coffee.tsx
function BuyMeACoffeeWidget (line 5) | function BuyMeACoffeeWidget() {
FILE: src/components/ui/collapsible-section.tsx
type CollapsibleSectionProps (line 7) | interface CollapsibleSectionProps {
function CollapsibleSection (line 15) | function CollapsibleSection({
FILE: src/components/ui/confirm-dialog.tsx
type ConfirmDialogProps (line 7) | interface ConfirmDialogProps {
function ConfirmDialog (line 18) | function ConfirmDialog({
function useConfirmDialog (line 131) | function useConfirmDialog() {
FILE: src/components/ui/cookie-consent.tsx
function CookieConsent (line 7) | function CookieConsent() {
function PrivacySettings (line 108) | function PrivacySettings() {
FILE: src/components/ui/github-stats.tsx
type GitHubStats (line 7) | interface GitHubStats {
function GitHubStats (line 12) | function GitHubStats() {
FILE: src/components/ui/markdown-preview.tsx
type MarkdownPreviewProps (line 10) | interface MarkdownPreviewProps {
FILE: src/components/ui/select.tsx
type SelectOption (line 13) | interface SelectOption {
type SelectProps (line 18) | interface SelectProps {
FILE: src/components/ui/theme-toggle.tsx
function ThemeToggle (line 8) | function ThemeToggle() {
FILE: src/components/ui/toast.tsx
type Toast (line 7) | interface Toast {
type ToastContextType (line 20) | interface ToastContextType {
function ToastProvider (line 37) | function ToastProvider({ children }: { children: ReactNode }) {
function useToast (line 127) | function useToast() {
function ToastContainer (line 135) | function ToastContainer() {
function ToastItem (line 149) | function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: (id: s...
function useSuccessToast (line 219) | function useSuccessToast() {
function useErrorToast (line 229) | function useErrorToast() {
function useWarningToast (line 239) | function useWarningToast() {
function useInfoToast (line 249) | function useInfoToast() {
FILE: src/constants/defaults.ts
constant DEFAULT_PREFIX (line 9) | const DEFAULT_PREFIX: ProfilePrefix = {
constant DEFAULT_DATA (line 23) | const DEFAULT_DATA: ProfileData = {
constant DEFAULT_LINK (line 67) | const DEFAULT_LINK: ProfileLinks = {
constant DEFAULT_SOCIAL (line 76) | const DEFAULT_SOCIAL: SocialLinks = {
constant DEFAULT_SUPPORT (line 103) | const DEFAULT_SUPPORT: SupportLinks = {
FILE: src/hooks/use-consent.ts
type ConsentStatus (line 5) | type ConsentStatus = 'pending' | 'accepted' | 'rejected';
type ConsentState (line 7) | interface ConsentState {
function useConsent (line 18) | function useConsent(): ConsentState {
FILE: src/hooks/use-local-storage.ts
function useLocalStorage (line 5) | function useLocalStorage<T>(key: string, initialValue: T) {
FILE: src/hooks/use-theme.ts
function useTheme (line 7) | function useTheme() {
FILE: src/lib/analytics.ts
type Window (line 7) | interface Window {
function hasAnalyticsConsent (line 15) | function hasAnalyticsConsent(): boolean {
function trackEvent (line 29) | function trackEvent(eventName: string, parameters?: Record<string, unkno...
function trackGitHubAutofill (line 52) | function trackGitHubAutofill(username?: string) {
function trackReadmeGenerated (line 64) | function trackReadmeGenerated(data?: {
function trackFileExported (line 84) | function trackFileExported(action: 'copy' | 'download' | 'json_export', ...
function initializeAnalytics (line 97) | function initializeAnalytics() {
function disableAnalytics (line 119) | function disableAnalytics() {
FILE: src/lib/asset-path.ts
function getAssetPath (line 6) | function getAssetPath(path: string): string {
FILE: src/lib/github-api.ts
type GitHubUser (line 1) | interface GitHubUser {
type GitHubRepo (line 15) | interface GitHubRepo {
type GitHubUserData (line 22) | interface GitHubUserData {
type GitHubApiError (line 35) | interface GitHubApiError {
function fetchGitHubUser (line 41) | async function fetchGitHubUser(username: string): Promise<GitHubUserData...
function mapLanguageToSkills (line 145) | function mapLanguageToSkills(language: string): string[] {
function generateSmartSubtitle (line 169) | function generateSmartSubtitle(userData: GitHubUserData): string {
FILE: src/lib/markdown-generator.ts
type GenerateMarkdownOptions (line 9) | interface GenerateMarkdownOptions {
function getSkillIconUrl (line 68) | function getSkillIconUrl(skill: string): string {
function generateMarkdown (line 325) | function generateMarkdown(options: GenerateMarkdownOptions): string {
function generateTitle (line 458) | function generateTitle(profile: Partial<ProfileFormData>): string {
FILE: src/lib/storage.ts
type SavedFormData (line 3) | interface SavedFormData {
constant STORAGE_KEY (line 12) | const STORAGE_KEY = 'github-profile-generator';
function saveFormData (line 14) | function saveFormData(data: SavedFormData): void {
function loadFormData (line 30) | function loadFormData(): SavedFormData | null {
function clearFormData (line 47) | function clearFormData(): void {
function hasFormData (line 55) | function hasFormData(): boolean {
FILE: src/lib/store.ts
type ThemeState (line 5) | interface ThemeState {
FILE: src/lib/validations.ts
type ProfileFormData (line 115) | type ProfileFormData = z.infer<typeof profileSchema>;
type LinksFormData (line 116) | type LinksFormData = z.infer<typeof linksSchema>;
type SocialFormData (line 117) | type SocialFormData = z.infer<typeof socialSchema>;
type SupportFormData (line 118) | type SupportFormData = z.infer<typeof supportSchema>;
type CompleteFormData (line 119) | type CompleteFormData = z.infer<typeof completeFormSchema>;
FILE: src/types/profile.ts
type ProfilePrefix (line 2) | interface ProfilePrefix {
type GithubStatsOptions (line 16) | interface GithubStatsOptions {
type TopLanguagesOptions (line 26) | interface TopLanguagesOptions {
type StreakStatsOptions (line 36) | interface StreakStatsOptions {
type ProfileData (line 40) | interface ProfileData {
type ProfileLinks (line 66) | interface ProfileLinks {
type SocialLinks (line 75) | interface SocialLinks {
type SupportLinks (line 102) | interface SupportLinks {
type Skills (line 106) | interface Skills {
FILE: src/types/skills.ts
type SkillCategory (line 1) | interface SkillCategory {
type CategorizedSkills (line 6) | interface CategorizedSkills {
type SkillIcons (line 10) | interface SkillIcons {
type SkillWebsites (line 14) | interface SkillWebsites {
type SkillState (line 18) | type SkillState = Record<string, boolean>;
FILE: src/types/theme.ts
type ThemeMode (line 1) | type ThemeMode = 'light' | 'dark' | 'system';
type ResolvedTheme (line 2) | type ResolvedTheme = 'light' | 'dark';
type AccessibilitySettings (line 4) | interface AccessibilitySettings {
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (333K chars).
[
{
"path": ".all-contributorsrc",
"chars": 2376,
"preview": "{\n \"files\": [\"README.md\"],\n \"imageSize\": 100,\n \"commit\": false,\n \"contributors\": [\n {\n \"login\": \"sarbikbetal"
},
{
"path": ".eslintignore",
"chars": 15,
"preview": "node_modules/**"
},
{
"path": ".eslintrc.json",
"chars": 360,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"es2021\": true\n },\n \"extends\": [\"plugin:react/recommended\", \"airbnb\", \"prettier\""
},
{
"path": ".github/FUNDING.yml",
"chars": 607,
"preview": "# These are supported funding model platforms\n\ngithub: rahuldkjain\npatreon: # Replace with a single Patreon username\nope"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 1271,
"preview": "---\nname: 🐛 Bug Report\nabout: Report a bug in GitHub Profile README Generator\ntitle: '[Bug] '\nlabels: ['bug']\nassignees:"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-enhancement-request.md",
"chars": 891,
"preview": "---\nname: Feature/Enhancement request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement, hacktoberfe"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 1371,
"preview": "---\nname: ✨ Feature Request\nabout: Suggest a new feature for GitHub Profile README Generator\ntitle: '[Feature] '\nlabels:"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 6052,
"preview": "<!--\n🚀 Thanks for contributing to GitHub Profile README Generator V2!\n\nBefore submitting your Pull Request, please ensur"
},
{
"path": ".github/config.yml",
"chars": 899,
"preview": "# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome\n\n# Comment to be posted to on f"
},
{
"path": ".github/workflows/deploy.yml",
"chars": 6486,
"preview": "name: Build and Deploy\n\non:\n push:\n branches: [master, dev]\n\n# Allow concurrent deployments for different environmen"
},
{
"path": ".gitignore",
"chars": 508,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": ".husky/pre-commit",
"chars": 71,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx --no-install lint-staged\n"
},
{
"path": ".prettierignore",
"chars": 45,
"preview": ".cache\npackage.json\npackage-lock.json\npublic\n"
},
{
"path": ".prettierrc",
"chars": 227,
"preview": "{\n \"singleQuote\": true,\n \"jsxSingleQuote\": false,\n \"tabWidth\": 2,\n \"printWidth\": 100,\n \"trailingComma\": \"es5\",\n \"s"
},
{
"path": "CHANGELOG.md",
"chars": 2815,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "CHEATSHEET.md",
"chars": 10078,
"preview": "# GitHub Profile Generator - Developer Cheatsheet\n\n## 🚀 Quick Start\n```bash\nnpm install && npm run dev\n```\nVisit: http:/"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3356,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CODE_STYLE_GUIDE.md",
"chars": 7541,
"preview": "# Coding Style Guide\n\n## Project Architecture\n\nThis project uses **Next.js 15** with **TypeScript** and **Tailwind CSS**"
},
{
"path": "COMMIT_CONVENTION.md",
"chars": 3835,
"preview": "# 📝 Commit Message Convention\n\nThis project follows [Conventional Commits](https://www.conventionalcommits.org/) specifi"
},
{
"path": "CONTRIBUTING.md",
"chars": 8437,
"preview": "# Contributing to GitHub Profile README Generator\n\nWhen contributing to this repository, please first discuss the change"
},
{
"path": "DEPLOYMENT.md",
"chars": 4132,
"preview": "# 🚀 Production Deployment Guide\n\n## Pre-Deployment Checklist\n\n### ✅ SEO & Performance\n\n- [x] **Meta Tags**: Complete Ope"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 9228,
"preview": "<p align=\"center\">\n <a href=\"https://rahuldkjain.github.io/github-profile-readme-generator\">\n <img alt=\"GitHub Profi"
},
{
"path": "env.example",
"chars": 952,
"preview": "# GitHub Profile README Generator - Environment Configuration\n\n# Google Analytics 4 Configuration\n# Get your GA4 Measure"
},
{
"path": "eslint.config.mjs",
"chars": 977,
"preview": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\ncon"
},
{
"path": "next.config.ts",
"chars": 3335,
"preview": "import type { NextConfig } from 'next';\nimport { PHASE_PRODUCTION_BUILD } from 'next/constants';\n\nconst nextConfig = (ph"
},
{
"path": "package.json",
"chars": 1896,
"preview": "{\n \"name\": \"github-profile-readme-generator\",\n \"version\": \"2.0.0\",\n \"description\": \"Generate GitHub profile README ea"
},
{
"path": "postcss.config.mjs",
"chars": 81,
"preview": "const config = {\n plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n"
},
{
"path": "public/manifest.json",
"chars": 766,
"preview": "{\n \"name\": \"GitHub Profile README Generator\",\n \"short_name\": \"GitHub README Gen\",\n \"description\": \"Create amazing Git"
},
{
"path": "public/robots.txt",
"chars": 201,
"preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nAllow: /\n\n# Host\nHost: https://rahuldkjain.github.io\n\n# Sitemap"
},
{
"path": "setupTests.js",
"chars": 123,
"preview": "import { configure } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\n\nconfigure({ adapter: new Adapter() }"
},
{
"path": "src/app/about/page.tsx",
"chars": 7330,
"preview": "import { Header } from '@/components/layout/header';\nimport { Footer } from '@/components/layout/footer';\nimport type { "
},
{
"path": "src/app/addons/page.tsx",
"chars": 9588,
"preview": "import { Header } from '@/components/layout/header';\nimport { Footer } from '@/components/layout/footer';\nimport type { "
},
{
"path": "src/app/globals.css",
"chars": 8060,
"preview": "@import 'tailwindcss';\n@plugin '@tailwindcss/typography';\n\n/* Screen reader only utility */\n.sr-only {\n position: absol"
},
{
"path": "src/app/layout.tsx",
"chars": 5044,
"preview": "import type { Metadata } from 'next';\nimport { Roboto_Mono } from 'next/font/google';\nimport localFont from 'next/font/l"
},
{
"path": "src/app/page.tsx",
"chars": 23441,
"preview": "'use client';\n\nimport { useState, useEffect, useMemo, lazy, Suspense, useCallback } from 'react';\nimport { useForm } fro"
},
{
"path": "src/app/robots.ts",
"chars": 376,
"preview": "import { MetadataRoute } from 'next';\n\nexport const dynamic = 'force-static';\n\nexport default function robots(): Metadat"
},
{
"path": "src/app/sitemap.ts",
"chars": 751,
"preview": "import { MetadataRoute } from 'next';\n\nexport const dynamic = 'force-static';\n\nexport default function sitemap(): Metada"
},
{
"path": "src/app/support/page.tsx",
"chars": 5100,
"preview": "import { Header } from '@/components/layout/header';\nimport { Footer } from '@/components/layout/footer';\nimport type { "
},
{
"path": "src/components/analytics/conditional-analytics.tsx",
"chars": 903,
"preview": "'use client';\n\nimport { useEffect } from 'react';\nimport { GoogleAnalytics } from '@next/third-parties/google';\nimport {"
},
{
"path": "src/components/forms/__tests__/form-input.test.tsx",
"chars": 4457,
"preview": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { d"
},
{
"path": "src/components/forms/form-checkbox.tsx",
"chars": 1254,
"preview": "'use client';\n\nimport { forwardRef } from 'react';\nimport type { InputHTMLAttributes } from 'react';\n\nexport interface F"
},
{
"path": "src/components/forms/form-input.tsx",
"chars": 1385,
"preview": "'use client';\n\nimport { forwardRef } from 'react';\nimport type { InputHTMLAttributes } from 'react';\n\nexport interface F"
},
{
"path": "src/components/forms/form-select.tsx",
"chars": 582,
"preview": "'use client';\n\nimport { forwardRef } from 'react';\nimport { Select, type SelectOption } from '@/components/ui/select';\n\n"
},
{
"path": "src/components/forms/form-textarea.tsx",
"chars": 1415,
"preview": "'use client';\n\nimport { forwardRef } from 'react';\nimport type { TextareaHTMLAttributes } from 'react';\n\nexport interfac"
},
{
"path": "src/components/forms/github-username-input.tsx",
"chars": 4529,
"preview": "'use client';\n\nimport { forwardRef } from 'react';\nimport {\n fetchGitHubUser,\n mapLanguageToSkills,\n generateSmartSub"
},
{
"path": "src/components/layout/footer.tsx",
"chars": 5782,
"preview": "import Link from 'next/link';\nimport Image from 'next/image';\nimport { getAssetPath } from '@/lib/asset-path';\n\nexport f"
},
{
"path": "src/components/layout/header.tsx",
"chars": 4165,
"preview": "'use client';\n\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport { usePathname } from 'next/navigatio"
},
{
"path": "src/components/layout/theme-provider.tsx",
"chars": 1380,
"preview": "'use client';\n\nimport { useEffect, useState } from 'react';\nimport { useTheme } from '@/hooks/use-theme';\nimport { useTh"
},
{
"path": "src/components/sections/basic-info-section.tsx",
"chars": 10894,
"preview": "'use client';\n\nimport { Upload } from 'lucide-react';\n\nimport { useState, useEffect } from 'react';\nimport { UseFormRegi"
},
{
"path": "src/components/sections/links-section.tsx",
"chars": 1484,
"preview": "'use client';\n\nimport { UseFormRegister, FieldErrors } from 'react-hook-form';\nimport { FormInput } from '@/components/f"
},
{
"path": "src/components/sections/skills-section.tsx",
"chars": 9546,
"preview": "'use client';\n\nimport { useState, useMemo, useEffect } from 'react';\nimport { Info } from 'lucide-react';\nimport { UseFo"
},
{
"path": "src/components/sections/social-section.tsx",
"chars": 5869,
"preview": "'use client';\n\nimport { Info } from 'lucide-react';\n\nimport { UseFormRegister, FieldErrors, UseFormWatch } from 'react-h"
},
{
"path": "src/components/ui/accessibility-menu.tsx",
"chars": 3188,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { Settings } from 'lucide-react';\nimport { Select } from '@/comp"
},
{
"path": "src/components/ui/buy-me-coffee.tsx",
"chars": 1156,
"preview": "'use client';\n\nimport { useEffect } from 'react';\n\nexport function BuyMeACoffeeWidget() {\n useEffect(() => {\n const "
},
{
"path": "src/components/ui/collapsible-section.tsx",
"chars": 1896,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Chevr"
},
{
"path": "src/components/ui/confirm-dialog.tsx",
"chars": 5075,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Alert"
},
{
"path": "src/components/ui/cookie-consent.tsx",
"chars": 4982,
"preview": "'use client';\n\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Cookie, ShieldCheck } from 'lucide-reac"
},
{
"path": "src/components/ui/github-stats.tsx",
"chars": 5705,
"preview": "'use client';\n\nimport { useEffect, useState } from 'react';\nimport { motion } from 'framer-motion';\nimport { useErrorToa"
},
{
"path": "src/components/ui/markdown-preview.tsx",
"chars": 7246,
"preview": "'use client';\n\nimport { useState, memo, useMemo } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remar"
},
{
"path": "src/components/ui/select.tsx",
"chars": 4107,
"preview": "'use client';\n\nimport { Fragment, forwardRef } from 'react';\nimport {\n Listbox,\n ListboxButton,\n ListboxOptions,\n Li"
},
{
"path": "src/components/ui/theme-toggle.tsx",
"chars": 1708,
"preview": "'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Sun, Moon, Monitor } from 'lucide-react';\nimport { "
},
{
"path": "src/components/ui/toast.tsx",
"chars": 7472,
"preview": "'use client';\n\nimport { createContext, useContext, useState, useCallback, ReactNode } from 'react';\nimport { motion, Ani"
},
{
"path": "src/constants/defaults.ts",
"chars": 2102,
"preview": "import type {\n ProfilePrefix,\n ProfileData,\n ProfileLinks,\n SocialLinks,\n SupportLinks,\n} from '@/types/profile';\n\n"
},
{
"path": "src/constants/skills.ts",
"chars": 4205,
"preview": "import type { CategorizedSkills, SkillState } from '@/types/skills';\n\nexport const categorizedSkills: CategorizedSkills "
},
{
"path": "src/hooks/use-consent.ts",
"chars": 2800,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\n\nexport type ConsentStatus = 'pending' | 'accepted' | 'rejec"
},
{
"path": "src/hooks/use-local-storage.ts",
"chars": 1553,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\n\nexport function useLocalStorage<T>(key: string"
},
{
"path": "src/hooks/use-theme.ts",
"chars": 1768,
"preview": "'use client';\n\nimport { useEffect } from 'react';\nimport { useThemeStore } from '@/lib/store';\nimport type { ThemeMode }"
},
{
"path": "src/lib/analytics.ts",
"chars": 3323,
"preview": "'use client';\n\n// Analytics utility for GA4 custom event tracking\n// Only tracks events if user has given consent\n\ndecla"
},
{
"path": "src/lib/asset-path.ts",
"chars": 455,
"preview": "/**\n * Get the correct asset path with basePath for GitHub Pages\n * Uses NEXT_PUBLIC_BASE_PATH environment variable if s"
},
{
"path": "src/lib/github-api.ts",
"chars": 5579,
"preview": "interface GitHubUser {\n login: string;\n name: string | null;\n bio: string | null;\n location: string | null;\n blog: "
},
{
"path": "src/lib/markdown-generator.ts",
"chars": 15126,
"preview": "import type {\n ProfileFormData,\n LinksFormData,\n SocialFormData,\n SupportFormData,\n} from './validations';\nimport { "
},
{
"path": "src/lib/storage.ts",
"chars": 2040,
"preview": "import type { ProfileFormData, LinksFormData, SocialFormData, SupportFormData } from './validations';\n\nexport interface "
},
{
"path": "src/lib/store.ts",
"chars": 1090,
"preview": "import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport type { ThemeMode, ResolvedTheme, "
},
{
"path": "src/lib/validations.ts",
"chars": 3610,
"preview": "import { z } from 'zod';\n\n// Profile validation schema\nexport const profileSchema = z.object({\n // Basic Information\n "
},
{
"path": "src/markdown-pages/about.md",
"chars": 3621,
"preview": "---\nslug: '/about'\ndate: '2019-05-04'\ntitle: '👨💻 About'\n---\n\n<a href=\"https://github.com/rahuldkjain/github-profile-rea"
},
{
"path": "src/markdown-pages/addons.md",
"chars": 3800,
"preview": "---\nslug: '/addons'\ndate: '2019-05-04'\ntitle: '🚀 Addons'\n---\n\nGitHub Profile README Generator tool uses few open-source "
},
{
"path": "src/markdown-pages/support.md",
"chars": 9648,
"preview": "---\nslug: '/support'\ndate: '2019-05-04'\ntitle: '💵 Support OSS'\n---\n\n> Think of giving not as a duty but as a privilege -"
},
{
"path": "src/styles/tailwind.css",
"chars": 1835,
"preview": "@tailwind base;\n\n@tailwind components;\n\n@tailwind utilities;\n\n@tailwind @screens;\n\n/* Additional vertical padding used b"
},
{
"path": "src/test/setup.ts",
"chars": 86,
"preview": "import '@testing-library/jest-dom';\n\n// Add custom matchers or global test setup here\n"
},
{
"path": "src/types/profile.ts",
"chars": 2181,
"preview": "// Profile data types\nexport interface ProfilePrefix {\n title: string;\n currentWork: string;\n currentLearn: string;\n "
},
{
"path": "src/types/skills.ts",
"chars": 314,
"preview": "export interface SkillCategory {\n title: string;\n skills: string[];\n}\n\nexport interface CategorizedSkills {\n [key: st"
},
{
"path": "src/types/theme.ts",
"chars": 236,
"preview": "export type ThemeMode = 'light' | 'dark' | 'system';\nexport type ResolvedTheme = 'light' | 'dark';\n\nexport interface Acc"
},
{
"path": "tailwind.config.js",
"chars": 508,
"preview": "module.exports = {\n purge: [],\n theme: {\n extend: {},\n fontSize: {\n xxs: '.60rem',\n xs: '.75rem',\n "
},
{
"path": "tailwind.config.ts",
"chars": 2589,
"preview": "import type { Config } from 'tailwindcss';\n\nconst config: Config = {\n content: [\n // Only scan the actual source fil"
},
{
"path": "tsconfig.json",
"chars": 602,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n "
},
{
"path": "vitest.config.ts",
"chars": 607,
"preview": "import { defineConfig } from 'vitest/config';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\n\nexport"
}
]
About this extraction
This page contains the full source code of the rahuldkjain/github-profile-readme-generator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (308.4 KB), approximately 80.3k tokens, and a symbol index with 110 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.