Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export function Sidebar() {
<aside
className={`
fixed top-0 left-0 bg-sidebar border-r border-sidebar-border z-50 transform transition-all duration-300 ease-in-out
${sidebarCollapsed ? "w-16" : "w-64"}
${sidebarCollapsed ? "lg:w-16 w-64" : "w-64"}
${isOpen ? "translate-x-0" : "-translate-x-full"}
lg:translate-x-0
flex flex-col
Expand Down Expand Up @@ -218,10 +218,11 @@ export function Sidebar() {
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
}`}
title={sidebarCollapsed ? "Dashboard" : ""}
aria-label="Dashboard"
>
<Home className="w-5 h-5" />
<Home className="w-5 h-5" aria-hidden="true" />
{!sidebarCollapsed && <span>Dashboard</span>}
{sidebarCollapsed && <span className="sr-only">Dashboard</span>}
</Link>

<div>
Expand All @@ -234,9 +235,10 @@ export function Sidebar() {
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
}`}
title="Action Required"
aria-label="Action Required"
>
<Zap className="w-5 h-5" />
<Zap className="w-5 h-5" aria-hidden="true" />
<span className="sr-only">Action Required</span>
</Link>
) : (
<>
Expand Down Expand Up @@ -347,9 +349,10 @@ export function Sidebar() {
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
}`}
title="Quick Wins"
aria-label="Quick Wins"
>
<Target className="w-5 h-5" />
<Target className="w-5 h-5" aria-hidden="true" />
<span className="sr-only">Quick Wins</span>
</Link>
) : (
<>
Expand Down Expand Up @@ -437,10 +440,11 @@ export function Sidebar() {
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
}`}
title={sidebarCollapsed ? "Settings" : ""}
aria-label="Settings"
>
<Wrench className="w-5 h-5" />
<Wrench className="w-5 h-5" aria-hidden="true" />
{!sidebarCollapsed && <span>Settings</span>}
{sidebarCollapsed && <span className="sr-only">Settings</span>}
</Link>

<Link
Expand All @@ -451,10 +455,11 @@ export function Sidebar() {
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
}`}
title={sidebarCollapsed ? "Favorites" : ""}
aria-label="Favorites"
>
<Star className="w-5 h-5" />
<Star className="w-5 h-5" aria-hidden="true" />
{!sidebarCollapsed && <span>Favorites</span>}
{sidebarCollapsed && <span className="sr-only">Favorites</span>}
</Link>
{!sidebarCollapsed && (
<>
Expand Down Expand Up @@ -520,13 +525,16 @@ export function Sidebar() {
size="sm"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className={`w-full ${sidebarCollapsed ? "justify-center px-2" : "justify-start"} text-sm text-muted-foreground hover:text-sidebar-foreground hover:bg-sidebar-accent/50 hidden lg:flex`}
title={sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
aria-label={sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
>
{sidebarCollapsed ? (
<PanelLeftOpen className="w-4 h-4" />
<>
<PanelLeftOpen className="w-4 h-4" aria-hidden="true" />
<span className="sr-only">Expand sidebar</span>
</>
) : (
<>
<PanelLeftClose className="w-4 h-4 mr-2" />
<PanelLeftClose className="w-4 h-4 mr-2" aria-hidden="true" />
<span>Collapse</span>
</>
)}
Expand All @@ -546,12 +554,18 @@ export function Sidebar() {
onClick={handleLogout}
disabled={isLoggingOut}
className={`w-full ${sidebarCollapsed ? "justify-center px-2" : "justify-start"} text-sm text-muted-foreground hover:text-sidebar-foreground hover:bg-sidebar-accent/50 disabled:opacity-50`}
title={sidebarCollapsed ? "Logout" : ""}
aria-label={isLoggingOut ? "Logging out..." : "Logout"}
>
{isLoggingOut ? (
<Loader2 className="w-4 h-4 animate-spin" />
<>
<Loader2 className="w-4 h-4 animate-spin" aria-hidden="true" />
{sidebarCollapsed && <span className="sr-only">Logging out...</span>}
</>
) : (
<LogOut className="w-4 h-4" />
<>
<LogOut className="w-4 h-4" aria-hidden="true" />
{sidebarCollapsed && <span className="sr-only">Logout</span>}
</>
)}
{!sidebarCollapsed && !isLoggingOut && <span className="ml-2">Logout</span>}
{!sidebarCollapsed && isLoggingOut && <span className="ml-2">Logging out...</span>}
Expand Down
22 changes: 3 additions & 19 deletions src/components/ui/user-hover-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,15 @@ import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { MapPin, Building2, Link as LinkIcon, Calendar, Award } from "lucide-react";
import { githubAPIClient } from "@/lib/api/github-api-client";

export interface UserProfile {
login: string;
name: string | null;
avatar_url: string;
bio: string | null;
company: string | null;
location: string | null;
blog: string | null;
twitter_username: string | null;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: string;
html_url: string;
}
import type { GitHubUserDetailed } from "@/types/github";

interface UserHoverCardProps {
username: string;
children: React.ReactNode;
showScore?: boolean;
}

function calculateOpenSourceScore(profile: UserProfile, contributions?: { commits: number; prs: number; stars: number }): number {
function calculateOpenSourceScore(profile: GitHubUserDetailed, contributions?: { commits: number; prs: number; stars: number }): number {
const commitsScore = (contributions?.commits || 0) * 2;
const prsScore = (contributions?.prs || 0) * 5;
const starsScore = contributions?.stars || 0;
Expand All @@ -44,7 +28,7 @@ function calculateOpenSourceScore(profile: UserProfile, contributions?: { commit
}

export function UserHoverCard({ username, children, showScore = false }: UserHoverCardProps) {
const [profile, setProfile] = useState<UserProfile | null>(null);
const [profile, setProfile] = useState<GitHubUserDetailed | null>(null);
const [loading, setLoading] = useState(false);
const [score, setScore] = useState<number | null>(null);

Expand Down
26 changes: 22 additions & 4 deletions src/lib/api/github-api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ class GitHubAPIClient {
}
}

private getRequestHeaders(): HeadersInit {
const headers: HeadersInit = {
Accept: "application/vnd.github.v3+json",
"User-Agent": "GitHubMon/1.0",
};
if (this.githubToken) {
headers["Authorization"] = `Bearer ${this.githubToken}`;
}
return headers;
}

private async fetchWithCache<T>(
endpoint: string,
useGithub = false,
Expand Down Expand Up @@ -858,7 +869,14 @@ class GitHubAPIClient {
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);

const eventsEndpoint = `/users/${username}/events/public?per_page=100`;
const events = await this.fetchWithCache<Array<{ type: string; created_at: string }>>(
const events = await this.fetchWithCache<Array<{
type: string;
created_at: string;
payload?: {
commits?: unknown[];
size?: number;
};
}>>(
eventsEndpoint,
true
);
Expand All @@ -872,16 +890,16 @@ class GitHubAPIClient {
if (eventDate < threeMonthsAgo) continue;

if (event.type === "PushEvent") {
commits += 1;
commits += event.payload?.commits?.length || event.payload?.size || 1;
} else if (event.type === "PullRequestEvent") {
prs += 1;
}
}
}

const starredEndpoint = `/users/${username}/starred?per_page=1`;
const response = await fetch(`https://api.github.com${starredEndpoint}`, {
headers: this.getHeaders(),
const response = await fetch(`${this.baseUrl}${starredEndpoint}`, {
headers: this.getRequestHeaders(),
});
const linkHeader = response.headers.get("link");
let stars = 0;
Expand Down