Skip to content

Commit 4b0bf61

Browse files
committed
feat: implement user registration and login functionality with enhanced validation and UI updates.
1 parent d23f1d8 commit 4b0bf61

7 files changed

Lines changed: 192 additions & 75 deletions

File tree

src/action/user.action.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { auth } from "@/lib/auth-config";
88
import { eq } from "drizzle-orm";
99
import { redirect } from "next/navigation";
1010
import { config } from "@/lib/config";
11+
import type { RegisterFormValues } from "@/types";
1112

1213
// Helper to get current authenticated user
1314
export async function getCurrentUser() {
@@ -32,6 +33,33 @@ export async function getCurrentUser() {
3233
};
3334
}
3435

36+
export async function signUpAction({
37+
name,
38+
email,
39+
password,
40+
userName,
41+
}: RegisterFormValues): Promise<APIResponse<{ user: UserResponse }>> {
42+
try {
43+
const result = await auth.api.signUpEmail({
44+
body: {
45+
email,
46+
password,
47+
name,
48+
userName,
49+
},
50+
headers: await headers(),
51+
});
52+
53+
if (!result || !result.user) {
54+
return { error: "Sign up failed", success: false };
55+
}
56+
57+
return { data: { user: result.user as UserResponse }, success: true };
58+
} catch (error: unknown) {
59+
return { error: `Sign up failed: ${error}`, success: false };
60+
}
61+
}
62+
3563
export async function loginAction(email: string, password: string) {
3664
try {
3765
// Use the server-side API for authentication

src/app/(root)/(home)/_components/Navbar.tsx

Lines changed: 99 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,48 @@
1-
import React from "react";
2-
import { headers } from "next/headers";
3-
import { auth } from "@/lib/auth-config";
1+
"use client";
2+
43
import { ThemeToggle } from "@/components/navigation/theme-toggle";
54
import { homeTabs } from "@/constants";
65
import Logo from "@/components/Logo";
76
import Link from "next/link";
87
import { Button } from "@/components/ui/button";
98
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
109
import { config } from "@/lib/config";
10+
import {
11+
DropdownMenu,
12+
DropdownMenuContent,
13+
DropdownMenuGroup,
14+
DropdownMenuItem,
15+
DropdownMenuLabel,
16+
DropdownMenuSeparator,
17+
DropdownMenuTrigger,
18+
} from "@/components/ui/dropdown-menu";
19+
import {
20+
IconCreditCard,
21+
IconDotsVertical,
22+
IconLogout,
23+
IconNotification,
24+
IconUserCircle,
25+
} from "@tabler/icons-react";
26+
import { authClient, useSession } from "@/lib/auth-client";
27+
import { useRouter } from "next/navigation";
1128

12-
const Navbar = async () => {
13-
const session = await auth.api.getSession({
14-
headers: await headers(),
15-
});
29+
const Navbar = () => {
30+
const { data: session } = useSession();
31+
const router = useRouter();
1632
const user = session?.user;
1733

34+
const handleSignOut = async () => {
35+
await authClient.signOut({
36+
fetchOptions: {
37+
onSuccess: () => {
38+
router.push(config.SIGN_IN);
39+
},
40+
},
41+
});
42+
};
43+
1844
return (
19-
<div className="sticky z-50 top-0 flex items-center justify-between w-full px-4 sm:px-8 py-2 xl:py-4 bg-gradient-to-r from-black/50 to-gray-700/50 dark:from-black/10 dark:to-black/20 backdrop-blur-lg border-b border-gray-200 dark:border-gray-700">
45+
<div className="sticky z-50 top-0 flex items-center justify-between w-full px-4 sm:px-8 py-2 xl:py-4 bg-linear-to-r from-black/50 to-gray-700/50 dark:from-black/10 dark:to-black/20 backdrop-blur-lg border-b border-gray-200 dark:border-gray-700">
2046
<Logo />
2147
<div>
2248
<ul className="hidden sm:flex gap-4 text-lg sm:text-base font-semibold text-slate-900 dark:text-white">
@@ -41,17 +67,71 @@ const Navbar = async () => {
4167
Dashboard
4268
</Button>
4369
</Link>
44-
<Link href="/subscriptions" className="hidden sm:block">
45-
<Button variant="outline" className="text-sm sm:text-base">
46-
My Subscriptions
47-
</Button>
48-
</Link>
49-
<Avatar className="h-8 w-8">
50-
<AvatarImage src={user.image || undefined} alt={user.name} />
51-
<AvatarFallback>
52-
{user.name?.charAt(0).toUpperCase() || "U"}
53-
</AvatarFallback>
54-
</Avatar>
70+
<DropdownMenu>
71+
<DropdownMenuTrigger>
72+
<Avatar className="h-8 w-8 rounded-full">
73+
<AvatarImage
74+
src={
75+
user.image ||
76+
`https://ui-avatars.com/api/?name=${user.name}&size=75`
77+
}
78+
alt={user.name}
79+
className="rounded-full"
80+
height={75}
81+
width={75}
82+
/>
83+
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
84+
</Avatar>
85+
</DropdownMenuTrigger>
86+
<DropdownMenuContent
87+
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
88+
side={"bottom"}
89+
align="end"
90+
sideOffset={4}
91+
>
92+
<DropdownMenuLabel className="p-0 font-normal">
93+
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
94+
<Avatar className="h-8 w-8 rounded-lg">
95+
<AvatarImage
96+
src={
97+
user.image ||
98+
`https://ui-avatars.com/api/?name=${user.name}&size=75`
99+
}
100+
alt={user.name}
101+
/>
102+
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
103+
</Avatar>
104+
<div className="grid flex-1 text-left text-sm leading-tight">
105+
<span className="truncate font-medium">{user.name}</span>
106+
<span className="text-muted-foreground truncate text-xs">
107+
{user.email}
108+
</span>
109+
</div>
110+
</div>
111+
</DropdownMenuLabel>
112+
<DropdownMenuSeparator />
113+
<DropdownMenuGroup>
114+
<DropdownMenuItem>
115+
<IconUserCircle />
116+
Account
117+
</DropdownMenuItem>
118+
<DropdownMenuItem>
119+
<IconCreditCard />
120+
Billing
121+
</DropdownMenuItem>
122+
<DropdownMenuItem>
123+
<IconNotification />
124+
Notifications
125+
</DropdownMenuItem>
126+
</DropdownMenuGroup>
127+
<DropdownMenuSeparator />
128+
<DropdownMenuItem onClick={() => handleSignOut()}>
129+
<IconLogout />
130+
Log Out
131+
</DropdownMenuItem>
132+
</DropdownMenuContent>
133+
{/* </DropdownMenuContent> */}
134+
</DropdownMenu>
55135
</div>
56136
) : (
57137
<div className="flex items-center gap-2 sm:gap-4">

src/app/(root)/auth/login/page.tsx

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@ import { useForm } from "@tanstack/react-form";
55
import { useRouter } from "next/navigation";
66
import Link from "next/link";
77
import { toast } from "sonner";
8-
import { Loader2, Mail, Lock, Eye, EyeOff, LogIn } from "lucide-react";
9-
8+
import {
9+
IconEye,
10+
IconEyeOff,
11+
IconLoader2,
12+
IconLock,
13+
IconMail,
14+
IconUserPlus,
15+
} from "@tabler/icons-react";
1016
import { Button } from "@/components/ui/button";
1117
import {
1218
Card,
1319
CardContent,
14-
CardDescription,
1520
CardFooter,
1621
CardHeader,
1722
CardTitle,
1823
} from "@/components/ui/card";
1924
import {
2025
Field,
21-
FieldDescription,
2226
FieldError,
2327
FieldGroup,
2428
FieldLabel,
@@ -89,9 +93,8 @@ export default function LoginPage() {
8993
>
9094
<FieldGroup>
9195
{/* Email Field */}
92-
<form.Field
93-
name="email"
94-
children={(field) => {
96+
<form.Field name="email">
97+
{(field) => {
9598
const isInvalid =
9699
field.state.meta.isTouched && !field.state.meta.isValid;
97100
return (
@@ -103,7 +106,7 @@ export default function LoginPage() {
103106
Email Address
104107
</FieldLabel>
105108
<div className="relative">
106-
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-foreground dark:text-foreground" />
109+
<IconMail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-foreground dark:text-foreground" />
107110
<Input
108111
id={field.name}
109112
name={field.name}
@@ -123,12 +126,11 @@ export default function LoginPage() {
123126
</Field>
124127
);
125128
}}
126-
/>
129+
</form.Field>
127130

128131
{/* Password Field */}
129-
<form.Field
130-
name="password"
131-
children={(field) => {
132+
<form.Field name="password">
133+
{(field) => {
132134
const isInvalid =
133135
field.state.meta.isTouched && !field.state.meta.isValid;
134136
return (
@@ -148,7 +150,7 @@ export default function LoginPage() {
148150
</Link>
149151
</div>
150152
<div className="relative">
151-
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-foreground dark:text-foreground" />
153+
<IconLock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-foreground dark:text-foreground" />
152154
<Input
153155
id={field.name}
154156
name={field.name}
@@ -167,9 +169,9 @@ export default function LoginPage() {
167169
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground dark:text-foreground hover:text-secondary/80 transition-colors"
168170
>
169171
{showPassword ? (
170-
<EyeOff className="h-4 w-4" />
172+
<IconEyeOff className="h-4 w-4" />
171173
) : (
172-
<Eye className="h-4 w-4" />
174+
<IconEye className="h-4 w-4" />
173175
)}
174176
</Button>
175177
</div>
@@ -179,7 +181,7 @@ export default function LoginPage() {
179181
</Field>
180182
);
181183
}}
182-
/>
184+
</form.Field>
183185

184186
{/* Submit Button */}
185187
<Button
@@ -190,12 +192,12 @@ export default function LoginPage() {
190192
>
191193
{isSubmitting ? (
192194
<>
193-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
195+
<IconLoader2 className="mr-2 h-4 w-4 animate-spin" />
194196
Signing in...
195197
</>
196198
) : (
197199
<>
198-
<LogIn className="mr-2 h-4 w-4" />
200+
<IconUserPlus className="mr-2 h-4 w-4" />
199201
Sign In
200202
</>
201203
)}

0 commit comments

Comments
 (0)