Skip to content

Commit

Permalink
Merge pull request #31 from namidapoo/feat/username-input
Browse files Browse the repository at this point in the history
feat: ユーザー名を検索可能にする
  • Loading branch information
namidapoo authored Dec 31, 2024
2 parents 2abdb6f + 2011677 commit 471def6
Show file tree
Hide file tree
Showing 22 changed files with 516 additions and 92 deletions.
4 changes: 3 additions & 1 deletion app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";

export const runtime = "edge";

export default function NotFound() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background text-foreground">
<div className="flex h-full flex-col items-center justify-center bg-background text-foreground">
<div className="space-y-4 text-center">
<h1 className="font-extrabold text-4xl tracking-tight lg:text-5xl">
404
Expand Down
59 changes: 26 additions & 33 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
import { Button } from "@/components/ui/button";
import { auth } from "@/lib/auth";
import { signIn } from "@/lib/auth";
import GitHubWhite from "@/public/github-mark-white.png";
import GitHubBlack from "@/public/github-mark.png";
import Image from "next/image";
import Link from "next/link";
import { ProfileForm } from "./profile-form";

export const runtime = "edge";

export default async function Home() {
const session = await auth();
if (!session?.user) return null;

return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 h-full pt-24 px-4 text-accent-foreground md:items-center justify-items-center">
<div className="col-span-1 w-full max-w-md space-y-8 text-center">
<h1 className="font-bold text-2xl text-blue-600 lg:text-5xl">
Dev Recap 2024
</h1>

<div className="space-y-3">
{session.user.image && (
<Image
src={session.user.image}
alt={session.user.login}
width={100}
height={100}
className="rounded-full mx-auto shadow"
priority
/>
)}
<p className="font-semibold text-xl">@{session.user.login}</p>
<p className="text-base lg:text-lg">今年もおつかれさまでした。</p>
<p className="text-base lg:text-lg">
あなたの1年間を振り返りましょう🥳
</p>
<div className="col-span-1 w-full max-w-md text-center space-y-2">
<div className="space-y-8">
<h1 className="font-bold text-2xl text-blue-600 lg:text-5xl">
Dev Recap 2024
</h1>
<div className="space-y-2">
<p className="text-base lg:text-lg">今年もおつかれさまでした。</p>
<p className="text-base lg:text-lg">
あなたの1年間を振り返りましょう🥳
</p>
</div>
</div>

<Button asChild size="lg" className="mx-auto w-full max-w-xs py-6">
<Link
href="/recap"
className="flex items-center justify-center gap-4 text-base"
>
<ProfileForm />
<form
action={async () => {
"use server";
await signIn("github");
}}
className="w-full max-w-xs mx-auto space-y-2"
>
<p className="text-md">または</p>
<Button size="lg" className="mx-auto w-full max-w-xs py-6 text-md">
<Image
src={GitHubWhite}
alt="GitHub"
Expand All @@ -57,9 +50,9 @@ export default async function Home() {
className="hidden dark:block"
priority
/>
振り返りを見る
</Link>
</Button>
GitHubでログイン
</Button>
</form>
</div>

<div className="hidden md:block col-span-2">
Expand Down
64 changes: 64 additions & 0 deletions app/profile-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter } from "next/navigation";
import type { FC } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";

const formSchema = z.object({
username: z.string().min(1, "ユーザー名は必須です。"),
});

export const ProfileForm: FC = () => {
const { push } = useRouter();

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
mode: "onBlur",
});

function onSubmit(values: z.infer<typeof formSchema>) {
push(`/recap/${values.username}`);
}

return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 w-full max-w-xs mx-auto pt-4"
>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem className="text-left text-lg">
<FormControl className="h-12">
<Input
placeholder="ユーザー名を入力してください。"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button className="mx-auto w-full max-w-xs py-6 text-md">
振り返りを見る
</Button>
</form>
</Form>
);
};
20 changes: 20 additions & 0 deletions app/recap/[user]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";

export const runtime = "edge";

export default function ErrorPage() {
return (
<div className="flex h-full flex-col items-center justify-center bg-background text-foreground">
<div className="space-y-4 text-center">
<p className="text-lg">ユーザーの情報を取得できませんでした。</p>
<Separator className="mx-auto w-40" />
<Button asChild size="lg">
<Link href="/">ホームに戻る</Link>
</Button>
</div>
</div>
);
}
25 changes: 25 additions & 0 deletions app/recap/[user]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { auth } from "@/lib/auth";
import { OverView } from "../components/overview";
import { fetchGitHubStats } from "../fetchGitHubStats";

export const runtime = "edge";

export default async function Page({
params,
}: {
params: Promise<{ user: string }>;
}) {
const { user } = await params;
const session = await auth();
// ログインしてない場合は環境変数からトークンを取得
if (!process.env.GITHUB_TOKEN) {
throw new Error("GITHUB_TOKEN is not defined");
}
const token = session?.accessToken ?? process.env.GITHUB_TOKEN;
const data = await fetchGitHubStats({
token,
login: user,
});

return <OverView user={user} data={data} />;
}
11 changes: 11 additions & 0 deletions app/recap/components/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use server";

import { signIn, signOut } from "@/lib/auth";

export async function handleSignIn(provider?: string) {
await signIn(provider);
}

