├── .all-contributorsrc ├── .env.example ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── documentation.md │ ├── documentation_update.yml │ └── feature_request.yml ├── assets │ └── gssoc24.png ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── PR-template.md │ ├── auto-comment-on-pr-merge.yml │ ├── autocomment-iss-close.yml │ ├── autocomment-issue.yml │ ├── autocomment-pr-raise.yml │ ├── close-old-issue.yml │ ├── close-old-pr.yml │ ├── codeql-analysis.yml │ ├── development.yml │ ├── eslint.yml │ ├── greetings.yml │ ├── lighthouse.yml │ └── prettier.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FAQs └── rating.md ├── LEARN.md ├── LICENSE ├── README.md ├── app ├── 400.tsx ├── _error.tsx ├── api │ ├── og │ │ └── route.tsx │ └── users │ │ └── route.ts ├── auth │ └── callback │ │ └── route.ts ├── compare │ ├── action.ts │ └── page.tsx ├── cover-letter │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx ├── leaderboard │ ├── action.ts │ └── page.tsx ├── not-found.tsx ├── page.tsx └── resume │ └── [username] │ ├── not-found.tsx │ ├── opengraph-image.tsx │ ├── page.tsx │ └── twitter-image.tsx ├── components.json ├── components ├── AboutProduct.jsx ├── AuthButton.tsx ├── ContributionGraph.tsx ├── Contributions.tsx ├── CopyLinkBtn.tsx ├── CoverLetterDialog.tsx ├── CoverLetterForm.tsx ├── CustomisationDrawer.tsx ├── Faq.tsx ├── Form.tsx ├── GitHubStarCount.tsx ├── Hamburger.tsx ├── LanguageChart.tsx ├── Loader.tsx ├── Organizations.tsx ├── ProfileTracking.tsx ├── RadarChart.tsx ├── RecentGenerations.tsx ├── RecetUsersLoading.tsx ├── Repositories.tsx ├── Resume.tsx ├── ShareBtn.tsx ├── Sidebar.tsx ├── StatsBox.tsx ├── UserTestimonials.tsx ├── cover-letter.tsx ├── footer.tsx ├── hoc │ └── withErrorBoundary.tsx ├── leaderboard │ └── crown.tsx ├── navbar.tsx ├── new-resume.tsx ├── shared │ ├── Background.svg │ └── ToggleBg.tsx ├── theme-provider.tsx └── ui │ ├── accordion.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── card-hover-effect.tsx │ ├── carousel.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── infinite-moving-cards.tsx │ ├── input.tsx │ ├── label.tsx │ ├── pagination.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── skeleton.tsx │ ├── table.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── tooltip.tsx │ └── use-toast.ts ├── config └── content │ ├── Testimonials.js │ └── faqs.js ├── eslint.config.mjs ├── lib ├── consts.ts ├── redis.ts ├── store │ ├── InitUser.tsx │ └── user.ts └── utils.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── GIT-RE Project Setup.mp4 ├── assets │ ├── Roboto-Bold.ttf │ └── Roboto-Italic.ttf ├── next.svg └── vercel.svg ├── tailwind.config.ts ├── tsconfig.json ├── types └── index.ts └── utils ├── Gemini.ts ├── GithubAPI.ts ├── cn.ts ├── format.ts ├── rating └── action.ts ├── resumeUtils.ts └── supabase ├── client.ts ├── getUserdata.ts ├── middleware.ts └── server.ts /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SUPABASE_URL= 2 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 3 | NEXT_PUBLIC_URL=https://localhost:3000/ 4 | NEXT_PUBLIC_UPSTASH_REDIS_URL= 5 | NEXT_PUBLIC_UPSTASH_REDIS_TOKEN= 6 | 7 | NEXT_PUBLIC_GEMINI_API_KEY= 8 | 9 | # Generate the token from https://github.com/settings/tokens?type=beta 10 | # Repository Access to public repositories (read-only) 11 | NEXT_PUBLIC_GITHUB_TOKEN= 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | ignorePatterns: ["node_modules/**", "dist/**"], 4 | env: { 5 | browser: true, 6 | es2021: true, 7 | }, 8 | extends: [ 9 | "plugin:react/recommended", 10 | "plugin:react/jsx-runtime", 11 | "next", 12 | "prettier", 13 | ], 14 | file: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"], 15 | parser: "@typescript-eslint/parser", 16 | parserOptions: { 17 | ecmaFeatures: { 18 | jsx: true, 19 | }, 20 | ecmaVersion: 12, 21 | sourceType: "module", 22 | }, 23 | plugins: ["react", "@typescript-eslint"], 24 | rules: { 25 | "react/no-direct-mutation-state": [ 26 | "error", // Keep the default as error 27 | { 28 | ignoreCallbacks: true, // Allow mutation within callbacks (optional) 29 | mutators: ["this.setState"], // Allow mutation using this.setState (optional) 30 | }, 31 | ], 32 | "react/no-unescaped-entities": "off", 33 | "@next/next/no-page-custom-font": "off", 34 | }, 35 | }; 36 | // eslint-disable-next-line no-undef 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 🐞 2 | description: File a bug report 3 | title: "[Bug]: " 4 | body: 5 | - type: checkboxes 6 | id: existing-issue 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the bug you encountered. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | id: what-happened 15 | attributes: 16 | label: Describe the bug 17 | description: A concise description of what you are experiencing. 18 | placeholder: Tell us what you see! 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: expected-behaviour 23 | attributes: 24 | label: Expected behavior 25 | description: A clear and concise description of what you expected to happen. 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: screenshots 30 | attributes: 31 | label: Add ScreenShots 32 | description: Add sufficient ScreenShots to explain your issue. 33 | - type: dropdown 34 | id: devices 35 | attributes: 36 | label: On which device are you experiencing this bug? 37 | multiple: true 38 | options: 39 | - Android 40 | - iPhone 41 | - Linux 42 | - Chrome 43 | - Windows 44 | - type: checkboxes 45 | id: terms 46 | attributes: 47 | label: Record 48 | options: 49 | - label: "I have read the Contributing Guidelines" 50 | required: true 51 | - label: "I'm a GSSOC'24 contributor" 52 | required: False 53 | - label: "I have starred the repository" 54 | required: true 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📝 Documentation 3 | about: Propose changes and improvements to JobLane Docs. 4 | title: '📝[Docs]: ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | **What Docs changes are you proposing?** 10 | Provide a clear description of the changes you're proposing for the documentation. Are you suggesting corrections, clarifications, additions, or updates? 11 | 12 | **Why do the Docs need this improvement? What is the motivation for this change? How will this change benefit the community?** 13 | Explain the motivation behind the proposed changes and how they will benefit the community or users of the documentation. 14 | 15 | **Describe the solution you'd like** 16 | Provide a clear and concise description of the changes you'd like to see implemented in the documentation. 17 | 18 | **Describe alternatives you've considered** 19 | If you've thought about alternative approaches or solutions, briefly describe them here. 20 | 21 | **Additional context** 22 | Add any additional context, examples, or screenshots related to the proposed documentation changes. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_update.yml: -------------------------------------------------------------------------------- 1 | name: 📝 Documentation Update 2 | description: Improve Documentation 3 | title: "[Documentation Update]: " 4 | body: 5 | - type: checkboxes 6 | id: existing-issue 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the updates you want to make. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | id: issue-description 15 | attributes: 16 | label: Issue Description 17 | description: Please provide a clear description of the documentation update you are suggesting. 18 | placeholder: Describe the improvement or correction you'd like to see in the documentation. 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: suggested-change 23 | attributes: 24 | label: Suggested Change 25 | description: Provide details of the proposed change to the documentation. 26 | placeholder: Explain how the documentation should be updated or corrected. 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: rationale 31 | attributes: 32 | label: Rationale 33 | description: Why is this documentation update necessary or beneficial? 34 | placeholder: Explain the importance or reasoning behind the suggested change. 35 | validations: 36 | required: False 37 | - type: dropdown 38 | id: urgency 39 | attributes: 40 | label: Urgency 41 | description: How urgently do you believe this documentation update is needed? 42 | options: 43 | - High 44 | - Medium 45 | - Low 46 | default: 0 47 | validations: 48 | required: true 49 | - type: checkboxes 50 | id: terms 51 | attributes: 52 | label: Record 53 | options: 54 | - label: "I have read the Contributing Guidelines" 55 | required: true 56 | - label: "I'm a GSSOC'24 contributor" 57 | required: false 58 | - label: "I have starred the repository" 59 | required: true 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature Request 2 | description: Suggest a feature 3 | title: "[Feature Request]: " 4 | body: 5 | - type: checkboxes 6 | id: existing-issue 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for this feature. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | id: feature-description 15 | attributes: 16 | label: Feature Description 17 | description: Please provide a detailed description of the feature you are requesting. 18 | placeholder: Describe the new feature or enhancement you'd like to see. 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: use-case 23 | attributes: 24 | label: Use Case 25 | description: How would this feature enhance your use of the project? 26 | placeholder: Describe a specific use case or scenario where this feature would be beneficial. 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: benefits 31 | attributes: 32 | label: Benefits 33 | description: What benefits would this feature bring to the project or community? 34 | placeholder: Explain the advantages of implementing this feature. 35 | - type: textarea 36 | id: screenShots 37 | attributes: 38 | label: Add ScreenShots 39 | description: If any... 40 | - type: dropdown 41 | id: priority 42 | attributes: 43 | label: Priority 44 | description: How important is this feature to you? 45 | options: 46 | - High 47 | - Medium 48 | - Low 49 | default: 0 50 | validations: 51 | required: true 52 | - type: checkboxes 53 | id: terms 54 | attributes: 55 | label: Record 56 | options: 57 | - label: "I have read the Contributing Guidelines" 58 | required: true 59 | - label: "I'm a GSSOC'24 contributor" 60 | required: false 61 | - label: "I have starred the repository" 62 | required: true 63 | -------------------------------------------------------------------------------- /.github/assets/gssoc24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashutosh-rath02/git-re/49942165e783195382f8fb3f3463b3e8a89da04f/.github/assets/gssoc24.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | # Specify labels for pull requests 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Related Issue 2 | [Cite any related issue(s) this pull request addresses. If none, simply state “None”] 3 | 4 | ## Description 5 | [Please include a brief description of the changes or features added] 6 | 7 | ## Type of PR 8 | 9 | - [ ] Bug fix 10 | - [ ] Feature enhancement 11 | - [ ] Documentation update 12 | - [ ] Other (specify): _______________ 13 | 14 | ## Screenshots / videos (if applicable) 15 | [Attach any relevant screenshots or videos demonstrating the changes] 16 | 17 | ## Checklist: 18 | - [ ] I have performed a self-review of my code 19 | - [ ] I have read and followed the Contribution Guidelines. 20 | - [ ] I have tested the changes thoroughly before submitting this pull request. 21 | - [ ] I have provided relevant issue numbers, screenshots, and videos after making the changes. 22 | - [ ] I have commented my code, particularly in hard-to-understand areas. 23 | 24 | 25 | ## Additional context: 26 | [Include any additional information or context that might be helpful for reviewers.] 27 | -------------------------------------------------------------------------------- /.github/workflows/PR-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Related Issue\* 4 | 5 | Closes #issue_number 6 | 7 | 8 | 9 | ## Description 10 | 11 | 13 | 14 | ## Screenshot Section 15 | 16 | 17 | 18 | ## Demo Video Section\* 19 | 20 | 21 | 22 | ## Checklist 23 | 24 | 25 | 26 | 27 | - [ ] I have attached screenshot in screenshot section 28 | - [ ] I have added video 29 | 30 | ## Task 31 | 32 | - [ ] Follow Us On social media - LinkedIn Twitter 33 | Let's connect 34 | - [ ] GitHub 35 | 36 | ## Enter you User Id's: 37 | 38 | LinkedIn: 39 |
GitHub: 40 |
Discord: 41 | 42 | If you Follow us in both social medias we provide 'level2' 43 | \*Only First time 44 | -------------------------------------------------------------------------------- /.github/workflows/auto-comment-on-pr-merge.yml: -------------------------------------------------------------------------------- 1 | name: Auto Comment on PR Merge 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | jobs: 8 | comment: 9 | if: github.event.pull_request.merged == true 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Add Comment to Merged PR 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | run: | 17 | curl -H "Authorization: token $GITHUB_TOKEN" \ 18 | -X POST \ 19 | -d '{"body":"🎉 Your pull request has been successfully merged! 🎉 Thank you for your contribution to our project. Your efforts are greatly appreciated. Keep up the fantastic work! 🚀"}' \ 20 | "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" 21 | -------------------------------------------------------------------------------- /.github/workflows/autocomment-iss-close.yml: -------------------------------------------------------------------------------- 1 | name: Comment on Issue Close 2 | 3 | on: 4 | issues: 5 | types: [closed] 6 | 7 | jobs: 8 | greet-on-close: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | steps: 13 | - name: Greet User 14 | uses: actions/github-script@v5 15 | with: 16 | github-token: ${{ secrets.MY_SECRET_TOKEN }} 17 | script: | 18 | const issue = context.payload.issue; 19 | const issueCreator = issue.user.login; 20 | const issueNumber = issue.number; 21 | 22 | const greetingMessage = `Hello @${issueCreator}! Your issue #${issueNumber} has been closed. Thank you for your contribution!`; 23 | 24 | github.rest.issues.createComment({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | issue_number: issueNumber, 28 | body: greetingMessage 29 | }); 30 | -------------------------------------------------------------------------------- /.github/workflows/autocomment-issue.yml: -------------------------------------------------------------------------------- 1 | name: Auto Comment on Issue 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | permissions: 8 | issues: write 9 | 10 | jobs: 11 | comment: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | issues: write 15 | steps: 16 | - name: Add Comment to Issue 17 | run: | 18 | COMMENT=$(cat </.git 42 | # Add upstream remote 43 | $ git remote add upstream https://github.com/ashutosh-rath02/git-re.git 44 | # Fetch and merge with upstream/master 45 | $ git fetch upstream 46 | $ git merge upstream/master 47 | 48 | ## Step 2: Create and Publish Working Branch 49 | $ git checkout -b //{} 50 | $ git push origin //{} 51 | 52 | ## Types: 53 | # wip - Work in Progress; long term work; mainstream changes; 54 | # feat - New Feature; future planned; non-mainstream changes; 55 | # bug - Bug Fixes 56 | # exp - Experimental; random experiemntal features; 57 | ``` 58 | 59 | - On Task Completion: 60 | 61 | ```bash 62 | ## Committing and pushing your work 63 | # Ensure branch 64 | $ git branch 65 | # Fetch and merge with upstream/master 66 | $ git fetch upstream 67 | $ git merge upstream/master 68 | # Add untracked files 69 | $ git add . 70 | # Commit all changes with appropriate commit message and description 71 | $ git commit -m "your-commit-message" -m "your-commit-description" 72 | # Fetch and merge with upstream/master again 73 | $ git fetch upstream 74 | $ git merge upstream/master 75 | # Push changes to your forked repository 76 | $ git push origin //{} 77 | 78 | ## Creating the PR using GitHub Website 79 | # Create Pull Request from //{} branch in your forked repository to the master branch in the upstream repository 80 | # After creating PR, add a Reviewer (Any Admin) and yourself as the assignee 81 | # Link Pull Request to appropriate Issue, or Project+Milestone (if no issue created) 82 | # IMPORTANT: Do Not Merge the PR unless specifically asked to by an admin. 83 | ``` 84 | 85 | - After PR Merge 86 | 87 | ```bash 88 | # Delete branch from forked repo 89 | $ git branch -d //{} 90 | $ git push --delete origin //{} 91 | # Fetch and merge with upstream/master 92 | $ git checkout master 93 | $ git pull upstream 94 | $ git push origin 95 | ``` 96 | 97 | - Always follow [commit message standards](https://chris.beams.io/posts/git-commit/) 98 | - About the [fork-and-branch workflow](https://blog.scottlowe.org/2015/01/27/using-fork-branch-git-workflow/) 99 | -------------------------------------------------------------------------------- /FAQs/rating.md: -------------------------------------------------------------------------------- 1 | ### Rating Categories and Weights 2 | 3 | 1. **Stars (Weight: 25%)** 4 | 2. **Number of Repositories (Weight: 15%)** 5 | 3. **Followers (Weight: 20%)** 6 | 4. **Organizations (Weight: 10%)** 7 | 5. **Contributions to High-Standard Repositories (Weight: 30%)** 8 | 9 | ## Point System 10 | 11 | ### Stars 12 | 13 | - **0-10 stars:** 1 point 14 | - **11-50 stars:** 2 points 15 | - **51-200 stars:** 3 points 16 | - **201-500 stars:** 4 points 17 | - **501+ stars:** 5 points 18 | 19 | ### Number of Repositories 20 | 21 | - **0-5 repos:** 1 point 22 | - **6-20 repos:** 2 points 23 | - **21-50 repos:** 3 points 24 | - **51-100 repos:** 4 points 25 | - **101+ repos:** 5 points 26 | 27 | ### Followers 28 | 29 | - **0-10 followers:** 1 point 30 | - **11-50 followers:** 2 points 31 | - **51-100 followers:** 3 points 32 | - **101-500 followers:** 4 points 33 | - **501+ followers:** 5 points 34 | 35 | ### Organizations 36 | 37 | - **Not part of any org:** 1 point 38 | - **Member of 1 org:** 2 points 39 | - **Member of 2-3 orgs:** 3 points 40 | - **Member of 4-5 orgs:** 4 points 41 | - **Member of 6+ orgs:** 5 points 42 | 43 | ### Contributions to High-Standard Repositories 44 | 45 | - **No contributions:** 1 point 46 | - **1-5 contributions:** 2 points 47 | - **6-20 contributions:** 3 points 48 | - **21-50 contributions:** 4 points 49 | - **51+ contributions:** 5 points 50 | 51 | ### Rating Calculation Formula 52 | 53 | To calculate the overall rating, we use the weighted sum of the scores from each category and then normalize it to a 5-point scale. 54 | 55 | $$ 56 | \text{Rating} = \left( \frac{\sum (\text{category points} \times \text{category weight})}{\text{max possible score}} \right) \times 5 57 | $$ 58 | 59 | ### Example Calculation 60 | 61 | Assume a user has: 62 | 63 | - 300 stars 64 | - 30 repositories 65 | - 100 followers 66 | - Member of 3 organizations 67 | - 15 contributions to high-standard repositories 68 | 69 | **Stars:** 300 stars → 4 points 70 | 71 | **Repositories:** 30 repos → 3 points 72 | 73 | **Followers:** 100 followers → 3 points 74 | 75 | **Organizations:** 3 orgs → 3 points 76 | 77 | **Contributions:** 15 contributions → 3 points 78 | 79 | Weighted scores: 80 | 81 | - Stars: 4 points \* 0.25 = 1.00 82 | - Repos: 3 points \* 0.15 = 0.45 83 | - Followers: 3 points \* 0.20 = 0.60 84 | - Organizations: 3 points \* 0.10 = 0.30 85 | - Contributions: 3 points \* 0.30 = 0.90 86 | 87 | Total weighted score = 1.00 + 0.45 + 0.60 + 0.30 + 0.90 = 3.25 88 | 89 | Normalized rating (out of 5) = (3.25 / 5) \* 5 = 3.25 90 | 91 | Thus, the user's rating is **3.25 out of 5**. 92 | -------------------------------------------------------------------------------- /app/400.tsx: -------------------------------------------------------------------------------- 1 | // pages/400.tsx 2 | 3 | import Link from "next/link"; 4 | 5 | const Custom400 = () => { 6 | return ( 7 |
8 |

400 - Bad Request

9 |

10 | The data could not be fetched, please reload the page or check the URL. 11 |

12 | 13 | Go back to Home 14 | 15 |
16 | ); 17 | }; 18 | 19 | export default Custom400; 20 | -------------------------------------------------------------------------------- /app/_error.tsx: -------------------------------------------------------------------------------- 1 | // app/error.tsx 2 | import React from "react"; 3 | 4 | interface ErrorProps { 5 | statusCode: number; 6 | message?: string; 7 | } 8 | 9 | const ErrorPage: React.FC = ({ statusCode, message }) => { 10 | let errorMessage = message || "An error occurred"; 11 | 12 | if (statusCode === 404) { 13 | errorMessage = "Page not found"; 14 | } else if (statusCode === 403) { 15 | errorMessage = "Access denied"; 16 | } else if (statusCode === 429) { 17 | errorMessage = "API rate limit exceeded"; 18 | } 19 | 20 | return ( 21 |
22 |

