diff options
Diffstat (limited to 'apps/web/src/app/contributors')
| -rw-r--r-- | apps/web/src/app/contributors/page.tsx | 484 |
1 files changed, 242 insertions, 242 deletions
diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx index 21703852..64792632 100644 --- a/apps/web/src/app/contributors/page.tsx +++ b/apps/web/src/app/contributors/page.tsx @@ -1,242 +1,242 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { GitHubContributeSection } from "@/components/gitHub-contribute-section"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Card, CardContent } from "@/components/ui/card"; -import { EXTERNAL_TOOLS } from "@/constants/site-constants"; -import { BasePage } from "../base-page"; - -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?per_page=100", - { - headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": "OpenCut-Web-App", - }, - next: { revalidate: 600 }, // 10 minutes - }, - ); - - if (!response.ok) { - console.error("Failed to fetch contributors"); - return []; - } - - const contributors = (await response.json()) as Contributor[]; - - const filteredContributors = contributors.filter( - (contributor) => contributor.type === "User", - ); - - return filteredContributors; - } catch (error) { - console.error("Error fetching contributors:", error); - return []; - } -} - -export default async function ContributorsPage() { - const contributors = await getContributors(); - const topContributors = contributors.slice(0, 2); - const otherContributors = contributors.slice(2); - const totalContributions = contributors.reduce( - (sum, c) => sum + c.contributions, - 0, - ); - - return ( - <BasePage - title="Contributors" - description="Meet the amazing people who contribute to OpenCut, the free and open-source video editor." - > - <div className="-mt-4 flex items-center justify-center gap-8 text-sm"> - <StatItem value={contributors.length} label="contributors" /> - <StatItem value={totalContributions} label="contributions" /> - </div> - - <div className="mx-auto flex max-w-6xl flex-col gap-20"> - {topContributors.length > 0 && ( - <TopContributorsSection contributors={topContributors} /> - )} - {otherContributors.length > 0 && ( - <AllContributorsSection contributors={otherContributors} /> - )} - <ExternalToolsSection /> - <GitHubContributeSection - title="Join the community" - description="OpenCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone." - /> - </div> - </BasePage> - ); -} - -function StatItem({ value, label }: { value: number; label: string }) { - return ( - <div className="flex items-center gap-2"> - <div className="bg-foreground size-2 rounded-full" /> - <span className="font-medium">{value}</span> - <span className="text-muted-foreground">{label}</span> - </div> - ); -} - -function TopContributorsSection({ - contributors, -}: { - contributors: Contributor[]; -}) { - return ( - <div className="flex flex-col gap-10"> - <div className="flex flex-col gap-2 text-center"> - <h2 className="text-2xl font-semibold">Top contributors</h2> - <p className="text-muted-foreground"> - Leading the way in contributions - </p> - </div> - - <div className="mx-auto flex w-full max-w-xl flex-col justify-center gap-6 md:flex-row"> - {contributors.map((contributor) => ( - <TopContributorCard key={contributor.id} contributor={contributor} /> - ))} - </div> - </div> - ); -} - -function TopContributorCard({ contributor }: { contributor: Contributor }) { - return ( - <Link - href={contributor.html_url} - target="_blank" - rel="noopener noreferrer" - className="w-full" - > - <Card> - <CardContent className="flex flex-col gap-6 p-8 text-center"> - <Avatar className="mx-auto size-28"> - <AvatarImage - src={contributor.avatar_url} - alt={`${contributor.login}'s avatar`} - /> - <AvatarFallback className="text-lg font-semibold"> - {contributor.login.charAt(0).toUpperCase()} - </AvatarFallback> - </Avatar> - <div className="flex flex-col gap-2"> - <h3 className="text-xl font-semibold">{contributor.login}</h3> - <div className="flex items-center justify-center gap-2"> - <span className="font-medium">{contributor.contributions}</span> - <span className="text-muted-foreground">contributions</span> - </div> - </div> - </CardContent> - </Card> - </Link> - ); -} - -function AllContributorsSection({ - contributors, -}: { - contributors: Contributor[]; -}) { - return ( - <div className="flex flex-col gap-12"> - <div className="flex flex-col gap-2 text-center"> - <h2 className="text-2xl font-semibold">All contributors</h2> - <p className="text-muted-foreground"> - Everyone who makes OpenCut better - </p> - </div> - - <div className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"> - {contributors.map((contributor) => ( - <Link - key={contributor.id} - href={contributor.html_url} - target="_blank" - rel="noopener noreferrer" - className="opacity-100 hover:opacity-70" - > - <div className="flex flex-col items-center gap-2 p-2"> - <Avatar className="size-16"> - <AvatarImage - src={contributor.avatar_url} - alt={`${contributor.login}'s avatar`} - /> - <AvatarFallback> - {contributor.login.charAt(0).toUpperCase()} - </AvatarFallback> - </Avatar> - <div className="text-center"> - <h3 className="text-sm font-medium">{contributor.login}</h3> - <p className="text-muted-foreground text-xs"> - {contributor.contributions} - </p> - </div> - </div> - </Link> - ))} - </div> - </div> - ); -} - -function ExternalToolsSection() { - return ( - <div className="flex flex-col gap-10"> - <div className="flex flex-col gap-2 text-center"> - <h2 className="text-2xl font-semibold">External tools</h2> - <p className="text-muted-foreground">Tools we use to build OpenCut</p> - </div> - - <div className="mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2"> - {EXTERNAL_TOOLS.map((tool, index) => ( - <Link - key={tool.url} - href={tool.url} - target="_blank" - className="block" - style={{ animationDelay: `${index * 100}ms` }} - > - <Card className="h-full"> - <CardContent className="flex items-center justify-center h-full flex-col gap-4 p-6 text-center"> - <tool.icon className="size-8" /> - <div className="flex flex-1 flex-col gap-2"> - <h3 className="text-lg font-semibold">{tool.name}</h3> - <p className="text-muted-foreground text-sm"> - {tool.description} - </p> - </div> - </CardContent> - </Card> - </Link> - ))} - </div> - </div> - ); -} +import type { Metadata } from "next";
+import Link from "next/link";
+import { GitHubContributeSection } from "@/components/gitHub-contribute-section";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { Card, CardContent } from "@/components/ui/card";
+import { EXTERNAL_TOOLS } from "@/constants/site-constants";
+import { BasePage } from "../base-page";
+
+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?per_page=100",
+ {
+ headers: {
+ Accept: "application/vnd.github.v3+json",
+ "User-Agent": "OpenCut-Web-App",
+ },
+ next: { revalidate: 600 }, // 10 minutes
+ },
+ );
+
+ if (!response.ok) {
+ console.error("Failed to fetch contributors");
+ return [];
+ }
+
+ const contributors = (await response.json()) as Contributor[];
+
+ const filteredContributors = contributors.filter(
+ (contributor) => contributor.type === "User",
+ );
+
+ return filteredContributors;
+ } catch (error) {
+ console.error("Error fetching contributors:", error);
+ return [];
+ }
+}
+
+export default async function ContributorsPage() {
+ const contributors = await getContributors();
+ const topContributors = contributors.slice(0, 2);
+ const otherContributors = contributors.slice(2);
+ const totalContributions = contributors.reduce(
+ (sum, c) => sum + c.contributions,
+ 0,
+ );
+
+ return (
+ <BasePage
+ title="Contributors"
+ description="Meet the amazing people who contribute to OpenCut, the free and open-source video editor."
+ >
+ <div className="-mt-4 flex items-center justify-center gap-8 text-sm">
+ <StatItem value={contributors.length} label="contributors" />
+ <StatItem value={totalContributions} label="contributions" />
+ </div>
+
+ <div className="mx-auto flex max-w-6xl flex-col gap-20">
+ {topContributors.length > 0 && (
+ <TopContributorsSection contributors={topContributors} />
+ )}
+ {otherContributors.length > 0 && (
+ <AllContributorsSection contributors={otherContributors} />
+ )}
+ <ExternalToolsSection />
+ <GitHubContributeSection
+ title="Join the community"
+ description="OpenCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone."
+ />
+ </div>
+ </BasePage>
+ );
+}
+
+function StatItem({ value, label }: { value: number; label: string }) {
+ return (
+ <div className="flex items-center gap-2">
+ <div className="bg-foreground size-2 rounded-full" />
+ <span className="font-medium">{value}</span>
+ <span className="text-muted-foreground">{label}</span>
+ </div>
+ );
+}
+
+function TopContributorsSection({
+ contributors,
+}: {
+ contributors: Contributor[];
+}) {
+ return (
+ <div className="flex flex-col gap-10">
+ <div className="flex flex-col gap-2 text-center">
+ <h2 className="text-2xl font-semibold">Top contributors</h2>
+ <p className="text-muted-foreground">
+ Leading the way in contributions
+ </p>
+ </div>
+
+ <div className="mx-auto flex w-full max-w-xl flex-col justify-center gap-6 md:flex-row">
+ {contributors.map((contributor) => (
+ <TopContributorCard key={contributor.id} contributor={contributor} />
+ ))}
+ </div>
+ </div>
+ );
+}
+
+function TopContributorCard({ contributor }: { contributor: Contributor }) {
+ return (
+ <Link
+ href={contributor.html_url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="w-full"
+ >
+ <Card>
+ <CardContent className="flex flex-col gap-6 p-8 text-center">
+ <Avatar className="mx-auto size-28">
+ <AvatarImage
+ src={contributor.avatar_url}
+ alt={`${contributor.login}'s avatar`}
+ />
+ <AvatarFallback className="text-lg font-semibold">
+ {contributor.login.charAt(0).toUpperCase()}
+ </AvatarFallback>
+ </Avatar>
+ <div className="flex flex-col gap-2">
+ <h3 className="text-xl font-semibold">{contributor.login}</h3>
+ <div className="flex items-center justify-center gap-2">
+ <span className="font-medium">{contributor.contributions}</span>
+ <span className="text-muted-foreground">contributions</span>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ </Link>
+ );
+}
+
+function AllContributorsSection({
+ contributors,
+}: {
+ contributors: Contributor[];
+}) {
+ return (
+ <div className="flex flex-col gap-12">
+ <div className="flex flex-col gap-2 text-center">
+ <h2 className="text-2xl font-semibold">All contributors</h2>
+ <p className="text-muted-foreground">
+ Everyone who makes OpenCut better
+ </p>
+ </div>
+
+ <div className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
+ {contributors.map((contributor) => (
+ <Link
+ key={contributor.id}
+ href={contributor.html_url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="opacity-100 hover:opacity-70"
+ >
+ <div className="flex flex-col items-center gap-2 p-2">
+ <Avatar className="size-16">
+ <AvatarImage
+ src={contributor.avatar_url}
+ alt={`${contributor.login}'s avatar`}
+ />
+ <AvatarFallback>
+ {contributor.login.charAt(0).toUpperCase()}
+ </AvatarFallback>
+ </Avatar>
+ <div className="text-center">
+ <h3 className="text-sm font-medium">{contributor.login}</h3>
+ <p className="text-muted-foreground text-xs">
+ {contributor.contributions}
+ </p>
+ </div>
+ </div>
+ </Link>
+ ))}
+ </div>
+ </div>
+ );
+}
+
+function ExternalToolsSection() {
+ return (
+ <div className="flex flex-col gap-10">
+ <div className="flex flex-col gap-2 text-center">
+ <h2 className="text-2xl font-semibold">External tools</h2>
+ <p className="text-muted-foreground">Tools we use to build OpenCut</p>
+ </div>
+
+ <div className="mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2">
+ {EXTERNAL_TOOLS.map((tool, index) => (
+ <Link
+ key={tool.url}
+ href={tool.url}
+ target="_blank"
+ className="block"
+ style={{ animationDelay: `${index * 100}ms` }}
+ >
+ <Card className="h-full">
+ <CardContent className="flex items-center justify-center h-full flex-col gap-4 p-6 text-center">
+ <tool.icon className="size-8" />
+ <div className="flex flex-1 flex-col gap-2">
+ <h3 className="text-lg font-semibold">{tool.name}</h3>
+ <p className="text-muted-foreground text-sm">
+ {tool.description}
+ </p>
+ </div>
+ </CardContent>
+ </Card>
+ </Link>
+ ))}
+ </div>
+ </div>
+ );
+}
|
