Skip to content

Commit

Permalink
Add "Additional Information" section to GitHub util (#3429)
Browse files Browse the repository at this point in the history
## Description

This PR adds some frontend enhancements to the GitHub util.
Specifically, an "additional information" section is added to the
username->global node id tool.

before/after:

<img width="2032" alt="image"
src="https://github.com/user-attachments/assets/35228bad-666c-4dc5-8267-64c6ce7d392a">



## Checklist
- [x] I have read and understood the [WATcloud
Guidelines](https://cloud.watonomous.ca/docs/community-docs/watcloud/guidelines)
- [x] I have performed a self-review of my code
  • Loading branch information
ben-z authored Dec 4, 2024
1 parent df6e587 commit 9bfef59
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 38 deletions.
36 changes: 4 additions & 32 deletions components/blog-post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Link, useConfig } from "nextra-theme-docs";
import React, { Fragment } from 'react';
import { SubscribeDialog } from './blog';
import Picture from './picture';
import { SocialLinks } from './ui/social-links';

// Reference for styling: https://github.com/vercel/turbo/blob/22585c9dcc23eb010ab01f177394358af03210d7/docs/pages/blog/turbo-1-10-0.mdx

Expand Down Expand Up @@ -49,38 +50,9 @@ export function Avatar({ username }: { username: string }) {
<dl className="ml-2 text-sm font-medium leading-4 text-left whitespace-no-wrap">
<dt className="sr-only">Name</dt>
<dd className="text-gray-800 dark:text-slate-50">{profile.watcloud_public_profile.full_name}</dd>
{profile.watcloud_public_profile.links && profile.watcloud_public_profile.links.map((link: string) => {
const iconSize = 16;
let icon = <LinkIcon size={iconSize} />;
let sr = "link";
if (link.startsWith("mailto:")) {
icon = <MailIcon size={iconSize} />;
sr = "email";
} else if (link.startsWith("https://github.com")) {
icon = <GithubIcon size={iconSize} />;
sr = "github";
} else if (link.startsWith("https://linkedin.com")) {
icon = <LinkedinIcon size={iconSize} />;
sr = "linkedin";
} else if (link.startsWith("https://twitter.com") || link.startsWith("https://x.com")) {
icon = <XIcon size={iconSize} />;
sr = "twitter";
}
return (
<Fragment key={link}>
<dt className="sr-only">{sr}</dt>
<dd className="inline-block">
<Link
className="text-xs hover:text-gray-900 dark:hover:text-white"
href={link}
target="_blank"
>
{icon}
</Link>
</dd>
</Fragment>
)
})}
{profile.watcloud_public_profile.links && (
<SocialLinks links={profile.watcloud_public_profile.links} />
)}
</dl>
</div>
);
Expand Down
110 changes: 104 additions & 6 deletions components/github.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,108 @@ import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Input } from "./ui/input";
import { useMDXComponents } from "nextra-theme-docs";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Link } from "nextra-theme-docs"
import { SocialLinks } from "./ui/social-links";

const usernameToIDFormSchema = z.object({
username: z.string(),
});

type RawData = {
request: {
url: string;
options: Record<string, any>;
};
response: {
headers: Record<string, string>;
data: any;
};
};

