feat: git activity feed
This commit is contained in:
parent
fdbcce0dcd
commit
310e944633
@ -1,10 +1,10 @@
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayload, type RequiredDataFromCollectionSlug } from 'payload'
|
||||
import { draftMode } from "next/headers";
|
||||
import { cache } from "react";
|
||||
import { RenderBlocks } from "@/blocks/RenderBlocks";
|
||||
import PageClient from "./page.client";
|
||||
|
||||
import { draftMode } from 'next/headers'
|
||||
import { cache } from 'react'
|
||||
import { RenderBlocks } from '@/blocks/RenderBlocks'
|
||||
import PageClient from './page.client'
|
||||
import GitProfile from '@/components/GitProfile'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
@ -63,9 +63,10 @@ export default async function Page({ params: paramsPromise }: Args) {
|
||||
//const { isEnabled: draft } = await draftMode()
|
||||
//const url = '/' + slug
|
||||
|
||||
const page: RequiredDataFromCollectionSlug<'pages'> | null = await queryPageBySlug({
|
||||
const page: RequiredDataFromCollectionSlug<'pages'> | null =
|
||||
(await queryPageBySlug({
|
||||
slug,
|
||||
}) || null
|
||||
})) || null
|
||||
|
||||
if (!page) {
|
||||
return <div /> // return <PayloadRedirects url={url} />
|
||||
@ -74,14 +75,14 @@ export default async function Page({ params: paramsPromise }: Args) {
|
||||
const { layout } = page
|
||||
|
||||
return (
|
||||
<article className="flex flex-col min-h-[100dvh] space-y-10" >
|
||||
<article className="flex flex-col min-h-[100dvh] space-y-10">
|
||||
<PageClient />
|
||||
{/* Allows redirects for valid pages too */}
|
||||
{/*<PayloadRedirects disableNotFound url={url} />*/}
|
||||
{/*draft && <LivePreviewListener />*/}
|
||||
|
||||
<RenderBlocks blocks={layout} />
|
||||
<GitProfile />
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
47
src/components/GitProfile/index.tsx
Normal file
47
src/components/GitProfile/index.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
|
||||
import { CommitDetails, getGitActivity } from '@/serverActions/getGitActivity'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { GitCommitCard } from '../git-commit-card'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
|
||||
const GitProfile = () => {
|
||||
const [commits, setCommits] = useState<CommitDetails[]>([])
|
||||
|
||||
useMemo(async () => {
|
||||
if (commits.length) return
|
||||
|
||||
const commitResponse = await getGitActivity()
|
||||
setCommits(commitResponse || [])
|
||||
}, [commits, setCommits])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!!commits.length ?
|
||||
commits.map((c) => {
|
||||
const refNameSplitArray = c.commitRefName.split('/')
|
||||
const refName = refNameSplitArray[refNameSplitArray.length - 1]
|
||||
|
||||
return (
|
||||
<GitCommitCard
|
||||
key={c.id + c.repoName}
|
||||
image={c.avatarUrl}
|
||||
title={c.repoName}
|
||||
description={c.commitMessage}
|
||||
dates={new Date(c.commitTimestamp).toLocaleString()}
|
||||
location={refName}
|
||||
repoWebsite={c.repoWebsite}
|
||||
/>
|
||||
)
|
||||
})
|
||||
:
|
||||
<div>
|
||||
<span className="block text-center">. . . loading contributions . . .</span>
|
||||
<Loader2 className='animate-spin size-14 mx-auto my-12' />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GitProfile
|
||||
70
src/components/git-commit-card.tsx
Normal file
70
src/components/git-commit-card.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description: string
|
||||
dates: string
|
||||
location: string
|
||||
image?: string
|
||||
repoWebsite?: string
|
||||
links?: readonly {
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
href: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export function GitCommitCard({
|
||||
title,
|
||||
description,
|
||||
dates,
|
||||
location,
|
||||
image,
|
||||
repoWebsite,
|
||||
links,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="border-l border-l-purple-800 dark:border-l-purple-300">
|
||||
<li className="relative ml-10 py-4 -top-4 list-none">
|
||||
<div className="absolute -left-15 flex items-center justify-center bg-white rounded-full">
|
||||
<Avatar className="border size-10 m-auto">
|
||||
<AvatarImage src={image} alt={title} className="object-contain" />
|
||||
<AvatarFallback>{title[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col justify-start ">
|
||||
{dates && <time className="text-xs text-muted-foreground leading-none">{dates}</time>}
|
||||
<div>
|
||||
<Link target="_blank" href={repoWebsite || ''} className="font-semibold hover:text-lg hover:underline hover:text-purple-800 dark:hover:text-purple-300 transition-all">
|
||||
{title}
|
||||
</Link>
|
||||
{location && (
|
||||
<span className="text-sm italic ml-2 text-purple-800 dark:text-purple-300">
|
||||
{`=> ${location}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<span className="prose dark:prose-invert text-sm text-muted-foreground mt-2">
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{links && links.length > 0 && (
|
||||
<div className="mt-2 flex flex-row flex-wrap items-start gap-2">
|
||||
{links?.map((link, idx) => (
|
||||
<Link href={link.href} key={idx}>
|
||||
<Badge key={idx} title={link.title} className="flex gap-2">
|
||||
{link.icon}
|
||||
{link.title}
|
||||
</Badge>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Link from "next/link";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
dates: string;
|
||||
location: string;
|
||||
image?: string;
|
||||
links?: readonly {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
href: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function HackathonCard({
|
||||
title,
|
||||
description,
|
||||
dates,
|
||||
location,
|
||||
image,
|
||||
links,
|
||||
}: Props) {
|
||||
return (
|
||||
<li className="relative ml-10 py-4">
|
||||
<div className="absolute -left-16 top-2 flex items-center justify-center bg-white rounded-full">
|
||||
<Avatar className="border size-12 m-auto">
|
||||
<AvatarImage src={image} alt={title} className="object-contain" />
|
||||
<AvatarFallback>{title[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col justify-start gap-1">
|
||||
{dates && (
|
||||
<time className="text-xs text-muted-foreground">{dates}</time>
|
||||
)}
|
||||
<h2 className="font-semibold leading-none">{title}</h2>
|
||||
{location && (
|
||||
<p className="text-sm text-muted-foreground">{location}</p>
|
||||
)}
|
||||
{description && (
|
||||
<span className="prose dark:prose-invert text-sm text-muted-foreground">
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{links && links.length > 0 && (
|
||||
<div className="mt-2 flex flex-row flex-wrap items-start gap-2">
|
||||
{links?.map((link, idx) => (
|
||||
<Link href={link.href} key={idx}>
|
||||
<Badge key={idx} title={link.title} className="flex gap-2">
|
||||
{link.icon}
|
||||
{link.title}
|
||||
</Badge>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
44
src/serverActions/getGitActivity.ts
Normal file
44
src/serverActions/getGitActivity.ts
Normal file
@ -0,0 +1,44 @@
|
||||
'use server'
|
||||
|
||||
type CommitContent = {
|
||||
Commits: [any],
|
||||
HeadCommit: {
|
||||
Message: string,
|
||||
Timestamp: string,
|
||||
}
|
||||
}
|
||||
|
||||
export type CommitDetails = {
|
||||
id: number,
|
||||
repoName: string,
|
||||
repoUrl: string,
|
||||
repoWebsite: string,
|
||||
commitMessage: string,
|
||||
commitTimestamp: string,
|
||||
commitRefName: string,
|
||||
avatarUrl: string,
|
||||
}
|
||||
|
||||
export const getGitActivity = async (): Promise<CommitDetails[]> => {
|
||||
const gitActivityResponse = await fetch('https://git.beitzah.net/api/v1/users/ysandler/activities/feeds?only-performed-by=true')
|
||||
const gitActivityJson = await gitActivityResponse.json()
|
||||
const commits = gitActivityJson
|
||||
.filter((a: any) => a.op_type === 'commit_repo' && !!a.content)
|
||||
.map((c: any) => {
|
||||
const repo = c.repo
|
||||
const commitContent = JSON.parse(c.content as string) as CommitContent
|
||||
|
||||
return {
|
||||
id: c.id,
|
||||
repoName: repo.full_name,
|
||||
repoUrl: repo.url,
|
||||
repoWebsite: repo.html_url,
|
||||
commitMessage: commitContent.HeadCommit.Message,
|
||||
commitTimestamp: commitContent.HeadCommit.Timestamp,
|
||||
commitRefName: c.ref_name || '',
|
||||
avatarUrl: repo.owner.avatar_url,
|
||||
}
|
||||
})
|
||||
|
||||
return commits
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user