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
12 changes: 1 addition & 11 deletions src/app/page.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import ContentSplitSection from '@/components/ContentSplitSection';
import Image from 'next/image';
// import EarthGlobe from '@/components/EarthGlobe';
import { MagicBox } from '@/components/MagicBox'
import { Button } from "@/components/ui/button"
import { SiGithub } from "react-icons/si";
import { SiLucide } from "react-icons/si";
import { SiNextdotjs } from "react-icons/si";
import { SiTailwindcss } from "react-icons/si";
import { SiMdx } from "react-icons/si";
import { SiThreedotjs } from "react-icons/si";
import { SiTypescript } from "react-icons/si";
import { FaHeartbeat } from "react-icons/fa";
import { CustomCard, CustomCardHorizontal } from '@/components/CustomCard';

<div className={`w-full rounded-lg overflow-hidden`}>
Expand Down Expand Up @@ -40,7 +30,7 @@ import { CustomCard, CustomCardHorizontal } from '@/components/CustomCard';
</div>

{/* set a grid of cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<CustomCardHorizontal
title="Earth Surface Forecasting"
description="Using Machine Learning to forecast the dynamics of Earth's surface, we can predict crop yield, forest health, the effects of a drought and more."
Expand Down
34 changes: 32 additions & 2 deletions src/app/resources/software/page.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
# Software libraries
import { GitHubCard } from "@/components/GithubCard";
import { DynamicGitHubCard } from "@/components/DynamicGitHubCard";
import { repositories } from "./repositories";

Find repositories here https://github.com/EarthyScience/WeatherGenerator-Land
<h1 className="text-3xl font-bold text-gray-900 text-center">
Software libraries
<h2 className="text-xl font-bold text-gray-900 text-center">
Find repositories here:
</h2>
</h1>

<DynamicGitHubCard
repoUrl="https://github.com/EarthyScience/WeatherGenerator-Land"
/>

<h1 className="text-3xl font-bold text-gray-900 text-center">
GitHub Repositories
</h1>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{repositories.map((repo, index) => (
<DynamicGitHubCard key ={index} repoUrl={repo} />
))}
</div>


{/* <GitHubCard
name="WeatherGenenerator-Land"
description="A Python package for generating synthetic weather data for land applications."
language="Julia"
stars={42}
forks={10}
updatedAt="2023-10-01"
/> */}
5 changes: 5 additions & 0 deletions src/app/resources/software/repositories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const repositories = [
"https://github.com/earthnet2021/earthnet-model-intercomparison-suite",
"https://github.com/earthnet2021/earthnet-toolkit",
"https://github.com/earthnet2021/earthnet-models-pytorch"
]
10 changes: 5 additions & 5 deletions src/components/CustomCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ export const CustomCard = ({
className = "",
}: CustomCardProps) => {
return (
<Card className={`w-full max-w-md hover:bg-[var(--accent)] transition-colors duration-200 ${className}`}>
<Card className={`w-full max-w-sm mx-auto min-w-0 hover:shadow-md transition-shadow duration-200 ${className}`}>
<CardHeader>
{/* Avatar and Title row */}
<div className="flex items-start gap-3">
<div className="flex items-start gap-2 sm:gap-3 md:gap-4">
{/* Avatar or Icon on the left */}
{avatarSrc ? (
<div className="border-[1.5px] border-gray-300 rounded-full p-0.5">
<Avatar className="h-12 w-12">
<Avatar className="h-10 w-10 sm:h-12 sm:w-12">
<AvatarImage src={avatarSrc} alt="Avatar" />
<AvatarFallback>{avatarFallback}</AvatarFallback>
</Avatar>
Expand All @@ -55,7 +55,7 @@ export const CustomCard = ({

{/* Title on the right */}
<div className="flex-1 min-w-0">
<CardTitle className="text-lg">
<CardTitle className="text-sm sm:text-base md:text-lg leading-tight">
{href ? (
<a href={href} target={target} className="hover:underline inline-flex items-center gap-2">
{title}
Expand Down Expand Up @@ -90,7 +90,7 @@ export const CustomCardHorizontal = ({
className = "",
}: CustomCardProps) => {
return (
<Card className={`w-full max-w-md transition-colors hover:bg-[var(--accent)] duration-200 ${className}`}>
<Card className={`w-full max-w-sm mx-auto min-w-0 hover:shadow-md transition-shadow duration-200 ${className}`}>
<div className="flex flex-col sm:flex-row sm:items-start gap-2 sm:gap-4 px-3 -mt-4 sm:mt-0 pb-0">
{/* Image Section — responsive positioning */}
<div className="flex justify-center sm:block">
Expand Down
31 changes: 31 additions & 0 deletions src/components/DynamicGitHubCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client'

import { useEffect, useState } from "react";
import { GitHubCard } from "@/components/GithubCard";
import repoLoader from '@/utils/repoData';
import { RepoCardData } from "@/utils/repoData";

interface Props {
repoUrl: string;
}

export const DynamicGitHubCard = ({ repoUrl }: Props) => {
const [repo, setRepo] = useState<RepoCardData | null>(null);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
(async () => {
try {
const data = await repoLoader.load(repoUrl);
setRepo(data);
} catch {
setError("Error loading repository data");
}
})();
}, [repoUrl]);

if (error) return <p>{error}</p>;
if (!repo) return <p>Loading...</p>;

return <GitHubCard {...repo} />;
};
128 changes: 128 additions & 0 deletions src/components/GithubCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from 'react';
import { Star, GitFork, Calendar } from 'lucide-react';
import { RiGitRepositoryLine } from "react-icons/ri";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";

export function GitHubCard({
name = "awesome-project",
description = "A fantastic open-source project that solves real-world problems with elegant code and comprehensive documentation.",
language = "Julia",
forks = 2718,
stars = 1618,
lastUpdated = "now",
href = "#"
}) {
// Language color mapping (common GitHub language colors)
const languageColors: { [key: string]: string } = {
JavaScript: '#f1e05a',
TypeScript: '#2b7489',
Python: '#3572A5',
Java: '#b07219',
Julia: '#a270ba',
'Jupyter Notebook': '#DA5B0B',
'C++': '#f34b7d',
C: '#555555',
'C#': '#239120',
Go: '#00ADD8',
Rust: '#dea584',
Ruby: '#701516',
PHP: '#4F5D95',
Swift: '#ffac45',
Kotlin: '#F18E33',
HTML: '#e34c26',
CSS: '#1572B6',
Shell: '#89e051',
Vue: '#2c3e50',
React: '#61dafb'
}

const languageColor = languageColors[language] || '#696358ff';

return (
<Card className="w-full max-w-sm mx-auto min-w-0 hover:shadow-md transition-shadow duration-200">
<CardHeader className="pb-2 sm:pb-3 -mt-3">
{/* Repository Name and Star Button */}
<div className="flex items-start justify-between gap-2 mb-2 min-w-0">
<a
href={href}
className="flex items-center gap-2 cursor-pointer min-w-0 flex-1"
target="_blank"
rel="noopener noreferrer"
>

<CardTitle className="flex items-center gap-2 text-base sm:text-lg font-semibold cursor-pointer min-w-0 flex-1 overflow-hidden">
<div className="w-4 h-4 flex items-center justify-center flex-shrink-0">
<RiGitRepositoryLine className="w-3 h-3 sm:w-4 sm:h-4" />
</div>
<span className="truncate min-w-0 flex-shrink max-w-sm">{name}</span>
</CardTitle>

<Button
variant="secondary"
size="sm"
className="flex items-center gap-1 text-xs px-2 py-1 sm:px-3 sm:py-1.5 flex-shrink-0 cursor-pointer h-7 sm:h-8 hover:[color:var(--accent-3)] transition-colors duration-200"
>
<Star size={10} className="sm:w-3 sm:h-3" />
<span className="hidden xs:inline sm:inline">Star us!</span>
<span className="xs:hidden sm:hidden">us!</span>
</Button>

</a>
</div>

{/* Description */}
<CardDescription className="text-xs sm:text-sm leading-relaxed line-clamp-2 sm:line-clamp-3 -mt-3">
{description}
</CardDescription>
</CardHeader>

<CardContent className="pt-0 pb-3 sm:pb-4 -mt-6 -mb-6">
{/* Stats Section */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4">
{/* Language */}
<div className="flex items-center gap-1.5 flex-shrink-0">
<div
className="w-2.5 h-2.5 sm:w-3 sm:h-3 rounded-full flex-shrink-0"
style={{ backgroundColor: languageColor }}
/>
<span className="text-xs sm:text-sm text-gray-600 truncate">{language}</span>
</div>

{/* Stats Row */}
<div className="flex items-center gap-3 sm:gap-4 text-xs sm:text-sm text-gray-500 min-w-0">
{/* Stars */}
<div className="flex items-center gap-1 flex-shrink-0">
<Star size={12} className="sm:w-3.5 sm:h-3.5" />
<span>{stars.toLocaleString()}</span>
</div>

{/* Forks */}
<div className="flex items-center gap-1 flex-shrink-0">
<GitFork size={12} className="sm:w-3.5 sm:h-3.5" />
<span>{forks.toLocaleString()}</span>
</div>

{/* Last Updated - Hide on very small screens */}
<div className="hidden xs:flex sm:flex items-center gap-1 min-w-0">
<Calendar size={10} className="sm:w-3 sm:h-3 flex-shrink-0" />
<span className="truncate text-xs">{lastUpdated}</span>
</div>
</div>
</div>

{/* Last Updated - Show on very small screens only */}
<div className="flex xs:hidden sm:hidden items-center gap-1 mt-2 text-xs text-gray-500">
<Calendar size={10} className="flex-shrink-0" />
<span className="truncate">Updated {lastUpdated}</span>
</div>
</CardContent>
</Card>
);
};
92 changes: 92 additions & 0 deletions src/utils/repoData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export interface RepoCardData {
name: string;
description: string;
language: string;
forks: number;
stars: number;
lastUpdated: string;
href: string;
}

interface GitHubRepoResponse {
name: string;
description: string | null;
language: string | null;
forks_count: number;
stargazers_count: number;
updated_at: string;
}

interface GitHubFetchOptions {
authorization?: string;
accept?: string;
}

function formatLastUpdated(dateString: string): string {
const updatedDate = new Date(dateString);
const now = new Date();
const diffDays = Math.ceil(Math.abs(now.getTime() - updatedDate.getTime()) / (1000 * 60 * 60 * 24));

if (diffDays === 1) return "1 day ago";
if (diffDays < 7) return `${diffDays} days ago`;
if (diffDays < 30) return `${Math.floor(diffDays / 7)} week(s) ago`;
if (diffDays < 365) return `${Math.floor(diffDays / 30)} month(s) ago`;
return `${Math.floor(diffDays / 365)} year(s) ago`;
}

async function github(
path: string,
{
authorization = process.env.GITHUB_TOKEN && `token ${process.env.GITHUB_TOKEN}`,
accept = "application/vnd.github.v3+json"
}: GitHubFetchOptions = {}
): Promise<GitHubRepoResponse> {
const url = new URL(path, "https://api.github.com");
const headers: Record<string, string> = {
accept,
...(authorization && { authorization })
};

const response = await fetch(url.toString(), { headers });
if (!response.ok) {
throw new Error(`fetch error: ${response.status} ${url.toString()}`);
}

return await response.json();
}

const repoLoader = {
async load(repoUrl: string): Promise<RepoCardData> {
const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
if (!match) throw new Error("Invalid GitHub repository URL");

const [, owner, repo] = match;
const repoFullName = `${owner}/${repo}`;

try {
const repoData = await github(`/repos/${repoFullName}`);
return {
name: repoData.name,
description: repoData.description || "No description provided",
language: repoData.language || "Unknown",
forks: repoData.forks_count,
stars: repoData.stargazers_count,
lastUpdated: formatLastUpdated(repoData.updated_at),
href: repoUrl
};
} catch (error) {
if (process.env.CI) throw error;
return {
name: repo,
description: "Repository data unavailable",
language: "Unknown",
forks: 0,
stars: 0,
lastUpdated: "unknown",
href: "#"
};
}
}
};

export default repoLoader;
Loading