Skip to content

Commit

Permalink
✨ add next-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
w3cj committed Sep 12, 2024
1 parent 658d51e commit a0186d9
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"check-file/folder-naming-convention": [
"error",
{
"src/**": "KEBAB_CASE"
"src/**/!^[.*": "KEBAB_CASE"
}
]
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"framer-motion": "^11.5.4",
"jiti": "^1.21.6",
"next": "14.2.9",
"next-auth": "^4.24.7",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
Expand Down
129 changes: 120 additions & 9 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import NextAuth from "next-auth";

import options from "@/config/auth";

const handler = NextAuth(options);

export { handler as GET, handler as POST };
3 changes: 2 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import { Suspense } from "react";

import AppNavbar from "@/components/app-navbar";
import Providers from "@/components/providers";
Expand Down Expand Up @@ -27,7 +28,7 @@ export default function RootLayout({
<Providers>
<AppNavbar />
<main className="flex-grow overflow-auto bg-[url(/light-bg.svg)] bg-cover dark:bg-[url(/dark-bg.svg)]">
{children}
<Suspense>{children}</Suspense>
</main>
</Providers>
</body>
Expand Down
13 changes: 13 additions & 0 deletions src/app/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CircularProgress } from "@nextui-org/react";

export default function Loading() {
return (
<CircularProgress
className="mx-auto"
classNames={{
svg: "w-36 h-36",
}}
aria-label="Loading page..."
/>
);
}
23 changes: 23 additions & 0 deletions src/app/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Card, CardBody, User } from "@nextui-org/react";
import { getServerSession } from "next-auth";

import options from "@/config/auth";

export default async function Profile() {
const session = await getServerSession(options);

return (
<Card className="mx-auto mt-4 max-w-md">
<CardBody>
<User
name={session?.user?.name}
description={session?.user?.email}
avatarProps={{
showFallback: !session?.user?.image,
src: session?.user?.image || "",
}}
/>
</CardBody>
</Card>
);
}
74 changes: 74 additions & 0 deletions src/components/app-navbar/auth-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"use client";

import {
Avatar,
Button,
CircularProgress,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownTrigger,
} from "@nextui-org/react";
import { IconBrandGoogle } from "@tabler/icons-react";
import { signIn, signOut, useSession } from "next-auth/react";

export default function AuthButton({ minimal = true }: { minimal?: boolean }) {
const { data, status } = useSession();

if (status === "loading") {
return <CircularProgress aria-label="Loading authentication status..." />;
}

if (status === "authenticated") {
const signOutClick = () =>
signOut({
callbackUrl: "/",
});
if (minimal) {
return (
<Button onClick={signOutClick} color="danger" variant="ghost">
<IconBrandGoogle />
Sign Out
</Button>
);
}

return (
<Dropdown placement="bottom-end">
<DropdownTrigger>
<Avatar
isBordered
as="button"
className="transition-transform"
showFallback={!data.user?.image}
src={data.user?.image || ""}
/>
</DropdownTrigger>
<DropdownMenu aria-label="Profile Actions" variant="flat">
<DropdownItem key="profile" className="h-14 gap-2">
<p className="font-semibold">Signed in as</p>
<p className="font-semibold">{data.user?.email}</p>
</DropdownItem>
<DropdownItem key="sign-out" color="danger" onClick={signOutClick}>
Sign Out
</DropdownItem>
</DropdownMenu>
</Dropdown>
);
}

return (
<Button
onClick={() =>
signIn("google", {
callbackUrl: "/profile",
})
}
color="danger"
variant="ghost"
>
<IconBrandGoogle />
Sign In
</Button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,28 @@ import {
NavbarMenuToggle,
} from "@nextui-org/react";
import { IconPackage } from "@tabler/icons-react";
import { useSession } from "next-auth/react";

import AuthButton from "./auth-button";
import { ThemeSwitcher } from "./theme-switcher";

export default function AppNavbar() {
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const { status } = useSession();

const menuItems = [
{
label: "Home",
href: "/",
},
{
];

if (status === "authenticated") {
menuItems.push({
label: "Profile",
href: "/profile",
},
];
});
}

return (
<Navbar onMenuOpenChange={setIsMenuOpen}>
Expand All @@ -54,6 +60,9 @@ export default function AppNavbar() {
<NavbarItem>
<ThemeSwitcher />
</NavbarItem>
<NavbarItem>
<AuthButton minimal={false} />
</NavbarItem>
</NavbarContent>
<NavbarMenu>
<NavbarMenuItem>
Expand All @@ -66,6 +75,9 @@ export default function AppNavbar() {
</Link>
</NavbarMenuItem>
))}
<NavbarMenuItem>
<AuthButton />
</NavbarMenuItem>
</NavbarMenu>
</Navbar>
);
Expand Down
File renamed without changes.
19 changes: 11 additions & 8 deletions src/components/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import { useRouter } from "next/navigation";
import { ReactNode } from "react";

import { NextUIProvider } from "@nextui-org/react";
import { SessionProvider } from "next-auth/react";
import { ThemeProvider as NextThemesProvider } from "next-themes";

export default function Providers({ children }: { children: ReactNode }) {
const router = useRouter();
return (
<NextUIProvider
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
navigate={router.push}
className="flex h-full w-full flex-col"
>
<NextThemesProvider attribute="class">{children}</NextThemesProvider>
</NextUIProvider>
<SessionProvider>
<NextUIProvider
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
navigate={router.push}
className="flex h-full w-full flex-col"
>
<NextThemesProvider attribute="class">{children}</NextThemesProvider>
</NextUIProvider>
</SessionProvider>
);
}
18 changes: 18 additions & 0 deletions src/config/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";

import { env } from "@/env/server";

const options: NextAuthOptions = {
pages: {
signIn: "/",
},
providers: [
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
}),
],
};

export default options;
4 changes: 4 additions & 0 deletions src/env/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { ZodError, z } from "zod";
export const env = createEnv({
server: {
NODE_ENV: z.enum(["development", "production"]),
NEXTAUTH_URL: z.string().url(),
NEXTAUTH_SECRET: z.string(),
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: z.string(),
},
onValidationError: (error: ZodError) => {
console.error(
Expand Down
3 changes: 3 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default } from "next-auth/middleware";

export const config = { matcher: ["/profile"] };

0 comments on commit a0186d9

Please sign in to comment.