export function GitHubUserCard({
login,
name,
bio,
avatar_url,
html_url,
public_repos,
followers,
following,
blog,
twitter_username,
email,
}: {
login: string,
name: string,
bio: string,
avatar_url: string,
html_url: string,
public_repos: number,
followers: number,
following: number,
blog?: string,
twitter_username?: string,
email?: string,
}) {
return (
<Card className="mt-2 max-w-96">
<CardHeader className="text-center items-center">
<Avatar className="h-20 w-20 mb-2 border">
<AvatarImage src={avatar_url} alt={`Avatar for @${login}`} />
<AvatarFallback>{(login || "U")[0]}</AvatarFallback>
</Avatar>
<CardTitle>{name}</CardTitle>
<CardDescription>
<Link href={html_url} style={{ color: "inherit", textDecoration: "none" }}>
@{login}
</Link>
</CardDescription>
<CardDescription>
<SocialLinks className="flex space-x-1" links={[
blog ? blog.match(/^https?:\/\//) ? blog : `http://${blog}` : "",
twitter_username ? `https://x.com/${twitter_username}` : "",
email ? `mailto:${email}` : "",
]} />
</CardDescription>
<CardDescription className="text-card-foreground">{bio}</CardDescription>
</CardHeader>
<CardContent className="grid grid-cols-3 gap-2 text-center leading-5">
<div>
<Link href={`${html_url}?tab=repositories`} style={{ color: "inherit", textDecoration: "none" }}>
<p className="font-semibold">{public_repos}</p>
<p>
<span className="text-sm text-muted-foreground">Public Repos</span>
</p>
</Link>
</div>
<div>
<Link href={`${html_url}?tab=followers`} style={{ color: "inherit", textDecoration: "none" }}>
<p className="font-semibold">{followers}</p>
<p>
<span className="text-sm text-muted-foreground">Followers</span>
</p>
</Link>
</div>
<div>
<Link href={`${html_url}?tab=following`} style={{ color: "inherit", textDecoration: "none" }}>
<p className="font-semibold">{following}</p>
<p>
<span className="text-sm text-muted-foreground">Following</span>
</p>
</Link>
</div>
</CardContent>
</Card>
)
}

export function UsernameToID() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [warningMessage, setWarningMessage] = useState<string>("");
const [errorMessage, setErrorMessage] = useState<string>("");
const [rawData, setRawData] = useState<Object | null>(null);
const [rawData, setRawData] = useState<RawData | null>(null);
const [globalNodeID, setGlobalNodeID] = useState<string>("");

// Use MDX components from the theme:
Expand Down Expand Up @@ -129,12 +221,18 @@ export function UsernameToID() {
</div>
)}
{rawData && (
<Details>
<Summary>Raw data</Summary>
<div className="mt-2">
<Pre hasCopyCode><Code>{JSON.stringify(rawData, null, 2)}</Code></Pre>
<>
<div className="mt-8">
<h2 className="text-xl font-bold">Additional Information</h2>
<GitHubUserCard {...rawData.response.data} />
<Details>
<Summary>Raw data</Summary>
<div className="mt-2">
<Pre hasCopyCode><Code>{JSON.stringify(rawData, null, 2)}</Code></Pre>
</div>
</Details>
</div>
</Details>
</>
)}
</>
);
Expand Down
48 changes: 48 additions & 0 deletions components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"

import { cn } from "@/lib/utils"

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName

export { Avatar, AvatarImage, AvatarFallback }
64 changes: 64 additions & 0 deletions components/ui/social-links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { cn } from "@/lib/utils";
import { SiX } from "@icons-pack/react-simple-icons";
import { GithubIcon, GlobeIcon, LinkedinIcon, MailIcon } from "lucide-react";

import { Link } from "nextra-theme-docs";
import { Fragment } from "react";

export const DEFAULT_ICON_SIZE = 16;


export function SocialLink({
link,
iconSize = DEFAULT_ICON_SIZE
}: {
link: string,
iconSize?: number,
}) {
let icon = <GlobeIcon size={iconSize} />;
let sr = "link";
if (link.startsWith("mailto:")) {
icon = <MailIcon size={iconSize} />;
sr = "email";
} else if (link.startsWith("https://github.com")) {
icon = <GithubIcon size={iconSize} />;
sr = "github";
} else if (link.startsWith("https://linkedin.com")) {
icon = <LinkedinIcon size={iconSize} />;
sr = "linkedin";
} else if (link.startsWith("https://twitter.com") || link.startsWith("https://x.com")) {
icon = <SiX size={iconSize} />;
sr = "twitter";
}
return (
<Fragment key={link}>
<dt className="sr-only">{sr}</dt>
<dd className="inline-block">
<Link
className="text-xs hover:text-gray-900 dark:hover:text-white"
href={link}
target="_blank"
rel="noopener noreferrer"
>
{icon}
</Link>
</dd>
</Fragment>
)
}

export function SocialLinks({
links,
className,
iconSize = DEFAULT_ICON_SIZE
}: {
links: string[],
className?: string,
iconSize?: number,
}) {
return (
<div className={cn(className)}>
{links.filter((link) => link).map((link) => <SocialLink key={link} link={link} iconSize={iconSize} />)}
</div>
)
}
Loading

0 comments on commit 9bfef59

Please sign in to comment.