Skip to content

Commit

Permalink
feat(ui): add repo onboarding UI to fetch all user repos from GitHub
Browse files Browse the repository at this point in the history
  • Loading branch information
Pulkitxm committed Oct 7, 2024
1 parent 1aeaf9e commit a7210bd
Show file tree
Hide file tree
Showing 29 changed files with 1,149 additions and 172 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/node_modules
/node_modules
test/
37 changes: 37 additions & 0 deletions core/actions/gh/repos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
"use server";

import axios from "axios";
import { getAccessToken } from "@/db/user";
import { formatTimeAgo } from "@/lib/time";
import { Repo } from "@/types/gh";

export async function getRepos(github_id: number) {
if (!github_id || github_id == -1) return [];
const access_token = await getAccessToken(github_id);
if (!access_token) return [];

try {
const res = await axios.get(
`https://api.github.com/user/repos?visibility=all&sort=updated`,
{
headers: {
Authorization: `token ${access_token}`,
Accept: "application/vnd.github.v3+json",
},
},
);

return (res.data as Repo[])
.map((i: any) => ({
name: i.name,
last_updated: formatTimeAgo(new Date(i.updated_at)),
private: i.private,
owner: i.owner.login.toLowerCase(),
}))
.slice(0, 10);
} catch (error) {
console.log(error);
return [];
}
}
12 changes: 12 additions & 0 deletions core/app/api/gh/repo/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { NextApiRequest, NextApiResponse } from "next";

type ResponseData = {
message: string;
};

export default function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>,
) {
res.status(200).json({ message: "Hello from Next.js!" });
}
32 changes: 28 additions & 4 deletions core/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import ProtectRoute from "@/components/ProtectRoute";
import React from "react";
"use client";

export default async function Dashboard() {
return (await ProtectRoute()) ?? <div>page</div>;
import { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { getRepos } from "@/actions/gh/repos";
import { Repo } from "@/types/gh";
import ProtectClientRoute from "@/components/ProtectRoute/ProtectClientRoute";
import Onboarding from "@/components/Dash/OnBoarding";

export default function DashBoard() {
const { data: session } = useSession();
const protectRoute = ProtectClientRoute();
const [repos, setRepos] = useState<Repo[]>([]);
const [loading, setLoading] = useState(false);

useEffect(() => {
(async () => {
setLoading(true);
setRepos(
await getRepos(session?.user.github_id ?? -1).finally(() => {
setLoading(false);
}),
);
})();
}, [session?.user.github_id]);

if (protectRoute) return protectRoute;

return <Onboarding repos={repos} loading={loading} />;
}
130 changes: 130 additions & 0 deletions core/components/Dash/OnBoarding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"use client";

import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { LockIcon, SearchIcon } from "lucide-react";
import Image from "next/image";
import { Repo } from "@/types/gh";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";

export default function Onboarding({
repos,
loading,
}: {
repos: Repo[];
loading: boolean;
}) {
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-black p-8 text-white">
<div className="mx-auto max-w-6xl">
<h1 className="mb-2 text-4xl font-bold">
Let&apos;s build something new.
</h1>
<p className="mb-8 text-gray-400">
To deploy a new Project, select the repository you want to deploy. The
repository must be hosted on GitHub.
</p>
<div className="flex flex-col gap-8 lg:flex-row">
<Card className="w-full border-gray-800 bg-gray-900 lg:w-1/2">
<CardHeader>
<CardTitle>Import Git Repository</CardTitle>
</CardHeader>
<CardContent>
<div className="mb-4 flex gap-2">
<Select>
<SelectTrigger className="w-[180px] border-gray-700 bg-gray-800">
<SelectValue placeholder="pulkitxm2" />
</SelectTrigger>
<SelectContent>
<SelectItem value="pulkitxm2">pulkitxm2</SelectItem>
</SelectContent>
</Select>
<div className="relative flex-grow">
<SearchIcon className="absolute left-3 top-1/2 size-2 -translate-y-1/2 transform text-gray-400" />
<Input
className="w-full border-gray-700 bg-gray-800 pl-10"
placeholder="Search..."
/>
</div>
</div>
<div className="h-[200px] max-h-[300px] overflow-y-auto p-2 px-5">
{loading
? Array.from({ length: 3 }).map((_, index) => (
<div
key={index}
className="flex items-center justify-between border-b border-gray-700 py-3"
>
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-full" />
<div className="flex flex-col">
<Skeleton className="h-4 w-32 rounded" />
<Skeleton className="mt-1 h-3 w-20 rounded" />
</div>
</div>
<Skeleton className="h-8 w-24 rounded" />
</div>
))
: repos.map((repo, index) => (
<div
key={index}
className="flex items-center justify-between border-b border-gray-800 py-3"
>
<div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-700 uppercase text-gray-300">
{repo.name[0]}
</div>
<div>
<div className="flex items-center">
<Link
href={`https://github.com/${repo.owner}/${repo.name}`.toLowerCase()}
target="_blank"
rel="noreferrer"
className="hover:underline"
>
{repo.name}
</Link>
{repo.private && (
<LockIcon className="ml-1 h-4 w-4" />
)}
</div>
<p className="text-sm text-gray-400">
{repo.last_updated}
</p>
</div>
</div>
<Button
variant="outline"
className="border-gray-600 bg-transparent hover:bg-gray-800"
>
Import
</Button>
</div>
))}
</div>
<Button variant="link" className="mt-4 text-blue-400">
Import Third-Party Git Repository →
</Button>
</CardContent>
</Card>
<div className="relative w-full lg:w-1/2">
<Image
className="w-full"
alt="Onboarding"
src="https://storage.googleapis.com/devitary-image-host.appspot.com/15848031292911696601-undraw_designer_life_w96d.svg"
fill
/>
</div>
</div>
</div>
</div>
);
}
35 changes: 0 additions & 35 deletions core/components/ProtectRoute.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { signIn } from "next-auth/react";
import { Button } from "./ui/button";
import { Button } from "@/components/ui/button";
import { GithubIcon } from "lucide-react";

export default function LoginWithGithub() {
Expand Down
9 changes: 9 additions & 0 deletions core/components/ProtectRoute/ProtectClientRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useSession } from "next-auth/react";
import ProtectRouteUI from "../ProtectRoute";

export default function ProtectClientRoute() {
const { data: session } = useSession();
if (!session) {
return <ProtectRouteUI />;
}
}
10 changes: 10 additions & 0 deletions core/components/ProtectRoute/ProtectServerRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/options";
import ProtectRouteUI from "../ProtectRoute";

export async function ProtectServerRoute() {
const session = await getServerSession(authOptions);
if (!session) {
return <ProtectRouteUI />;
}
}
26 changes: 26 additions & 0 deletions core/components/ProtectRoute/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import LoginWithGithub from "./LoginWithGithub";

export default function ProtectRouteUI() {
return (
<div className="flex h-full w-full items-center justify-center text-white">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Unauthorized Access</CardTitle>
<CardDescription>
You are not authorized to view this page.
</CardDescription>
</CardHeader>
<CardContent>
<LoginWithGithub />
</CardContent>
</Card>
</div>
);
}
36 changes: 36 additions & 0 deletions core/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";

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

const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}

export { Badge, badgeVariants };
25 changes: 25 additions & 0 deletions core/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from "react";

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

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";

export { Input };
Loading

0 comments on commit a7210bd

Please sign in to comment.