mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-09-18 13:25:32 -04:00
Migrate to app router (#2254)
This commit is contained in:
86
app/projects/page.module.css
Normal file
86
app/projects/page.module.css
Normal file
@@ -0,0 +1,86 @@
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex-grow: 1;
|
||||
margin: 0.6em;
|
||||
width: 370px;
|
||||
padding: 1.2em 1.2em 0.8em 1.2em;
|
||||
border: 1px solid var(--colors-kindaLight);
|
||||
border-radius: var(--radii-corner);
|
||||
font-size: 0.85em;
|
||||
color: var(--colors-mediumDark);
|
||||
transition: border var(--transitions-fade);
|
||||
}
|
||||
|
||||
.card .name {
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card .description {
|
||||
margin-top: 0.7em;
|
||||
margin-bottom: 0.5em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.card .meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.card .metaItem {
|
||||
margin-right: 1.5em;
|
||||
line-height: 2;
|
||||
color: var(--colors-medium);
|
||||
}
|
||||
|
||||
.card .metaLink {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.card .metaLink:hover,
|
||||
.card .metaLink:focus-visible {
|
||||
color: var(--colors-link) !important;
|
||||
}
|
||||
|
||||
.card .metaIcon {
|
||||
display: inline;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: -0.3em;
|
||||
margin-right: 0.5em;
|
||||
stroke-width: 0.75;
|
||||
}
|
||||
|
||||
.card .metaLanguage {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.15em;
|
||||
height: 1.15em;
|
||||
margin-right: 0.5em;
|
||||
border-radius: 50%;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.viewMore {
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
font-weight: 500px;
|
||||
}
|
||||
|
||||
.githubIcon {
|
||||
display: inline;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: -0.2em;
|
||||
margin: 0 0.15em;
|
||||
fill: var(--colors-text);
|
||||
}
|
175
app/projects/page.tsx
Normal file
175
app/projects/page.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import { graphql } from "@octokit/graphql";
|
||||
import Content from "../../components/Content";
|
||||
import PageTitle from "../../components/PageTitle";
|
||||
import Link from "../../components/Link";
|
||||
import RelativeTime from "../../components/RelativeTime";
|
||||
import commaNumber from "comma-number";
|
||||
import config from "../../lib/config";
|
||||
import { metadata as defaultMetadata } from "../layout";
|
||||
import { GoStar, GoRepoForked } from "react-icons/go";
|
||||
import { SiGithub } from "react-icons/si";
|
||||
import type { Metadata } from "next";
|
||||
import type { User, Repository } from "@octokit/graphql-schema";
|
||||
import type { Project } from "../../types";
|
||||
|
||||
import styles from "./page.module.css";
|
||||
|
||||
export const revalidate = 600; // 10 minutes
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Projects",
|
||||
openGraph: {
|
||||
...defaultMetadata.openGraph,
|
||||
title: "Projects",
|
||||
url: "/projects",
|
||||
},
|
||||
alternates: {
|
||||
...defaultMetadata.alternates,
|
||||
canonical: "/projects",
|
||||
},
|
||||
};
|
||||
|
||||
async function getRepos(): Promise<Project[] | null> {
|
||||
// don't fail the entire site build if the required API key for this page is missing
|
||||
if (!process.env.GH_PUBLIC_TOKEN || process.env.GH_PUBLIC_TOKEN === "") {
|
||||
console.warn(`ERROR: I can't fetch any GitHub projects without "GH_PUBLIC_TOKEN" set! Skipping for now...`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/graphql/reference/objects#repository
|
||||
const { user } = await graphql<{ user: User }>(
|
||||
`
|
||||
query ($username: String!, $sort: RepositoryOrderField!, $limit: Int) {
|
||||
user(login: $username) {
|
||||
repositories(
|
||||
first: $limit
|
||||
isLocked: false
|
||||
isFork: false
|
||||
ownerAffiliations: OWNER
|
||||
privacy: PUBLIC
|
||||
orderBy: { field: $sort, direction: DESC }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
url
|
||||
description
|
||||
pushedAt
|
||||
stargazerCount
|
||||
forkCount
|
||||
primaryLanguage {
|
||||
name
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
username: config.authorSocial.github,
|
||||
sort: "STARGAZERS",
|
||||
limit: 12,
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
authorization: `token ${process.env.GH_PUBLIC_TOKEN}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const results = user.repositories.edges as Array<{ node: Repository }>;
|
||||
|
||||
const repos = results.map<Project>(({ node: repo }) => ({
|
||||
name: repo.name,
|
||||
url: repo.url,
|
||||
description: repo.description as string,
|
||||
updatedAt: repo.pushedAt,
|
||||
stars: repo.stargazerCount,
|
||||
forks: repo.forkCount,
|
||||
language: repo.primaryLanguage as Project["language"],
|
||||
}));
|
||||
|
||||
return repos;
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const repos = await getRepos();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle>💾 Projects</PageTitle>
|
||||
|
||||
<Content>
|
||||
<div className={styles.grid}>
|
||||
{repos?.map((repo) => (
|
||||
<div key={repo.name} className={styles.card}>
|
||||
<Link
|
||||
// @ts-ignore
|
||||
href={repo.url}
|
||||
className={styles.name}
|
||||
>
|
||||
{repo.name}
|
||||
</Link>
|
||||
|
||||
{repo.description && <p className={styles.description}>{repo.description}</p>}
|
||||
|
||||
<div className={styles.meta}>
|
||||
{repo.language && (
|
||||
<div className={styles.metaItem}>
|
||||
{repo.language.color && (
|
||||
<span className={styles.metaLanguage} style={{ backgroundColor: repo.language.color }} />
|
||||
)}
|
||||
{repo.language.name}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{repo.stars && repo.stars > 0 && (
|
||||
<div className={styles.metaItem}>
|
||||
<Link
|
||||
// @ts-ignore
|
||||
href={`${repo.url}/stargazers`}
|
||||
title={`${commaNumber(repo.stars)} ${repo.stars === 1 ? "star" : "stars"}`}
|
||||
underline={false}
|
||||
className={styles.metaLink}
|
||||
>
|
||||
<GoStar className={styles.metaIcon} />
|
||||
{commaNumber(repo.stars)}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{repo.forks && repo.forks > 0 && (
|
||||
<div className={styles.metaItem}>
|
||||
<Link
|
||||
// @ts-ignore
|
||||
href={`${repo.url}/network/members`}
|
||||
title={`${commaNumber(repo.forks)} ${repo.forks === 1 ? "fork" : "forks"}`}
|
||||
underline={false}
|
||||
className={styles.metaLink}
|
||||
>
|
||||
<GoRepoForked className={styles.metaIcon} />
|
||||
{commaNumber(repo.forks)}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* only use relative "time ago" on client side, since it'll be outdated via SSG and cause hydration errors */}
|
||||
<div className={styles.metaItem}>
|
||||
<RelativeTime date={repo.updatedAt} verb="Updated" staticFormat="MMM D, YYYY" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className={styles.viewMore}>
|
||||
<Link href={`https://github.com/${config.authorSocial.github}`}>
|
||||
View more on <SiGithub className={styles.githubIcon} /> GitHub...
|
||||
</Link>
|
||||
</p>
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user