Skip to content
Open
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
203 changes: 140 additions & 63 deletions src/pages/Contributors/Contributors.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useMemo } from "react";
import {
Container,
Grid,
Expand All @@ -10,6 +10,11 @@ import {
Box,
CircularProgress,
Alert,
TextField,
MenuItem,
Select,
FormControl,
InputLabel,
} from "@mui/material";
import { FaGithub } from "react-icons/fa";
import { Link } from "react-router-dom";
Expand All @@ -22,14 +27,18 @@ interface Contributor {
avatar_url: string;
contributions: number;
html_url: string;
type?: string;
}

const ContributorsPage = () => {
const [contributors, setContributors] = useState<Contributor[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [search, setSearch] = useState("");
const [sortOrder, setSortOrder] = useState("desc");
const [minPRs, setMinPRs] = useState<string>("");
const [prType, setPrType] = useState<string>("all");

// Fetch contributors from GitHub API
useEffect(() => {
const fetchContributors = async () => {
try {
Expand All @@ -43,10 +52,32 @@ const ContributorsPage = () => {
setLoading(false);
}
};

fetchContributors();
}, []);

const handleClearFilters = () => {
setSearch("");
setSortOrder("desc");
setMinPRs("");
setPrType("all");
};

const filtered = useMemo(() => {
return contributors
.filter((c) => {
const matchesSearch = c.login.toLowerCase().includes(search.toLowerCase());
const matchesMinPR = minPRs === "" || c.contributions >= parseInt(minPRs, 10);
const matchesType = prType === "all" || (c.type && c.type.toLowerCase() === prType.toLowerCase());
return matchesSearch && matchesMinPR && matchesType;
})
.sort((a, b) => {
if (sortOrder === "desc") {
return b.contributions - a.contributions;
}
return a.contributions - b.contributions;
});
}, [contributors, search, sortOrder, minPRs, prType]);

if (loading) {
return (
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
Expand All @@ -70,69 +101,115 @@ const ContributorsPage = () => {
🤝 Contributors
</Typography>

<Box sx={{ display: "flex", gap: 2, mb: 4, flexWrap: "wrap", alignItems: "center" }}>
<TextField
label="Search by username"
variant="outlined"
size="small"
value={search}
onChange={(e) => setSearch(e.target.value)}
sx={{ flex: 1, minWidth: 200 }}
/>
<TextField
label="Min PR Count"
type="number"
variant="outlined"
size="small"
value={minPRs}
onChange={(e) => setMinPRs(e.target.value)}
sx={{ width: 130 }}
/>
<FormControl size="small" sx={{ minWidth: 150 }}>
<InputLabel>PR Type</InputLabel>
<Select
value={prType}
label="PR Type"
onChange={(e) => setPrType(e.target.value)}
>
<MenuItem value="all">All Types</MenuItem>
<MenuItem value="merged">Merged</MenuItem>
<MenuItem value="open">Open</MenuItem>
<MenuItem value="closed">Closed</MenuItem>
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 180 }}>
<InputLabel>Sort by Contributions</InputLabel>
<Select
value={sortOrder}
label="Sort by Contributions"
onChange={(e) => setSortOrder(e.target.value)}
>
<MenuItem value="desc">Most to Least</MenuItem>
<MenuItem value="asc">Least to Most</MenuItem>
</Select>
</FormControl>
<Button
variant="outlined"
color="secondary"
onClick={handleClearFilters}
sx={{ textTransform: "none", height: 40 }}
>
Clear Filters
</Button>
</Box>

<Typography variant="body2" sx={{ mb: 2 }} color="text.secondary">
Showing {filtered.length} of {contributors.length} contributors
</Typography>

<Grid container spacing={4}>
{contributors.map((contributor) => (
{filtered.map((contributor) => (
<Grid item xs={12} sm={6} md={3} key={contributor.id}>
<Card
sx={{
textAlign: "center",
p: 2,
borderRadius: "10px",
border: "1px solid #E0E0E0",
boxShadow: "0 4px 10px rgba(0,0,0,0.1)",
transition: "transform 0.3s ease-in-out",
"&:hover": {
transform: "scale(1.05)",
boxShadow: "0 8px 15px rgba(0,0,0,0.2)",
borderColor: "#C0C0C0",
outlineColor: "#B3B3B3",
},
}}
<Card
sx={{
textAlign: "center",
p: 2,
borderRadius: "10px",
border: "1px solid #E0E0E0",
boxShadow: "0 4px 10px rgba(0,0,0,0.1)",
transition: "transform 0.3s ease-in-out",
"&:hover": {
transform: "scale(1.05)",
boxShadow: "0 8px 15px rgba(0,0,0,0.2)",
borderColor: "#C0C0C0",
},
}}
>
<Link
to={`/contributor/${contributor.login}`}
style={{ textDecoration: "none" }}
>
<Link
to={`/contributor/${contributor.login}`}
style={{ textDecoration: "none" }}
>
<Avatar
src={contributor.avatar_url}
alt={contributor.login}
sx={{ width: 100, height: 100, mx: "auto", mb: 2 }}
/>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
{contributor.login}
</Typography>

<Typography variant="body2" color="text.secondary">
{contributor.contributions} Contributions
</Typography>
{/*
<Typography variant="body2" sx={{ mt: 2 }}>
Thank you for your valuable contributions to our
community!
</Typography> */}
</CardContent>
</Link>

<Box sx={{ mt: 2 }}>
<Button
variant="contained"
startIcon={<FaGithub />}
href={contributor.html_url}
target="_blank"
sx={{
backgroundColor: "#333333",
textTransform: "none",
color: "#FFFFFF",
"&:hover": {
backgroundColor: "#555555",
},
}}
>
GitHub
</Button>
</Box>
</Card>
<Avatar
src={contributor.avatar_url}
alt={contributor.login}
sx={{ width: 100, height: 100, mx: "auto", mb: 2 }}
/>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
{contributor.login}
</Typography>
<Typography variant="body2" color="text.secondary">
{contributor.contributions} Contributions
</Typography>
</CardContent>
</Link>
<Box sx={{ mt: 2 }}>
<Button
variant="contained"
startIcon={<FaGithub />}
href={contributor.html_url}
target="_blank"
sx={{
backgroundColor: "#333333",
textTransform: "none",
color: "#FFFFFF",
"&:hover": { backgroundColor: "#555555" },
}}
>
GitHub
</Button>
</Box>
</Card>
</Grid>
))}
</Grid>
Expand Down