Introducing Nuvyx UI v1.0.0

GitHub Profile Card

Beautiful GitHub profile cards with customizable themes, activity graphs, and real-time data fetching.

GitHub Profile Card

Select Theme

MI

Mihir Jaiswal

MihirJaiswal
Follow

👨‍💻 Aspiring Web Developer/AI engineer | Engineering 🚀|🤖 Turning coffee ☕ into code... and occasionally, code into coffee. ☕🖥️

Indore, India
Joined Jun 2023
16followers
0following
41repos

Top Languages

TypeScript
69%
JavaScript
12%
HTML
11%

Activity

1 years on GitHub

Installation Guide

1

Install Dependencies

UI Components

npx shadcn@latest init

Utility Functions

npm install clsx tailwind-merge
2

Setup Configuration

Create file: /lib/utils.ts
/lib/utils.ts
Configuration
1import { clsx, type ClassValue } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4export function cn(...inputs: ClassValue[]) {
5    return twMerge(clsx(inputs));
6}
3

Copy Component Code

GitHub Profile Card.tsx
TypeScript
1"use client";
2
3import { useState, useEffect, useCallback } from "react";
4import Link from "next/link";
5import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
6import { Button } from "@/components/ui/button";
7import {
8  Card,
9  CardContent,
10  CardFooter,
11  CardHeader,
12} from "@/components/ui/card";
13import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
14import {
15  Tooltip,
16  TooltipContent,
17  TooltipProvider,
18  TooltipTrigger,
19} from "@/components/ui/tooltip";
20import {
21  AtSign,
22  BookOpen,
23  Calendar,
24  ExternalLink,
25  Github,
26  GitFork,
27  MapPin,
28  Star,
29  AlertCircle,
30} from "lucide-react";
31import { cn } from "@/lib/utils";
32
33export type ThemeOption = {
34  id: string;
35  name: string;
36  description: string;
37  cardBg: string;
38  cardBorder: string;
39  cardHoverShadow: string;
40  accentColor: string;
41  accentColorLight: string;
42  graphColor: string;
43  graphBgColor: string;
44  badgeBg: string;
45  badgeText: string;
46  textMuted: string;
47  textNormal: string;
48  tabsBg: string;
49};
50
51export const themes: ThemeOption[] = [
52  {
53    id: "github-light",
54    name: "GitHub Light",
55    description: "The classic GitHub light theme",
56    cardBg: "bg-white",
57    cardBorder: "border border-border",
58    cardHoverShadow: "hover:shadow-md",
59    accentColor: "text-[#0969da] dark:text-[#0550a0]",
60    accentColorLight: "text-[#0969da] dark:text-[#0550a0]",
61    graphColor: "text-[#0550a0] dark:text-[#0969da]",
62    graphBgColor: "text-[#0969da]/10 dark:text-[#0550a0]/10",
63    badgeBg: "bg-[#f6f8fa] dark:bg-[#24292f]",
64    badgeText: "text-[#24292f] dark:text-[#24292f]",
65    textMuted: "text-gray-700",
66    textNormal: "text-black",
67    tabsBg: "bg-[#bbd5fc] dark:bg-[#74aafc]",
68  },
69  {
70    id: "github-dark",
71    name: "GitHub Dark",
72    description: "GitHub's dark mode theme",
73    cardBg: "bg-[#0d1117]",
74    cardBorder: "border border-[#30363d]",
75    cardHoverShadow: "hover:shadow-md hover:shadow-black/20",
76    accentColor: "text-[#58a6ff]",
77    accentColorLight: "text-[#58a6ff]/10",
78    graphColor: "text-[#58a6ff]",
79    graphBgColor: "text-[#58a6ff]/10",
80    badgeBg: "bg-[#21262d]",
81    badgeText: "text-[#c9d1d9]",
82    textMuted: "text-gray-300",
83    textNormal: "text-white",
84    tabsBg: "bg-[#161b22] dark:bg-[#74aafc]",
85  },
86  {
87    id: "ocean",
88    name: "Ocean",
89    description: "A calming blue theme",
90    cardBg: "bg-[#f0f7ff] dark:bg-[#051c33]",
91    cardBorder: "border border-[#cce4ff] dark:border-[#0a3866]",
92    cardHoverShadow:
93      "hover:shadow-md hover:shadow-blue-200 dark:hover:shadow-md dark:hover:shadow-blue-900/30",
94    accentColor: "text-[#0057b7] dark:text-[#58a6ff]",
95    accentColorLight: "text-[#0057b7]/10 dark:text-[#58a6ff]/10",
96    graphColor: "text-[#0057b7] dark:text-[#58a6ff]",
97    graphBgColor: "text-[#0057b7]/10 dark:text-[#58a6ff]/10",
98    badgeBg: "bg-[#e0f0ff] dark:bg-[#0a3866]",
99    badgeText: "text-[#0057b7] dark:text-[#88bbff]",
100    textMuted: "text-gray-700 dark:text-gray-300",
101    textNormal: "text-black dark:text-white",
102    tabsBg: "bg-[#dbeaff] dark:bg-[#0a2b4d]",
103  },
104  {
105    id: "forest",
106    name: "Forest",
107    description: "A refreshing green theme",
108    cardBg: "bg-[#f0fff4] dark:bg-[#071f0e]",
109    cardBorder: "border border-[#c6f6d5] dark:border-[#1a4031]",
110    cardHoverShadow:
111      "hover:shadow-md hover:shadow-green-200 dark:hover:shadow-md dark:hover:shadow-green-900/30",
112    accentColor: "text-[#2f855a] dark:text-[#4ade80]",
113    accentColorLight: "text-[#2f855a]/10 dark:text-[#4ade80]/10",
114    graphColor: "text-[#2f855a] dark:text-[#4ade80]",
115    graphBgColor: "text-[#2f855a]/10 dark:text-[#4ade80]/10",
116    badgeBg: "bg-[#e0fff0] dark:bg-[#1a4031]",
117    badgeText: "text-[#2f855a] dark:text-[#7eeaa4]",
118    textMuted: "text-gray-700 dark:text-gray-300",
119    textNormal: "text-black dark:text-white",
120    tabsBg: "bg-[#d7f7e0] dark:bg-[#133929]",
121  },
122  {
123    id: "sunset",
124    name: "Sunset",
125    description: "A warm orange theme",
126    cardBg: "bg-[#fff7ed] dark:bg-[#271807]",
127    cardBorder: "border border-[#ffedd5] dark:border-[#4a2912]",
128    cardHoverShadow:
129      "hover:shadow-md hover:shadow-orange-200 dark:hover:shadow-md dark:hover:shadow-orange-900/30",
130    accentColor: "text-[#9a3412]",
131    accentColorLight: "text-[#c2410c]/10 dark:text-[#fb923c]/10",
132    graphColor: "text-[#9a3412]",
133    graphBgColor: "text-[#c2410c]/10 dark:text-[#fb923c]/10",
134    badgeBg: "bg-[#ffedd5] dark:bg-[#4a2912]",
135    badgeText: "text-[#9a3412] dark:text-[#fdba74]",
136    textMuted: "text-gray-700 dark:text-gray-300",
137    textNormal: "text-black dark:text-white",
138    tabsBg: "bg-[#ffe8cc] dark:bg-[#3e230f]",
139  },
140  {
141    id: "nuvyx",
142    name: "Nuvyx",
143    description: "A cyberpunk theme with purple accents",
144    cardBg: "bg-[#0f0f13] dark:bg-[#0f0f13]",
145    cardBorder: "border border-[#2a2a3a] dark:border-[#2a2a3a]",
146    cardHoverShadow:
147      "hover:shadow-md hover:shadow-purple-900/30 dark:hover:shadow-md dark:hover:shadow-purple-900/30",
148    accentColor: "text-[#b48eff] dark:text-[#b48eff]",
149    accentColorLight: "text-[#b48eff]/20 dark:text-[#b48eff]/20",
150    graphColor: "text-[#b48eff] dark:text-[#b48eff]",
151    graphBgColor: "text-[#b48eff]/10 dark:text-[#b48eff]/10",
152    badgeBg: "bg-[#2a2a3a] dark:bg-[#2a2a3a]",
153    badgeText: "text-[#c4b5fd] dark:text-[#c4b5fd]",
154    textMuted: "text-gray-300",
155    textNormal: "text-white",
156    tabsBg: "bg-[#1a1a2e] dark:bg-[#1a1a2e]",
157  },
158];
159
160export type ManualProfileData = {
161  login: string;
162  name: string;
163  avatarUrl: string;
164  bio?: string;
165  location?: string;
166  followers: number;
167  following: number;
168  publicRepos: number;
169  createdAt: string;
170  languages?: Array<{
171    name: string;
172    color: string;
173    percentage: number;
174  }>;
175  pinnedRepos?: Array<{
176    name: string;
177    description?: string;
178    language?: string;
179    languageColor?: string;
180    stars: number;
181    forks: number;
182  }>;
183  contributionData?: Array<number>;
184};
185
186// Define proper types for GitHub API responses
187interface GitHubUserProfile {
188  login: string;
189  name: string | null;
190  avatar_url: string;
191  bio: string | null;
192  location: string | null;
193  followers: number;
194  following: number;
195  public_repos: number;
196  created_at: string;
197}
198
199interface GitHubRepository {
200  name: string;
201  description: string | null;
202  language: string | null;
203  stargazers_count: number;
204  forks_count: number;
205}
206
207interface GitHubProfileCardProps {
208  username?: string;
209  githubToken?: string;
210  manualMode?: boolean;
211  profileData?: ManualProfileData;
212  themeId?: string;
213}
214
215export function GitHubProfileCard({
216  username,
217  githubToken,
218  manualMode = false,
219  profileData,
220  themeId,
221}: GitHubProfileCardProps) {
222  const currentTheme =
223    themes.find((theme) => theme.id === themeId) || themes[0];
224  const [isLoaded, setIsLoaded] = useState(manualMode);
225  const [loading, setLoading] = useState(!manualMode);
226  const [error, setError] = useState<string | null>(null);
227  const [profile, setProfile] = useState<ManualProfileData | null>(
228    manualMode ? profileData || null : null
229  );
230  const [rateLimit, setRateLimit] = useState<{
231    remaining: number;
232    limit: number;
233  } | null>(null);
234
235  const fetchProfileData = useCallback(async () => {
236    setLoading(true);
237    setError(null);
238
239    try {
240      const headers: HeadersInit = {};
241      if (githubToken) {
242        headers.Authorization = `token ${githubToken}`;
243      }
244
245      const profileResponse = await fetch(
246        `https://api.github.com/users/${username}`,
247        {
248          headers,
249        }
250      );
251
252      const rateLimitRemaining = profileResponse.headers.get(
253        "x-ratelimit-remaining"
254      );
255      const rateLimitLimit = profileResponse.headers.get("x-ratelimit-limit");
256
257      if (rateLimitRemaining && rateLimitLimit) {
258        setRateLimit({
259          remaining: Number.parseInt(rateLimitRemaining, 10),
260          limit: Number.parseInt(rateLimitLimit, 10),
261        });
262      }
263
264      if (!profileResponse.ok) {
265        if (profileResponse.status === 403 && rateLimitRemaining === "0") {
266          throw new Error(
267            "GitHub API rate limit exceeded. Please provide a GitHub token."
268          );
269        } else {
270          throw new Error(
271            `Failed to fetch profile data: ${profileResponse.status}`
272          );
273        }
274      }
275
276      const profileData: GitHubUserProfile = await profileResponse.json();
277
278      let pinnedRepos: Array<{
279        name: string;
280        description?: string;
281        language?: string;
282        languageColor?: string;
283        stars: number;
284        forks: number;
285      }> = [];
286
287      try {
288        const reposResponse = await fetch(
289          `https://api.github.com/users/${username}/repos?sort=updated&per_page=4`,
290          {
291            headers,
292          }
293        );
294
295        if (reposResponse.ok) {
296          const repos: GitHubRepository[] = await reposResponse.json();
297          pinnedRepos = repos.map((repo) => ({
298            name: repo.name,
299            description: repo.description || undefined,
300            language: repo.language || undefined,
301            languageColor: getLanguageColor(repo.language || undefined),
302            stars: repo.stargazers_count,
303            forks: repo.forks_count,
304          }));
305        }
306      } catch (err) {
307        console.error(
308          "Failed to fetch repos, continuing with profile data",
309          err
310        );
311      }
312
313      const contributionData = Array.from({ length: 12 }, () => Math.random());
314
315      const languages = [
316        { name: "JavaScript", color: "#f1e05a", percentage: 40 },
317        { name: "TypeScript", color: "#3178c6", percentage: 30 },
318        { name: "Python", color: "#3572A5", percentage: 20 },
319      ];
320
321      const transformedProfile: ManualProfileData = {
322        login: profileData.login,
323        name: profileData.name || profileData.login,
324        avatarUrl: profileData.avatar_url,
325        bio: profileData.bio || undefined,
326        location: profileData.location || undefined,
327        followers: profileData.followers,
328        following: profileData.following,
329        publicRepos: profileData.public_repos,
330        createdAt: profileData.created_at,
331        languages: languages,
332        pinnedRepos: pinnedRepos,
333        contributionData: contributionData,
334      };
335
336      setProfile(transformedProfile);
337      setLoading(false);
338      setIsLoaded(true);
339    } catch (err) {
340      setError(
341        err instanceof Error ? err.message : "An unknown error occurred"
342      );
343      setLoading(false);
344    }
345  }, [username, githubToken]);
346
347  useEffect(() => {
348    if (manualMode && profileData) {
349      setProfile(profileData);
350      setLoading(false);
351      setIsLoaded(true);
352      return;
353    }
354
355    if (!manualMode && username) {
356      fetchProfileData();
357    }
358  }, [manualMode, profileData, username, githubToken, fetchProfileData]);
359
360  const formatDate = (dateString: string) => {
361    const date = new Date(dateString);
362    return new Intl.DateTimeFormat("en-US", {
363      year: "numeric",
364      month: "short",
365    }).format(date);
366  };
367
368  const getYearsOnGitHub = (dateString: string) => {
369    const joinDate = new Date(dateString);
370    const now = new Date();
371    const diffTime = Math.abs(now.getTime() - joinDate.getTime());
372    const diffYears = diffTime / (1000 * 60 * 60 * 24 * 365.25);
373    return Math.floor(diffYears);
374  };
375
376  const getLanguageColor = (language?: string) => {
377    if (!language) return "#858585";
378
379    const languageColors: Record<string, string> = {
380      JavaScript: "#f1e05a",
381      TypeScript: "#3178c6",
382      Python: "#3572A5",
383      Java: "#b07219",
384      Go: "#00ADD8",
385      Rust: "#dea584",
386      C: "#555555",
387      "C++": "#f34b7d",
388      "C#": "#178600",
389      PHP: "#4F5D95",
390      Ruby: "#701516",
391      Swift: "#F05138",
392      Kotlin: "#A97BFF",
393      Dart: "#00B4AB",
394      HTML: "#e34c26",
395      CSS: "#563d7c",
396      Shell: "#89e051",
397    };
398
399    return languageColors[language] || "#858585";
400  };
401
402  if (loading) {
403    return (
404      <Card
405        className={cn(
406          "w-full max-w-md overflow-hidden transition-all duration-300",
407          currentTheme.cardBg,
408          currentTheme.cardBorder
409        )}
410      >
411        <CardContent className="flex h-40 items-center justify-center">
412          <div className="flex flex-col items-center gap-2">
413            <div className="h-6 w-6 animate-spin rounded-full border-2 border-current border-t-transparent" />
414            <p className="text-sm font-medium text-muted-foreground">
415              Loading profile...
416            </p>
417          </div>
418        </CardContent>
419      </Card>
420    );
421  }
422
423  if (error) {
424    return (
425      <Card
426        className={cn(
427          "w-full max-w-md overflow-hidden transition-all duration-300",
428          currentTheme.cardBg,
429          currentTheme.cardBorder
430        )}
431      >
432        <CardContent className="flex h-36 items-center justify-center">
433          <div className="flex flex-col items-center gap-2">
434            <AlertCircle className="h-8 w-8 text-red-500" />
435            <p className="text-center text-sm font-medium text-muted-foreground">
436              Failed to load profile data.
437              <br />
438              {error}
439            </p>
440            {error.includes("rate limit") && (
441              <Button
442                variant="outline"
443                size="sm"
444                className="mt-2"
445                onClick={() => setError(null)}
446              >
447                Use manual mode instead
448              </Button>
449            )}
450          </div>
451        </CardContent>
452      </Card>
453    );
454  }
455
456  if (!profile) return null;
457
458  return (
459    <>
460      {isLoaded && (
461        <div className="w-full max-w-md">
462          <Card
463            className={cn(
464              "overflow-hidden transition-all duration-300",
465              currentTheme.cardBg,
466              currentTheme.cardBorder,
467              currentTheme.cardHoverShadow,
468              currentTheme.id === "github-light" &&
469                "bg-gradient-to-br from-white to-gray-50",
470              currentTheme.id === "github-dark" &&
471                "bg-gradient-to-br from-[#0d1117] to-[#161b22]",
472              currentTheme.id === "ocean" &&
473                "bg-gradient-to-br from-[#f0f7ff] to-[#e6f0ff] dark:from-[#051c33] dark:to-[#072440]",
474              currentTheme.id === "forest" &&
475                "bg-gradient-to-br from-[#f0fff4] to-[#e6ffec] dark:from-[#071f0e] dark:to-[#0a2a13]",
476              currentTheme.id === "sunset" &&
477                "bg-gradient-to-br from-[#fff7ed] to-[#fff0e0] dark:from-[#271807] dark:to-[#33200a]",
478              currentTheme.id === "nuvyx" &&
479                "bg-gradient-to-br from-[#0f0f13] to-[#13131a]"
480            )}
481          >
482            <CardHeader className="pb-2 pt-3">
483              <div className="flex items-start gap-4">
484                <div className="relative">
485                  <Avatar className="h-12 w-12 border-2 border-background shadow-sm">
486                    <AvatarImage src={profile.avatarUrl} alt={profile.name} />
487                    <AvatarFallback>
488                      {profile.name.substring(0, 2).toUpperCase()}
489                    </AvatarFallback>
490                  </Avatar>
491                </div>
492
493                <div className="flex-1 space-y-1">
494                  <div className="flex items-center justify-between">
495                    <div>
496                      <h2
497                        className={cn(
498                          "text-lg font-bold leading-none",
499                          currentTheme.textNormal
500                        )}
501                      >
502                        {profile.name}
503                      </h2>
504                      <div
505                        className={cn(
506                          "flex items-center text-sm text-muted-foreground",
507                          currentTheme.textMuted
508                        )}
509                      >
510                        <AtSign
511                          className={cn("mr-1 h-3 w-3", currentTheme.textMuted)}
512                        />
513                        <span
514                          className={cn("font-medium", currentTheme.textMuted)}
515                        >
516                          {profile.login}
517                        </span>
518                      </div>
519                    </div>
520                    <Button
521                      variant="outline"
522                      size="sm"
523                      className={cn(
524                        "h-8 gap-1 text-xs",
525                        currentTheme.accentColor
526                      )}
527                      asChild
528                    >
529                      <Link
530                        href={`https://github.com/${profile.login}`}
531                        target="_blank"
532                      >
533                        <Github
534                          className={cn("h-3 w-3", currentTheme.accentColor)}
535                        />
536                        Follow
537                      </Link>
538                    </Button>
539                  </div>
540
541                  {profile.bio && (
542                    <p
543                      className={cn(
544                        "line-clamp-2 text-sm text-muted-foreground",
545                        currentTheme.textMuted
546                      )}
547                    >
548                      {profile.bio}
549                    </p>
550                  )}
551
552                  <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
553                    {profile.location && (
554                      <div className="flex items-center gap-1">
555                        <MapPin
556                          className={cn("h-3 w-3", currentTheme.textMuted)}
557                        />
558                        <span
559                          className={cn("font-medium", currentTheme.textMuted)}
560                        >
561                          {profile.location}
562                        </span>
563                      </div>
564                    )}
565                    <div className="flex items-center gap-1">
566                      <Calendar
567                        className={cn("h-3 w-3", currentTheme.textMuted)}
568                      />
569                      <span
570                        className={cn("font-medium", currentTheme.textMuted)}
571                      >
572                        Joined {formatDate(profile.createdAt)}
573                      </span>
574                    </div>
575                    {rateLimit && !manualMode && (
576                      <div className="ml-auto text-xs">
577                        <Tooltip>
578                          <TooltipTrigger asChild>
579                            <span
580                              className={cn(
581                                "font-medium",
582                                currentTheme.textMuted
583                              )}
584                            >
585                              {rateLimit.remaining}/{rateLimit.limit}
586                            </span>
587                          </TooltipTrigger>
588                          <TooltipContent>
589                            <p
590                              className={cn("text-sm", currentTheme.textMuted)}
591                            >
592                              GitHub API requests remaining
593                            </p>
594                          </TooltipContent>
595                        </Tooltip>
596                      </div>
597                    )}
598                  </div>
599                </div>
600              </div>
601            </CardHeader>
602
603            <CardContent className="pb-2 pt-0">
604              <div className="mb-3 grid grid-cols-3 gap-2 rounded-md border p-1.5 text-center">
605                <div className="flex flex-col">
606                  <span
607                    className={cn(
608                      "text-base font-semibold",
609                      currentTheme.textNormal
610                    )}
611                  >
612                    {profile.followers.toLocaleString()}
613                  </span>
614                  <span
615                    className={cn(
616                      "text-xs text-muted-foreground",
617                      currentTheme.textMuted
618                    )}
619                  >
620                    followers
621                  </span>
622                </div>
623                <div className="flex flex-col">
624                  <span
625                    className={cn(
626                      "text-base font-semibold",
627                      currentTheme.textNormal
628                    )}
629                  >
630                    {profile.following.toLocaleString()}
631                  </span>
632                  <span
633                    className={cn(
634                      "text-xs text-muted-foreground",
635                      currentTheme.textMuted
636                    )}
637                  >
638                    following
639                  </span>
640                </div>
641                <div className="flex flex-col">
642                  <span
643                    className={cn(
644                      "text-base font-semibold",
645                      currentTheme.textNormal
646                    )}
647                  >
648                    {profile.publicRepos.toLocaleString()}
649                  </span>
650                  <span
651                    className={cn(
652                      "text-xs text-muted-foreground",
653                      currentTheme.textMuted
654                    )}
655                  >
656                    repos
657                  </span>
658                </div>
659              </div>
660
661              <div>
662                <Tabs defaultValue="overview" className="w-full">
663                  <TabsList
664                    className={cn(
665                      "grid w-full grid-cols-2",
666                      currentTheme.tabsBg
667                    )}
668                  >
669                    <TabsTrigger
670                      value="overview"
671                      className={currentTheme.accentColor}
672                    >
673                      Overview
674                    </TabsTrigger>
675                    <TabsTrigger
676                      value="repositories"
677                      className={currentTheme.accentColor}
678                    >
679                      Repositories
680                    </TabsTrigger>
681                  </TabsList>
682
683                  <TabsContent
684                    value="overview"
685                    className={cn("mt-3 space-y-3", currentTheme.textMuted)}
686                  >
687                    {profile.languages && profile.languages.length > 0 && (
688                      <div>
689                        <h3
690                          className={cn(
691                            "mb-2 text-xs font-medium uppercase",
692                            currentTheme.textMuted
693                          )}
694                        >
695                          Top Languages
696                        </h3>
697                        <div
698                          className={cn(
699                            "mt-2 w-full space-y-2",
700                            currentTheme.accentColor
701                          )}
702                        >
703                          {profile.languages.slice(0, 3).map((item, index) => (
704                            <div key={index} className="space-y-1">
705                              <div className="flex items-center justify-between text-xs">
706                                <div className="flex items-center gap-1.5">
707                                  <div
708                                    className="h-2 w-2 rounded-full"
709                                    style={{ backgroundColor: item.color }}
710                                  />
711                                  <span className="font-medium">
712                                    {item.name}
713                                  </span>
714                                </div>
715                                <span className="font-medium">
716                                  {item.percentage}%
717                                </span>
718                              </div>
719                              <div className="relative h-2 w-full overflow-hidden rounded-full bg-gray-100/30">
720                                <div
721                                  className="absolute h-full rounded-full"
722                                  style={{
723                                    backgroundColor: item.color,
724                                    width: `${item.percentage}%`,
725                                  }}
726                                />
727                              </div>
728                            </div>
729                          ))}
730                        </div>
731                      </div>
732                    )}
733
734                    {profile.contributionData &&
735                      profile.contributionData.length > 0 && (
736                        <div>
737                          <h3
738                            className={cn(
739                              "mb-2 text-xs font-medium uppercase text-muted-foreground",
740                              currentTheme.textMuted
741                            )}
742                          >
743                            Activity
744                          </h3>
745                          <div
746                            className={cn(
747                              "h-[40px] w-full overflow-hidden rounded-md p-2",
748                              currentTheme.textMuted
749                            )}
750                          >
751                            <svg
752                              className={cn(
753                                "h-full w-full",
754                                currentTheme.textMuted
755                              )}
756                              viewBox="0 0 100 30"
757                              preserveAspectRatio="none"
758                            >
759                              <polyline
760                                points={profile.contributionData
761                                  .map(
762                                    (value, index) =>
763                                      `${
764                                        index *
765                                        (100 /
766                                          (profile.contributionData?.length ||
767                                            1))
768                                      },${30 - value * 30}`
769                                  )
770                                  .join(" ")}
771                                fill="none"
772                                stroke="currentColor"
773                                strokeWidth="1.5"
774                                strokeLinecap="round"
775                                strokeLinejoin="round"
776                                className={currentTheme.graphColor}
777                              />
778                              <path
779                                d={`M0,30 ${profile.contributionData
780                                  .map(
781                                    (value, index) =>
782                                      `L${
783                                        index *
784                                        (100 /
785                                          (profile.contributionData?.length ||
786                                            1))
787                                      },${30 - value * 30}`
788                                  )
789                                  .join(" ")} L100,30 Z`}
790                                fill="currentColor"
791                                className={currentTheme.graphBgColor}
792                              />
793                            </svg>
794                          </div>
795                        </div>
796                      )}
797                  </TabsContent>
798                  <TabsContent value="repositories" className="mt-3">
799                    {profile.pinnedRepos && profile.pinnedRepos.length > 0 ? (
800                      <div className={cn("space-y-3", currentTheme.textMuted)}>
801                        {profile.pinnedRepos.slice(0, 2).map((repo) => (
802                          <div key={repo.name}>
803                            <Card
804                              className={cn(
805                                "transition-all duration-300 hover:shadow-sm hover:translate-y-[-2px]",
806                                currentTheme.cardBg,
807                                currentTheme.cardBorder
808                              )}
809                            >
810                              <CardContent className="p-2">
811                                <div
812                                  className={cn(
813                                    "flex items-start justify-between",
814                                    currentTheme.textMuted
815                                  )}
816                                >
817                                  <div className="space-y-1">
818                                    <div className="flex items-center gap-1">
819                                      <BookOpen
820                                        className={cn(
821                                          "h-3.5 w-3.5 text-muted-foreground",
822                                          currentTheme.textMuted
823                                        )}
824                                      />
825                                      <Link
826                                        href={`https://github.com/${profile.login}/${repo.name}`}
827                                        target="_blank"
828                                        className={cn(
829                                          "text-sm font-medium hover:underline",
830                                          currentTheme.accentColor
831                                        )}
832                                      >
833                                        {repo.name}
834                                      </Link>
835                                    </div>
836                                    {repo.description && (
837                                      <p
838                                        className={cn(
839                                          "line-clamp-1 text-xs text-muted-foreground",
840                                          currentTheme.textMuted
841                                        )}
842                                      >
843                                        {repo.description}
844                                      </p>
845                                    )}
846                                  </div>
847                                  <Button
848                                    variant="ghost"
849                                    size="icon"
850                                    className="h-6 w-6"
851                                    asChild
852                                  >
853                                    <Link
854                                      href={`https://github.com/${profile.login}/${repo.name}`}
855                                      target="_blank"
856                                    >
857                                      <ExternalLink className="h-3 w-3" />
858                                    </Link>
859                                  </Button>
860                                </div>
861                                <div
862                                  className={cn(
863                                    "mt-2 flex items-center justify-between",
864                                    currentTheme.textMuted
865                                  )}
866                                >
867                                  {repo.language && (
868                                    <div className="flex items-center gap-1.5">
869                                      <div
870                                        className="h-2 w-2 rounded-full"
871                                        style={{
872                                          backgroundColor: repo.languageColor,
873                                        }}
874                                      />
875                                      <span className="text-xs">
876                                        {repo.language}
877                                      </span>
878                                    </div>
879                                  )}
880                                  <div
881                                    className={cn(
882                                      "flex items-center gap-3 text-xs text-muted-foreground",
883                                      currentTheme.textMuted
884                                    )}
885                                  >
886                                    <div
887                                      className={cn(
888                                        "flex items-center gap-1",
889                                        currentTheme.textMuted
890                                      )}
891                                    >
892                                      <Star
893                                        className={cn(
894                                          "h-3 w-3",
895                                          currentTheme.textMuted
896                                        )}
897                                      />
898                                      <span
899                                        className={cn(
900                                          "text-xs",
901                                          currentTheme.textMuted
902                                        )}
903                                      >
904                                        {repo.stars}
905                                      </span>
906                                    </div>
907                                    <div
908                                      className={cn(
909                                        "flex items-center gap-1",
910                                        currentTheme.textMuted
911                                      )}
912                                    >
913                                      <GitFork
914                                        className={cn(
915                                          "h-3 w-3",
916                                          currentTheme.textMuted
917                                        )}
918                                      />
919                                      <span
920                                        className={cn(
921                                          "text-xs",
922                                          currentTheme.textMuted
923                                        )}
924                                      >
925                                        {repo.forks}
926                                      </span>
927                                    </div>
928                                  </div>
929                                </div>
930                              </CardContent>
931                            </Card>
932                          </div>
933                        ))}
934                        {profile.pinnedRepos.length > 2 && (
935                          <div className="text-center">
936                            <Button
937                              variant="ghost"
938                              size="sm"
939                              className={cn(
940                                "text-xs",
941                                currentTheme.accentColor
942                              )}
943                              asChild
944                            >
945                              <Link
946                                href={`https://github.com/${profile.login}?tab=repositories`}
947                                target="_blank"
948                              >
949                                View all repositories
950                              </Link>
951                            </Button>
952                          </div>
953                        )}
954                      </div>
955                    ) : (
956                      <div
957                        className={cn(
958                          "flex h-20 items-center justify-center text-sm text-muted-foreground",
959                          currentTheme.textMuted
960                        )}
961                      >
962                        No repositories available
963                      </div>
964                    )}
965                  </TabsContent>
966                </Tabs>
967              </div>
968            </CardContent>
969            <CardFooter
970              className={cn(
971                "flex items-center justify-between border-t px-3 text-xs",
972                currentTheme.id === "github-light" && "border-gray-100",
973                currentTheme.id === "github-dark" && "border-gray-800",
974                currentTheme.id === "ocean" && "border-blue-100",
975                currentTheme.id === "forest" && "border-green-100",
976                currentTheme.id === "sunset" && "border-orange-100"
977              )}
978            >
979              <span
980                className={cn("text-muted-foreground", currentTheme.textMuted)}
981              >
982                {getYearsOnGitHub(profile.createdAt)} years on GitHub
983              </span>
984              <TooltipProvider>
985                <Tooltip>
986                  <TooltipTrigger asChild>
987                    <Button
988                      variant="ghost"
989                      size="icon"
990                      className="h-6 w-6"
991                      asChild
992                    >
993                      <Link
994                        href={`https://github.com/${profile.login}`}
995                        target="_blank"
996                      >
997                        <Github
998                          className={cn("h-3.5 w-3.5", currentTheme.textMuted)}
999                        />
1000                      </Link>
1001                    </Button>
1002                  </TooltipTrigger>
1003                  <TooltipContent>
1004                    <p>View full profile</p>
1005                  </TooltipContent>
1006                </Tooltip>
1007              </TooltipProvider>
1008            </CardFooter>
1009          </Card>
1010        </div>
1011      )}
1012    </>
1013  );
1014}
1015
4

