Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ユーザー名を検索可能にする #31

Merged
merged 11 commits into from
Dec 31, 2024
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
67 changes: 30 additions & 37 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,58 @@
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-lg">or</p>
<Button size="lg" className="mx-auto w-full max-w-xs py-6 text-lg">
<Image
src={GitHubWhite}
alt="GitHub"
width={20}
height={20}
width={24}
height={24}
className="dark:hidden"
priority
/>
<Image
src={GitHubBlack}
alt="GitHub"
width={20}
height={20}
width={24}
height={24}
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">
<FormControl className="py-6 text-lg">
<Input
placeholder="ユーザー名を入力してください。"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button className="mx-auto w-full max-w-xs py-6 text-lg">
振り返りを見る
</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
Loading
Loading