87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title} }
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | // Inspired by react-hot-toast library
4 | import * as React from "react"
5 |
6 | import type {
7 | ToastActionElement,
8 | ToastProps,
9 | } from "@/components/ui/toast"
10 |
11 | const TOAST_LIMIT = 1
12 | const TOAST_REMOVE_DELAY = 1000000
13 |
14 | type ToasterToast = ToastProps & {
15 | id: string
16 | title?: React.ReactNode
17 | description?: React.ReactNode
18 | action?: ToastActionElement
19 | }
20 |
21 | const actionTypes = {
22 | ADD_TOAST: "ADD_TOAST",
23 | UPDATE_TOAST: "UPDATE_TOAST",
24 | DISMISS_TOAST: "DISMISS_TOAST",
25 | REMOVE_TOAST: "REMOVE_TOAST",
26 | } as const
27 |
28 | let count = 0
29 |
30 | function genId() {
31 | count = (count + 1) % Number.MAX_SAFE_INTEGER
32 | return count.toString()
33 | }
34 |
35 | type ActionType = typeof actionTypes
36 |
37 | type Action =
38 | | {
39 | type: ActionType["ADD_TOAST"]
40 | toast: ToasterToast
41 | }
42 | | {
43 | type: ActionType["UPDATE_TOAST"]
44 | toast: Partial
45 | }
46 | | {
47 | type: ActionType["DISMISS_TOAST"]
48 | toastId?: ToasterToast["id"]
49 | }
50 | | {
51 | type: ActionType["REMOVE_TOAST"]
52 | toastId?: ToasterToast["id"]
53 | }
54 |
55 | interface State {
56 | toasts: ToasterToast[]
57 | }
58 |
59 | const toastTimeouts = new Map>()
60 |
61 | const addToRemoveQueue = (toastId: string) => {
62 | if (toastTimeouts.has(toastId)) {
63 | return
64 | }
65 |
66 | const timeout = setTimeout(() => {
67 | toastTimeouts.delete(toastId)
68 | dispatch({
69 | type: "REMOVE_TOAST",
70 | toastId: toastId,
71 | })
72 | }, TOAST_REMOVE_DELAY)
73 |
74 | toastTimeouts.set(toastId, timeout)
75 | }
76 |
77 | export const reducer = (state: State, action: Action): State => {
78 | switch (action.type) {
79 | case "ADD_TOAST":
80 | return {
81 | ...state,
82 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
83 | }
84 |
85 | case "UPDATE_TOAST":
86 | return {
87 | ...state,
88 | toasts: state.toasts.map((t) =>
89 | t.id === action.toast.id ? { ...t, ...action.toast } : t
90 | ),
91 | }
92 |
93 | case "DISMISS_TOAST": {
94 | const { toastId } = action
95 |
96 | // ! Side effects ! - This could be extracted into a dismissToast() action,
97 | // but I'll keep it here for simplicity
98 | if (toastId) {
99 | addToRemoveQueue(toastId)
100 | } else {
101 | state.toasts.forEach((toast) => {
102 | addToRemoveQueue(toast.id)
103 | })
104 | }
105 |
106 | return {
107 | ...state,
108 | toasts: state.toasts.map((t) =>
109 | t.id === toastId || toastId === undefined
110 | ? {
111 | ...t,
112 | open: false,
113 | }
114 | : t
115 | ),
116 | }
117 | }
118 | case "REMOVE_TOAST":
119 | if (action.toastId === undefined) {
120 | return {
121 | ...state,
122 | toasts: [],
123 | }
124 | }
125 | return {
126 | ...state,
127 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
128 | }
129 | }
130 | }
131 |
132 | const listeners: Array<(state: State) => void> = []
133 |
134 | let memoryState: State = { toasts: [] }
135 |
136 | function dispatch(action: Action) {
137 | memoryState = reducer(memoryState, action)
138 | listeners.forEach((listener) => {
139 | listener(memoryState)
140 | })
141 | }
142 |
143 | type Toast = Omit
144 |
145 | function toast({ ...props }: Toast) {
146 | const id = genId()
147 |
148 | const update = (props: ToasterToast) =>
149 | dispatch({
150 | type: "UPDATE_TOAST",
151 | toast: { ...props, id },
152 | })
153 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
154 |
155 | dispatch({
156 | type: "ADD_TOAST",
157 | toast: {
158 | ...props,
159 | id,
160 | open: true,
161 | onOpenChange: (open) => {
162 | if (!open) dismiss()
163 | },
164 | },
165 | })
166 |
167 | return {
168 | id: id,
169 | dismiss,
170 | update,
171 | }
172 | }
173 |
174 | function useToast() {
175 | const [state, setState] = React.useState(memoryState)
176 |
177 | React.useEffect(() => {
178 | listeners.push(setState)
179 | return () => {
180 | const index = listeners.indexOf(setState)
181 | if (index > -1) {
182 | listeners.splice(index, 1)
183 | }
184 | }
185 | }, [state])
186 |
187 | return {
188 | ...state,
189 | toast,
190 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
191 | }
192 | }
193 |
194 | export { useToast, toast }
195 |
--------------------------------------------------------------------------------
/config/content/Testimonials.js:
--------------------------------------------------------------------------------
1 | const testimonials = [
2 | {
3 | quote:
4 | "Interesting ! Is there a way to display public repositories from organizations made by the user ?",
5 | name: "Yannick",
6 | title: "Full-Stack Developer",
7 | },
8 | {
9 | quote:
10 | "The idea is very cool :) Would love to be able to export the result as a PDF I can share",
11 | name: "Matt El Mouktafi",
12 | title: "Founder @Achiev",
13 | },
14 | {
15 | quote: "Looks nice. Good luck to you today",
16 | name: "Garen Orchyan",
17 | title: "Building Asyncinterview",
18 | },
19 | ];
20 |
21 | export default testimonials;
22 |
--------------------------------------------------------------------------------
/config/content/faqs.js:
--------------------------------------------------------------------------------
1 | const faqs = [
2 | {
3 | question:"What is Git-Re?",
4 | answer:"Git-Re is a platform designed to help you elevate your GitHub profile into a dynamic resume effortlessly. It showcases your projects and contributions in a visually appealing format.",
5 | },
6 | {
7 | question: "Is Git-Re free to use?",
8 | answer: "Yes, Git-Re is free to use. We aim to make it accessible to all GitHub users looking to enhance their profiles.",
9 | },
10 | {
11 | question: "How do I create a dynamic resume on Git-Re?",
12 | answer: "To create a dynamic resume, click on the \"Build Resume\" button on the homepage, authorize Git-Re to access your GitHub profile, and select the repositories you want to showcase.",
13 | },
14 | {
15 | question: "What is the Leaderboard feature?",
16 | answer: "The Leaderboard showcases top users based on various criteria such as contributions, stars received, and activity on their repositories. It allows users to compare their performance with others in the community.",
17 | },
18 | {
19 | question: "How is the ranking determined on the Leaderboard?",
20 | answer: "Rankings on the Leaderboard are determined by a combination of factors, including the number of contributions, stars, and forks on your repositories. Detailed criteria can be found at https://github.com/ashutosh-rath02/git-re/blob/main/FAQs/rating.md",
21 | },
22 | {
23 | question: "How can I compare my performance with other users on Git-Re?",
24 | answer: "You can compare your performance with other users by checking the compare feature, which ranks users based on their GitHub activity. Enter the username of the two individuals and a comparison will be displayed based on their profiles.",
25 | },
26 | {
27 | question: "How do I provide feedback or suggest features?",
28 | answer: "We welcome your feedback and suggestions! Please visit the \"Contribute\" section on GitHub or contact us through the links provided on the homepage.",
29 | },
30 | ];
31 |
32 | export default faqs;
33 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import tseslint from "typescript-eslint";
3 | import pluginReactConfig from "eslint-plugin-react/configs/recommended.js";
4 |
5 | export default [
6 | { languageOptions: { globals: globals.browser } },
7 | ...tseslint.configs.recommended,
8 | pluginReactConfig,
9 | ];
10 |
--------------------------------------------------------------------------------
/lib/consts.ts:
--------------------------------------------------------------------------------
1 | export const CACHE_TTL = 60 * 60 * 3; // 3 hours
2 |
--------------------------------------------------------------------------------
/lib/redis.ts:
--------------------------------------------------------------------------------
1 | import { Redis } from "@upstash/redis";
2 |
3 | const redis = new Redis({
4 | url: process.env.NEXT_PUBLIC_UPSTASH_REDIS_URL,
5 | token: process.env.NEXT_PUBLIC_UPSTASH_REDIS_TOKEN,
6 | });
7 |
8 | export default redis;
9 |
--------------------------------------------------------------------------------
/lib/store/InitUser.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { User } from "@supabase/supabase-js";
3 | import React, { useEffect, useRef } from "react";
4 | import { useUser } from "./user";
5 |
6 | export default function InitUser({ user }: { user: User | null }) {
7 | const initState = useRef(false);
8 |
9 | useEffect(() => {
10 | if (!initState.current) {
11 | useUser.setState;
12 | ({ user });
13 | }
14 |
15 | initState.current = true;
16 | // eslint-disable-next-line
17 | }, []);
18 | return <>>;
19 | }
20 |
--------------------------------------------------------------------------------
/lib/store/user.ts:
--------------------------------------------------------------------------------
1 | import { User } from "@supabase/supabase-js";
2 | import { create } from "zustand";
3 |
4 | interface UserState {
5 | user: User | null;
6 | }
7 |
8 | export const useUser = create()((set) => ({
9 | user: null,
10 | }));
11 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, type NextRequest } from "next/server";
2 | import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
3 |
4 | export async function middleware(request: NextRequest) {
5 | const res = NextResponse.next();
6 | const supabase = createMiddlewareClient({ req: request, res });
7 | await supabase.auth.getUser();
8 | return res;
9 | }
10 |
11 | export const config = {
12 | matcher: [
13 | /*
14 | * Match all request paths except for the ones starting with:
15 | * - _next/static (static files)
16 | * - _next/image (image optimization files)
17 | * - favicon.ico (favicon file)
18 | * Feel free to modify this pattern to include more paths.
19 | */
20 | "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
21 | ],
22 | };
23 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: 'https',
7 | hostname: 'avatars.githubusercontent.com',
8 | pathname: '/u/*',
9 | },
10 | ],
11 | },
12 | };
13 |
14 | module.exports = nextConfig;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "git-re",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@google/generative-ai": "^0.16.0",
13 | "@hookform/resolvers": "^3.9.1",
14 | "@radix-ui/react-accordion": "^1.2.0",
15 | "@radix-ui/react-avatar": "^1.1.0",
16 | "@radix-ui/react-dialog": "^1.1.1",
17 | "@radix-ui/react-dropdown-menu": "^2.0.6",
18 | "@radix-ui/react-icons": "^1.3.0",
19 | "@radix-ui/react-label": "^2.1.0",
20 | "@radix-ui/react-select": "^2.1.1",
21 | "@radix-ui/react-separator": "^1.0.3",
22 | "@radix-ui/react-slot": "^1.1.0",
23 | "@radix-ui/react-toast": "^1.1.5",
24 | "@radix-ui/react-tooltip": "^1.1.2",
25 | "@studio-freight/lenis": "^1.0.42",
26 | "@supabase/auth-helpers-nextjs": "^0.10.0",
27 | "@supabase/ssr": "^0.4.0",
28 | "@supabase/supabase-js": "^2.45.0",
29 | "@tabler/icons-react": "^3.5.0",
30 | "@upstash/redis": "^1.33.0",
31 | "@vercel/analytics": "^1.3.1",
32 | "@vercel/og": "^0.6.2",
33 | "@vercel/speed-insights": "^1.0.11",
34 | "axios": "^1.7.7",
35 | "chart.js": "^4.4.6",
36 | "chartjs-plugin-datalabels": "^2.2.0",
37 | "class-variance-authority": "^0.7.0",
38 | "clsx": "^2.1.1",
39 | "date-fns": "^3.6.0",
40 | "embla-carousel-autoplay": "^8.1.6",
41 | "embla-carousel-react": "^8.1.6",
42 | "file-saver": "^2.0.5",
43 | "framer-motion": "^11.3.21",
44 | "graphql": "^16.9.0",
45 | "graphql-request": "^7.1.2",
46 | "html2canvas": "^1.4.1",
47 | "html2pdf.js": "^0.10.2",
48 | "jspdf": "^2.5.1",
49 | "mini-svg-data-uri": "^1.4.4",
50 | "next": "^14.2.5",
51 | "next-share": "^0.27.0",
52 | "next-themes": "^0.3.0",
53 | "nextjs-toploader": "^1.6.12",
54 | "react": "^18",
55 | "react-chartjs-2": "^5.2.0",
56 | "react-dom": "^18",
57 | "react-github-calendar": "^4.1.6",
58 | "react-hook-form": "^7.52.1",
59 | "react-icons": "^5.2.1",
60 | "react-scroll": "^1.9.0",
61 | "recharts": "^2.12.7",
62 | "styled-components": "^6.1.12",
63 | "tailwind-merge": "^2.4.0",
64 | "tailwindcss-animate": "^1.0.7",
65 | "twin.macro": "^3.4.1",
66 | "vaul": "^0.9.1",
67 | "zod": "^3.23.8",
68 | "zustand": "^4.5.4"
69 | },
70 | "devDependencies": {
71 | "@types/chart.js": "^2.9.41",
72 | "@types/file-saver": "^2.0.7",
73 | "@types/node": "^22.9.0",
74 | "@types/react": "^18",
75 | "@types/react-dom": "^18",
76 | "@types/react-scroll": "^1.8.10",
77 | "autoprefixer": "^10.4.20",
78 | "eslint": "^8",
79 | "eslint-config-next": "14.2.5",
80 | "postcss": "^8",
81 | "tailwindcss": "^3.4.14",
82 | "typescript": "^5"
83 | },
84 | "browser": {
85 | "fs": false,
86 | "path": false,
87 | "os": false
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/GIT-RE Project Setup.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashutosh-rath02/git-re/49942165e783195382f8fb3f3463b3e8a89da04f/public/GIT-RE Project Setup.mp4
--------------------------------------------------------------------------------
/public/assets/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashutosh-rath02/git-re/49942165e783195382f8fb3f3463b3e8a89da04f/public/assets/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/public/assets/Roboto-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashutosh-rath02/git-re/49942165e783195382f8fb3f3463b3e8a89da04f/public/assets/Roboto-Italic.ttf
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | "./pages/**/*.{ts,tsx}",
6 | "./components/**/*.{ts,tsx}",
7 | "./app/**/*.{ts,tsx}",
8 | "./src/**/*.{ts,tsx}",
9 | ],
10 | prefix: "",
11 | theme: {
12 | container: {
13 | center: true,
14 | padding: "2rem",
15 | screens: {
16 | "2xl": "1400px",
17 | },
18 | },
19 | extend: {
20 | colors: {
21 | border: "hsl(var(--border))",
22 | input: "hsl(var(--input))",
23 | ring: "hsl(var(--ring))",
24 | background: "hsl(var(--background))",
25 | foreground: "hsl(var(--foreground))",
26 | primary: {
27 | DEFAULT: "hsl(var(--primary))",
28 | foreground: "hsl(var(--primary-foreground))",
29 | },
30 | primaryLight: "#2D71ED",
31 | secondaryText: "#7E7E7E",
32 | navbarSecondary: "#3C3C3C",
33 | secondary: {
34 | DEFAULT: "hsl(var(--secondary))",
35 | foreground: "hsl(var(--secondary-foreground))",
36 | },
37 | destructive: {
38 | DEFAULT: "hsl(var(--destructive))",
39 | foreground: "hsl(var(--destructive-foreground))",
40 | },
41 | muted: {
42 | DEFAULT: "hsl(var(--muted))",
43 | foreground: "hsl(var(--muted-foreground))",
44 | },
45 | accent: {
46 | DEFAULT: "hsl(var(--accent))",
47 | foreground: "hsl(var(--accent-foreground))",
48 | },
49 | popover: {
50 | DEFAULT: "hsl(var(--popover))",
51 | foreground: "hsl(var(--popover-foreground))",
52 | },
53 | card: {
54 | DEFAULT: "hsl(var(--card))",
55 | foreground: "hsl(var(--card-foreground))",
56 | },
57 | },
58 | borderRadius: {
59 | lg: "var(--radius)",
60 | md: "calc(var(--radius) - 2px)",
61 | sm: "calc(var(--radius) - 4px)",
62 | },
63 | keyframes: {
64 | scroll: {
65 | to: {
66 | transform: "translate(calc(-50% - 0.5rem))",
67 | },
68 | },
69 | "accordion-down": {
70 | from: { height: "0" },
71 | to: { height: "var(--radix-accordion-content-height)" },
72 | },
73 | "accordion-up": {
74 | from: { height: "var(--radix-accordion-content-height)" },
75 | to: { height: "0" },
76 | },
77 | shimmer: {
78 | from: {
79 | backgroundPosition: "0 0",
80 | },
81 | to: {
82 | backgroundPosition: "-200% 0",
83 | },
84 | },
85 | },
86 | animation: {
87 | "accordion-down": "accordion-down 0.2s ease-out",
88 | "accordion-up": "accordion-up 0.2s ease-out",
89 | shimmer: "shimmer 2s linear infinite",
90 | scroll:
91 | "scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite",
92 | },
93 | },
94 | },
95 | plugins: [require("tailwindcss-animate")],
96 | };
97 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2015",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx",
29 | ".next/types/**/*.ts",
30 | "app/resume/[username]/styles.js"
31 | ],
32 | "exclude": ["node_modules"]
33 | }
34 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface RepoLanguageStats {
2 | [language: string]: number;
3 | }
4 |
5 | export interface LanguageDetail {
6 | name: string;
7 | bytes: number;
8 | }
9 |
10 | export interface Language {
11 | name: string;
12 | percent: number;
13 | url: string;
14 | }
15 |
16 | export interface UserData {
17 | name: string;
18 | avatar_url: string;
19 | bio: string;
20 | username: string;
21 | }
22 |
23 | export type UsageData = {
24 | user_id: string;
25 | date: string;
26 | usage_count: number;
27 | };
28 |
--------------------------------------------------------------------------------
/utils/Gemini.ts:
--------------------------------------------------------------------------------
1 | import { GoogleGenerativeAI } from "@google/generative-ai";
2 |
3 | const MODEL_NAME = "gemini-1.5-pro";
4 | const API_KEY1 = process.env.NEXT_PUBLIC_GEMINI_API_KEY as string;
5 |
6 | const chatHistory = [
7 | {
8 | role: "user",
9 | parts: [
10 | {
11 | text: `You are an expert in generating cover letter. Your task is to generate cover letter for given job description and resume details. If you are unable to generate then provide output as Information is insufficient!!`,
12 | },
13 | ],
14 | },
15 | {
16 | role: "model",
17 | parts: [{ text: "How can I help you ?" }],
18 | },
19 | ];
20 |
21 | type Props = {
22 | jobDescription: string;
23 | project: string;
24 | skills: string;
25 | experience: string;
26 | };
27 |
28 | export async function getCoverLetter({
29 | jobDescription,
30 | project,
31 | skills,
32 | experience,
33 | }: Props) {
34 | if (!API_KEY1) throw new Error("Invalid Api Key!");
35 |
36 | const genAI = new GoogleGenerativeAI(API_KEY1);
37 | const model = genAI.getGenerativeModel({ model: MODEL_NAME });
38 |
39 | model.generationConfig = {
40 | temperature: 0.9,
41 | topK: 1,
42 | topP: 1,
43 | };
44 |
45 | const chat = model.startChat({
46 | history: chatHistory,
47 | });
48 |
49 | const result = await chat.sendMessage(
50 | `Job Description : \n${jobDescription}.\n Projects : \n${project} \nSkills: \n${skills} \nExperience: \n${experience}`
51 | );
52 | const response = result.response;
53 | return response.text();
54 | }
55 |
--------------------------------------------------------------------------------
/utils/GithubAPI.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { RepoLanguageStats, LanguageDetail, Language } from "../types";
3 |
4 | export const fetchUserContributionLanguages = async (
5 | username: string
6 | ): Promise => {
7 | try {
8 | const repoResponse = await axios.get(
9 | `https://api.github.com/search/repositories?q=user:${username}+fork:true`,
10 | {
11 | headers: {
12 | Authorization: `Bearer ${process.env.NEXT_PUBLIC_GITHUB_TOKEN}`,
13 | Accept: "application/vnd.github+json",
14 | },
15 | }
16 | );
17 | const repos = repoResponse.data.items;
18 |
19 | let languageStats: RepoLanguageStats = {};
20 |
21 | for (const repo of repos) {
22 | const languagesResponse = await axios.get(repo.languages_url);
23 | const languages: RepoLanguageStats = languagesResponse.data;
24 |
25 | for (const [language, bytes] of Object.entries(languages)) {
26 | languageStats[language] = (languageStats[language] || 0) + bytes;
27 | }
28 | }
29 |
30 | const totalBytes = Object.values(languageStats).reduce(
31 | (sum, bytes) => sum + bytes,
32 | 0
33 | );
34 | return Object.entries(languageStats)
35 | .sort((a, b) => b[1] - a[1])
36 | .map(
37 | ([name, bytes]): Language => ({
38 | name,
39 | percent: (bytes / totalBytes) * 100,
40 | url: `https://github.com/search?q=user%3A${username}+language%3A${encodeURIComponent(
41 | name
42 | )}`,
43 | })
44 | );
45 | } catch (error) {
46 | console.error("Error fetching user contribution languages:", error);
47 | return [];
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/utils/format.ts:
--------------------------------------------------------------------------------
1 | export const getRankSuffix = (rank?: number): string => {
2 | switch (rank) {
3 | case 1:
4 | return "st";
5 | case 2:
6 | return "nd";
7 | case 3:
8 | return "rd";
9 | default:
10 | return "th";
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/utils/rating/action.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 | import { supabaseServer } from "../supabase/server";
3 |
4 | interface UserStats {
5 | stars_recieved: number;
6 | followers: number;
7 | public_repos: number;
8 | organizations_count: number;
9 | total_prs_merged: number;
10 | total_issues_created: number;
11 | rating: number;
12 | }
13 |
14 | const getPointsForStars = (stars: number): number => {
15 | if (stars > 500) return 5;
16 | if (stars > 200) return 4;
17 | if (stars > 50) return 3;
18 | if (stars > 10) return 2;
19 | return 1;
20 | };
21 |
22 | const getPointsForRepos = (repos: number): number => {
23 | if (repos > 101) return 5;
24 | if (repos > 51) return 4;
25 | if (repos > 21) return 3;
26 | if (repos > 6) return 2;
27 | return 1;
28 | };
29 |
30 | const getPointsForFollowers = (followers: number): number => {
31 | if (followers > 501) return 5;
32 | if (followers > 101) return 4;
33 | if (followers > 51) return 3;
34 | if (followers > 11) return 2;
35 | return 1;
36 | };
37 |
38 | const getPointsForOrganizations = (orgs: number): number => {
39 | if (orgs > 6) return 5;
40 | if (orgs > 4) return 4;
41 | if (orgs > 2) return 3;
42 | if (orgs > 1) return 2;
43 | return 1;
44 | };
45 |
46 | const getPointsForContributions = (prs: number, issues: number): number => {
47 | const contributions = prs + issues;
48 | if (contributions > 51) return 5;
49 | if (contributions > 21) return 4;
50 | if (contributions > 6) return 3;
51 | if (contributions > 1) return 2;
52 | return 1;
53 | };
54 |
55 | export const calculateRating = async (username: string): Promise => {
56 | try {
57 | const supabase = supabaseServer();
58 | const { data, error } = await supabase
59 | .from("recent_users")
60 | .select(
61 | "stars_recieved, followers, public_repos, organizations_count, total_prs_merged, total_issues_created, rating"
62 | )
63 | .eq("username", username)
64 | .single();
65 |
66 | if (error) {
67 | console.log("Error fetching user stats", error);
68 | throw error;
69 | }
70 |
71 | if (!data) {
72 | console.log("User not found");
73 | return 0;
74 | }
75 |
76 | const {
77 | stars_recieved,
78 | followers,
79 | public_repos,
80 | organizations_count,
81 | total_prs_merged,
82 | total_issues_created,
83 | rating,
84 | }: UserStats = data;
85 |
86 | const pointsStars = getPointsForStars(stars_recieved);
87 | const pointsRepos = getPointsForRepos(public_repos);
88 | const pointsFollowers = getPointsForFollowers(followers);
89 | const pointsOrganizations = getPointsForOrganizations(organizations_count);
90 | const pointsContributions = getPointsForContributions(
91 | total_prs_merged,
92 | total_issues_created
93 | );
94 |
95 | const weightedStars = pointsStars * 0.25;
96 | const weightedRepos = pointsRepos * 0.15;
97 | const weightedFollowers = pointsFollowers * 0.2;
98 | const weightedOrganizations = pointsOrganizations * 0.1;
99 | const weightedContributions = pointsContributions * 0.3;
100 |
101 | const totalWeightedScore =
102 | weightedStars +
103 | weightedRepos +
104 | weightedFollowers +
105 | weightedOrganizations +
106 | weightedContributions;
107 | const normalizedRating = (totalWeightedScore / 5) * 5;
108 |
109 | if (rating !== normalizedRating) {
110 | const { error: updateError } = await supabase
111 | .from("recent_users")
112 | .update({ rating: normalizedRating })
113 | .eq("username", username);
114 | if (updateError) {
115 | console.log("Error updating user rating", updateError);
116 | throw updateError;
117 | }
118 | }
119 |
120 | return normalizedRating;
121 | } catch (e) {
122 | console.error(e);
123 | return 0;
124 | }
125 | };
126 |
--------------------------------------------------------------------------------
/utils/supabase/client.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserClient } from "@supabase/ssr";
2 |
3 | export function supabaseBrowser() {
4 | return createBrowserClient(
5 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7 | );
8 | }
9 |
10 | import { createClient } from "@supabase/supabase-js";
11 |
12 | const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
13 | const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
14 |
15 | export const supabase = createClient(supabaseUrl!, supabaseKey!);
16 |
--------------------------------------------------------------------------------
/utils/supabase/getUserdata.ts:
--------------------------------------------------------------------------------
1 | import { supabaseServer } from "@/utils/supabase/server";
2 | export const getUserData = async () => {
3 | const supabase = supabaseServer();
4 | const user = await supabase.auth.getUser();
5 | return user.data;
6 | };
7 |
--------------------------------------------------------------------------------
/utils/supabase/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient, type CookieOptions } from "@supabase/ssr";
2 | import { NextResponse, type NextRequest } from "next/server";
3 |
4 | export async function updateSession(request: NextRequest) {
5 | let response = NextResponse.next({
6 | request: {
7 | headers: request.headers,
8 | },
9 | });
10 |
11 | const supabase = createServerClient(
12 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
13 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
14 | {
15 | cookies: {
16 | get(name: string) {
17 | return request.cookies.get(name)?.value;
18 | },
19 | set(name: string, value: string, options: CookieOptions) {
20 | request.cookies.set({
21 | name,
22 | value,
23 | ...options,
24 | });
25 | response = NextResponse.next({
26 | request: {
27 | headers: request.headers,
28 | },
29 | });
30 | response.cookies.set({
31 | name,
32 | value,
33 | ...options,
34 | });
35 | },
36 | remove(name: string, options: CookieOptions) {
37 | request.cookies.set({
38 | name,
39 | value: "",
40 | ...options,
41 | });
42 | response = NextResponse.next({
43 | request: {
44 | headers: request.headers,
45 | },
46 | });
47 | response.cookies.set({
48 | name,
49 | value: "",
50 | ...options,
51 | });
52 | },
53 | },
54 | }
55 | );
56 |
57 | await supabase.auth.getUser();
58 |
59 | return response;
60 | }
61 |
--------------------------------------------------------------------------------
/utils/supabase/server.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient, type CookieOptions } from "@supabase/ssr";
2 | import { cookies } from "next/headers";
3 |
4 | export function supabaseServer() {
5 | const cookieStore = cookies();
6 |
7 | return createServerClient(
8 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
9 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10 | {
11 | cookies: {
12 | get(name: string) {
13 | return cookieStore.get(name)?.value;
14 | },
15 | set(name: string, value: string, options: CookieOptions) {
16 | try {
17 | cookieStore.set({ name, value, ...options });
18 | } catch (error) {
19 | // The `set` method was called from a Server Component.
20 | // This can be ignored if you have middleware refreshing
21 | // user sessions.
22 | }
23 | },
24 | remove(name: string, options: CookieOptions) {
25 | try {
26 | cookieStore.set({ name, value: "", ...options });
27 | } catch (error) {
28 | // The `delete` method was called from a Server Component.
29 | // This can be ignored if you have middleware refreshing
30 | // user sessions.
31 | }
32 | },
33 | },
34 | }
35 | );
36 | }
37 |
--------------------------------------------------------------------------------