{errorMessage}

23 |
24 | ); 25 | }; 26 | 27 | export default ErrorPage; 28 | -------------------------------------------------------------------------------- /app/api/og/route.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from "next/og"; 2 | 3 | export const runtime = "edge"; 4 | 5 | export async function GET(request: Request) { 6 | const fontData = await fetch( 7 | new URL("/public/assets/Roboto-Bold.ttf", import.meta.url) 8 | ).then((res) => res.arrayBuffer()); 9 | 10 | const italicData = await fetch( 11 | new URL("/public/assets/Roboto-Italic.ttf", import.meta.url) 12 | ).then((res) => res.arrayBuffer()); 13 | try { 14 | const { searchParams } = new URL(request.url); 15 | 16 | const hasTitle = searchParams.has("username"); 17 | const title = hasTitle 18 | ? searchParams.get("username")?.slice(0, 100) 19 | : "My website"; 20 | 21 | return new ImageResponse( 22 | ( 23 |
35 |

43 | git-re 44 |

45 |

53 | Level up your resume game with 54 | 61 | git-re 62 | {" "} 63 | , 64 |

65 |

66 | Just like{" "} 67 | 74 | {title} 75 | 76 | did. Get started now! 77 |

78 |
79 | ), 80 | { 81 | fonts: [ 82 | { 83 | name: "Typewriter", 84 | data: fontData, 85 | style: "normal", 86 | }, 87 | { 88 | name: "italic", 89 | data: italicData, 90 | style: "normal", 91 | }, 92 | ], 93 | } 94 | ); 95 | } catch (e: any) { 96 | return new Response("Failed to generate OG image", { status: 500 }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/api/users/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import axios from "axios"; 3 | import { supabaseBrowser } from "@/utils/supabase/client"; 4 | import redis from "@/lib/redis"; 5 | import { CACHE_TTL } from "@/lib/consts"; 6 | 7 | const supabase = supabaseBrowser(); 8 | 9 | const getFromCache = async (key: string) => { 10 | const cachedData = await redis.get(key) as any; 11 | if (cachedData) { 12 | console.log("LOADED FROM CACHE"); 13 | return JSON.parse(cachedData); 14 | } 15 | return null; 16 | }; 17 | 18 | const setInCache = async (key: string, data: any, expirationSeconds: number) => { 19 | await redis.set(key, JSON.stringify(data), { 20 | ex: expirationSeconds, 21 | }); 22 | }; 23 | 24 | export async function POST(request: NextRequest) { 25 | try { 26 | const { git_username } = await request.json(); 27 | 28 | if (!git_username) { 29 | return new Response("Username is required", { status: 400 }); 30 | } 31 | 32 | const { data: existingUser, error: existingUserError } = await supabase 33 | .from("recent_users") 34 | .select("*") 35 | .eq("username", git_username) 36 | .single(); 37 | 38 | if (existingUserError && existingUserError.code !== "PGRST116") { 39 | throw new Error(existingUserError.message); 40 | } 41 | 42 | if (existingUser) { 43 | return new Response("Username already exists", { status: 200 }); 44 | } 45 | 46 | const response = await axios.get(`https://api.github.com/users/${git_username}`, { 47 | headers: { 48 | Authorization: `Bearer ${process.env.NEXT_PUBLIC_GITHUB_TOKEN}`, 49 | Accept: "application/vnd.github+json", 50 | }, 51 | }); 52 | 53 | const userData = { 54 | name: response.data.name || "", 55 | avatar_url: response.data.avatar_url || "", 56 | bio: response.data.bio || "", 57 | username: response.data.login || "", 58 | }; 59 | 60 | if (!userData.username) { 61 | console.log("Data is NULL. User is rate-limited by GitHub"); 62 | return new Response("Data is NULL. You are rate-limited, please try again later.", { status: 500 }); 63 | } 64 | 65 | const { data, error } = await supabase 66 | .from("recent_users") 67 | .insert([ 68 | { 69 | username: userData.username, 70 | name: userData.name, 71 | bio: userData.bio, 72 | avatar_url: userData.avatar_url, 73 | }, 74 | ]); 75 | 76 | if (error) { 77 | throw new Error(error.message); 78 | } 79 | 80 | return new Response("Username added successfully", { status: 200 }); 81 | } catch (error) { 82 | console.error("Error:", error); 83 | 84 | if (error instanceof Error) { 85 | return new Response(error.message || "Internal Server Error", { status: 500 }); 86 | } 87 | 88 | return new Response("Unexpected Error", { status: 500 }); 89 | } 90 | } 91 | 92 | export async function GET() { 93 | const cacheKey = "recent-users"; 94 | try { 95 | const cachedUsers = await getFromCache(cacheKey); 96 | 97 | if (cachedUsers) { 98 | return new Response(JSON.stringify(cachedUsers), { 99 | status: 200, 100 | headers: { "Content-Type": "application/json" }, 101 | }); 102 | } 103 | 104 | const { data: users, error } = await supabase 105 | .from("recent_users") 106 | .select("*") 107 | .order("id", { ascending: false }) 108 | .limit(7); 109 | 110 | if (error) { 111 | return new Response( 112 | JSON.stringify({ 113 | err_message: error.message, 114 | details: error.details, 115 | error_code: error.code, 116 | }), 117 | { status: 500, headers: { "Content-Type": "application/json" } } 118 | ); 119 | } 120 | 121 | await setInCache(cacheKey, users, CACHE_TTL); 122 | 123 | return new Response(JSON.stringify(users), { 124 | status: 200, 125 | headers: { "Content-Type": "application/json" }, 126 | }); 127 | } catch (error) { 128 | console.error("Error fetching users:", error); 129 | 130 | if (error instanceof Error) { 131 | return new Response(error.message || "Internal Server Error", { status: 500 }); 132 | } 133 | 134 | return new Response("Unexpected Error", { status: 500 }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /app/auth/callback/route.ts: -------------------------------------------------------------------------------- 1 | import { cookies } from "next/headers"; 2 | import { NextResponse } from "next/server"; 3 | import { type CookieOptions, createServerClient } from "@supabase/ssr"; 4 | 5 | export async function GET(request: Request) { 6 | const { searchParams, origin } = new URL(request.url); 7 | const code = searchParams.get("code"); 8 | 9 | // If "next" parameter is present, use it as the redirect URL; otherwise, default to "/" 10 | const next = searchParams.get("next") ?? "/"; 11 | 12 | if (code) { 13 | const cookieStore = cookies(); 14 | 15 | // Create a Supabase client with the necessary credentials and cookie management 16 | const supabase = createServerClient( 17 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 18 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, 19 | { 20 | cookies: { 21 | get(name: string) { 22 | return cookieStore.get(name)?.value; 23 | }, 24 | // Set a cookie with options 25 | set(name: string, value: string, options: CookieOptions) { 26 | cookieStore.set({ name, value, ...options }); 27 | }, 28 | remove(name: string, options: CookieOptions) { 29 | cookieStore.delete({ name, ...options }); 30 | }, 31 | }, 32 | } 33 | ); 34 | 35 | // Authorization code exchange for a session 36 | const { error } = await supabase.auth.exchangeCodeForSession(code); 37 | 38 | if (!error) { 39 | return NextResponse.redirect(`${origin}${next}`); 40 | } 41 | } 42 | 43 | return NextResponse.redirect(`${origin}/?error=User cancelled login.`); 44 | } 45 | -------------------------------------------------------------------------------- /app/compare/action.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { supabaseServer } from "@/utils/supabase/server"; 4 | 5 | const supabase = supabaseServer(); 6 | 7 | export async function fetchSuggestions() { 8 | try { 9 | const { data, error } = await supabase.from("recent_users") 10 | .select("*", { count: "exact" }) 11 | .neq("rating", "-0.1") 12 | .order("rating", { ascending: false }) 13 | 14 | if (error) throw error; 15 | 16 | return data; 17 | } catch (error) { 18 | throw error; 19 | } 20 | } 21 | 22 | 23 | export async function createUser(username: string) { 24 | try { 25 | const { data, error } = await supabase.from("recent_users") 26 | .insert([{ username }]) 27 | 28 | if (error) throw error; 29 | 30 | return data; 31 | } catch (error) { 32 | throw error; 33 | } 34 | } -------------------------------------------------------------------------------- /app/cover-letter/page.tsx: -------------------------------------------------------------------------------- 1 | import CoverLetter from "@/components/cover-letter"; 2 | import { supabaseServer } from "@/utils/supabase/server"; 3 | import React from "react"; 4 | 5 | export default async function Page() { 6 | const supabase = supabaseServer(); 7 | const { data } = await supabase.auth.getUser(); 8 | 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashutosh-rath02/git-re/49942165e783195382f8fb3f3463b3e8a89da04f/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | body { 5 | scrollbar-width: thin; 6 | scrollbar-color: #051129 #2e2e2ed7; 7 | } 8 | ::-webkit-scrollbar { 9 | width: 12px; 10 | } 11 | ::-webkit-scrollbar-track { 12 | background: #2e2e2ed7; 13 | } 14 | ::-webkit-scrollbar-thumb { 15 | background: #051129; 16 | border-radius: 20px; 17 | border: 3px solid #2e2e2ed7; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb:hover { 21 | background: #0d214a; 22 | } 23 | 24 | @layer base { 25 | :root { 26 | --background: 0 0% 100%; 27 | --foreground: 222.2 84% 4.9%; 28 | --card: 0 0% 100%; 29 | --card-foreground: 222.2 84% 4.9%; 30 | --popover: 0 0% 100%; 31 | --popover-foreground: 222.2 84% 4.9%; 32 | --primary: 221.2 83.2% 53.3%; 33 | --primary-foreground: 210 40% 98%; 34 | --secondary: 210 40% 96.1%; 35 | --secondary-foreground: 222.2 47.4% 11.2%; 36 | --muted: 210 40% 96.1%; 37 | --muted-foreground: 215.4 16.3% 46.9%; 38 | --accent: 210 40% 96.1%; 39 | --accent-foreground: 222.2 47.4% 11.2%; 40 | --destructive: 0 84.2% 60.2%; 41 | --destructive-foreground: 210 40% 98%; 42 | --border: 214.3 31.8% 91.4%; 43 | --input: 214.3 31.8% 91.4%; 44 | --ring: 221.2 83.2% 53.3%; 45 | --radius: 0.75rem; 46 | } 47 | 48 | .dark { 49 | --background: 222.2 84% 4.9%; 50 | --foreground: 210 40% 98%; 51 | --card: 222.2 84% 4.9%; 52 | --card-foreground: 210 40% 98%; 53 | --popover: 222.2 84% 4.9%; 54 | --popover-foreground: 210 40% 98%; 55 | --primary: 217.2 91.2% 59.8%; 56 | --primary-foreground: 222.2 47.4% 11.2%; 57 | --secondary: 217.2 32.6% 17.5%; 58 | --secondary-foreground: 210 40% 98%; 59 | --muted: 217.2 32.6% 17.5%; 60 | --muted-foreground: 215 20.2% 65.1%; 61 | --accent: 217.2 32.6% 17.5%; 62 | --accent-foreground: 210 40% 98%; 63 | --destructive: 0 62.8% 30.6%; 64 | --destructive-foreground: 210 40% 98%; 65 | --border: 217.2 32.6% 17.5%; 66 | --input: 217.2 32.6% 17.5%; 67 | --ring: 224.3 76.3% 48%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } 79 | 80 | .loader-container { 81 | position: relative; 82 | width: 120px; 83 | height: 80px; 84 | display: flex; 85 | justify-content: space-between; 86 | align-items: center; 87 | } 88 | 89 | .ball { 90 | width: 20px; 91 | height: 20px; 92 | border-radius: 50%; 93 | animation: bounce 1s ease-in-out infinite alternate, 94 | changeColor 2s linear infinite; 95 | } 96 | 97 | .ball1 { 98 | animation-duration: 1.5s; 99 | } 100 | .ball2 { 101 | } 102 | .ball3 { 103 | animation-duration: 0.8s; 104 | } 105 | 106 | .scroll-bar::-webkit-scrollbar { 107 | width: 10px; 108 | scrollbar-width: none; 109 | } 110 | 111 | .scroll-bar::-webkit-scrollbar-track { 112 | -webkit-box-shadow: inset 0 0 6px #3576df; 113 | border-radius: 10px; 114 | 115 | border-left: 3px solid transparent; 116 | border-right: 3px solid transparent; 117 | } 118 | 119 | .scroll-bar::-webkit-scrollbar-thumb { 120 | border-radius: 20px; 121 | background-color: #3576df; 122 | } 123 | 124 | .scroll-bar::-webkit-scrollbar-thumb:hover { 125 | background-color: #2558a6; 126 | } 127 | 128 | .scroll-bar:hover::-webkit-scrollbar { 129 | scrollbar-width: auto; 130 | } 131 | 132 | @keyframes bounce { 133 | 0%, 134 | 100% { 135 | transform: translateY(0); 136 | } 137 | 50% { 138 | transform: translateY(-30px); 139 | } 140 | } 141 | 142 | @keyframes changeColor { 143 | 0%, 144 | 100% { 145 | background-color: #3498db; 146 | } 147 | 50% { 148 | background-color: #e74c3c; 149 | } 150 | } 151 | 152 | @keyframes spin { 153 | 0% { 154 | transform: rotate(0deg); 155 | } 156 | 100% { 157 | transform: rotate(360deg); 158 | } 159 | } 160 | 161 | .loader1 { 162 | border: 4px solid rgba(255, 255, 255, 0.3); 163 | border-top: 4px solid #3498db; 164 | border-radius: 50%; 165 | width: 24px; 166 | height: 24px; 167 | animation: spin 2s linear infinite; 168 | } 169 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | import { ThemeProvider } from "@/components/theme-provider"; 5 | import Navbar from "@/components/navbar"; 6 | import Footer from "@/components/footer"; 7 | import { Analytics } from "@vercel/analytics/react"; 8 | import { Toaster } from "@/components/ui/toaster"; 9 | import { SpeedInsights } from "@vercel/speed-insights/next"; 10 | import NextTopLoader from "nextjs-toploader"; 11 | const inter = Inter({ subsets: ["latin"] }); 12 | 13 | export const metadata: Metadata = { 14 | title: "git-re", 15 | description: "Generate your GitHub profile into a resume effortlessly.", 16 | generator: "Next.js", 17 | applicationName: "git-re", 18 | referrer: "origin-when-cross-origin", 19 | keywords: [ 20 | "Next.js", 21 | "React", 22 | "JavaScript", 23 | "resume", 24 | "github", 25 | "profile", 26 | "generator", 27 | "project", 28 | ], 29 | authors: [ 30 | { name: "Ashutosh Rath", url: "https://github.com/ashutosh-rath02" }, 31 | ], 32 | creator: "Ashutosh Rath", 33 | formatDetection: { 34 | email: false, 35 | address: false, 36 | telephone: false, 37 | }, 38 | metadataBase: new URL(new URL(process.env.NEXT_PUBLIC_URL!)), 39 | openGraph: { 40 | title: "git-re", 41 | description: "Generate your GitHub profile into a resume effortlessly.", 42 | url: "https://git-re.vercel.app/", 43 | siteName: "git-re", 44 | images: [ 45 | { 46 | url: "https://res.cloudinary.com/dhnkuonev/image/upload/v1707138008/Screenshot_2024-02-05_182909_kajlun.png", 47 | width: 800, 48 | height: 600, 49 | }, 50 | { 51 | url: "https://res.cloudinary.com/dhnkuonev/image/upload/v1707138008/Screenshot_2024-02-05_182909_kajlun.png", 52 | width: 1800, 53 | height: 1600, 54 | alt: "git-re", 55 | }, 56 | ], 57 | }, 58 | twitter: { 59 | card: "summary_large_image", 60 | title: "git-re", 61 | description: "Generate your GitHub profile into a resume effortlessly.", 62 | creator: "@v_ashu_dev", 63 | images: [ 64 | "https://res.cloudinary.com/dhnkuonev/image/upload/v1707138008/Screenshot_2024-02-05_182909_kajlun.png", 65 | ], 66 | }, 67 | }; 68 | 69 | export default function RootLayout({ 70 | children, 71 | }: { 72 | children: React.ReactNode; 73 | }) { 74 | return ( 75 | 76 | 77 | 83 | 84 |
85 | 86 |
{children}
87 |
88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /app/leaderboard/action.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { supabaseServer } from "@/utils/supabase/server"; 3 | const supabase = supabaseServer(); 4 | 5 | type LeaderboardProp = { 6 | page?: number; 7 | searchQuery?: string; 8 | }; 9 | 10 | export const getLeaderboard = async ({ 11 | page = 1, 12 | searchQuery = "", 13 | }: LeaderboardProp) => { 14 | try { 15 | const itemsPerPage = 20; 16 | const from = (page - 1) * itemsPerPage; 17 | const to = from + itemsPerPage - 1; 18 | 19 | let query = supabase 20 | .from("recent_users") 21 | .select("*", { count: "exact" }) 22 | .neq("rating", "-0.1") 23 | .order("rating", { ascending: false }) 24 | .range(from, to); 25 | 26 | if (searchQuery) { 27 | query = query.ilike("username", `%${searchQuery}%`); 28 | } 29 | 30 | const { data, error, count } = await query; 31 | const maxPages = count ? Math.ceil(count / itemsPerPage) : 0; 32 | 33 | if (error) throw error; 34 | 35 | // Fetch individual ranks for each user 36 | const usersWithRanks = await Promise.all( 37 | data.map(async (user) => ({ 38 | ...user, 39 | rank: await getIndividualUserRank(user.username), 40 | })) 41 | ); 42 | 43 | return { data: usersWithRanks, maxPages }; 44 | } catch (error) { 45 | throw error; 46 | } 47 | }; 48 | 49 | export const getIndividualUserRank = async (username: string) => { 50 | try { 51 | const { data, error } = await supabase.rpc("get_user_rank", { 52 | user_username: username, 53 | }); 54 | if (error) { 55 | throw error; 56 | } 57 | return data; 58 | } catch (error) { 59 | console.error(error); 60 | throw error; 61 | } 62 | }; -------------------------------------------------------------------------------- /app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | const NotFound = () => { 4 | return ( 5 |
6 |
7 |
8 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |

Oops! Page not found.

28 |

29 | The page you're looking for doesn't exist or has been moved. Let's get you back on track. 30 |

31 | 35 | Go to Home 36 | 37 |
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default NotFound; 44 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | "use client"; 3 | import { useEffect, useState } from "react"; 4 | import Form from "@/components/Form"; 5 | import AuthButton from "@/components/AuthButton"; 6 | import RecentGenerations from "@/components/RecentGenerations"; 7 | import UserTestimonails from "@/components/UserTestimonials"; 8 | import withErrorBoundary from "@/components/hoc/withErrorBoundary"; 9 | import { supabaseBrowser } from "@/utils/supabase/client"; 10 | import "./globals.css"; 11 | import { useSearchParams } from "next/navigation"; 12 | import { toast } from "@/components/ui/use-toast"; 13 | import Lenis from "@studio-freight/lenis"; 14 | import Faq from "../components/Faq"; 15 | 16 | export default function Home() { 17 | const supabase = supabaseBrowser(); 18 | const searchParams = useSearchParams(); 19 | const error = searchParams.get("error"); 20 | const [data, setData] = useState({ user: null }); 21 | if (error) { 22 | toast({ 23 | title: "Error", 24 | description: error, 25 | variant: "destructive", 26 | }); 27 | } 28 | useEffect(() => { 29 | const fetchUser = async () => { 30 | const { data: user } = await supabase.auth.getUser(); 31 | setData({ user: user.user }); 32 | }; 33 | fetchUser(); 34 | }, []); 35 | useEffect(() => { 36 | const lenis = new Lenis(); 37 | function raf(time: any) { 38 | lenis.raf(time); 39 | requestAnimationFrame(raf); 40 | } 41 | requestAnimationFrame(raf); 42 | }, []); 43 | return ( 44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |

52 | 53 | git-re 54 | 55 |

56 |

57 | Elevate your  58 | 59 | GitHub 60 | 61 |  to a dynamic  62 | 63 | resume 64 | 65 |  effortlessly. 66 |

67 |
68 |
75 | {data.user ? ( 76 |
77 | ) : ( 78 | 79 | )} 80 |
81 | 85 | git-re - Elevate your GitHub to a dynamic resume effortlessly | Product Hunt 91 | 92 |
93 |
94 |
95 | {/*
96 |
97 |

101 | Here are some of our recent{" "} 102 | 103 | users 104 | 105 |

106 |
107 | 108 |
*/} 109 |
113 | 114 |
115 | 116 |
117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /app/resume/[username]/not-found.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { NextPageContext } from "next"; 4 | 5 | interface ErrorProps { 6 | statusCode: number; 7 | message?: string; 8 | } 9 | 10 | const ErrorPage = ({ statusCode, message }: ErrorProps) => { 11 | let errorMessage = message || "An error occurred"; 12 | 13 | if (statusCode === 404) { 14 | errorMessage = "Page not found"; 15 | } else if (statusCode === 403) { 16 | errorMessage = "Access denied"; 17 | } else if (statusCode === 429) { 18 | errorMessage = "API rate limit exceeded"; 19 | } 20 | 21 | return ( 22 |
23 |

{errorMessage}

24 |
25 | ); 26 | }; 27 | 28 | ErrorPage.getInitialProps = async ({ 29 | res, 30 | err, 31 | }: NextPageContext): Promise => { 32 | const statusCode = res?.statusCode || err?.statusCode || 500; 33 | return { statusCode }; 34 | }; 35 | 36 | export default ErrorPage; 37 | -------------------------------------------------------------------------------- /app/resume/[username]/opengraph-image.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from "next/og"; 2 | 3 | export const runtime = "edge"; 4 | export const alt = "Git-re OG Image"; 5 | export const size = { 6 | width: 1200, 7 | height: 630, 8 | }; 9 | 10 | export const contentType = "image/png"; 11 | export default async function Image(props: { 12 | params: { 13 | username: string; 14 | }; 15 | }) { 16 | const fontData = await fetch( 17 | new URL("/public/assets/Roboto-Bold.ttf", import.meta.url) 18 | ).then((res) => res.arrayBuffer()); 19 | 20 | const italicData = await fetch( 21 | new URL("/public/assets/Roboto-Italic.ttf", import.meta.url) 22 | ).then((res) => res.arrayBuffer()); 23 | 24 | const { username } = props.params; 25 | 26 | return new ImageResponse( 27 | ( 28 |
40 |

48 | git-re 49 |

50 |

58 | Level up your resume game with 59 | 66 | git-re 67 | {" "} 68 | , 69 |

70 |

71 | Just like{" "} 72 | 79 | {username} 80 | 81 | did. Get started now! 82 |

83 |
84 | ), 85 | { 86 | ...size, 87 | fonts: [ 88 | { 89 | name: "Typewriter", 90 | data: fontData, 91 | style: "normal", 92 | }, 93 | { 94 | name: "italic", 95 | data: italicData, 96 | style: "normal", 97 | }, 98 | ], 99 | } 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /app/resume/[username]/page.tsx: -------------------------------------------------------------------------------- 1 | import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"; 2 | import React from "react"; 3 | import { cookies } from "next/headers"; 4 | import { redirect } from "next/navigation"; 5 | import { Metadata } from "next"; 6 | import Head from "next/head"; 7 | import { NewResume } from "@/components/new-resume"; 8 | 9 | type Props = { 10 | params: { username: string }; 11 | searchParams: { [key: string]: string | string[] | undefined }; 12 | }; 13 | 14 | export async function generateMetadata({ params }: Props): Promise { 15 | return { 16 | title: `${params.username} | git-re `, 17 | description: `Check out ${params.username}'s resume on git-re`, 18 | metadataBase: new URL(process.env.NEXT_PUBLIC_URL! + "/resume/"), 19 | 20 | openGraph: { 21 | url: `${process.env.NEXT_PUBLIC_URL}/resume/${params.username}`, 22 | images: [ 23 | { 24 | url: `${process.env.NEXT_PUBLIC_URL}/api/og?username=${params.username}`, 25 | }, 26 | ], 27 | }, 28 | twitter: { 29 | card: "summary_large_image", 30 | title: "git-re", 31 | description: `Hey, Check out my resume at ${process.env.NEXT_PUBLIC_URL}/resume/${params.username}. Want to create your's? Visit https://git-re.vercel.app/ `, 32 | creator: "@v_ashu_dev", 33 | images: [ 34 | `${process.env.NEXT_PUBLIC_URL}api/og?username=${params.username}`, 35 | ], 36 | }, 37 | }; 38 | } 39 | export default async function Home({ 40 | params, 41 | }: { 42 | params: { username: string }; 43 | }) { 44 | const supabase = createServerComponentClient({ cookies }); 45 | const { 46 | data: { user }, 47 | } = await supabase.auth.getUser(); 48 | const { data: recent_user } = await supabase 49 | .from("recent_users") 50 | .select("*") 51 | .eq("username", params.username) 52 | .single(); 53 | if (!user && !recent_user) { 54 | redirect("/"); 55 | } 56 | return ( 57 |
58 | 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /app/resume/[username]/twitter-image.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from "next/og"; 2 | 3 | export const runtime = "edge"; 4 | export const alt = "Git-re OG Image"; 5 | export const size = { 6 | width: 1200, 7 | height: 630, 8 | }; 9 | 10 | export const contentType = "image/png"; 11 | export default async function Image(props: { 12 | params: { 13 | username: string; 14 | }; 15 | }) { 16 | const fontData = await fetch( 17 | new URL("/public/assets/Roboto-Bold.ttf", import.meta.url) 18 | ).then((res) => res.arrayBuffer()); 19 | 20 | const italicData = await fetch( 21 | new URL("/public/assets/Roboto-Italic.ttf", import.meta.url) 22 | ).then((res) => res.arrayBuffer()); 23 | 24 | const { username } = props.params; 25 | 26 | return new ImageResponse( 27 | ( 28 |
40 |

48 | git-re 49 |

50 |

58 | Level up your resume game with 59 | 66 | git-re 67 | {" "} 68 | , 69 |

70 |

71 | Just like{" "} 72 | 79 | {username} 80 | 81 | did. Get started now! 82 |

83 |
84 | ), 85 | { 86 | ...size, 87 | fonts: [ 88 | { 89 | name: "Typewriter", 90 | data: fontData, 91 | style: "normal", 92 | }, 93 | { 94 | name: "italic", 95 | data: italicData, 96 | style: "normal", 97 | }, 98 | ], 99 | } 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/AboutProduct.jsx: -------------------------------------------------------------------------------- 1 | const AboutProduct = ({ username }) => { 2 | const profileLink = `https://github.com/${username}`; 3 | 4 | return ( 5 |
6 |

7 | Discover more about this developer's journey on 8 | 14 | GitHub 15 | 16 | . 17 |

18 |

19 | This résumé is auto-generated, showcasing the synergy between data and 20 | design. 21 |

22 |

23 | Crafted with 💙 by{" "} 24 | 30 | Vasudev 31 | 32 | 🪶 33 |

34 |
35 | ); 36 | }; 37 | export default AboutProduct; 38 | -------------------------------------------------------------------------------- /components/AuthButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { Button } from "./ui/button"; 4 | import { supabaseBrowser } from "@/utils/supabase/client"; 5 | import { User } from "@supabase/supabase-js"; 6 | import { usePathname, useRouter } from "next/navigation"; 7 | import { IconCubeUnfolded, IconLogout } from "@tabler/icons-react"; 8 | import { cn } from "@/lib/utils"; 9 | import Link from "next/link"; 10 | 11 | type AuthButtonProps = { 12 | // Null if not authenticated 13 | user: User | null; 14 | height?: number; 15 | width?: number; 16 | className?: string; 17 | }; 18 | 19 | export default function AuthButton({ 20 | user, 21 | height, 22 | width, 23 | className, 24 | }: AuthButtonProps) { 25 | const router = useRouter(); 26 | const pathname = usePathname(); 27 | const [isLoading, setLoading] = useState(false); 28 | 29 | const handleLoginWithGithub = () => { 30 | const supabase = supabaseBrowser(); 31 | setLoading(true); 32 | supabase.auth.signInWithOAuth({ 33 | provider: "github", 34 | options: { 35 | redirectTo: location.origin + "/auth/callback", 36 | }, 37 | }); 38 | }; 39 | 40 | const handleLogout = async () => { 41 | setLoading(true); 42 | const supabase = supabaseBrowser(); 43 | await supabase.auth.signOut(); 44 | // Refresh the router to update the UI 45 | router.refresh(); 46 | setLoading(false); 47 | }; 48 | 49 | return user ? ( 50 | <> 51 | {pathname.includes("resume") && ( 52 | 57 | Cover-Letter 58 | 59 | )} 60 | 69 | 70 | ) : ( 71 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /components/ContributionGraph.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import Image from "next/image"; 3 | import React from "react"; 4 | 5 | interface ContributionGraphProps { 6 | username: string; 7 | } 8 | 9 | const ContributionGraph: React.FC = ({ username }) => { 10 | const graphUrl = `https://ghchart.rshah.org/${username}`; 11 | 12 | return ( 13 |
14 |

15 | Contribution Graph 16 |

17 | Contribution Graph 22 |
23 | ); 24 | }; 25 | 26 | export default ContributionGraph; 27 | -------------------------------------------------------------------------------- /components/Contributions.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { fetchContributions } from "@/utils/resumeUtils"; 3 | 4 | interface ContributionsProps { 5 | username: string; 6 | contributionCount: number; 7 | } 8 | 9 | const Contributions: React.FC = ({ 10 | username, 11 | contributionCount, 12 | }) => { 13 | const [contributions, setContributions] = useState([]); 14 | 15 | useEffect(() => { 16 | const fetchAndSetContributions = async () => { 17 | try { 18 | const contributionsData = await fetchContributions(username); 19 | setContributions( 20 | contributionsData.slice(0, contributionCount).map((contribution) => ({ 21 | organizationName: contribution.organizationName, 22 | repository: contribution.repository, 23 | url: contribution.url, 24 | repoUrl: contribution.repoUrl, 25 | commitCount: contribution.commitCount, 26 | })) 27 | ); 28 | } catch (error) { 29 | console.error("Failed to fetch contributions:", error); 30 | } 31 | }; 32 | 33 | fetchAndSetContributions(); 34 | }, [username, contributionCount]); 35 | 36 | return ( 37 |
38 |

39 | Contributions 40 |

41 | 67 |
68 | ); 69 | }; 70 | 71 | export default Contributions; 72 | -------------------------------------------------------------------------------- /components/CopyLinkBtn.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useState } from "react"; 2 | import { cn } from "@/lib/utils"; 3 | import { toast } from "@/components/ui/use-toast"; 4 | import { CheckCircledIcon, CheckIcon } from "@radix-ui/react-icons"; 5 | 6 | interface CopyLinkProps { 7 | url: string; 8 | children: ReactNode; 9 | className: string; 10 | } 11 | 12 | const CopyLinkBtn: React.FC = ({ url, children, className }) => { 13 | const [isCopied, setCopied] = useState(false); 14 | 15 | const toClipboard = () => { 16 | navigator.clipboard 17 | .writeText(url) 18 | .then(() => { 19 | toast({ 20 | title: "Link Copied Successfully!", 21 | description: "", 22 | variant: "default", 23 | }); 24 | setCopied(true); 25 | setTimeout(() => setCopied(false), 2000); 26 | }) 27 | .catch(() => { 28 | toast({ 29 | title: "Error", 30 | description: "Error Copying Link", 31 | variant: "destructive", 32 | }); 33 | }); 34 | }; 35 | return ( 36 | 39 | ); 40 | }; 41 | 42 | export default CopyLinkBtn; 43 | -------------------------------------------------------------------------------- /components/CoverLetterDialog.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dialog, 3 | DialogContent, 4 | DialogDescription, 5 | DialogHeader, 6 | DialogTitle, 7 | DialogTrigger, 8 | } from "@/components/ui/dialog"; 9 | import { SetStateAction, useState } from "react"; 10 | import { Textarea } from "./ui/textarea"; 11 | import { Button } from "./ui/button"; 12 | import { useToast } from "./ui/use-toast"; 13 | 14 | type Props = { 15 | setIsResponseGenerated: React.Dispatch>; 16 | setResponse: React.Dispatch>; 17 | isResponseGenerated: boolean; 18 | response: string; 19 | isError: boolean; 20 | }; 21 | 22 | export default function CoverLetterDialog({ 23 | isResponseGenerated, 24 | setIsResponseGenerated, 25 | response, 26 | setResponse, 27 | isError, 28 | }: Props) { 29 | const { toast } = useToast(); 30 | const copyHandler = () => { 31 | navigator.clipboard.writeText(response); 32 | toast({ 33 | title: "Cover Letter", 34 | description: "Text Copied!!", 35 | }); 36 | }; 37 | 38 | return ( 39 | 40 | 41 | 42 | {!isError && ( 43 | <> 44 | 45 | Cover Letter 46 | 47 | 48 | 53 | 54 | 55 | 56 | )} 57 | {isError && ( 58 | <> 59 | 60 | Error 61 | 62 | {response} 63 | 64 | )} 65 | 66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /components/CustomisationDrawer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Drawer, 4 | DrawerClose, 5 | DrawerContent, 6 | DrawerDescription, 7 | DrawerFooter, 8 | DrawerHeader, 9 | DrawerTitle, 10 | DrawerTrigger, 11 | } from "@/components/ui/drawer"; 12 | import { Button } from "@/components/ui/button"; 13 | import { MinusIcon, PlusIcon } from "@radix-ui/react-icons"; 14 | import { IconCubeUnfolded } from "@tabler/icons-react"; 15 | 16 | type CustomisationDrawerProps = { 17 | contributions: number; 18 | setContributions: (count: number) => void; 19 | organisations: number; 20 | setOrganisations: (count: number) => void; 21 | isContributionsExists: boolean; 22 | isOrganisationsExists: boolean; 23 | }; 24 | 25 | const CustomisationDrawer: React.FC = ({ 26 | contributions, 27 | setContributions, 28 | organisations, 29 | setOrganisations, 30 | isContributionsExists, 31 | isOrganisationsExists, 32 | }) => { 33 | return ( 34 |
35 | 36 | 37 | 41 | 42 | 43 |
44 | 45 | Resume Customization 46 | 47 | Customize how your resume is generated 48 | 49 | 50 |
51 |
52 | 64 |
65 |
66 | {contributions} 67 |
68 |
69 | Contributions 70 |
71 |
72 | 82 |
83 |
84 | 96 |
97 |
98 | {organisations} 99 |
100 |
101 | ORGANISATIONS 102 |
103 |
104 | 114 |
115 |
116 | 117 | 118 | 119 | 120 | 121 |
122 |
123 |
124 |
125 | ); 126 | }; 127 | 128 | export default CustomisationDrawer; 129 | -------------------------------------------------------------------------------- /components/Faq.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { use, useState } from "react"; 3 | import { 4 | Accordion, 5 | AccordionContent, 6 | AccordionItem, 7 | AccordionTrigger, 8 | } from "@/components/ui/accordion"; 9 | import faqs from "../config/content/faqs"; 10 | const Faq = () => { 11 | return ( 12 | <> 13 |

FAQs

14 |
15 | 16 | {faqs.map((faq, index) => ( 17 | 18 | {faq.question} 19 | {faq.answer} 20 | 21 | ))} 22 | 23 |
24 | 25 | ); 26 | }; 27 | 28 | export default Faq; 29 | -------------------------------------------------------------------------------- /components/Form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { FormEvent, useState } from "react"; 3 | import { Button } from "@/components/ui/button"; 4 | import { Input } from "@/components/ui/input"; 5 | import { useRouter } from "next/navigation"; 6 | import { toast, useToast } from "@/components/ui/use-toast"; 7 | import axios from "axios"; 8 | 9 | export default function Form() { 10 | const [username, setUsername] = useState(""); 11 | const [usernameFound, setUsernameFound] = useState(true); 12 | const [isLoading, setIsLoading] = useState(false); 13 | const router = useRouter(); 14 | 15 | const handleUsernameChange = (e: React.ChangeEvent) => { 16 | setUsername(e.target.value); 17 | if (!usernameFound) { 18 | setUsernameFound(true); // Reset the state when user starts typing 19 | } 20 | }; 21 | 22 | const handleSubmit = async (e: FormEvent) => { 23 | e.preventDefault(); 24 | 25 | //Regex to match github username validation 26 | const regex = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i; 27 | if (!regex.test(username)) { 28 | toast({ 29 | title: "Error", 30 | description: "Not a valid GitHub username.", 31 | variant: "destructive", 32 | }); 33 | return; 34 | } 35 | 36 | setIsLoading(true); 37 | 38 | try { 39 | const githubResponse = await axios.get( 40 | `https://api.github.com/users/${username}`, 41 | { 42 | headers: { 43 | Authorization: `Bearer ${process.env.NEXT_PUBLIC_GITHUB_TOKEN}`, 44 | Accept: "application/vnd.github+json", 45 | }, 46 | } 47 | ); 48 | if (githubResponse.status === 404) { 49 | setUsernameFound(false); 50 | setIsLoading(false); 51 | return; 52 | } 53 | 54 | if (githubResponse.status !== 200) { 55 | console.log("Unexpected error occurred"); 56 | setIsLoading(false); 57 | return; 58 | } 59 | 60 | const saveResponse = await axios.post("/api/users", { 61 | git_username: username, 62 | }); 63 | router.push(`/resume/${username}`); 64 | } catch (error: any) { 65 | if ( 66 | error.response?.status === 404 || 67 | error.response?.data.message === "Not Found" 68 | ) { 69 | toast({ 70 | title: "Error", 71 | description: "Username Not Found", 72 | variant: "destructive", 73 | }); 74 | setIsLoading(false); 75 | setUsernameFound(false); 76 | return; 77 | } 78 | console.error(`Error occurred: ${error.message}`); 79 | setIsLoading(false); 80 | } 81 | }; 82 | 83 | return ( 84 |
85 | 86 |
87 | 94 |
95 | 106 | 107 |
108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /components/GitHubStarCount.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useState } from 'react'; 4 | 5 | interface GitHubStarCountProps { 6 | repoUrl: string; 7 | } 8 | 9 | const GitHubStarCount: React.FC = ({ repoUrl }) => { 10 | const [stars, setStars] = useState(null); 11 | const [error, setError] = useState(null); 12 | 13 | useEffect(() => { 14 | const fetchStarCount = async () => { 15 | try { 16 | const repoPath = repoUrl.replace('https://github.com', ''); 17 | const response = await fetch(`https://api.github.com/repos${repoPath}`, { 18 | headers: { 19 | 'Authorization': `token ${process.env.NEXT_PUBLIC_GITHUB_TOKEN}` 20 | } 21 | }); 22 | 23 | 24 | 25 | if (!response.ok) { 26 | throw new Error('Network response was not ok'); 27 | } 28 | 29 | const data = await response.json(); 30 | if (data && typeof data.stargazers_count === 'number') { 31 | setStars(data.stargazers_count); 32 | } else { 33 | throw new Error('Invalid data format'); 34 | } 35 | } catch (error) { 36 | console.error('Error fetching star count:', error); 37 | setError('error'); 38 | } 39 | }; 40 | 41 | fetchStarCount(); 42 | }, [repoUrl]); 43 | 44 | if (error) { 45 | return {error}; 46 | } 47 | 48 | return ( 49 | 50 | {stars !== null ? `${stars}` : '0'} 51 | 52 | ); 53 | }; 54 | 55 | export default GitHubStarCount; 56 | -------------------------------------------------------------------------------- /components/Hamburger.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { HamburgerMenuIcon } from "@radix-ui/react-icons"; 4 | import { Cross1Icon } from "@radix-ui/react-icons"; 5 | import Link from "next/link"; 6 | 7 | const Hamburger = () => { 8 | const [menuOpen, setMenuOpen] = useState(false); 9 | return ( 10 |
11 | {!menuOpen ? ( 12 | setMenuOpen((prev) => !prev)} 15 | /> 16 | ) : ( 17 | { 20 | setMenuOpen((prev) => !prev); 21 | }} 22 | /> 23 | )} 24 | {menuOpen && ( 25 |
26 |
27 | 31 | Testimonials 32 | 33 |
34 |
35 | 40 | Leaderboard 41 | 42 |
43 |
44 | 49 | Compare 50 | 51 |
52 |
53 | )} 54 |
55 | ); 56 | }; 57 | 58 | export default Hamburger; 59 | -------------------------------------------------------------------------------- /components/LanguageChart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Language } from "../types"; 3 | 4 | interface LanguageBarChartProps { 5 | languages: Language[] | undefined; 6 | } 7 | 8 | const LanguageBarChart: React.FC = ({ languages }) => { 9 | const hasLanguages = Array.isArray(languages) && languages.length > 0; 10 | 11 | return ( 12 |
13 |

Most Used Languages

14 |
15 | {hasLanguages ? ( 16 | languages.map((language) => ( 17 | 37 | )) 38 | ) : ( 39 |

No language data available.

40 | )} 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default LanguageBarChart; 47 | -------------------------------------------------------------------------------- /components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Loader() { 4 | return ( 5 |
6 | Loading... 7 |
8 |
9 |
10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /components/Organizations.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { fetchOrganizations, Organization } from "@/utils/resumeUtils"; 3 | 4 | interface OrganizationsProps { 5 | username: string; 6 | count: number; 7 | } 8 | 9 | const Organizations: React.FC = ({ username, count }) => { 10 | const [organizations, setOrganizations] = useState([]); 11 | 12 | useEffect(() => { 13 | const fetchAndSetOrganizations = async () => { 14 | try { 15 | const orgsData = await fetchOrganizations(username); 16 | setOrganizations(orgsData.slice(0, count)); 17 | } catch (error) { 18 | console.error("Failed to fetch organizations:", error); 19 | } 20 | }; 21 | 22 | fetchAndSetOrganizations(); 23 | }, [username, count]); 24 | 25 | return ( 26 |
27 |

28 | Organizations 29 |

30 | 57 |
58 | ); 59 | }; 60 | 61 | export default Organizations; 62 | -------------------------------------------------------------------------------- /components/RadarChart.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useRef, useState } from "react"; 4 | import { Radar } from "react-chartjs-2"; 5 | import { 6 | Chart as ChartJS, 7 | RadialLinearScale, 8 | PointElement, 9 | LineElement, 10 | Filler, 11 | Tooltip, 12 | Legend, 13 | ChartOptions, 14 | } from "chart.js"; 15 | import { useTheme } from "next-themes"; 16 | 17 | ChartJS.register( 18 | RadialLinearScale, 19 | PointElement, 20 | LineElement, 21 | Filler, 22 | Tooltip, 23 | Legend 24 | ); 25 | 26 | interface RadarChartData { 27 | login: string; 28 | totalCommits: number; 29 | totalIssuesCreated: number; 30 | totalPRsMerged: number; 31 | forks: number; 32 | } 33 | 34 | interface RadarChartProps { 35 | data1: RadarChartData; 36 | data2: RadarChartData; 37 | } 38 | 39 | const RadarChart: React.FC = ({ data1, data2 }) => { 40 | const { theme, systemTheme } = useTheme(); 41 | // const isDarkMode = theme === "dark"; 42 | const chartRef = useRef(null); 43 | const [chartDimensions, setChartDimensions] = useState<{ 44 | width: number; 45 | height: number; 46 | }>({ 47 | width: 600, 48 | height: 600, 49 | }); 50 | const [isDarkMode, setIsDarkMode] = useState(false); 51 | 52 | useEffect(() => { 53 | const handleResize = () => { 54 | if (chartRef.current) { 55 | const width = chartRef.current.offsetWidth; 56 | const height = chartRef.current.offsetHeight; 57 | setChartDimensions({ width, height }); 58 | } 59 | }; 60 | 61 | handleResize(); 62 | window.addEventListener("resize", handleResize); 63 | return () => window.removeEventListener("resize", handleResize); 64 | }, [chartRef]); 65 | 66 | useEffect(() => { 67 | if (theme === "system") { 68 | setIsDarkMode(systemTheme === "dark"); 69 | } else { 70 | setIsDarkMode(theme === "dark"); 71 | } 72 | }, [theme, systemTheme]); 73 | 74 | const data = { 75 | labels: ["Commits", "Issues", "Pull Requests", "Forks"], 76 | datasets: [ 77 | { 78 | label: data1.login, 79 | data: [ 80 | data1.totalCommits, 81 | data1.totalIssuesCreated, 82 | data1.totalPRsMerged, 83 | data1.forks, 84 | ], 85 | backgroundColor: "rgba(54, 162, 235, 0.2)", 86 | borderColor: "rgba(54, 162, 235, 1)", 87 | borderWidth: 1, 88 | fill: true, 89 | }, 90 | { 91 | label: data2.login, 92 | data: [ 93 | data2.totalCommits, 94 | data2.totalIssuesCreated, 95 | data2.totalPRsMerged, 96 | data2.forks, 97 | ], 98 | backgroundColor: "rgba(220, 252, 231, 0.2)", 99 | borderColor: "rgba(52, 211, 153, 1)", 100 | borderWidth: 1, 101 | fill: true, 102 | }, 103 | ], 104 | }; 105 | 106 | const options: ChartOptions<"radar"> = { 107 | maintainAspectRatio: false, 108 | scales: { 109 | r: { 110 | angleLines: { 111 | color: isDarkMode ? "rgb(0, 128, 128)" : "rgb(128, 128, 128)", 112 | }, 113 | grid: { 114 | color: isDarkMode ? "rgb(0, 128, 128)" : "rgb(128, 128, 128)", 115 | }, 116 | suggestedMin: 0, 117 | suggestedMax: Math.max(data1.totalCommits, data2.totalCommits), 118 | ticks: { 119 | stepSize: Math.ceil( 120 | Math.max(data1.totalCommits, data2.totalCommits) / 5 121 | ), 122 | color: isDarkMode ? "rgba(255, 255, 255, 0.7)" : "rgba(0, 0, 0, 0.7)", 123 | backdropColor: isDarkMode 124 | ? "rgba(0, 0, 0, 1)" 125 | : "rgba(255, 255, 255, 1)", 126 | }, 127 | pointLabels: { 128 | font: { 129 | size: 12, 130 | }, 131 | color: isDarkMode ? "rgba(255, 255, 255, 0.7)" : "rgba(0, 0, 0, 0.7)", 132 | }, 133 | }, 134 | }, 135 | }; 136 | 137 | return ( 138 |
142 |
147 | 153 |
154 |
155 | ); 156 | }; 157 | 158 | export default RadarChart; 159 | -------------------------------------------------------------------------------- /components/RecentGenerations.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import axios from "axios"; 3 | import { cn } from "@/utils/cn"; 4 | import Link from "next/link"; 5 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 6 | import { HoverEffect } from "./ui/card-hover-effect"; 7 | import { useState, useEffect } from "react"; 8 | import type { UserData } from "@/types"; 9 | import { useRef } from "react"; 10 | import Autoplay from "embla-carousel-autoplay"; 11 | import { 12 | Carousel, 13 | CarouselContent, 14 | CarouselItem, 15 | CarouselNext, 16 | CarouselPrevious, 17 | } from "@/components/ui/carousel"; 18 | import RecentGenerationsLoader from "./RecetUsersLoading"; 19 | import Image from "next/image"; 20 | 21 | const RecentGenerations = () => { 22 | const [usersData, setUsersData] = useState([]); 23 | const [loading, setLoading] = useState(true); 24 | 25 | useEffect(() => { 26 | const fetchUsers = async () => { 27 | try { 28 | setLoading(true); 29 | const { data } = await axios.get("/api/users"); 30 | setUsersData(data); 31 | } catch (error) { 32 | console.error(`error fetching the data ${error}`); 33 | } finally { 34 | setLoading(false); 35 | } 36 | }; 37 | fetchUsers(); 38 | }, []); 39 | 40 | const plugin = useRef(Autoplay({ delay: 3000, stopOnInteraction: true })); 41 | 42 | const Card = ({ 43 | className, 44 | children, 45 | }: { 46 | className?: string; 47 | children: React.ReactNode; 48 | }) => { 49 | return ( 50 |
56 |
57 |
{children}
58 |
59 |
60 | ); 61 | }; 62 | 63 | if (loading) { 64 | return ; 65 | } 66 | 67 | return ( 68 |
69 | 76 | 77 | {usersData.map((item, index) => ( 78 | 82 | 87 | 88 |
89 |

{item.bio ? item.bio : "No bio available"}

90 |
91 |
92 | 93 | 94 | 95 | {item.username?.slice(0, 2)} 96 | 97 | 98 |
99 |

100 | {item?.name} 101 |

102 |

103 | {item.username} 104 |

105 |
106 |
107 |
108 | 109 |
110 | ))} 111 |
112 | 113 | 114 | {usersData.length == 0 && ( 115 |
116 |
117 | Loading... 118 |
119 | )} 120 | 121 |
122 | ); 123 | }; 124 | 125 | export default RecentGenerations; 126 | -------------------------------------------------------------------------------- /components/RecetUsersLoading.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/utils/cn"; 2 | import { useState, useEffect, useRef } from "react"; 3 | import Autoplay from "embla-carousel-autoplay"; 4 | import { Skeleton } from "@/components/ui/skeleton"; 5 | import { 6 | Carousel, 7 | CarouselContent, 8 | CarouselItem, 9 | CarouselNext, 10 | CarouselPrevious, 11 | } from "@/components/ui/carousel"; 12 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 13 | import { HoverEffect } from "./ui/card-hover-effect"; 14 | 15 | const RecentGenerationsLoader = () => { 16 | const plugin = useRef(Autoplay({ delay: 3000, stopOnInteraction: true })); 17 | 18 | const Card = ({ 19 | className, 20 | children, 21 | }: { 22 | className?: string; 23 | children: React.ReactNode; 24 | }) => { 25 | return ( 26 |
32 |
33 |
{children}
34 |
35 |
36 | ); 37 | }; 38 | 39 | const carouselItems = Array.from({ length: 3 }, (_, index) => ( 40 | 44 |
45 | 46 |
47 | 48 | 49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 |
66 |
67 | )); 68 | 69 | return ( 70 |
71 | 79 | {carouselItems} 80 | 81 | 82 | 83 |
84 | ); 85 | }; 86 | 87 | export default RecentGenerationsLoader; 88 | -------------------------------------------------------------------------------- /components/Repositories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface GitHubRepo { 4 | id: number; 5 | name: string; 6 | html_url: string; 7 | language: string; 8 | homepage?: string; 9 | watchers: number; 10 | forks: number; 11 | watchersLabel: string; 12 | forksLabel: string; 13 | date: number; 14 | } 15 | 16 | interface RepositoriesProps { 17 | repos: GitHubRepo[]; 18 | repoCount: number; 19 | } 20 | 21 | const Repositories: React.FC = ({ repos, repoCount }) => { 22 | return ( 23 |
24 |

25 | Popular Repositories 26 |

27 |
    28 | {repos.slice(0, repoCount).map((repo) => ( 29 |
  • 30 |
    31 |
    32 | 38 | {repo.name} 39 | 40 | ({repo.date}) 41 |
    42 | {repo.homepage && ( 43 | <> 44 | 50 | {repo.homepage} 51 | 52 | 53 | )} 54 |

    55 | {repo.language} 56 |  -  57 | {"Owner"} 58 |

    59 |

    60 | This repository has  61 | {repo.watchers} 62 | {repo.watchersLabel} and  63 | {repo.forks} 64 | {repo.forksLabel}. If you would like more information about this 65 | repository and my contributed code, please visit the 66 | 72 | repository  73 | 74 | on GitHub. 75 |

    76 |
    77 |
  • 78 | ))} 79 |
80 |
81 | ); 82 | }; 83 | 84 | export default Repositories; 85 | -------------------------------------------------------------------------------- /components/ShareBtn.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | LinkedinShareButton, 3 | LinkedinIcon, 4 | WhatsappIcon, 5 | WhatsappShareButton, 6 | TwitterIcon, 7 | TwitterShareButton, 8 | } from "next-share"; 9 | import { 10 | Dialog, 11 | DialogContent, 12 | DialogDescription, 13 | DialogHeader, 14 | DialogTitle, 15 | DialogTrigger, 16 | } from "@/components/ui/dialog"; 17 | import { CopyIcon, Share1Icon } from "@radix-ui/react-icons"; 18 | import { Button } from "./ui/button"; 19 | import CopyLinkBtn from "./CopyLinkBtn"; 20 | 21 | const ShareBtn = ({ username }: { username: string }) => { 22 | return ( 23 | 24 | 25 | 28 | 29 | 30 | 31 | Share your resume 32 | 33 | 37 | 38 | 39 | 43 | 44 | 45 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ); 62 | }; 63 | export default ShareBtn; 64 | -------------------------------------------------------------------------------- /components/StatsBox.tsx: -------------------------------------------------------------------------------- 1 | // import React, { useEffect, useState } from "react"; 2 | // import axios from "axios"; 3 | // import redis from "@/lib/redis"; 4 | // import { CACHE_TTL } from "@/lib/consts"; 5 | // import { fetchUserStats } from "@/utils/resumeUtils"; 6 | 7 | // interface GitHubData { 8 | // followers: number; 9 | // publicRepos: number; 10 | // starsReceived: number; 11 | // forks: number; 12 | // totalCommits: number; 13 | // organizations: number; 14 | // totalIssues: number; 15 | // totalPRsMerged: number; 16 | // userJoinedDate: Date; 17 | // } 18 | 19 | // const StatsBox = ({ username }: { username: string }) => { 20 | // const [userData, setUserData] = useState({ 21 | // followers: 0, 22 | // publicRepos: 0, 23 | // starsReceived: 0, 24 | // forks: 0, 25 | // totalCommits: 0, 26 | // organizations: 0, 27 | // totalIssues: 0, 28 | // totalPRsMerged: 0, 29 | // userJoinedDate: new Date(), 30 | // }); 31 | 32 | // useEffect(() => { 33 | // const fetchUserData = async () => { 34 | // await fetchUserStats(username).then((data) => { 35 | // setUserData(data); 36 | // }); 37 | // }; 38 | // fetchUserData(); 39 | // }, [username]); 40 | 41 | // return ( 42 | //
43 | //

GitHub Stats

44 | //
45 | //
46 | //
47 | //

Years on GitHub:

48 | // 49 | // {new Date().getFullYear() - 50 | // new Date(userData.userJoinedDate).getFullYear()} 51 | // 52 | //
53 | //
54 | //
55 | //
56 | //

Followers:

57 | // {userData.followers} 58 | //
59 | //
60 | //
61 | //
62 | //

Public Repositories:

63 | // {userData.publicRepos} 64 | //
65 | //
66 | //
67 | //
68 | //

Stars Received:

69 | // {userData.starsReceived} 70 | //
71 | //
72 | //
73 | //
74 | //

Forks:

75 | // {userData.forks} 76 | //
77 | //
78 | //
79 | //
80 | //

Organizations:

81 | // {userData.organizations} 82 | //
83 | //
84 | // {/* Uncomment if needed */} 85 | // {/*
86 | //
87 | //

Total Commits:

88 | // {userData.totalCommits} 89 | //
90 | //
91 | //
92 | //
93 | //

Total Issues Created:

94 | // {userData.totalIssues} 95 | //
96 | //
97 | //
98 | //
99 | //

Total PRs Merged:

100 | // {userData.totalPRsMerged} 101 | //
102 | //
*/} 103 | //
104 | //
105 | // ); 106 | // }; 107 | 108 | // export default StatsBox; 109 | -------------------------------------------------------------------------------- /components/UserTestimonials.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { InfiniteMovingCards } from "@/components/ui/infinite-moving-cards"; 4 | import testimonials from "@/config/content/Testimonials"; 5 | 6 | const UserTestimonails = () => { 7 | return ( 8 |
9 |

10 | Hear what our Users say about{" "} 11 | 12 | git-re 13 | 14 |

15 |
16 |
17 | 22 |
23 |
24 |
25 | ); 26 | }; 27 | 28 | export default UserTestimonails; 29 | -------------------------------------------------------------------------------- /components/cover-letter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useRouter } from "next/navigation"; 3 | import React, { useEffect, useState } from "react"; 4 | import { Button } from "./ui/button"; 5 | import CoverLetterForm from "./CoverLetterForm"; 6 | import CoverLetterDialog from "./CoverLetterDialog"; 7 | import { supabase } from "@/utils/supabase/client"; 8 | import { UserData } from "@/types"; 9 | 10 | type Props = { 11 | user: any; 12 | }; 13 | 14 | export default function CoverLetter({ user }: Props) { 15 | const router = useRouter(); 16 | const [isJobDescription, setIsJobDescription] = useState(true); 17 | const [isResume, setIsResume] = useState(false); 18 | const [isSubmit, setIsSubmit] = useState(false); 19 | 20 | const [isResponseGenerated, setIsResponseGenerated] = useState(false); 21 | const [isError, setIsError] = useState(false); 22 | const [response, setResponse] = useState(""); 23 | 24 | useEffect(() => { 25 | if (user) { 26 | checkUsageLimit(); 27 | } else { 28 | router.push("/"); 29 | } 30 | }, [user]); 31 | 32 | const checkUsageLimit = async () => { 33 | const { data, error } = await supabase 34 | .from("usage_tracking") 35 | .select("usage_count") 36 | .eq("github_username", user!.user_metadata.preferred_username) 37 | .eq("date", new Date().toISOString().split("T")[0]); 38 | 39 | if (data && data[0] && data[0].usage_count >= 2) { 40 | setIsResponseGenerated(true); 41 | setIsError(true); 42 | setResponse( 43 | "You have reached your daily limit!! Please try again tomorrow!!" 44 | ); 45 | } 46 | }; 47 | 48 | return ( 49 |
50 |
51 |
52 |
53 | 63 |
64 | 74 |
75 | 85 |
86 |
87 | 88 |
89 | 101 |
102 | 109 |
110 |
111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import Link from "next/link"; 3 | import { 4 | IconBrandGithub, 5 | IconBrandGithubFilled, 6 | IconBrandLinkedin, 7 | IconBrandX, 8 | IconStarFilled, 9 | } from "@tabler/icons-react"; 10 | import { Button } from "./ui/button"; 11 | import { ModeToggle } from "./shared/ToggleBg"; 12 | import { GitHubLogoIcon } from "@radix-ui/react-icons"; 13 | import GitHubStarCount from './GitHubStarCount'; 14 | 15 | export default function Footer() { 16 | const currentYear = new Date().getFullYear(); 17 | 18 | const icons: { title: string; href: string; icon: ReactNode }[] = [ 19 | { 20 | title: "LinkedIn", 21 | href: "https://www.linkedin.com/in/rathashutosh/", 22 | icon: , 23 | }, 24 | { 25 | title: "Github", 26 | href: "https://github.com/ashutosh-rath02", 27 | icon: , 28 | }, 29 | { 30 | title: "Twitter", 31 | href: "https://twitter.com/v_ashu_dev", 32 | icon: , 33 | }, 34 | ]; 35 | 36 | return ( 37 |
38 |
39 |
40 | 44 | 45 | git-re 46 | 47 |
48 |

49 | {"Code · Showcase · Impress"} 50 |

51 |

52 | {" "} 53 | © {currentYear} Made with 🤯 by{" "} 54 | 59 | Ashutosh Rath 60 | 61 |

62 | 68 | 69 | 70 |

Stars on

71 | 72 |
73 | {/* LINKS */} 74 |
75 |
76 |
77 |

78 | Important Links 79 |

80 | 85 | Contribute 86 | 87 | 92 | Vote on Product Hunt 93 | 94 |
95 |
96 |
97 | 98 |
99 | {icons.map(({ title, href, icon }) => ( 100 | 107 | {icon} 108 | 109 | ))} 110 |
111 |
112 |
113 |
114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /components/hoc/withErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | // app/components/withErrorBoundary.tsx 2 | "use client"; 3 | 4 | import React, { Component, ErrorInfo, ReactNode } from "react"; 5 | import ErrorPage from "../../app/_error"; 6 | 7 | interface Props { 8 | children: ReactNode; 9 | } 10 | 11 | interface State { 12 | hasError: boolean; 13 | error?: Error; 14 | } 15 | 16 | class ErrorBoundary extends Component { 17 | state: State = { 18 | hasError: false, 19 | error: undefined, 20 | }; 21 | 22 | static getDerivedStateFromError(error: Error): State { 23 | return { hasError: true, error }; 24 | } 25 | 26 | componentDidCatch(error: Error, errorInfo: ErrorInfo) { 27 | console.error("Error caught by Error Boundary: ", error, errorInfo); 28 | } 29 | 30 | render() { 31 | if (this.state.hasError) { 32 | return ; 33 | } 34 | 35 | return this.props.children; 36 | } 37 | } 38 | 39 | const withErrorBoundary =

( 40 | WrappedComponent: React.ComponentType

41 | ) => { 42 | return (props: P) => ( 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default withErrorBoundary; 50 | -------------------------------------------------------------------------------- /components/leaderboard/crown.tsx: -------------------------------------------------------------------------------- 1 | import { IconCrown } from "@tabler/icons-react"; 2 | import React from "react"; 3 | 4 | export default function Crown({ rank }: { rank: number }) { 5 | let crownColor; 6 | 7 | switch (rank) { 8 | case 1: 9 | crownColor = "text-yellow-400"; 10 | break; 11 | case 2: 12 | crownColor = "text-gray-600 dark:text-gray-200"; 13 | break; 14 | case 3: 15 | crownColor = "text-yellow-700"; 16 | break; 17 | default: 18 | crownColor = "text-transparent"; // hides the crown if the rank is not 1, 2, or 3 19 | } 20 | 21 | return ( 22 |

23 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ModeToggle } from "./shared/ToggleBg"; 3 | import { GitHubLogoIcon } from "@radix-ui/react-icons"; 4 | import { IoMdGitNetwork } from "react-icons/io"; 5 | import Link from "next/link"; 6 | 7 | import AuthButton from "./AuthButton"; 8 | import Hamburger from "./Hamburger"; 9 | import { supabaseServer } from "@/utils/supabase/server"; 10 | 11 | export default async function Navbar() { 12 | const repositoryUrl = "https://github.com/ashutosh-rath02/git-re"; 13 | 14 | const supabase = supabaseServer(); 15 | const { data } = await supabase.auth.getUser(); 16 | 17 | return ( 18 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /components/shared/Background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /components/shared/ToggleBg.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; 5 | import { useTheme } from "next-themes"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from "@/components/ui/dropdown-menu"; 14 | 15 | export function ModeToggle() { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | Light 30 | 31 | setTheme("dark")}> 32 | Dark 33 | 34 | setTheme("system")}> 35 | System 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDownIcon } from "@radix-ui/react-icons" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 56 | 57 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 58 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | newDefault: 15 | "bg-primary text-primary-foreground shadow hover:bg-primary/90 dark:text-white", 16 | destructive: 17 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 18 | outline: 19 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 20 | secondary: 21 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 22 | ghost: "hover:bg-accent hover:text-accent-foreground", 23 | link: "text-primary underline-offset-4 hover:underline", 24 | }, 25 | size: { 26 | default: "h-9 px-4 py-2", 27 | sm: "h-8 rounded-md px-3 text-xs", 28 | lg: "h-10 rounded-md px-8", 29 | icon: "h-9 w-9", 30 | }, 31 | }, 32 | defaultVariants: { 33 | variant: "default", 34 | size: "default", 35 | }, 36 | } 37 | ); 38 | 39 | export interface ButtonProps 40 | extends React.ButtonHTMLAttributes, 41 | VariantProps { 42 | asChild?: boolean; 43 | } 44 | 45 | const Button = React.forwardRef( 46 | ({ className, variant, size, asChild = false, ...props }, ref) => { 47 | const Comp = asChild ? Slot : "button"; 48 | return ( 49 | 54 | ); 55 | } 56 | ); 57 | Button.displayName = "Button"; 58 | 59 | export { Button, buttonVariants }; 60 | -------------------------------------------------------------------------------- /components/ui/card-hover-effect.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/utils/cn"; 2 | import { AnimatePresence, motion } from "framer-motion"; 3 | import Link from "next/link"; 4 | import { useState } from "react"; 5 | import type { UserData } from "@/types"; 6 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 7 | 8 | export const HoverEffect = ({ 9 | items, 10 | className, 11 | }: { 12 | items: UserData[]; 13 | className?: string; 14 | }) => { 15 | let [hoveredIndex, setHoveredIndex] = useState(null); 16 | 17 | return ( 18 |
24 | {items.map((item, idx) => ( 25 | setHoveredIndex(idx)} 30 | onMouseLeave={() => setHoveredIndex(null)} 31 | > 32 | 33 | {hoveredIndex === idx && ( 34 | 47 | )} 48 | 49 | 50 |
51 |

{item.bio ? item.bio : "No bio available"}

52 |
53 |
54 | 55 | 56 | {item.username?.slice(0, 2)} 57 | 58 |
59 |

{item?.name}

60 |

61 | {item.username} 62 |

63 |
64 |
65 |
66 | 67 | ))} 68 |
69 | ); 70 | }; 71 | 72 | export const Card = ({ 73 | className, 74 | children, 75 | }: { 76 | className?: string; 77 | children: React.ReactNode; 78 | }) => { 79 | return ( 80 |
86 |
87 |
{children}
88 |
89 |
90 | ); 91 | }; 92 | export const CardTitle = ({ 93 | className, 94 | children, 95 | }: { 96 | className?: string; 97 | children: React.ReactNode; 98 | }) => { 99 | return ( 100 |

101 | {children} 102 |

103 | ); 104 | }; 105 | export const CardDescription = ({ 106 | className, 107 | children, 108 | }: { 109 | className?: string; 110 | children: React.ReactNode; 111 | }) => { 112 | return ( 113 |

119 | {children} 120 |

121 | ); 122 | }; 123 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { Cross2Icon } from "@radix-ui/react-icons" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogTrigger, 116 | DialogClose, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /components/ui/drawer.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { Drawer as DrawerPrimitive } from "vaul" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Drawer = ({ 9 | shouldScaleBackground = true, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 16 | ) 17 | Drawer.displayName = "Drawer" 18 | 19 | const DrawerTrigger = DrawerPrimitive.Trigger 20 | 21 | const DrawerPortal = DrawerPrimitive.Portal 22 | 23 | const DrawerClose = DrawerPrimitive.Close 24 | 25 | const DrawerOverlay = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 34 | )) 35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName 36 | 37 | const DrawerContent = React.forwardRef< 38 | React.ElementRef, 39 | React.ComponentPropsWithoutRef 40 | >(({ className, children, ...props }, ref) => ( 41 | 42 | 43 | 51 |
52 | {children} 53 | 54 | 55 | )) 56 | DrawerContent.displayName = "DrawerContent" 57 | 58 | const DrawerHeader = ({ 59 | className, 60 | ...props 61 | }: React.HTMLAttributes) => ( 62 |
66 | ) 67 | DrawerHeader.displayName = "DrawerHeader" 68 | 69 | const DrawerFooter = ({ 70 | className, 71 | ...props 72 | }: React.HTMLAttributes) => ( 73 |
77 | ) 78 | DrawerFooter.displayName = "DrawerFooter" 79 | 80 | const DrawerTitle = React.forwardRef< 81 | React.ElementRef, 82 | React.ComponentPropsWithoutRef 83 | >(({ className, ...props }, ref) => ( 84 | 92 | )) 93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName 94 | 95 | const DrawerDescription = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, ...props }, ref) => ( 99 | 104 | )) 105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName 106 | 107 | export { 108 | Drawer, 109 | DrawerPortal, 110 | DrawerOverlay, 111 | DrawerTrigger, 112 | DrawerClose, 113 | DrawerContent, 114 | DrawerHeader, 115 | DrawerFooter, 116 | DrawerTitle, 117 | DrawerDescription, 118 | } 119 | -------------------------------------------------------------------------------- /components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { Slot } from "@radix-ui/react-slot" 4 | import { 5 | Controller, 6 | ControllerProps, 7 | FieldPath, 8 | FieldValues, 9 | FormProvider, 10 | useFormContext, 11 | } from "react-hook-form" 12 | 13 | import { cn } from "@/lib/utils" 14 | import { Label } from "@/components/ui/label" 15 | 16 | const Form = FormProvider 17 | 18 | type FormFieldContextValue< 19 | TFieldValues extends FieldValues = FieldValues, 20 | TName extends FieldPath = FieldPath 21 | > = { 22 | name: TName 23 | } 24 | 25 | const FormFieldContext = React.createContext( 26 | {} as FormFieldContextValue 27 | ) 28 | 29 | const FormField = < 30 | TFieldValues extends FieldValues = FieldValues, 31 | TName extends FieldPath = FieldPath 32 | >({ 33 | ...props 34 | }: ControllerProps) => { 35 | return ( 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | const useFormField = () => { 43 | const fieldContext = React.useContext(FormFieldContext) 44 | const itemContext = React.useContext(FormItemContext) 45 | const { getFieldState, formState } = useFormContext() 46 | 47 | const fieldState = getFieldState(fieldContext.name, formState) 48 | 49 | if (!fieldContext) { 50 | throw new Error("useFormField should be used within ") 51 | } 52 | 53 | const { id } = itemContext 54 | 55 | return { 56 | id, 57 | name: fieldContext.name, 58 | formItemId: `${id}-form-item`, 59 | formDescriptionId: `${id}-form-item-description`, 60 | formMessageId: `${id}-form-item-message`, 61 | ...fieldState, 62 | } 63 | } 64 | 65 | type FormItemContextValue = { 66 | id: string 67 | } 68 | 69 | const FormItemContext = React.createContext( 70 | {} as FormItemContextValue 71 | ) 72 | 73 | const FormItem = React.forwardRef< 74 | HTMLDivElement, 75 | React.HTMLAttributes 76 | >(({ className, ...props }, ref) => { 77 | const id = React.useId() 78 | 79 | return ( 80 | 81 |
82 | 83 | ) 84 | }) 85 | FormItem.displayName = "FormItem" 86 | 87 | const FormLabel = React.forwardRef< 88 | React.ElementRef, 89 | React.ComponentPropsWithoutRef 90 | >(({ className, ...props }, ref) => { 91 | const { error, formItemId } = useFormField() 92 | 93 | return ( 94 |