feat: add contributors page to showcase OpenCut contributors and their contributions
This commit is contained in:
277
apps/web/src/app/contributors/page.tsx
Normal file
277
apps/web/src/app/contributors/page.tsx
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
|
import { Header } from "@/components/header";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { GithubIcon } from "@/components/icons";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Contributors - OpenCut",
|
||||||
|
description:
|
||||||
|
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
|
||||||
|
openGraph: {
|
||||||
|
title: "Contributors - OpenCut",
|
||||||
|
description:
|
||||||
|
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Contributor {
|
||||||
|
id: number;
|
||||||
|
login: string;
|
||||||
|
avatar_url: string;
|
||||||
|
html_url: string;
|
||||||
|
contributions: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getContributors(): Promise<Contributor[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
"https://api.github.com/repos/OpenCut-app/OpenCut/contributors",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "OpenCut-Web-App",
|
||||||
|
},
|
||||||
|
next: { revalidate: 600 }, // Cache for 10 minutes
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error("Failed to fetch contributors");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const contributors = await response.json();
|
||||||
|
|
||||||
|
// Filter out bots and add additional contributor info if needed
|
||||||
|
const filteredContributors = contributors.filter(
|
||||||
|
(contributor: any) => contributor.type === "User"
|
||||||
|
);
|
||||||
|
|
||||||
|
return filteredContributors;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching contributors:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ContributorsPage() {
|
||||||
|
const contributors = await getContributors();
|
||||||
|
const topContributor = contributors[0];
|
||||||
|
const otherContributors = contributors.slice(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main className="relative">
|
||||||
|
{/* Background decoration */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
|
||||||
|
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative container mx-auto px-4 py-16">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
{/* Hero Section */}
|
||||||
|
<div className="text-center mb-20">
|
||||||
|
<div className="inline-flex items-center gap-2 bg-muted/50 text-muted-foreground px-3 py-1 rounded-full text-sm mb-6">
|
||||||
|
<GithubIcon className="h-3 w-3" />
|
||||||
|
Open Source
|
||||||
|
</div>
|
||||||
|
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6 bg-gradient-to-br from-foreground to-muted-foreground bg-clip-text text-transparent">
|
||||||
|
Contributors
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
|
||||||
|
Meet the amazing developers who are building the future of video
|
||||||
|
editing
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Quick stats */}
|
||||||
|
<div className="flex items-center justify-center gap-8 text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 bg-foreground rounded-full" />
|
||||||
|
<span className="font-medium">{contributors.length}</span>
|
||||||
|
<span className="text-muted-foreground">contributors</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 bg-foreground rounded-full" />
|
||||||
|
<span className="font-medium">
|
||||||
|
{contributors.reduce((sum, c) => sum + c.contributions, 0)}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground">contributions</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top Contributor Spotlight */}
|
||||||
|
{topContributor && (
|
||||||
|
<div className="mb-20">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-2xl font-semibold mb-2">
|
||||||
|
Top Contributor
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Leading the way in contributions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={topContributor.html_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="group block"
|
||||||
|
>
|
||||||
|
<div className="relative mx-auto max-w-md">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-muted/50 to-muted/30 rounded-2xl blur group-hover:blur-md transition-all duration-300" />
|
||||||
|
<Card className="relative bg-background/80 backdrop-blur-sm border-2 group-hover:border-muted-foreground/20 transition-all duration-300 group-hover:shadow-xl">
|
||||||
|
<CardContent className="p-8 text-center">
|
||||||
|
<div className="relative mb-6">
|
||||||
|
<Avatar className="h-24 w-24 mx-auto ring-4 ring-background shadow-2xl">
|
||||||
|
<AvatarImage
|
||||||
|
src={topContributor.avatar_url}
|
||||||
|
alt={`${topContributor.login}'s avatar`}
|
||||||
|
/>
|
||||||
|
<AvatarFallback className="text-lg font-semibold">
|
||||||
|
{topContributor.login.charAt(0).toUpperCase()}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="absolute -top-2 -right-2 bg-foreground text-background rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2 group-hover:text-foreground/80 transition-colors">
|
||||||
|
{topContributor.login}
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-center justify-center gap-2 text-muted-foreground">
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{topContributor.contributions}
|
||||||
|
</span>
|
||||||
|
<span>contributions</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Other Contributors */}
|
||||||
|
{otherContributors.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-2xl font-semibold mb-2">
|
||||||
|
All Contributors
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Everyone who makes OpenCut better
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
||||||
|
{otherContributors.map((contributor, index) => (
|
||||||
|
<Link
|
||||||
|
key={contributor.id}
|
||||||
|
href={contributor.html_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="group block"
|
||||||
|
style={{
|
||||||
|
animationDelay: `${index * 50}ms`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-center p-4 rounded-xl hover:bg-muted/50 transition-all duration-300 group-hover:scale-105">
|
||||||
|
<Avatar className="h-16 w-16 mx-auto mb-3 ring-2 ring-transparent group-hover:ring-muted-foreground/20 transition-all duration-300">
|
||||||
|
<AvatarImage
|
||||||
|
src={contributor.avatar_url}
|
||||||
|
alt={`${contributor.login}'s avatar`}
|
||||||
|
/>
|
||||||
|
<AvatarFallback className="font-medium">
|
||||||
|
{contributor.login.charAt(0).toUpperCase()}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<h3 className="font-medium text-sm truncate group-hover:text-foreground transition-colors mb-1">
|
||||||
|
{contributor.login}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{contributor.contributions}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{contributors.length === 0 && (
|
||||||
|
<div className="text-center py-20">
|
||||||
|
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-muted/50 flex items-center justify-center">
|
||||||
|
<GithubIcon className="h-8 w-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-medium mb-3">
|
||||||
|
No contributors found
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mb-8 max-w-md mx-auto">
|
||||||
|
Unable to load contributors at the moment. Check back later or
|
||||||
|
view on GitHub.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="https://github.com/OpenCut-app/OpenCut/graphs/contributors"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button variant="outline" className="gap-2">
|
||||||
|
<GithubIcon className="h-4 w-4" />
|
||||||
|
View on GitHub
|
||||||
|
<ExternalLink className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<div className="mt-32 text-center">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<h2 className="text-3xl font-bold mb-4">Join the community</h2>
|
||||||
|
<p className="text-lg text-muted-foreground mb-10 leading-relaxed">
|
||||||
|
OpenCut is built by developers like you. Every contribution,
|
||||||
|
no matter how small, helps make video editing more accessible
|
||||||
|
for everyone.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Link
|
||||||
|
href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button size="lg" className="gap-2 group">
|
||||||
|
<GithubIcon className="h-4 w-4 group-hover:scale-110 transition-transform" />
|
||||||
|
Start Contributing
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="https://github.com/OpenCut-app/OpenCut/issues"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button variant="outline" size="lg" className="gap-2 group">
|
||||||
|
Browse Issues
|
||||||
|
<ExternalLink className="h-4 w-4 group-hover:scale-110 transition-transform" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -36,19 +36,17 @@ export function Header() {
|
|||||||
|
|
||||||
const rightContent = (
|
const rightContent = (
|
||||||
<nav className="flex items-center">
|
<nav className="flex items-center">
|
||||||
<Link href="https://github.com/OpenCut-app/OpenCut" target="_blank">
|
<Link href="/contributors">
|
||||||
<Button
|
<Button variant="text" className="text-sm">
|
||||||
variant="text"
|
Contributors
|
||||||
className="flex items-center text-sm text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
<span className="hidden sm:inline">GitHub</span>
|
|
||||||
<span className="text-foreground flex items-center">
|
|
||||||
{star}+
|
|
||||||
<Star className="w-4 h-4 ml-1" />
|
|
||||||
</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={session ? "/editor" : "/login"}>
|
<Link href="https://github.com/OpenCut-app/OpenCut" target="_blank">
|
||||||
|
<Button variant="text" className="text-sm">
|
||||||
|
GitHub
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href={session ? "/editor" : "/auth/login"}>
|
||||||
<Button size="sm" className="text-sm ml-4">
|
<Button size="sm" className="text-sm ml-4">
|
||||||
Start editing
|
Start editing
|
||||||
<ArrowRight className="h-4 w-4" />
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
@ -20,3 +20,20 @@ export function GoogleIcon({ className }: { className?: string }) {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GithubIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
viewBox="0 -3.5 256 256"
|
||||||
|
preserveAspectRatio="xMinYMin meet"
|
||||||
|
>
|
||||||
|
<g fill="#161614">
|
||||||
|
<path d="M127.505 0C57.095 0 0 57.085 0 127.505c0 56.336 36.534 104.13 87.196 120.99 6.372 1.18 8.712-2.766 8.712-6.134 0-3.04-.119-13.085-.173-23.739-35.473 7.713-42.958-15.044-42.958-15.044-5.8-14.738-14.157-18.656-14.157-18.656-11.568-7.914.872-7.752.872-7.752 12.804.9 19.546 13.14 19.546 13.14 11.372 19.493 29.828 13.857 37.104 10.6 1.144-8.242 4.449-13.866 8.095-17.05-28.32-3.225-58.092-14.158-58.092-63.014 0-13.92 4.981-25.295 13.138-34.224-1.324-3.212-5.688-16.18 1.235-33.743 0 0 10.707-3.427 35.073 13.07 10.17-2.826 21.078-4.242 31.914-4.29 10.836.048 21.752 1.464 31.942 4.29 24.337-16.497 35.029-13.07 35.029-13.07 6.94 17.563 2.574 30.531 1.25 33.743 8.175 8.929 13.122 20.303 13.122 34.224 0 48.972-29.828 59.756-58.22 62.912 4.573 3.957 8.648 11.717 8.648 23.612 0 17.06-.148 30.791-.148 34.991 0 3.393 2.295 7.369 8.759 6.117 50.634-16.879 87.122-64.656 87.122-120.973C255.009 57.085 197.922 0 127.505 0" />
|
||||||
|
|
||||||
|
<path d="M47.755 181.634c-.28.633-1.278.823-2.185.389-.925-.416-1.445-1.28-1.145-1.916.275-.652 1.273-.834 2.196-.396.927.415 1.455 1.287 1.134 1.923M54.027 187.23c-.608.564-1.797.302-2.604-.589-.834-.889-.99-2.077-.373-2.65.627-.563 1.78-.3 2.616.59.834.899.996 2.08.36 2.65M58.33 194.39c-.782.543-2.06.034-2.849-1.1-.781-1.133-.781-2.493.017-3.038.792-.545 2.05-.055 2.85 1.07.78 1.153.78 2.513-.019 3.069M65.606 202.683c-.699.77-2.187.564-3.277-.488-1.114-1.028-1.425-2.487-.724-3.258.707-.772 2.204-.555 3.302.488 1.107 1.026 1.445 2.496.7 3.258M75.01 205.483c-.307.998-1.741 1.452-3.185 1.028-1.442-.437-2.386-1.607-2.095-2.616.3-1.005 1.74-1.478 3.195-1.024 1.44.435 2.386 1.596 2.086 2.612M85.714 206.67c.036 1.052-1.189 1.924-2.705 1.943-1.525.033-2.758-.818-2.774-1.852 0-1.062 1.197-1.926 2.721-1.951 1.516-.03 2.758.815 2.758 1.86M96.228 206.267c.182 1.026-.872 2.08-2.377 2.36-1.48.27-2.85-.363-3.039-1.38-.184-1.052.89-2.105 2.367-2.378 1.508-.262 2.857.355 3.049 1.398" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user