Final Steps

Update Import Paths

Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button to match your UI components location.

Props

NameTypeDefaultDescription
usernamestringundefinedGitHub username to fetch profile data for. Required when not using manualMode.
githubTokenstringundefinedOptional GitHub API token for increased rate limits (unauthenticated: 60/hr, authenticated: 5,000/hr). Store securely using environment variables.
manualModebooleanfalseWhen true, uses provided profileData instead of fetching from GitHub API. Useful for avoiding rate limits or displaying custom profile data.
profileDataManualProfileDataundefinedProfile data object for manual mode. Required when manualMode is true. Includes fields for username, name, followers, etc.
themeIdstringgithub-lightVisual theme for the card. Options: github-light, github-dark, ocean, forest, sunset, nuvyx. Some themes support automatic light/dark mode switching.

Examples

JO

John Doe

johndoe
Follow

Frontend developer passionate about React and UI/UX design

San Francisco, CA
Joined Mar 2015
1,250followers
320following
45repos

Top Languages

JavaScript
45%
TypeScript
35%
CSS
20%

Activity

10 years on GitHub
JA

Jane Smith

janesmith
Follow

Backend engineer working with Node.js and Go

Austin, TX
Joined Jun 2017
830followers
145following
32repos

Top Languages

TypeScript
50%
Go
30%
Python
20%

Activity

7 years on GitHub
DE

Developer Smith

devsmith
Follow

Full-stack developer specializing in React & Node.js

Seattle, WA
Joined Jan 2018
620followers
210following
29repos

Top Languages

JavaScript
40%
TypeScript
35%
Python
25%

Activity

7 years on GitHub
TE

Tech Guru

techguru
Follow

Software architect focused on scalable systems

Boston, MA
Joined Jul 2016
1,850followers
105following
38repos

Top Languages

Java
45%
Kotlin
30%
Rust
25%

Activity

8 years on GitHub
CO

Code Artist

codeartist
Follow

UI/UX designer who codes with React & CSS wizardry

Portland, OR
Joined Mar 2019
740followers
280following
25repos

Top Languages

TypeScript
40%
CSS
35%
HTML
25%

Activity

6 years on GitHub
NI

Night Coder

nightcoder
Follow

DevOps engineer passionate about automation and cloud infrastructure

Amsterdam, Netherlands
Joined Dec 2017
920followers
125following
31repos

Top Languages

Python
45%
Go
30%
Shell
25%

Activity

7 years on GitHub