export async function handleSignOut(provider?: string) {
await signOut({ redirectTo: "/" });
}
23 changes: 23 additions & 0 deletions app/recap/components/auth-components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Button } from "@/components/ui/button";
import type { ComponentPropsWithRef, FC } from "react";
import { handleSignIn, handleSignOut } from "./action";

type Props = { provider?: string } & ComponentPropsWithRef<typeof Button>;

export const SignIn: FC<Props> = ({ provider, ...props }) => {
return (
<form action={() => handleSignIn(provider)}>
<Button {...props}>サインイン</Button>
</form>
);
};

export const SignOut: FC<Props> = ({ provider, ...props }) => {
return (
<form className="w-full" action={() => handleSignOut(provider)}>
<Button variant="ghost" className="w-full p-0" {...props}>
ログアウト
</Button>
</form>
);
};
14 changes: 7 additions & 7 deletions app/recap/components/graph/languages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ export const LanguagesUsageGraph: FC<Props> = ({ data }) => {
<CardHeader className="items-start pb-0">
<CardTitle>言語の使用率</CardTitle>
<CardDescription>
{limitedData.length === 0 ? (
"データがありません。"
) : (
<>
あなたが最も使用した言語は <b>{limitedData[0].language}</b> です。
</>
)}
{limitedData.length === 0 ? (
"データがありません。"
) : (
<>
あなたが最も使用した言語は <b>{limitedData[0].language}</b> です。
</>
)}
</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
Expand Down
4 changes: 3 additions & 1 deletion app/recap/components/grass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ export const ContributionGrass: FC<Props> = ({
</TooltipProvider>

<div className="flex items-end justify-between">
<p className="font-bold text-foreground">{totalContributions} contributions</p>
<p className="font-bold text-foreground">
{totalContributions} contributions
</p>
<div className="flex gap-2 items-center">
<ActivityLevelIndicator />
<Button
Expand Down
18 changes: 17 additions & 1 deletion app/recap/components/header/breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import {
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Slash } from "lucide-react";
import { useSession } from "next-auth/react";
import { signOut } from "next-auth/react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { FC } from "react";
Expand All @@ -27,7 +29,21 @@ export const Breadcrumbs: FC = () => {
asChild
className="text-lg font-extrabold text-accent-foreground"
>
<Link href="/">Dev Recap 2024</Link>
{session?.user ? (
<Button
variant="link"
className="hover:no-underline"
onClick={() =>
signOut({
redirectTo: "/",
})
}
>
Dev Recap 2024
</Button>
) : (
<Link href="/">Dev Recap 2024</Link>
)}
</BreadcrumbLink>
</BreadcrumbItem>
{isOnRecap && session?.user && (
Expand Down
2 changes: 2 additions & 0 deletions app/recap/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import React, { type FC } from "react";
import { ModeToggle } from "../theme-toggle/theme-toggle";
import { UserButton } from "../user-button";
import { Breadcrumbs } from "./breadcrumbs";

export const Header: FC = () => {
Expand Down Expand Up @@ -46,6 +47,7 @@ export const Header: FC = () => {
className="dark:hidden"
/>
</Link>
<UserButton />
<ModeToggle />
</div>
</header>
Expand Down
34 changes: 15 additions & 19 deletions app/recap/components/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { auth } from "@/lib/auth";
import { format } from "date-fns";
import {
GitCommitHorizontal,
Expand All @@ -25,33 +24,28 @@ import { WeeklyContributionsGraph } from "./graph/weekly";
import { ReposContributions } from "./repos-contributions";

type Props = {
user: string;
data: Stats;
};

export const OverView: FC<Props> = async ({ data }) => {
const session = await auth();
if (!session) return null;

export const OverView: FC<Props> = async ({ user, data }) => {
return (
<div className="space-y-4 pb-4">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:gap-12 xl:justify-center 2xl:gap-12">
<Card className="relative">
<Avatar className="h-16 w-16 border absolute top-1/2 right-6 transform -translate-y-1/2">
<AvatarImage
src={session.user?.image ?? ""}
alt={session.user?.login ?? ""}
/>
<AvatarFallback>{session.user?.login}</AvatarFallback>
<AvatarImage src={data.userProfile.avatarUrl} alt={user} />
<AvatarFallback>{user}</AvatarFallback>
</Avatar>
<CardHeader className="pr-24 pb-2">
<CardTitle className="text-xl truncate">
<a
href={`https://github.com/${session.user.login}`}
href={`https://github.com/${user}`}
target="_blank"
rel="noopener noreferrer"
className="hover:underline"
>
{session.user.login}
{user}
</a>
<p className="text-xs text-muted-foreground truncate font-normal">
Joined on{" "}
Expand Down Expand Up @@ -188,13 +182,15 @@ export const OverView: FC<Props> = async ({ data }) => {
<CardHeader>
<CardTitle>リポジトリごとの統計</CardTitle>
<CardDescription>
{data.repositoriesByCommitCount.length === 0 ? (
"データがありません。"
) : (
<>
あなたが最もコミットしたリポジトリは <b>{data.repositoriesByCommitCount[0].nameWithOwner}</b> です。
</>
)}
{data.repositoriesByCommitCount.length === 0 ? (
"データがありません。"
) : (
<>
あなたが最もコミットしたリポジトリは{" "}
<b>{data.repositoriesByCommitCount[0].nameWithOwner}</b>{" "}
です。
</>
)}
</CardDescription>
</CardHeader>
<CardContent>
Expand Down
Loading

0 comments on commit 471def6

Please sign in to comment.