feat: git activity feed
This commit is contained in:
parent
fdbcce0dcd
commit
310e944633
@ -1,10 +1,10 @@
|
|||||||
import configPromise from '@payload-config'
|
import configPromise from '@payload-config'
|
||||||
import { getPayload, type RequiredDataFromCollectionSlug } from 'payload'
|
import { getPayload, type RequiredDataFromCollectionSlug } from 'payload'
|
||||||
import { draftMode } from "next/headers";
|
import { draftMode } from 'next/headers'
|
||||||
import { cache } from "react";
|
import { cache } from 'react'
|
||||||
import { RenderBlocks } from "@/blocks/RenderBlocks";
|
import { RenderBlocks } from '@/blocks/RenderBlocks'
|
||||||
import PageClient from "./page.client";
|
import PageClient from './page.client'
|
||||||
|
import GitProfile from '@/components/GitProfile'
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
const payload = await getPayload({ config: configPromise })
|
const payload = await getPayload({ config: configPromise })
|
||||||
@ -63,9 +63,10 @@ export default async function Page({ params: paramsPromise }: Args) {
|
|||||||
//const { isEnabled: draft } = await draftMode()
|
//const { isEnabled: draft } = await draftMode()
|
||||||
//const url = '/' + slug
|
//const url = '/' + slug
|
||||||
|
|
||||||
const page: RequiredDataFromCollectionSlug<'pages'> | null = await queryPageBySlug({
|
const page: RequiredDataFromCollectionSlug<'pages'> | null =
|
||||||
|
(await queryPageBySlug({
|
||||||
slug,
|
slug,
|
||||||
}) || null
|
})) || null
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
return <div /> // return <PayloadRedirects url={url} />
|
return <div /> // return <PayloadRedirects url={url} />
|
||||||
@ -74,14 +75,14 @@ export default async function Page({ params: paramsPromise }: Args) {
|
|||||||
const { layout } = page
|
const { layout } = page
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className="flex flex-col min-h-[100dvh] space-y-10" >
|
<article className="flex flex-col min-h-[100dvh] space-y-10">
|
||||||
<PageClient />
|
<PageClient />
|
||||||
{/* Allows redirects for valid pages too */}
|
{/* Allows redirects for valid pages too */}
|
||||||
{/*<PayloadRedirects disableNotFound url={url} />*/}
|
{/*<PayloadRedirects disableNotFound url={url} />*/}
|
||||||
{/*draft && <LivePreviewListener />*/}
|
{/*draft && <LivePreviewListener />*/}
|
||||||
|
|
||||||
<RenderBlocks blocks={layout} />
|
<RenderBlocks blocks={layout} />
|
||||||
|
<GitProfile />
|
||||||
</article>
|
</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