Skip to content

Commit 7139a49

Browse files
committed
feat(auth, icons): add gitHub and google login buttons, implement user profile component, and enhance authentication flow with logout functionality
1 parent e0d845f commit 7139a49

File tree

11 files changed

+242
-1
lines changed

11 files changed

+242
-1
lines changed

next.config.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4-
/* config options here */
4+
images: {
5+
remotePatterns: [
6+
{
7+
protocol: "https",
8+
hostname: "avatars.githubusercontent.com",
9+
pathname: "/**",
10+
},
11+
{
12+
protocol: "https",
13+
hostname: "lh3.googleusercontent.com",
14+
pathname: "/**",
15+
},
16+
],
17+
},
518
};
619

720
export default nextConfig;

src/actions/auth.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ export async function signup(formData: FormData) {
3939
revalidatePath("/", "layout");
4040
redirect("/");
4141
}
42+
43+
export async function logout() {
44+
const supabase = await createClient();
45+
await supabase.auth.signOut();
46+
}

src/components/auth/github-button.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client";
2+
3+
import { createClient } from "@/lib/supabase/client";
4+
import { Button } from "@heroui/react";
5+
import { GitHub } from "@/components/icons";
6+
7+
export default function GitHubButton() {
8+
const supabase = createClient();
9+
10+
const loginWithGithub = async () => {
11+
await supabase.auth.signInWithOAuth({
12+
provider: "github",
13+
options: {
14+
redirectTo: `${window.location.origin}/auth/callback`,
15+
},
16+
});
17+
};
18+
19+
return (
20+
<Button
21+
size="lg"
22+
onPress={loginWithGithub}
23+
startContent={
24+
<GitHub className="size-4 text-white dark:text-neutral-950" />
25+
}
26+
className="bg-neutral-950 dark:bg-white text-white dark:text-neutral-950 font-medium w-full"
27+
>
28+
Continue with GitHub
29+
</Button>
30+
);
31+
}

src/components/auth/google-button.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client";
2+
3+
import { createClient } from "@/lib/supabase/client";
4+
import { Button } from "@heroui/react";
5+
import { Google } from "@/components/icons";
6+
7+
export default function GoogleButton() {
8+
const supabase = createClient();
9+
10+
const loginWithGoogle = async () => {
11+
await supabase.auth.signInWithOAuth({
12+
provider: "google",
13+
options: {
14+
redirectTo: `${window.location.origin}/auth/callback`,
15+
},
16+
});
17+
};
18+
19+
return (
20+
<Button
21+
size="lg"
22+
onPress={loginWithGoogle}
23+
startContent={
24+
<Google className="size-4 text-white dark:text-neutral-950" />
25+
}
26+
className="bg-neutral-950 dark:bg-white text-white dark:text-neutral-950 font-medium w-full"
27+
>
28+
Continue with Google
29+
</Button>
30+
);
31+
}

src/components/auth/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default as GitHubButton } from "./github-button";
2+
export { default as GoogleButton } from "./google-button";
3+
export { default as UserProfile } from "./user-profile";

src/components/auth/user-profile.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
3+
import type { UserResponse } from "@/types";
4+
5+
import { useEffect, useState } from "react";
6+
import { useRouter } from "next/navigation";
7+
import {
8+
Avatar,
9+
Button,
10+
Dropdown,
11+
DropdownItem,
12+
DropdownMenu,
13+
DropdownTrigger,
14+
} from "@heroui/react";
15+
import { createClient } from "@/lib/supabase/client";
16+
17+
export default function UserProfile() {
18+
const router = useRouter();
19+
const supabase = createClient();
20+
const [user, setUser] = useState<UserResponse | null>(null);
21+
22+
useEffect(() => {
23+
const getUser = async () => {
24+
const {
25+
data: { user },
26+
error,
27+
} = await supabase.auth.getUser();
28+
29+
if (user) setUser(user);
30+
};
31+
32+
getUser();
33+
}, []);
34+
35+
const handleLogout = async () => {
36+
const supabase = await createClient();
37+
await supabase.auth.signOut();
38+
router.refresh();
39+
};
40+
41+
if (!user) return <p>Loading...</p>;
42+
43+
return (
44+
<>
45+
<p className="text-xs font-medium">Hi, {user?.user_metadata.name}</p>
46+
<Dropdown>
47+
<DropdownTrigger>
48+
<Button isIconOnly variant="bordered" size="sm">
49+
<Avatar
50+
size="sm"
51+
radius="sm"
52+
src={user?.user_metadata.avatar_url}
53+
name={`${user?.user_metadata.full_name} avatar`}
54+
className="bg-primary text-white text-xl"
55+
/>
56+
</Button>
57+
</DropdownTrigger>
58+
<DropdownMenu
59+
aria-label="Example with disabled actions"
60+
disabledKeys={["profile"]}
61+
>
62+
<DropdownItem key="profile">
63+
<span className="text-primary font-semibold">
64+
{user?.user_metadata.name}
65+
</span>
66+
<span>{user?.email}</span>
67+
</DropdownItem>
68+
<DropdownItem key="logout" onPress={handleLogout}>
69+
Log out
70+
</DropdownItem>
71+
</DropdownMenu>
72+
</Dropdown>
73+
</>
74+
);
75+
}

src/components/icons/facebook.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
type Props = {
2+
className: string;
3+
};
4+
5+
export default function Facebook({ className }: Props) {
6+
return (
7+
<svg
8+
className={`${className}`}
9+
viewBox="126.445 2.281 589 589"
10+
xmlns="http://www.w3.org/2000/svg"
11+
>
12+
<circle cx="420.945" cy="296.781" r="294.5" fill="#3c5a9a" />
13+
<path
14+
d="M516.704 92.677h-65.239c-38.715 0-81.777 16.283-81.777 72.402.189 19.554 0 38.281 0 59.357H324.9v71.271h46.174v205.177h84.847V294.353h56.002l5.067-70.117h-62.531s.14-31.191 0-40.249c0-22.177 23.076-20.907 24.464-20.907 10.981 0 32.332.032 37.813 0V92.677h-.032z"
15+
fill="#ffffff"
16+
/>
17+
</svg>
18+
);
19+
}

src/components/icons/github.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type Props = {
2+
className: string;
3+
};
4+
5+
export default function GitHub({ className }: Props) {
6+
return (
7+
<svg
8+
className={`${className}`}
9+
xmlns="http://www.w3.org/2000/svg"
10+
viewBox="0 0 97.707 97.707"
11+
fill="currentColor"
12+
>
13+
<path
14+
fillRule="evenodd"
15+
clipRule="evenodd"
16+
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
17+
/>
18+
</svg>
19+
);
20+
}

src/components/icons/google.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
type Props = {
2+
className: string;
3+
};
4+
5+
export default function Google({ className }: Props) {
6+
return (
7+
<svg
8+
className={`${className}`}
9+
viewBox="-3 0 262 262"
10+
xmlns="http://www.w3.org/2000/svg"
11+
preserveAspectRatio="xMidYMid"
12+
>
13+
<path
14+
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
15+
fill="#4285F4"
16+
/>
17+
<path
18+
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
19+
fill="#34A853"
20+
/>
21+
<path
22+
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
23+
fill="#FBBC05"
24+
/>
25+
<path
26+
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
27+
fill="#EB4335"
28+
/>
29+
</svg>
30+
);
31+
}

src/components/icons/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default as Facebook } from "./facebook";
2+
export { default as GitHub } from "./github";
3+
export { default as Google } from "./google";

0 commit comments

Comments
 (0)